├── .cargo └── config.toml ├── .github └── workflows │ ├── build-latest.yml │ └── release-with-version.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── check.sh ├── data └── live_reload.html ├── homepage ├── CNAME ├── Staple.lock ├── Staple.toml ├── data │ ├── builtin-function.md │ ├── develop-mode.md │ ├── get-started.md │ ├── index.json │ ├── markdown-ext.md │ └── template.md └── templates │ └── staple │ ├── article.html │ ├── base.html │ ├── cross.html │ ├── index.html │ ├── page.html │ └── statics │ ├── bg.png │ ├── prime.js │ └── style.less ├── readme.md ├── rustfmt.toml ├── src ├── app.rs ├── command │ ├── add.rs │ ├── build.rs │ ├── develop.rs │ ├── init.rs │ ├── list.rs │ ├── mod.rs │ └── new.rs ├── config.rs ├── constants.rs ├── data │ ├── mod.rs │ └── types │ │ ├── article.pest │ │ ├── json.rs │ │ ├── markdown.rs │ │ └── mod.rs ├── error.rs ├── main.rs ├── server │ ├── mod.rs │ └── ws.rs ├── template.rs └── util │ ├── filter.rs │ ├── lock.rs │ └── mod.rs └── update_local_new_version.sh /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | fc = "fmt --all -- --check" 3 | cc = "clippy --all-targets -- -D warnings" 4 | -------------------------------------------------------------------------------- /.github/workflows/build-latest.yml: -------------------------------------------------------------------------------- 1 | name: Develop Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - develop 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v1 16 | - name: Cache cargo build 17 | uses: actions/cache@v1 18 | with: 19 | path: target 20 | key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} 21 | - name: Format 22 | run: cargo fmt --all -- --check 23 | - name: Clippy 24 | run: cargo clippy --all-targets -- -D warnings 25 | - name: Run cargo-tarpaulin 26 | uses: actions-rs/tarpaulin@v0.1 27 | with: 28 | args: '--ignore-tests --ciserver github-ci --out Lcov -- --test-threads 1' 29 | 30 | - name: upload to Coveralls 31 | uses: coverallsapp/github-action@master 32 | with: 33 | github-token: ${{ secrets.GITHUB_TOKEN }} 34 | path-to-lcov: './lcov.info' -------------------------------------------------------------------------------- /.github/workflows/release-with-version.yml: -------------------------------------------------------------------------------- 1 | name: Build Release Version 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*.*.*" 7 | 8 | jobs: 9 | release: 10 | name: Release on ${{ matrix.platform }} 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | platform: [macos, ubuntu] 15 | include: 16 | - platform: macos 17 | target: x86_64-apple-darwin 18 | os: macos-latest 19 | bin: staple 20 | 21 | - platform: ubuntu 22 | target: x86_64-unknown-linux-gnu 23 | os: ubuntu-latest 24 | bin: staple 25 | 26 | steps: 27 | - name: Install toolchain 28 | uses: actions-rs/toolchain@v1 29 | with: 30 | toolchain: stable 31 | override: true 32 | 33 | - name: Checkout code 34 | uses: actions/checkout@v1 35 | 36 | - name: Run Code Test 37 | uses: actions-rs/cargo@v1 38 | with: 39 | command: test 40 | args: -- --test-threads=1 41 | 42 | - name: Run code build 43 | uses: actions-rs/cargo@v1 44 | with: 45 | command: build 46 | args: --locked --release --target ${{ matrix.target }} 47 | 48 | - name: Prepare assets 49 | shell: bash 50 | run: | 51 | cd target/${{ matrix.target }}/release 52 | strip ${{ matrix.bin }} 53 | tar czvf staple-${{ matrix.platform }}.tar.gz ${{ matrix.bin }} 54 | - name: Release assets 55 | uses: softprops/action-gh-release@v1 56 | with: 57 | files: target/${{ matrix.target }}/release/staple-${{ matrix.platform }}.tar.gz 58 | env: 59 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} 60 | 61 | publish-to-cargo: 62 | name: Publish to Cargo 63 | needs: release 64 | runs-on: ubuntu-latest 65 | steps: 66 | - name: Checkout code 67 | uses: actions/checkout@v1 68 | - name: publish to cargo 69 | env: 70 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_PUBLISH_TOKEN }} 71 | run: cargo publish 72 | 73 | release-homepage: 74 | name: Release Homepage 75 | runs-on: ubuntu-latest 76 | needs: publish-to-cargo 77 | steps: 78 | - name: Checkout code 79 | uses: actions/checkout@v1 80 | - name: Install less 81 | run: npm install less 82 | - name: Build Homepage 83 | run: cd homepage && cargo run -- build 84 | - name: Deploy site to gh-pages branch 85 | uses: alex-page/blazing-fast-gh-pages-deploy@v1.1.0 86 | with: 87 | site-directory: homepage/public 88 | repo-token: ${{ secrets.GH_PAT }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /target 3 | **/*.rs.bk 4 | .idea 5 | homepage/public/* 6 | homepage/public 7 | /homepage/templates/staple/statics/style.css 8 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "actix" 5 | version = "0.8.3" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "671ce3d27313f236827a5dd153a1073ad03ef31fc77f562020263e7830cf1ef7" 8 | dependencies = [ 9 | "actix-http", 10 | "actix-rt", 11 | "actix_derive", 12 | "bitflags", 13 | "bytes", 14 | "crossbeam-channel", 15 | "derive_more 0.14.1", 16 | "futures", 17 | "hashbrown 0.3.1", 18 | "lazy_static", 19 | "log", 20 | "parking_lot 0.8.0", 21 | "smallvec", 22 | "tokio-codec", 23 | "tokio-executor", 24 | "tokio-io", 25 | "tokio-tcp", 26 | "tokio-timer", 27 | "trust-dns-resolver", 28 | ] 29 | 30 | [[package]] 31 | name = "actix-codec" 32 | version = "0.1.2" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "9f2c11af4b06dc935d8e1b1491dad56bfb32febc49096a91e773f8535c176453" 35 | dependencies = [ 36 | "bytes", 37 | "futures", 38 | "log", 39 | "tokio-codec", 40 | "tokio-io", 41 | ] 42 | 43 | [[package]] 44 | name = "actix-connect" 45 | version = "0.2.5" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "9fade9bd4bb46bacde89f1e726c7a3dd230536092712f5d94d77ca57c087fca0" 48 | dependencies = [ 49 | "actix-codec", 50 | "actix-rt", 51 | "actix-service", 52 | "actix-utils", 53 | "derive_more 0.15.0", 54 | "either", 55 | "futures", 56 | "http", 57 | "log", 58 | "tokio-current-thread", 59 | "tokio-tcp", 60 | "trust-dns-resolver", 61 | ] 62 | 63 | [[package]] 64 | name = "actix-files" 65 | version = "0.1.6" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "095a533b67977f96ab0ff0ae37e75208607abdcc22983b314ddac06651165ec5" 68 | dependencies = [ 69 | "actix-http", 70 | "actix-service", 71 | "actix-web", 72 | "bitflags", 73 | "bytes", 74 | "derive_more 0.15.0", 75 | "futures", 76 | "log", 77 | "mime", 78 | "mime_guess", 79 | "percent-encoding 2.1.0", 80 | "v_htmlescape", 81 | ] 82 | 83 | [[package]] 84 | name = "actix-http" 85 | version = "0.2.10" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "cdf758ebbc4abfecbdc1ce7408601b2d7e0cd7e4766ef61183cd8ce16c194d64" 88 | dependencies = [ 89 | "actix-codec", 90 | "actix-connect", 91 | "actix-server-config", 92 | "actix-service", 93 | "actix-threadpool", 94 | "actix-utils", 95 | "base64", 96 | "bitflags", 97 | "brotli2", 98 | "bytes", 99 | "chrono", 100 | "copyless", 101 | "derive_more 0.15.0", 102 | "either", 103 | "encoding_rs", 104 | "failure", 105 | "flate2", 106 | "futures", 107 | "h2", 108 | "hashbrown 0.5.0", 109 | "http", 110 | "httparse", 111 | "indexmap", 112 | "language-tags", 113 | "lazy_static", 114 | "log", 115 | "mime", 116 | "percent-encoding 2.1.0", 117 | "rand 0.7.2", 118 | "regex", 119 | "serde", 120 | "serde_json", 121 | "serde_urlencoded", 122 | "sha1", 123 | "slab", 124 | "time", 125 | "tokio-current-thread", 126 | "tokio-tcp", 127 | "tokio-timer", 128 | "trust-dns-resolver", 129 | ] 130 | 131 | [[package]] 132 | name = "actix-router" 133 | version = "0.1.5" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "23224bb527e204261d0291102cb9b52713084def67d94f7874923baefe04ccf7" 136 | dependencies = [ 137 | "bytes", 138 | "http", 139 | "log", 140 | "regex", 141 | "serde", 142 | "string", 143 | ] 144 | 145 | [[package]] 146 | name = "actix-rt" 147 | version = "0.2.5" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "168620aaf00fcd2a16e621790abaf180ef7377c2f8355b4ca5775d6afc778ed8" 150 | dependencies = [ 151 | "actix-threadpool", 152 | "copyless", 153 | "futures", 154 | "tokio-current-thread", 155 | "tokio-executor", 156 | "tokio-reactor", 157 | "tokio-timer", 158 | ] 159 | 160 | [[package]] 161 | name = "actix-server" 162 | version = "0.6.1" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "dd626534af8d0a738e5f74901fe603af0445708f91b86a7d763d80df10d562a5" 165 | dependencies = [ 166 | "actix-rt", 167 | "actix-server-config", 168 | "actix-service", 169 | "futures", 170 | "log", 171 | "mio", 172 | "net2", 173 | "num_cpus", 174 | "slab", 175 | "tokio-io", 176 | "tokio-reactor", 177 | "tokio-signal", 178 | "tokio-tcp", 179 | "tokio-timer", 180 | ] 181 | 182 | [[package]] 183 | name = "actix-server-config" 184 | version = "0.1.2" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "483a34989c682d93142bacad6300375bb6ad8002d2e0bb249dbad86128b9ff30" 187 | dependencies = [ 188 | "futures", 189 | "tokio-io", 190 | "tokio-tcp", 191 | ] 192 | 193 | [[package]] 194 | name = "actix-service" 195 | version = "0.4.2" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "bca5b48e928841ff7e7dce1fdb5b0d4582f6b1b976e08f4bac3f640643e0773f" 198 | dependencies = [ 199 | "futures", 200 | ] 201 | 202 | [[package]] 203 | name = "actix-testing" 204 | version = "0.1.0" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "af001e97ac6750994824d400a1b7087055aab14317aa012f528d0b2b363f37f1" 207 | dependencies = [ 208 | "actix-rt", 209 | "actix-server", 210 | "actix-server-config", 211 | "actix-service", 212 | "futures", 213 | "log", 214 | "net2", 215 | "tokio-reactor", 216 | "tokio-tcp", 217 | ] 218 | 219 | [[package]] 220 | name = "actix-threadpool" 221 | version = "0.1.2" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "6b5ae85d13da7e6fb86b1b7bc83185e0e3bd4cc5f421c887e1803796c034d35d" 224 | dependencies = [ 225 | "derive_more 0.15.0", 226 | "futures", 227 | "lazy_static", 228 | "log", 229 | "num_cpus", 230 | "parking_lot 0.9.0", 231 | "threadpool", 232 | ] 233 | 234 | [[package]] 235 | name = "actix-utils" 236 | version = "0.4.7" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "908c3109948f5c37a8b57fd343a37dcad5bb1d90bfd06300ac96b17bbe017b95" 239 | dependencies = [ 240 | "actix-codec", 241 | "actix-service", 242 | "bytes", 243 | "either", 244 | "futures", 245 | "log", 246 | "tokio-current-thread", 247 | "tokio-timer", 248 | ] 249 | 250 | [[package]] 251 | name = "actix-web" 252 | version = "1.0.8" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "36e59485f007a4be3df228791ff6c4aedcbe7bb09bd9225c3554f538aca4a584" 255 | dependencies = [ 256 | "actix-codec", 257 | "actix-http", 258 | "actix-router", 259 | "actix-rt", 260 | "actix-server", 261 | "actix-server-config", 262 | "actix-service", 263 | "actix-testing", 264 | "actix-threadpool", 265 | "actix-utils", 266 | "actix-web-codegen", 267 | "awc", 268 | "bytes", 269 | "derive_more 0.15.0", 270 | "encoding_rs", 271 | "futures", 272 | "hashbrown 0.5.0", 273 | "log", 274 | "mime", 275 | "net2", 276 | "parking_lot 0.9.0", 277 | "regex", 278 | "serde", 279 | "serde_json", 280 | "serde_urlencoded", 281 | "time", 282 | "url 2.1.0", 283 | ] 284 | 285 | [[package]] 286 | name = "actix-web-actors" 287 | version = "1.0.2" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "008c1b686048a16fef4ef2fc6b6e5fcf5f29829891ad87fc0848ade26b285627" 290 | dependencies = [ 291 | "actix", 292 | "actix-codec", 293 | "actix-http", 294 | "actix-web", 295 | "bytes", 296 | "futures", 297 | ] 298 | 299 | [[package]] 300 | name = "actix-web-codegen" 301 | version = "0.1.3" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "068a33520e21c1eea89726be4d6b3ce2e6b81046904367e1677287695a043abb" 304 | dependencies = [ 305 | "proc-macro2 1.0.5", 306 | "quote 1.0.2", 307 | "syn 1.0.5", 308 | ] 309 | 310 | [[package]] 311 | name = "actix_derive" 312 | version = "0.4.0" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "0bf5f6d7bf2d220ae8b4a7ae02a572bb35b7c4806b24049af905ab8110de156c" 315 | dependencies = [ 316 | "proc-macro2 0.4.30", 317 | "quote 0.6.13", 318 | "syn 0.15.44", 319 | ] 320 | 321 | [[package]] 322 | name = "adler32" 323 | version = "1.0.4" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" 326 | 327 | [[package]] 328 | name = "aho-corasick" 329 | version = "0.7.6" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" 332 | dependencies = [ 333 | "memchr", 334 | ] 335 | 336 | [[package]] 337 | name = "ansi_term" 338 | version = "0.11.0" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 341 | dependencies = [ 342 | "winapi 0.3.8", 343 | ] 344 | 345 | [[package]] 346 | name = "arc-swap" 347 | version = "0.4.3" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "f1a1eca3195b729bbd64e292ef2f5fff6b1c28504fed762ce2b1013dde4d8e92" 350 | 351 | [[package]] 352 | name = "atty" 353 | version = "0.2.13" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" 356 | dependencies = [ 357 | "libc", 358 | "winapi 0.3.8", 359 | ] 360 | 361 | [[package]] 362 | name = "autocfg" 363 | version = "0.1.6" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "b671c8fb71b457dd4ae18c4ba1e59aa81793daacc361d82fcd410cef0d491875" 366 | 367 | [[package]] 368 | name = "autocfg" 369 | version = "1.0.0" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 372 | 373 | [[package]] 374 | name = "awc" 375 | version = "0.2.7" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "364537de6ac9f996780f9dd097d6c4ca7c91dd5735153e9fb545037479becd10" 378 | dependencies = [ 379 | "actix-codec", 380 | "actix-http", 381 | "actix-service", 382 | "base64", 383 | "bytes", 384 | "derive_more 0.15.0", 385 | "futures", 386 | "log", 387 | "mime", 388 | "percent-encoding 2.1.0", 389 | "rand 0.7.2", 390 | "serde", 391 | "serde_json", 392 | "serde_urlencoded", 393 | "tokio-timer", 394 | ] 395 | 396 | [[package]] 397 | name = "backtrace" 398 | version = "0.3.38" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "690a62be8920ccf773ee00ef0968649b0e724cda8bd5b12286302b4ae955fdf5" 401 | dependencies = [ 402 | "backtrace-sys", 403 | "cfg-if", 404 | "libc", 405 | "rustc-demangle", 406 | ] 407 | 408 | [[package]] 409 | name = "backtrace-sys" 410 | version = "0.1.31" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "82a830b4ef2d1124a711c71d263c5abdc710ef8e907bd508c88be475cebc422b" 413 | dependencies = [ 414 | "cc", 415 | "libc", 416 | ] 417 | 418 | [[package]] 419 | name = "base64" 420 | version = "0.10.1" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" 423 | dependencies = [ 424 | "byteorder", 425 | ] 426 | 427 | [[package]] 428 | name = "bitflags" 429 | version = "1.2.1" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 432 | 433 | [[package]] 434 | name = "block-buffer" 435 | version = "0.7.3" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" 438 | dependencies = [ 439 | "block-padding", 440 | "byte-tools", 441 | "byteorder", 442 | "generic-array", 443 | ] 444 | 445 | [[package]] 446 | name = "block-padding" 447 | version = "0.1.4" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "6d4dc3af3ee2e12f3e5d224e5e1e3d73668abbeb69e566d361f7d5563a4fdf09" 450 | dependencies = [ 451 | "byte-tools", 452 | ] 453 | 454 | [[package]] 455 | name = "brotli-sys" 456 | version = "0.3.2" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "4445dea95f4c2b41cde57cc9fee236ae4dbae88d8fcbdb4750fc1bb5d86aaecd" 459 | dependencies = [ 460 | "cc", 461 | "libc", 462 | ] 463 | 464 | [[package]] 465 | name = "brotli2" 466 | version = "0.3.2" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "0cb036c3eade309815c15ddbacec5b22c4d1f3983a774ab2eac2e3e9ea85568e" 469 | dependencies = [ 470 | "brotli-sys", 471 | "libc", 472 | ] 473 | 474 | [[package]] 475 | name = "bstr" 476 | version = "0.2.13" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | checksum = "31accafdb70df7871592c058eca3985b71104e15ac32f64706022c58867da931" 479 | dependencies = [ 480 | "memchr", 481 | ] 482 | 483 | [[package]] 484 | name = "byte-tools" 485 | version = "0.3.1" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" 488 | 489 | [[package]] 490 | name = "byteorder" 491 | version = "1.3.2" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" 494 | 495 | [[package]] 496 | name = "bytes" 497 | version = "0.4.12" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" 500 | dependencies = [ 501 | "byteorder", 502 | "iovec", 503 | ] 504 | 505 | [[package]] 506 | name = "c2-chacha" 507 | version = "0.2.3" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" 510 | dependencies = [ 511 | "ppv-lite86", 512 | ] 513 | 514 | [[package]] 515 | name = "cc" 516 | version = "1.0.45" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "4fc9a35e1f4290eb9e5fc54ba6cf40671ed2a2514c3eeb2b2a908dda2ea5a1be" 519 | 520 | [[package]] 521 | name = "cfg-if" 522 | version = "0.1.10" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 525 | 526 | [[package]] 527 | name = "chrono" 528 | version = "0.4.9" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "e8493056968583b0193c1bb04d6f7684586f3726992d6c573261941a895dbd68" 531 | dependencies = [ 532 | "libc", 533 | "num-integer", 534 | "num-traits", 535 | "serde", 536 | "time", 537 | ] 538 | 539 | [[package]] 540 | name = "chrono-tz" 541 | version = "0.5.2" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "65d96be7c3e993c9ee4356442db24ba364c924b6b8331866be6b6952bfe74b9d" 544 | dependencies = [ 545 | "chrono", 546 | "parse-zoneinfo", 547 | ] 548 | 549 | [[package]] 550 | name = "clap" 551 | version = "2.33.0" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | checksum = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" 554 | dependencies = [ 555 | "ansi_term", 556 | "atty", 557 | "bitflags", 558 | "strsim", 559 | "textwrap", 560 | "unicode-width", 561 | "vec_map", 562 | ] 563 | 564 | [[package]] 565 | name = "clicolors-control" 566 | version = "1.0.1" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "90082ee5dcdd64dc4e9e0d37fbf3ee325419e39c0092191e0393df65518f741e" 569 | dependencies = [ 570 | "atty", 571 | "lazy_static", 572 | "libc", 573 | "winapi 0.3.8", 574 | ] 575 | 576 | [[package]] 577 | name = "cloudabi" 578 | version = "0.0.3" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" 581 | dependencies = [ 582 | "bitflags", 583 | ] 584 | 585 | [[package]] 586 | name = "colored" 587 | version = "2.0.0" 588 | source = "registry+https://github.com/rust-lang/crates.io-index" 589 | checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" 590 | dependencies = [ 591 | "atty", 592 | "lazy_static", 593 | "winapi 0.3.8", 594 | ] 595 | 596 | [[package]] 597 | name = "console" 598 | version = "0.9.1" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "f5d540c2d34ac9dd0deb5f3b5f54c36c79efa78f6b3ad19106a554d07a7b5d9f" 601 | dependencies = [ 602 | "clicolors-control", 603 | "encode_unicode", 604 | "lazy_static", 605 | "libc", 606 | "regex", 607 | "termios", 608 | "unicode-width", 609 | "winapi 0.3.8", 610 | ] 611 | 612 | [[package]] 613 | name = "copy_dir" 614 | version = "0.1.2" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "6e4281031634644843bd2f5aa9c48cf98fc48d6b083bd90bb11becf10deaf8b0" 617 | dependencies = [ 618 | "walkdir 0.1.8", 619 | ] 620 | 621 | [[package]] 622 | name = "copyless" 623 | version = "0.1.4" 624 | source = "registry+https://github.com/rust-lang/crates.io-index" 625 | checksum = "6ff9c56c9fb2a49c05ef0e431485a22400af20d33226dc0764d891d09e724127" 626 | 627 | [[package]] 628 | name = "crc32fast" 629 | version = "1.2.0" 630 | source = "registry+https://github.com/rust-lang/crates.io-index" 631 | checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" 632 | dependencies = [ 633 | "cfg-if", 634 | ] 635 | 636 | [[package]] 637 | name = "crossbeam-channel" 638 | version = "0.3.9" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "c8ec7fcd21571dc78f96cc96243cab8d8f035247c3efd16c687be154c3fa9efa" 641 | dependencies = [ 642 | "crossbeam-utils 0.6.6", 643 | ] 644 | 645 | [[package]] 646 | name = "crossbeam-utils" 647 | version = "0.6.6" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" 650 | dependencies = [ 651 | "cfg-if", 652 | "lazy_static", 653 | ] 654 | 655 | [[package]] 656 | name = "crossbeam-utils" 657 | version = "0.7.2" 658 | source = "registry+https://github.com/rust-lang/crates.io-index" 659 | checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" 660 | dependencies = [ 661 | "autocfg 1.0.0", 662 | "cfg-if", 663 | "lazy_static", 664 | ] 665 | 666 | [[package]] 667 | name = "derive_more" 668 | version = "0.14.1" 669 | source = "registry+https://github.com/rust-lang/crates.io-index" 670 | checksum = "6d944ac6003ed268757ef1ee686753b57efc5fcf0ebe7b64c9fc81e7e32ff839" 671 | dependencies = [ 672 | "proc-macro2 0.4.30", 673 | "quote 0.6.13", 674 | "rustc_version", 675 | "syn 0.15.44", 676 | ] 677 | 678 | [[package]] 679 | name = "derive_more" 680 | version = "0.15.0" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "7a141330240c921ec6d074a3e188a7c7ef95668bb95e7d44fa0e5778ec2a7afe" 683 | dependencies = [ 684 | "lazy_static", 685 | "proc-macro2 0.4.30", 686 | "quote 0.6.13", 687 | "regex", 688 | "rustc_version", 689 | "syn 0.15.44", 690 | ] 691 | 692 | [[package]] 693 | name = "deunicode" 694 | version = "0.4.3" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | checksum = "850878694b7933ca4c9569d30a34b55031b9b139ee1fc7b94a527c4ef960d690" 697 | 698 | [[package]] 699 | name = "digest" 700 | version = "0.8.1" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" 703 | dependencies = [ 704 | "generic-array", 705 | ] 706 | 707 | [[package]] 708 | name = "dtoa" 709 | version = "0.4.4" 710 | source = "registry+https://github.com/rust-lang/crates.io-index" 711 | checksum = "ea57b42383d091c85abcc2706240b94ab2a8fa1fc81c10ff23c4de06e2a90b5e" 712 | 713 | [[package]] 714 | name = "either" 715 | version = "1.5.3" 716 | source = "registry+https://github.com/rust-lang/crates.io-index" 717 | checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" 718 | 719 | [[package]] 720 | name = "encode_unicode" 721 | version = "0.3.6" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 724 | 725 | [[package]] 726 | name = "encoding_rs" 727 | version = "0.8.20" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "87240518927716f79692c2ed85bfe6e98196d18c6401ec75355760233a7e12e9" 730 | dependencies = [ 731 | "cfg-if", 732 | ] 733 | 734 | [[package]] 735 | name = "enum-as-inner" 736 | version = "0.2.1" 737 | source = "registry+https://github.com/rust-lang/crates.io-index" 738 | checksum = "3d58266c97445680766be408285e798d3401c6d4c378ec5552e78737e681e37d" 739 | dependencies = [ 740 | "proc-macro2 0.4.30", 741 | "quote 0.6.13", 742 | "syn 0.15.44", 743 | ] 744 | 745 | [[package]] 746 | name = "env_logger" 747 | version = "0.7.1" 748 | source = "registry+https://github.com/rust-lang/crates.io-index" 749 | checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" 750 | dependencies = [ 751 | "atty", 752 | "humantime", 753 | "log", 754 | "regex", 755 | "termcolor", 756 | ] 757 | 758 | [[package]] 759 | name = "failure" 760 | version = "0.1.6" 761 | source = "registry+https://github.com/rust-lang/crates.io-index" 762 | checksum = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9" 763 | dependencies = [ 764 | "backtrace", 765 | "failure_derive", 766 | ] 767 | 768 | [[package]] 769 | name = "failure_derive" 770 | version = "0.1.6" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08" 773 | dependencies = [ 774 | "proc-macro2 1.0.5", 775 | "quote 1.0.2", 776 | "syn 1.0.5", 777 | "synstructure", 778 | ] 779 | 780 | [[package]] 781 | name = "fake-simd" 782 | version = "0.1.2" 783 | source = "registry+https://github.com/rust-lang/crates.io-index" 784 | checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" 785 | 786 | [[package]] 787 | name = "filetime" 788 | version = "0.2.7" 789 | source = "registry+https://github.com/rust-lang/crates.io-index" 790 | checksum = "6bd7380b54ced79dda72ecc35cc4fbbd1da6bba54afaa37e96fd1c2a308cd469" 791 | dependencies = [ 792 | "cfg-if", 793 | "libc", 794 | "redox_syscall", 795 | "winapi 0.3.8", 796 | ] 797 | 798 | [[package]] 799 | name = "flate2" 800 | version = "1.0.12" 801 | source = "registry+https://github.com/rust-lang/crates.io-index" 802 | checksum = "ad3c5233c9a940c8719031b423d7e6c16af66e031cb0420b0896f5245bf181d3" 803 | dependencies = [ 804 | "cfg-if", 805 | "crc32fast", 806 | "libc", 807 | "miniz-sys", 808 | "miniz_oxide", 809 | ] 810 | 811 | [[package]] 812 | name = "fnv" 813 | version = "1.0.6" 814 | source = "registry+https://github.com/rust-lang/crates.io-index" 815 | checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" 816 | 817 | [[package]] 818 | name = "fs2" 819 | version = "0.4.3" 820 | source = "registry+https://github.com/rust-lang/crates.io-index" 821 | checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" 822 | dependencies = [ 823 | "libc", 824 | "winapi 0.3.8", 825 | ] 826 | 827 | [[package]] 828 | name = "fsevent" 829 | version = "0.4.0" 830 | source = "registry+https://github.com/rust-lang/crates.io-index" 831 | checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6" 832 | dependencies = [ 833 | "bitflags", 834 | "fsevent-sys", 835 | ] 836 | 837 | [[package]] 838 | name = "fsevent-sys" 839 | version = "2.0.1" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0" 842 | dependencies = [ 843 | "libc", 844 | ] 845 | 846 | [[package]] 847 | name = "fuchsia-cprng" 848 | version = "0.1.1" 849 | source = "registry+https://github.com/rust-lang/crates.io-index" 850 | checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 851 | 852 | [[package]] 853 | name = "fuchsia-zircon" 854 | version = "0.3.3" 855 | source = "registry+https://github.com/rust-lang/crates.io-index" 856 | checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 857 | dependencies = [ 858 | "bitflags", 859 | "fuchsia-zircon-sys", 860 | ] 861 | 862 | [[package]] 863 | name = "fuchsia-zircon-sys" 864 | version = "0.3.3" 865 | source = "registry+https://github.com/rust-lang/crates.io-index" 866 | checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 867 | 868 | [[package]] 869 | name = "futures" 870 | version = "0.1.29" 871 | source = "registry+https://github.com/rust-lang/crates.io-index" 872 | checksum = "1b980f2816d6ee8673b6517b52cb0e808a180efc92e5c19d02cdda79066703ef" 873 | 874 | [[package]] 875 | name = "generic-array" 876 | version = "0.12.3" 877 | source = "registry+https://github.com/rust-lang/crates.io-index" 878 | checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" 879 | dependencies = [ 880 | "typenum", 881 | ] 882 | 883 | [[package]] 884 | name = "getrandom" 885 | version = "0.1.12" 886 | source = "registry+https://github.com/rust-lang/crates.io-index" 887 | checksum = "473a1265acc8ff1e808cd0a1af8cee3c2ee5200916058a2ca113c29f2d903571" 888 | dependencies = [ 889 | "cfg-if", 890 | "libc", 891 | "wasi", 892 | ] 893 | 894 | [[package]] 895 | name = "globset" 896 | version = "0.4.5" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "7ad1da430bd7281dde2576f44c84cc3f0f7b475e7202cd503042dff01a8c8120" 899 | dependencies = [ 900 | "aho-corasick", 901 | "bstr", 902 | "fnv", 903 | "log", 904 | "regex", 905 | ] 906 | 907 | [[package]] 908 | name = "globwalk" 909 | version = "0.8.0" 910 | source = "registry+https://github.com/rust-lang/crates.io-index" 911 | checksum = "178270263374052c40502e9f607134947de75302c1348d1a0e31db67c1691446" 912 | dependencies = [ 913 | "bitflags", 914 | "ignore", 915 | "walkdir 2.3.1", 916 | ] 917 | 918 | [[package]] 919 | name = "h2" 920 | version = "0.1.26" 921 | source = "registry+https://github.com/rust-lang/crates.io-index" 922 | checksum = "a5b34c246847f938a410a03c5458c7fee2274436675e76d8b903c08efc29c462" 923 | dependencies = [ 924 | "byteorder", 925 | "bytes", 926 | "fnv", 927 | "futures", 928 | "http", 929 | "indexmap", 930 | "log", 931 | "slab", 932 | "string", 933 | "tokio-io", 934 | ] 935 | 936 | [[package]] 937 | name = "hashbrown" 938 | version = "0.3.1" 939 | source = "registry+https://github.com/rust-lang/crates.io-index" 940 | checksum = "29fba9abe4742d586dfd0c06ae4f7e73a1c2d86b856933509b269d82cdf06e18" 941 | 942 | [[package]] 943 | name = "hashbrown" 944 | version = "0.5.0" 945 | source = "registry+https://github.com/rust-lang/crates.io-index" 946 | checksum = "e1de41fb8dba9714efd92241565cdff73f78508c95697dd56787d3cba27e2353" 947 | 948 | [[package]] 949 | name = "heck" 950 | version = "0.3.1" 951 | source = "registry+https://github.com/rust-lang/crates.io-index" 952 | checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" 953 | dependencies = [ 954 | "unicode-segmentation", 955 | ] 956 | 957 | [[package]] 958 | name = "hostname" 959 | version = "0.1.5" 960 | source = "registry+https://github.com/rust-lang/crates.io-index" 961 | checksum = "21ceb46a83a85e824ef93669c8b390009623863b5c195d1ba747292c0c72f94e" 962 | dependencies = [ 963 | "libc", 964 | "winutil", 965 | ] 966 | 967 | [[package]] 968 | name = "http" 969 | version = "0.1.19" 970 | source = "registry+https://github.com/rust-lang/crates.io-index" 971 | checksum = "d7e06e336150b178206af098a055e3621e8336027e2b4d126bda0bc64824baaf" 972 | dependencies = [ 973 | "bytes", 974 | "fnv", 975 | "itoa", 976 | ] 977 | 978 | [[package]] 979 | name = "httparse" 980 | version = "1.3.4" 981 | source = "registry+https://github.com/rust-lang/crates.io-index" 982 | checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" 983 | 984 | [[package]] 985 | name = "humansize" 986 | version = "1.1.0" 987 | source = "registry+https://github.com/rust-lang/crates.io-index" 988 | checksum = "b6cab2627acfc432780848602f3f558f7e9dd427352224b0d9324025796d2a5e" 989 | 990 | [[package]] 991 | name = "humantime" 992 | version = "1.3.0" 993 | source = "registry+https://github.com/rust-lang/crates.io-index" 994 | checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" 995 | dependencies = [ 996 | "quick-error", 997 | ] 998 | 999 | [[package]] 1000 | name = "idna" 1001 | version = "0.1.5" 1002 | source = "registry+https://github.com/rust-lang/crates.io-index" 1003 | checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" 1004 | dependencies = [ 1005 | "matches", 1006 | "unicode-bidi", 1007 | "unicode-normalization", 1008 | ] 1009 | 1010 | [[package]] 1011 | name = "idna" 1012 | version = "0.2.0" 1013 | source = "registry+https://github.com/rust-lang/crates.io-index" 1014 | checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" 1015 | dependencies = [ 1016 | "matches", 1017 | "unicode-bidi", 1018 | "unicode-normalization", 1019 | ] 1020 | 1021 | [[package]] 1022 | name = "ignore" 1023 | version = "0.4.16" 1024 | source = "registry+https://github.com/rust-lang/crates.io-index" 1025 | checksum = "22dcbf2a4a289528dbef21686354904e1c694ac642610a9bff9e7df730d9ec72" 1026 | dependencies = [ 1027 | "crossbeam-utils 0.7.2", 1028 | "globset", 1029 | "lazy_static", 1030 | "log", 1031 | "memchr", 1032 | "regex", 1033 | "same-file", 1034 | "thread_local", 1035 | "walkdir 2.3.1", 1036 | "winapi-util", 1037 | ] 1038 | 1039 | [[package]] 1040 | name = "indexmap" 1041 | version = "1.3.0" 1042 | source = "registry+https://github.com/rust-lang/crates.io-index" 1043 | checksum = "712d7b3ea5827fcb9d4fda14bf4da5f136f0db2ae9c8f4bd4e2d1c6fde4e6db2" 1044 | dependencies = [ 1045 | "autocfg 0.1.6", 1046 | ] 1047 | 1048 | [[package]] 1049 | name = "inotify" 1050 | version = "0.6.1" 1051 | source = "registry+https://github.com/rust-lang/crates.io-index" 1052 | checksum = "40b54539f3910d6f84fbf9a643efd6e3aa6e4f001426c0329576128255994718" 1053 | dependencies = [ 1054 | "bitflags", 1055 | "inotify-sys", 1056 | "libc", 1057 | ] 1058 | 1059 | [[package]] 1060 | name = "inotify-sys" 1061 | version = "0.1.3" 1062 | source = "registry+https://github.com/rust-lang/crates.io-index" 1063 | checksum = "e74a1aa87c59aeff6ef2cc2fa62d41bc43f54952f55652656b18a02fd5e356c0" 1064 | dependencies = [ 1065 | "libc", 1066 | ] 1067 | 1068 | [[package]] 1069 | name = "iovec" 1070 | version = "0.1.4" 1071 | source = "registry+https://github.com/rust-lang/crates.io-index" 1072 | checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" 1073 | dependencies = [ 1074 | "libc", 1075 | ] 1076 | 1077 | [[package]] 1078 | name = "ipconfig" 1079 | version = "0.2.1" 1080 | source = "registry+https://github.com/rust-lang/crates.io-index" 1081 | checksum = "aa79fa216fbe60834a9c0737d7fcd30425b32d1c58854663e24d4c4b328ed83f" 1082 | dependencies = [ 1083 | "socket2", 1084 | "widestring", 1085 | "winapi 0.3.8", 1086 | "winreg", 1087 | ] 1088 | 1089 | [[package]] 1090 | name = "itertools" 1091 | version = "0.9.0" 1092 | source = "registry+https://github.com/rust-lang/crates.io-index" 1093 | checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" 1094 | dependencies = [ 1095 | "either", 1096 | ] 1097 | 1098 | [[package]] 1099 | name = "itoa" 1100 | version = "0.4.4" 1101 | source = "registry+https://github.com/rust-lang/crates.io-index" 1102 | checksum = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" 1103 | 1104 | [[package]] 1105 | name = "kernel32-sys" 1106 | version = "0.2.2" 1107 | source = "registry+https://github.com/rust-lang/crates.io-index" 1108 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 1109 | dependencies = [ 1110 | "winapi 0.2.8", 1111 | "winapi-build", 1112 | ] 1113 | 1114 | [[package]] 1115 | name = "language-tags" 1116 | version = "0.2.2" 1117 | source = "registry+https://github.com/rust-lang/crates.io-index" 1118 | checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" 1119 | 1120 | [[package]] 1121 | name = "lazy_static" 1122 | version = "1.4.0" 1123 | source = "registry+https://github.com/rust-lang/crates.io-index" 1124 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 1125 | 1126 | [[package]] 1127 | name = "lazycell" 1128 | version = "1.2.1" 1129 | source = "registry+https://github.com/rust-lang/crates.io-index" 1130 | checksum = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" 1131 | 1132 | [[package]] 1133 | name = "libc" 1134 | version = "0.2.62" 1135 | source = "registry+https://github.com/rust-lang/crates.io-index" 1136 | checksum = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba" 1137 | 1138 | [[package]] 1139 | name = "linked-hash-map" 1140 | version = "0.5.2" 1141 | source = "registry+https://github.com/rust-lang/crates.io-index" 1142 | checksum = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83" 1143 | 1144 | [[package]] 1145 | name = "lock_api" 1146 | version = "0.2.0" 1147 | source = "registry+https://github.com/rust-lang/crates.io-index" 1148 | checksum = "ed946d4529956a20f2d63ebe1b69996d5a2137c91913fe3ebbeff957f5bca7ff" 1149 | dependencies = [ 1150 | "scopeguard", 1151 | ] 1152 | 1153 | [[package]] 1154 | name = "lock_api" 1155 | version = "0.3.1" 1156 | source = "registry+https://github.com/rust-lang/crates.io-index" 1157 | checksum = "f8912e782533a93a167888781b836336a6ca5da6175c05944c86cf28c31104dc" 1158 | dependencies = [ 1159 | "scopeguard", 1160 | ] 1161 | 1162 | [[package]] 1163 | name = "log" 1164 | version = "0.4.8" 1165 | source = "registry+https://github.com/rust-lang/crates.io-index" 1166 | checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" 1167 | dependencies = [ 1168 | "cfg-if", 1169 | ] 1170 | 1171 | [[package]] 1172 | name = "lru-cache" 1173 | version = "0.1.2" 1174 | source = "registry+https://github.com/rust-lang/crates.io-index" 1175 | checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" 1176 | dependencies = [ 1177 | "linked-hash-map", 1178 | ] 1179 | 1180 | [[package]] 1181 | name = "maplit" 1182 | version = "1.0.2" 1183 | source = "registry+https://github.com/rust-lang/crates.io-index" 1184 | checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" 1185 | 1186 | [[package]] 1187 | name = "matches" 1188 | version = "0.1.8" 1189 | source = "registry+https://github.com/rust-lang/crates.io-index" 1190 | checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" 1191 | 1192 | [[package]] 1193 | name = "memchr" 1194 | version = "2.2.1" 1195 | source = "registry+https://github.com/rust-lang/crates.io-index" 1196 | checksum = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" 1197 | 1198 | [[package]] 1199 | name = "mime" 1200 | version = "0.3.14" 1201 | source = "registry+https://github.com/rust-lang/crates.io-index" 1202 | checksum = "dd1d63acd1b78403cc0c325605908475dd9b9a3acbf65ed8bcab97e27014afcf" 1203 | 1204 | [[package]] 1205 | name = "mime_guess" 1206 | version = "2.0.1" 1207 | source = "registry+https://github.com/rust-lang/crates.io-index" 1208 | checksum = "1a0ed03949aef72dbdf3116a383d7b38b4768e6f960528cd6a6044aa9ed68599" 1209 | dependencies = [ 1210 | "mime", 1211 | "unicase", 1212 | ] 1213 | 1214 | [[package]] 1215 | name = "miniz-sys" 1216 | version = "0.1.12" 1217 | source = "registry+https://github.com/rust-lang/crates.io-index" 1218 | checksum = "1e9e3ae51cea1576ceba0dde3d484d30e6e5b86dee0b2d412fe3a16a15c98202" 1219 | dependencies = [ 1220 | "cc", 1221 | "libc", 1222 | ] 1223 | 1224 | [[package]] 1225 | name = "miniz_oxide" 1226 | version = "0.3.3" 1227 | source = "registry+https://github.com/rust-lang/crates.io-index" 1228 | checksum = "304f66c19be2afa56530fa7c39796192eef38618da8d19df725ad7c6d6b2aaae" 1229 | dependencies = [ 1230 | "adler32", 1231 | ] 1232 | 1233 | [[package]] 1234 | name = "mio" 1235 | version = "0.6.19" 1236 | source = "registry+https://github.com/rust-lang/crates.io-index" 1237 | checksum = "83f51996a3ed004ef184e16818edc51fadffe8e7ca68be67f9dee67d84d0ff23" 1238 | dependencies = [ 1239 | "fuchsia-zircon", 1240 | "fuchsia-zircon-sys", 1241 | "iovec", 1242 | "kernel32-sys", 1243 | "libc", 1244 | "log", 1245 | "miow", 1246 | "net2", 1247 | "slab", 1248 | "winapi 0.2.8", 1249 | ] 1250 | 1251 | [[package]] 1252 | name = "mio-extras" 1253 | version = "2.0.5" 1254 | source = "registry+https://github.com/rust-lang/crates.io-index" 1255 | checksum = "46e73a04c2fa6250b8d802134d56d554a9ec2922bf977777c805ea5def61ce40" 1256 | dependencies = [ 1257 | "lazycell", 1258 | "log", 1259 | "mio", 1260 | "slab", 1261 | ] 1262 | 1263 | [[package]] 1264 | name = "mio-uds" 1265 | version = "0.6.7" 1266 | source = "registry+https://github.com/rust-lang/crates.io-index" 1267 | checksum = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" 1268 | dependencies = [ 1269 | "iovec", 1270 | "libc", 1271 | "mio", 1272 | ] 1273 | 1274 | [[package]] 1275 | name = "miow" 1276 | version = "0.2.1" 1277 | source = "registry+https://github.com/rust-lang/crates.io-index" 1278 | checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" 1279 | dependencies = [ 1280 | "kernel32-sys", 1281 | "net2", 1282 | "winapi 0.2.8", 1283 | "ws2_32-sys", 1284 | ] 1285 | 1286 | [[package]] 1287 | name = "net2" 1288 | version = "0.2.33" 1289 | source = "registry+https://github.com/rust-lang/crates.io-index" 1290 | checksum = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" 1291 | dependencies = [ 1292 | "cfg-if", 1293 | "libc", 1294 | "winapi 0.3.8", 1295 | ] 1296 | 1297 | [[package]] 1298 | name = "nom" 1299 | version = "4.2.3" 1300 | source = "registry+https://github.com/rust-lang/crates.io-index" 1301 | checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" 1302 | dependencies = [ 1303 | "memchr", 1304 | "version_check", 1305 | ] 1306 | 1307 | [[package]] 1308 | name = "notify" 1309 | version = "4.0.14" 1310 | source = "registry+https://github.com/rust-lang/crates.io-index" 1311 | checksum = "199628fc33b21bc767baa057490b00b382ecbae030803a7b36292422d15b778b" 1312 | dependencies = [ 1313 | "bitflags", 1314 | "filetime", 1315 | "fsevent", 1316 | "fsevent-sys", 1317 | "inotify", 1318 | "kernel32-sys", 1319 | "libc", 1320 | "mio", 1321 | "mio-extras", 1322 | "walkdir 2.3.1", 1323 | "winapi 0.3.8", 1324 | ] 1325 | 1326 | [[package]] 1327 | name = "num-integer" 1328 | version = "0.1.41" 1329 | source = "registry+https://github.com/rust-lang/crates.io-index" 1330 | checksum = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" 1331 | dependencies = [ 1332 | "autocfg 0.1.6", 1333 | "num-traits", 1334 | ] 1335 | 1336 | [[package]] 1337 | name = "num-traits" 1338 | version = "0.2.8" 1339 | source = "registry+https://github.com/rust-lang/crates.io-index" 1340 | checksum = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" 1341 | dependencies = [ 1342 | "autocfg 0.1.6", 1343 | ] 1344 | 1345 | [[package]] 1346 | name = "num_cpus" 1347 | version = "1.10.1" 1348 | source = "registry+https://github.com/rust-lang/crates.io-index" 1349 | checksum = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" 1350 | dependencies = [ 1351 | "libc", 1352 | ] 1353 | 1354 | [[package]] 1355 | name = "opaque-debug" 1356 | version = "0.2.3" 1357 | source = "registry+https://github.com/rust-lang/crates.io-index" 1358 | checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" 1359 | 1360 | [[package]] 1361 | name = "parking_lot" 1362 | version = "0.8.0" 1363 | source = "registry+https://github.com/rust-lang/crates.io-index" 1364 | checksum = "fa7767817701cce701d5585b9c4db3cdd02086398322c1d7e8bf5094a96a2ce7" 1365 | dependencies = [ 1366 | "lock_api 0.2.0", 1367 | "parking_lot_core 0.5.0", 1368 | "rustc_version", 1369 | ] 1370 | 1371 | [[package]] 1372 | name = "parking_lot" 1373 | version = "0.9.0" 1374 | source = "registry+https://github.com/rust-lang/crates.io-index" 1375 | checksum = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" 1376 | dependencies = [ 1377 | "lock_api 0.3.1", 1378 | "parking_lot_core 0.6.2", 1379 | "rustc_version", 1380 | ] 1381 | 1382 | [[package]] 1383 | name = "parking_lot_core" 1384 | version = "0.5.0" 1385 | source = "registry+https://github.com/rust-lang/crates.io-index" 1386 | checksum = "cb88cb1cb3790baa6776844f968fea3be44956cf184fa1be5a03341f5491278c" 1387 | dependencies = [ 1388 | "cfg-if", 1389 | "cloudabi", 1390 | "libc", 1391 | "rand 0.6.5", 1392 | "redox_syscall", 1393 | "rustc_version", 1394 | "smallvec", 1395 | "winapi 0.3.8", 1396 | ] 1397 | 1398 | [[package]] 1399 | name = "parking_lot_core" 1400 | version = "0.6.2" 1401 | source = "registry+https://github.com/rust-lang/crates.io-index" 1402 | checksum = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" 1403 | dependencies = [ 1404 | "cfg-if", 1405 | "cloudabi", 1406 | "libc", 1407 | "redox_syscall", 1408 | "rustc_version", 1409 | "smallvec", 1410 | "winapi 0.3.8", 1411 | ] 1412 | 1413 | [[package]] 1414 | name = "parse-zoneinfo" 1415 | version = "0.3.0" 1416 | source = "registry+https://github.com/rust-lang/crates.io-index" 1417 | checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41" 1418 | dependencies = [ 1419 | "regex", 1420 | ] 1421 | 1422 | [[package]] 1423 | name = "percent-encoding" 1424 | version = "1.0.1" 1425 | source = "registry+https://github.com/rust-lang/crates.io-index" 1426 | checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" 1427 | 1428 | [[package]] 1429 | name = "percent-encoding" 1430 | version = "2.1.0" 1431 | source = "registry+https://github.com/rust-lang/crates.io-index" 1432 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 1433 | 1434 | [[package]] 1435 | name = "pest" 1436 | version = "2.1.2" 1437 | source = "registry+https://github.com/rust-lang/crates.io-index" 1438 | checksum = "7e4fb201c5c22a55d8b24fef95f78be52738e5e1361129be1b5e862ecdb6894a" 1439 | dependencies = [ 1440 | "ucd-trie", 1441 | ] 1442 | 1443 | [[package]] 1444 | name = "pest_derive" 1445 | version = "2.1.0" 1446 | source = "registry+https://github.com/rust-lang/crates.io-index" 1447 | checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" 1448 | dependencies = [ 1449 | "pest", 1450 | "pest_generator", 1451 | ] 1452 | 1453 | [[package]] 1454 | name = "pest_generator" 1455 | version = "2.1.1" 1456 | source = "registry+https://github.com/rust-lang/crates.io-index" 1457 | checksum = "7b9fcf299b5712d06ee128a556c94709aaa04512c4dffb8ead07c5c998447fc0" 1458 | dependencies = [ 1459 | "pest", 1460 | "pest_meta", 1461 | "proc-macro2 1.0.5", 1462 | "quote 1.0.2", 1463 | "syn 1.0.5", 1464 | ] 1465 | 1466 | [[package]] 1467 | name = "pest_meta" 1468 | version = "2.1.2" 1469 | source = "registry+https://github.com/rust-lang/crates.io-index" 1470 | checksum = "df43fd99896fd72c485fe47542c7b500e4ac1e8700bf995544d1317a60ded547" 1471 | dependencies = [ 1472 | "maplit", 1473 | "pest", 1474 | "sha-1", 1475 | ] 1476 | 1477 | [[package]] 1478 | name = "ppv-lite86" 1479 | version = "0.2.6" 1480 | source = "registry+https://github.com/rust-lang/crates.io-index" 1481 | checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" 1482 | 1483 | [[package]] 1484 | name = "proc-macro-error" 1485 | version = "0.2.6" 1486 | source = "registry+https://github.com/rust-lang/crates.io-index" 1487 | checksum = "aeccfe4d5d8ea175d5f0e4a2ad0637e0f4121d63bd99d356fb1f39ab2e7c6097" 1488 | dependencies = [ 1489 | "proc-macro2 1.0.5", 1490 | "quote 1.0.2", 1491 | "syn 1.0.5", 1492 | ] 1493 | 1494 | [[package]] 1495 | name = "proc-macro2" 1496 | version = "0.4.30" 1497 | source = "registry+https://github.com/rust-lang/crates.io-index" 1498 | checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" 1499 | dependencies = [ 1500 | "unicode-xid 0.1.0", 1501 | ] 1502 | 1503 | [[package]] 1504 | name = "proc-macro2" 1505 | version = "1.0.5" 1506 | source = "registry+https://github.com/rust-lang/crates.io-index" 1507 | checksum = "90cf5f418035b98e655e9cdb225047638296b862b42411c4e45bb88d700f7fc0" 1508 | dependencies = [ 1509 | "unicode-xid 0.2.0", 1510 | ] 1511 | 1512 | [[package]] 1513 | name = "pulldown-cmark" 1514 | version = "0.6.1" 1515 | source = "registry+https://github.com/rust-lang/crates.io-index" 1516 | checksum = "1c205cc82214f3594e2d50686730314f817c67ffa80fe800cf0db78c3c2b9d9e" 1517 | dependencies = [ 1518 | "bitflags", 1519 | "memchr", 1520 | "unicase", 1521 | ] 1522 | 1523 | [[package]] 1524 | name = "quick-error" 1525 | version = "1.2.2" 1526 | source = "registry+https://github.com/rust-lang/crates.io-index" 1527 | checksum = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" 1528 | 1529 | [[package]] 1530 | name = "quote" 1531 | version = "0.6.13" 1532 | source = "registry+https://github.com/rust-lang/crates.io-index" 1533 | checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" 1534 | dependencies = [ 1535 | "proc-macro2 0.4.30", 1536 | ] 1537 | 1538 | [[package]] 1539 | name = "quote" 1540 | version = "1.0.2" 1541 | source = "registry+https://github.com/rust-lang/crates.io-index" 1542 | checksum = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" 1543 | dependencies = [ 1544 | "proc-macro2 1.0.5", 1545 | ] 1546 | 1547 | [[package]] 1548 | name = "rand" 1549 | version = "0.6.5" 1550 | source = "registry+https://github.com/rust-lang/crates.io-index" 1551 | checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" 1552 | dependencies = [ 1553 | "autocfg 0.1.6", 1554 | "libc", 1555 | "rand_chacha 0.1.1", 1556 | "rand_core 0.4.2", 1557 | "rand_hc 0.1.0", 1558 | "rand_isaac", 1559 | "rand_jitter", 1560 | "rand_os", 1561 | "rand_pcg", 1562 | "rand_xorshift", 1563 | "winapi 0.3.8", 1564 | ] 1565 | 1566 | [[package]] 1567 | name = "rand" 1568 | version = "0.7.2" 1569 | source = "registry+https://github.com/rust-lang/crates.io-index" 1570 | checksum = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412" 1571 | dependencies = [ 1572 | "getrandom", 1573 | "libc", 1574 | "rand_chacha 0.2.1", 1575 | "rand_core 0.5.1", 1576 | "rand_hc 0.2.0", 1577 | ] 1578 | 1579 | [[package]] 1580 | name = "rand_chacha" 1581 | version = "0.1.1" 1582 | source = "registry+https://github.com/rust-lang/crates.io-index" 1583 | checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" 1584 | dependencies = [ 1585 | "autocfg 0.1.6", 1586 | "rand_core 0.3.1", 1587 | ] 1588 | 1589 | [[package]] 1590 | name = "rand_chacha" 1591 | version = "0.2.1" 1592 | source = "registry+https://github.com/rust-lang/crates.io-index" 1593 | checksum = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" 1594 | dependencies = [ 1595 | "c2-chacha", 1596 | "rand_core 0.5.1", 1597 | ] 1598 | 1599 | [[package]] 1600 | name = "rand_core" 1601 | version = "0.3.1" 1602 | source = "registry+https://github.com/rust-lang/crates.io-index" 1603 | checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 1604 | dependencies = [ 1605 | "rand_core 0.4.2", 1606 | ] 1607 | 1608 | [[package]] 1609 | name = "rand_core" 1610 | version = "0.4.2" 1611 | source = "registry+https://github.com/rust-lang/crates.io-index" 1612 | checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" 1613 | 1614 | [[package]] 1615 | name = "rand_core" 1616 | version = "0.5.1" 1617 | source = "registry+https://github.com/rust-lang/crates.io-index" 1618 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 1619 | dependencies = [ 1620 | "getrandom", 1621 | ] 1622 | 1623 | [[package]] 1624 | name = "rand_hc" 1625 | version = "0.1.0" 1626 | source = "registry+https://github.com/rust-lang/crates.io-index" 1627 | checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" 1628 | dependencies = [ 1629 | "rand_core 0.3.1", 1630 | ] 1631 | 1632 | [[package]] 1633 | name = "rand_hc" 1634 | version = "0.2.0" 1635 | source = "registry+https://github.com/rust-lang/crates.io-index" 1636 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 1637 | dependencies = [ 1638 | "rand_core 0.5.1", 1639 | ] 1640 | 1641 | [[package]] 1642 | name = "rand_isaac" 1643 | version = "0.1.1" 1644 | source = "registry+https://github.com/rust-lang/crates.io-index" 1645 | checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" 1646 | dependencies = [ 1647 | "rand_core 0.3.1", 1648 | ] 1649 | 1650 | [[package]] 1651 | name = "rand_jitter" 1652 | version = "0.1.4" 1653 | source = "registry+https://github.com/rust-lang/crates.io-index" 1654 | checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" 1655 | dependencies = [ 1656 | "libc", 1657 | "rand_core 0.4.2", 1658 | "winapi 0.3.8", 1659 | ] 1660 | 1661 | [[package]] 1662 | name = "rand_os" 1663 | version = "0.1.3" 1664 | source = "registry+https://github.com/rust-lang/crates.io-index" 1665 | checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" 1666 | dependencies = [ 1667 | "cloudabi", 1668 | "fuchsia-cprng", 1669 | "libc", 1670 | "rand_core 0.4.2", 1671 | "rdrand", 1672 | "winapi 0.3.8", 1673 | ] 1674 | 1675 | [[package]] 1676 | name = "rand_pcg" 1677 | version = "0.1.2" 1678 | source = "registry+https://github.com/rust-lang/crates.io-index" 1679 | checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" 1680 | dependencies = [ 1681 | "autocfg 0.1.6", 1682 | "rand_core 0.4.2", 1683 | ] 1684 | 1685 | [[package]] 1686 | name = "rand_xorshift" 1687 | version = "0.1.1" 1688 | source = "registry+https://github.com/rust-lang/crates.io-index" 1689 | checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" 1690 | dependencies = [ 1691 | "rand_core 0.3.1", 1692 | ] 1693 | 1694 | [[package]] 1695 | name = "rdrand" 1696 | version = "0.4.0" 1697 | source = "registry+https://github.com/rust-lang/crates.io-index" 1698 | checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 1699 | dependencies = [ 1700 | "rand_core 0.3.1", 1701 | ] 1702 | 1703 | [[package]] 1704 | name = "redox_syscall" 1705 | version = "0.1.40" 1706 | source = "registry+https://github.com/rust-lang/crates.io-index" 1707 | checksum = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1" 1708 | 1709 | [[package]] 1710 | name = "regex" 1711 | version = "1.3.9" 1712 | source = "registry+https://github.com/rust-lang/crates.io-index" 1713 | checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" 1714 | dependencies = [ 1715 | "aho-corasick", 1716 | "memchr", 1717 | "regex-syntax", 1718 | "thread_local", 1719 | ] 1720 | 1721 | [[package]] 1722 | name = "regex-syntax" 1723 | version = "0.6.18" 1724 | source = "registry+https://github.com/rust-lang/crates.io-index" 1725 | checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" 1726 | 1727 | [[package]] 1728 | name = "remove_dir_all" 1729 | version = "0.5.3" 1730 | source = "registry+https://github.com/rust-lang/crates.io-index" 1731 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 1732 | dependencies = [ 1733 | "winapi 0.3.8", 1734 | ] 1735 | 1736 | [[package]] 1737 | name = "resolv-conf" 1738 | version = "0.6.2" 1739 | source = "registry+https://github.com/rust-lang/crates.io-index" 1740 | checksum = "b263b4aa1b5de9ffc0054a2386f96992058bb6870aab516f8cdeb8a667d56dcb" 1741 | dependencies = [ 1742 | "hostname", 1743 | "quick-error", 1744 | ] 1745 | 1746 | [[package]] 1747 | name = "rustc-demangle" 1748 | version = "0.1.16" 1749 | source = "registry+https://github.com/rust-lang/crates.io-index" 1750 | checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" 1751 | 1752 | [[package]] 1753 | name = "rustc_version" 1754 | version = "0.2.3" 1755 | source = "registry+https://github.com/rust-lang/crates.io-index" 1756 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 1757 | dependencies = [ 1758 | "semver", 1759 | ] 1760 | 1761 | [[package]] 1762 | name = "ryu" 1763 | version = "1.0.0" 1764 | source = "registry+https://github.com/rust-lang/crates.io-index" 1765 | checksum = "c92464b447c0ee8c4fb3824ecc8383b81717b9f1e74ba2e72540aef7b9f82997" 1766 | 1767 | [[package]] 1768 | name = "same-file" 1769 | version = "1.0.5" 1770 | source = "registry+https://github.com/rust-lang/crates.io-index" 1771 | checksum = "585e8ddcedc187886a30fa705c47985c3fa88d06624095856b36ca0b82ff4421" 1772 | dependencies = [ 1773 | "winapi-util", 1774 | ] 1775 | 1776 | [[package]] 1777 | name = "scopeguard" 1778 | version = "1.0.0" 1779 | source = "registry+https://github.com/rust-lang/crates.io-index" 1780 | checksum = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" 1781 | 1782 | [[package]] 1783 | name = "semver" 1784 | version = "0.9.0" 1785 | source = "registry+https://github.com/rust-lang/crates.io-index" 1786 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 1787 | dependencies = [ 1788 | "semver-parser", 1789 | ] 1790 | 1791 | [[package]] 1792 | name = "semver-parser" 1793 | version = "0.7.0" 1794 | source = "registry+https://github.com/rust-lang/crates.io-index" 1795 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 1796 | 1797 | [[package]] 1798 | name = "serde" 1799 | version = "1.0.101" 1800 | source = "registry+https://github.com/rust-lang/crates.io-index" 1801 | checksum = "9796c9b7ba2ffe7a9ce53c2287dfc48080f4b2b362fcc245a259b3a7201119dd" 1802 | dependencies = [ 1803 | "serde_derive", 1804 | ] 1805 | 1806 | [[package]] 1807 | name = "serde_derive" 1808 | version = "1.0.101" 1809 | source = "registry+https://github.com/rust-lang/crates.io-index" 1810 | checksum = "4b133a43a1ecd55d4086bd5b4dc6c1751c68b1bfbeba7a5040442022c7e7c02e" 1811 | dependencies = [ 1812 | "proc-macro2 1.0.5", 1813 | "quote 1.0.2", 1814 | "syn 1.0.5", 1815 | ] 1816 | 1817 | [[package]] 1818 | name = "serde_json" 1819 | version = "1.0.57" 1820 | source = "registry+https://github.com/rust-lang/crates.io-index" 1821 | checksum = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c" 1822 | dependencies = [ 1823 | "itoa", 1824 | "ryu", 1825 | "serde", 1826 | ] 1827 | 1828 | [[package]] 1829 | name = "serde_urlencoded" 1830 | version = "0.6.1" 1831 | source = "registry+https://github.com/rust-lang/crates.io-index" 1832 | checksum = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97" 1833 | dependencies = [ 1834 | "dtoa", 1835 | "itoa", 1836 | "serde", 1837 | "url 2.1.0", 1838 | ] 1839 | 1840 | [[package]] 1841 | name = "sha-1" 1842 | version = "0.8.1" 1843 | source = "registry+https://github.com/rust-lang/crates.io-index" 1844 | checksum = "23962131a91661d643c98940b20fcaffe62d776a823247be80a48fcb8b6fce68" 1845 | dependencies = [ 1846 | "block-buffer", 1847 | "digest", 1848 | "fake-simd", 1849 | "opaque-debug", 1850 | ] 1851 | 1852 | [[package]] 1853 | name = "sha1" 1854 | version = "0.6.0" 1855 | source = "registry+https://github.com/rust-lang/crates.io-index" 1856 | checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" 1857 | 1858 | [[package]] 1859 | name = "signal-hook" 1860 | version = "0.1.10" 1861 | source = "registry+https://github.com/rust-lang/crates.io-index" 1862 | checksum = "4f61c4d59f3aaa9f61bba6450a9b80ba48362fd7d651689e7a10c453b1f6dc68" 1863 | dependencies = [ 1864 | "libc", 1865 | "signal-hook-registry", 1866 | ] 1867 | 1868 | [[package]] 1869 | name = "signal-hook-registry" 1870 | version = "1.1.1" 1871 | source = "registry+https://github.com/rust-lang/crates.io-index" 1872 | checksum = "1797d48f38f91643908bb14e35e79928f9f4b3cefb2420a564dde0991b4358dc" 1873 | dependencies = [ 1874 | "arc-swap", 1875 | "libc", 1876 | ] 1877 | 1878 | [[package]] 1879 | name = "slab" 1880 | version = "0.4.2" 1881 | source = "registry+https://github.com/rust-lang/crates.io-index" 1882 | checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" 1883 | 1884 | [[package]] 1885 | name = "slug" 1886 | version = "0.1.4" 1887 | source = "registry+https://github.com/rust-lang/crates.io-index" 1888 | checksum = "b3bc762e6a4b6c6fcaade73e77f9ebc6991b676f88bb2358bddb56560f073373" 1889 | dependencies = [ 1890 | "deunicode", 1891 | ] 1892 | 1893 | [[package]] 1894 | name = "smallvec" 1895 | version = "0.6.10" 1896 | source = "registry+https://github.com/rust-lang/crates.io-index" 1897 | checksum = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7" 1898 | 1899 | [[package]] 1900 | name = "socket2" 1901 | version = "0.3.11" 1902 | source = "registry+https://github.com/rust-lang/crates.io-index" 1903 | checksum = "e8b74de517221a2cb01a53349cf54182acdc31a074727d3079068448c0676d85" 1904 | dependencies = [ 1905 | "cfg-if", 1906 | "libc", 1907 | "redox_syscall", 1908 | "winapi 0.3.8", 1909 | ] 1910 | 1911 | [[package]] 1912 | name = "staple" 1913 | version = "0.3.2" 1914 | dependencies = [ 1915 | "actix", 1916 | "actix-files", 1917 | "actix-web", 1918 | "actix-web-actors", 1919 | "chrono", 1920 | "colored", 1921 | "console", 1922 | "copy_dir", 1923 | "env_logger", 1924 | "fs2", 1925 | "itertools", 1926 | "log", 1927 | "notify", 1928 | "pest", 1929 | "pest_derive", 1930 | "pulldown-cmark", 1931 | "regex", 1932 | "serde", 1933 | "serde_derive", 1934 | "serde_json", 1935 | "structopt", 1936 | "tempfile", 1937 | "tera", 1938 | "thiserror", 1939 | "toml", 1940 | "url 2.1.0", 1941 | "walkdir 2.3.1", 1942 | ] 1943 | 1944 | [[package]] 1945 | name = "string" 1946 | version = "0.2.1" 1947 | source = "registry+https://github.com/rust-lang/crates.io-index" 1948 | checksum = "d24114bfcceb867ca7f71a0d3fe45d45619ec47a6fbfa98cb14e14250bfa5d6d" 1949 | dependencies = [ 1950 | "bytes", 1951 | ] 1952 | 1953 | [[package]] 1954 | name = "strsim" 1955 | version = "0.8.0" 1956 | source = "registry+https://github.com/rust-lang/crates.io-index" 1957 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 1958 | 1959 | [[package]] 1960 | name = "structopt" 1961 | version = "0.3.3" 1962 | source = "registry+https://github.com/rust-lang/crates.io-index" 1963 | checksum = "6d4f66a4c0ddf7aee4677995697366de0749b0139057342eccbb609b12d0affc" 1964 | dependencies = [ 1965 | "clap", 1966 | "structopt-derive", 1967 | ] 1968 | 1969 | [[package]] 1970 | name = "structopt-derive" 1971 | version = "0.3.3" 1972 | source = "registry+https://github.com/rust-lang/crates.io-index" 1973 | checksum = "8fe0c13e476b4e21ff7f5c4ace3818b6d7bdc16897c31c73862471bc1663acae" 1974 | dependencies = [ 1975 | "heck", 1976 | "proc-macro-error", 1977 | "proc-macro2 1.0.5", 1978 | "quote 1.0.2", 1979 | "syn 1.0.5", 1980 | ] 1981 | 1982 | [[package]] 1983 | name = "syn" 1984 | version = "0.15.44" 1985 | source = "registry+https://github.com/rust-lang/crates.io-index" 1986 | checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" 1987 | dependencies = [ 1988 | "proc-macro2 0.4.30", 1989 | "quote 0.6.13", 1990 | "unicode-xid 0.1.0", 1991 | ] 1992 | 1993 | [[package]] 1994 | name = "syn" 1995 | version = "1.0.5" 1996 | source = "registry+https://github.com/rust-lang/crates.io-index" 1997 | checksum = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf" 1998 | dependencies = [ 1999 | "proc-macro2 1.0.5", 2000 | "quote 1.0.2", 2001 | "unicode-xid 0.2.0", 2002 | ] 2003 | 2004 | [[package]] 2005 | name = "synstructure" 2006 | version = "0.12.1" 2007 | source = "registry+https://github.com/rust-lang/crates.io-index" 2008 | checksum = "3f085a5855930c0441ca1288cf044ea4aecf4f43a91668abdb870b4ba546a203" 2009 | dependencies = [ 2010 | "proc-macro2 1.0.5", 2011 | "quote 1.0.2", 2012 | "syn 1.0.5", 2013 | "unicode-xid 0.2.0", 2014 | ] 2015 | 2016 | [[package]] 2017 | name = "tempfile" 2018 | version = "3.1.0" 2019 | source = "registry+https://github.com/rust-lang/crates.io-index" 2020 | checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" 2021 | dependencies = [ 2022 | "cfg-if", 2023 | "libc", 2024 | "rand 0.7.2", 2025 | "redox_syscall", 2026 | "remove_dir_all", 2027 | "winapi 0.3.8", 2028 | ] 2029 | 2030 | [[package]] 2031 | name = "tera" 2032 | version = "1.5.0" 2033 | source = "registry+https://github.com/rust-lang/crates.io-index" 2034 | checksum = "1381c83828bedd5ce4e59473110afa5381ffe523406d9ade4b77c9f7be70ff9a" 2035 | dependencies = [ 2036 | "chrono", 2037 | "chrono-tz", 2038 | "globwalk", 2039 | "humansize", 2040 | "lazy_static", 2041 | "percent-encoding 2.1.0", 2042 | "pest", 2043 | "pest_derive", 2044 | "rand 0.7.2", 2045 | "regex", 2046 | "serde", 2047 | "serde_json", 2048 | "slug", 2049 | "unic-segment", 2050 | ] 2051 | 2052 | [[package]] 2053 | name = "termcolor" 2054 | version = "1.1.0" 2055 | source = "registry+https://github.com/rust-lang/crates.io-index" 2056 | checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" 2057 | dependencies = [ 2058 | "winapi-util", 2059 | ] 2060 | 2061 | [[package]] 2062 | name = "termios" 2063 | version = "0.3.1" 2064 | source = "registry+https://github.com/rust-lang/crates.io-index" 2065 | checksum = "72b620c5ea021d75a735c943269bb07d30c9b77d6ac6b236bc8b5c496ef05625" 2066 | dependencies = [ 2067 | "libc", 2068 | ] 2069 | 2070 | [[package]] 2071 | name = "textwrap" 2072 | version = "0.11.0" 2073 | source = "registry+https://github.com/rust-lang/crates.io-index" 2074 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 2075 | dependencies = [ 2076 | "unicode-width", 2077 | ] 2078 | 2079 | [[package]] 2080 | name = "thiserror" 2081 | version = "1.0.2" 2082 | source = "registry+https://github.com/rust-lang/crates.io-index" 2083 | checksum = "79067843a369b7df0391d33d48636936ee91aa6d6370931eebe1278e6f88a723" 2084 | dependencies = [ 2085 | "thiserror-impl", 2086 | ] 2087 | 2088 | [[package]] 2089 | name = "thiserror-impl" 2090 | version = "1.0.2" 2091 | source = "registry+https://github.com/rust-lang/crates.io-index" 2092 | checksum = "2dc6215837a07b345fd86880601a9c55bb5974e0cec507c48bbd3aaa006559a9" 2093 | dependencies = [ 2094 | "proc-macro2 1.0.5", 2095 | "quote 1.0.2", 2096 | "syn 1.0.5", 2097 | ] 2098 | 2099 | [[package]] 2100 | name = "thread_local" 2101 | version = "1.0.1" 2102 | source = "registry+https://github.com/rust-lang/crates.io-index" 2103 | checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" 2104 | dependencies = [ 2105 | "lazy_static", 2106 | ] 2107 | 2108 | [[package]] 2109 | name = "threadpool" 2110 | version = "1.7.1" 2111 | source = "registry+https://github.com/rust-lang/crates.io-index" 2112 | checksum = "e2f0c90a5f3459330ac8bc0d2f879c693bb7a2f59689c1083fc4ef83834da865" 2113 | dependencies = [ 2114 | "num_cpus", 2115 | ] 2116 | 2117 | [[package]] 2118 | name = "time" 2119 | version = "0.1.42" 2120 | source = "registry+https://github.com/rust-lang/crates.io-index" 2121 | checksum = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" 2122 | dependencies = [ 2123 | "libc", 2124 | "redox_syscall", 2125 | "winapi 0.3.8", 2126 | ] 2127 | 2128 | [[package]] 2129 | name = "tokio-codec" 2130 | version = "0.1.1" 2131 | source = "registry+https://github.com/rust-lang/crates.io-index" 2132 | checksum = "5c501eceaf96f0e1793cf26beb63da3d11c738c4a943fdf3746d81d64684c39f" 2133 | dependencies = [ 2134 | "bytes", 2135 | "futures", 2136 | "tokio-io", 2137 | ] 2138 | 2139 | [[package]] 2140 | name = "tokio-current-thread" 2141 | version = "0.1.6" 2142 | source = "registry+https://github.com/rust-lang/crates.io-index" 2143 | checksum = "d16217cad7f1b840c5a97dfb3c43b0c871fef423a6e8d2118c604e843662a443" 2144 | dependencies = [ 2145 | "futures", 2146 | "tokio-executor", 2147 | ] 2148 | 2149 | [[package]] 2150 | name = "tokio-executor" 2151 | version = "0.1.8" 2152 | source = "registry+https://github.com/rust-lang/crates.io-index" 2153 | checksum = "0f27ee0e6db01c5f0b2973824547ce7e637b2ed79b891a9677b0de9bd532b6ac" 2154 | dependencies = [ 2155 | "crossbeam-utils 0.6.6", 2156 | "futures", 2157 | ] 2158 | 2159 | [[package]] 2160 | name = "tokio-io" 2161 | version = "0.1.12" 2162 | source = "registry+https://github.com/rust-lang/crates.io-index" 2163 | checksum = "5090db468dad16e1a7a54c8c67280c5e4b544f3d3e018f0b913b400261f85926" 2164 | dependencies = [ 2165 | "bytes", 2166 | "futures", 2167 | "log", 2168 | ] 2169 | 2170 | [[package]] 2171 | name = "tokio-reactor" 2172 | version = "0.1.10" 2173 | source = "registry+https://github.com/rust-lang/crates.io-index" 2174 | checksum = "c56391be9805bc80163151c0b9e5164ee64f4b0200962c346fea12773158f22d" 2175 | dependencies = [ 2176 | "crossbeam-utils 0.6.6", 2177 | "futures", 2178 | "lazy_static", 2179 | "log", 2180 | "mio", 2181 | "num_cpus", 2182 | "parking_lot 0.9.0", 2183 | "slab", 2184 | "tokio-executor", 2185 | "tokio-io", 2186 | "tokio-sync", 2187 | ] 2188 | 2189 | [[package]] 2190 | name = "tokio-signal" 2191 | version = "0.2.7" 2192 | source = "registry+https://github.com/rust-lang/crates.io-index" 2193 | checksum = "dd6dc5276ea05ce379a16de90083ec80836440d5ef8a6a39545a3207373b8296" 2194 | dependencies = [ 2195 | "futures", 2196 | "libc", 2197 | "mio", 2198 | "mio-uds", 2199 | "signal-hook", 2200 | "tokio-executor", 2201 | "tokio-io", 2202 | "tokio-reactor", 2203 | "winapi 0.3.8", 2204 | ] 2205 | 2206 | [[package]] 2207 | name = "tokio-sync" 2208 | version = "0.1.7" 2209 | source = "registry+https://github.com/rust-lang/crates.io-index" 2210 | checksum = "d06554cce1ae4a50f42fba8023918afa931413aded705b560e29600ccf7c6d76" 2211 | dependencies = [ 2212 | "fnv", 2213 | "futures", 2214 | ] 2215 | 2216 | [[package]] 2217 | name = "tokio-tcp" 2218 | version = "0.1.3" 2219 | source = "registry+https://github.com/rust-lang/crates.io-index" 2220 | checksum = "1d14b10654be682ac43efee27401d792507e30fd8d26389e1da3b185de2e4119" 2221 | dependencies = [ 2222 | "bytes", 2223 | "futures", 2224 | "iovec", 2225 | "mio", 2226 | "tokio-io", 2227 | "tokio-reactor", 2228 | ] 2229 | 2230 | [[package]] 2231 | name = "tokio-timer" 2232 | version = "0.2.11" 2233 | source = "registry+https://github.com/rust-lang/crates.io-index" 2234 | checksum = "f2106812d500ed25a4f38235b9cae8f78a09edf43203e16e59c3b769a342a60e" 2235 | dependencies = [ 2236 | "crossbeam-utils 0.6.6", 2237 | "futures", 2238 | "slab", 2239 | "tokio-executor", 2240 | ] 2241 | 2242 | [[package]] 2243 | name = "tokio-udp" 2244 | version = "0.1.5" 2245 | source = "registry+https://github.com/rust-lang/crates.io-index" 2246 | checksum = "f02298505547f73e60f568359ef0d016d5acd6e830ab9bc7c4a5b3403440121b" 2247 | dependencies = [ 2248 | "bytes", 2249 | "futures", 2250 | "log", 2251 | "mio", 2252 | "tokio-codec", 2253 | "tokio-io", 2254 | "tokio-reactor", 2255 | ] 2256 | 2257 | [[package]] 2258 | name = "toml" 2259 | version = "0.5.6" 2260 | source = "registry+https://github.com/rust-lang/crates.io-index" 2261 | checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" 2262 | dependencies = [ 2263 | "serde", 2264 | ] 2265 | 2266 | [[package]] 2267 | name = "trust-dns-proto" 2268 | version = "0.7.4" 2269 | source = "registry+https://github.com/rust-lang/crates.io-index" 2270 | checksum = "5559ebdf6c2368ddd11e20b11d6bbaf9e46deb803acd7815e93f5a7b4a6d2901" 2271 | dependencies = [ 2272 | "byteorder", 2273 | "enum-as-inner", 2274 | "failure", 2275 | "futures", 2276 | "idna 0.1.5", 2277 | "lazy_static", 2278 | "log", 2279 | "rand 0.6.5", 2280 | "smallvec", 2281 | "socket2", 2282 | "tokio-executor", 2283 | "tokio-io", 2284 | "tokio-reactor", 2285 | "tokio-tcp", 2286 | "tokio-timer", 2287 | "tokio-udp", 2288 | "url 1.7.2", 2289 | ] 2290 | 2291 | [[package]] 2292 | name = "trust-dns-resolver" 2293 | version = "0.11.1" 2294 | source = "registry+https://github.com/rust-lang/crates.io-index" 2295 | checksum = "6c9992e58dba365798803c0b91018ff6c8d3fc77e06977c4539af2a6bfe0a039" 2296 | dependencies = [ 2297 | "cfg-if", 2298 | "failure", 2299 | "futures", 2300 | "ipconfig", 2301 | "lazy_static", 2302 | "log", 2303 | "lru-cache", 2304 | "resolv-conf", 2305 | "smallvec", 2306 | "tokio-executor", 2307 | "trust-dns-proto", 2308 | ] 2309 | 2310 | [[package]] 2311 | name = "typenum" 2312 | version = "1.11.2" 2313 | source = "registry+https://github.com/rust-lang/crates.io-index" 2314 | checksum = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" 2315 | 2316 | [[package]] 2317 | name = "ucd-trie" 2318 | version = "0.1.2" 2319 | source = "registry+https://github.com/rust-lang/crates.io-index" 2320 | checksum = "8f00ed7be0c1ff1e24f46c3d2af4859f7e863672ba3a6e92e7cff702bf9f06c2" 2321 | 2322 | [[package]] 2323 | name = "unic-char-property" 2324 | version = "0.9.0" 2325 | source = "registry+https://github.com/rust-lang/crates.io-index" 2326 | checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" 2327 | dependencies = [ 2328 | "unic-char-range", 2329 | ] 2330 | 2331 | [[package]] 2332 | name = "unic-char-range" 2333 | version = "0.9.0" 2334 | source = "registry+https://github.com/rust-lang/crates.io-index" 2335 | checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" 2336 | 2337 | [[package]] 2338 | name = "unic-common" 2339 | version = "0.9.0" 2340 | source = "registry+https://github.com/rust-lang/crates.io-index" 2341 | checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" 2342 | 2343 | [[package]] 2344 | name = "unic-segment" 2345 | version = "0.9.0" 2346 | source = "registry+https://github.com/rust-lang/crates.io-index" 2347 | checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" 2348 | dependencies = [ 2349 | "unic-ucd-segment", 2350 | ] 2351 | 2352 | [[package]] 2353 | name = "unic-ucd-segment" 2354 | version = "0.9.0" 2355 | source = "registry+https://github.com/rust-lang/crates.io-index" 2356 | checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" 2357 | dependencies = [ 2358 | "unic-char-property", 2359 | "unic-char-range", 2360 | "unic-ucd-version", 2361 | ] 2362 | 2363 | [[package]] 2364 | name = "unic-ucd-version" 2365 | version = "0.9.0" 2366 | source = "registry+https://github.com/rust-lang/crates.io-index" 2367 | checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" 2368 | dependencies = [ 2369 | "unic-common", 2370 | ] 2371 | 2372 | [[package]] 2373 | name = "unicase" 2374 | version = "2.5.1" 2375 | source = "registry+https://github.com/rust-lang/crates.io-index" 2376 | checksum = "2e2e6bd1e59e56598518beb94fd6db628ded570326f0a98c679a304bd9f00150" 2377 | dependencies = [ 2378 | "version_check", 2379 | ] 2380 | 2381 | [[package]] 2382 | name = "unicode-bidi" 2383 | version = "0.3.4" 2384 | source = "registry+https://github.com/rust-lang/crates.io-index" 2385 | checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" 2386 | dependencies = [ 2387 | "matches", 2388 | ] 2389 | 2390 | [[package]] 2391 | name = "unicode-normalization" 2392 | version = "0.1.8" 2393 | source = "registry+https://github.com/rust-lang/crates.io-index" 2394 | checksum = "141339a08b982d942be2ca06ff8b076563cbe223d1befd5450716790d44e2426" 2395 | dependencies = [ 2396 | "smallvec", 2397 | ] 2398 | 2399 | [[package]] 2400 | name = "unicode-segmentation" 2401 | version = "1.3.0" 2402 | source = "registry+https://github.com/rust-lang/crates.io-index" 2403 | checksum = "1967f4cdfc355b37fd76d2a954fb2ed3871034eb4f26d60537d88795cfc332a9" 2404 | 2405 | [[package]] 2406 | name = "unicode-width" 2407 | version = "0.1.5" 2408 | source = "registry+https://github.com/rust-lang/crates.io-index" 2409 | checksum = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" 2410 | 2411 | [[package]] 2412 | name = "unicode-xid" 2413 | version = "0.1.0" 2414 | source = "registry+https://github.com/rust-lang/crates.io-index" 2415 | checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 2416 | 2417 | [[package]] 2418 | name = "unicode-xid" 2419 | version = "0.2.0" 2420 | source = "registry+https://github.com/rust-lang/crates.io-index" 2421 | checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 2422 | 2423 | [[package]] 2424 | name = "url" 2425 | version = "1.7.2" 2426 | source = "registry+https://github.com/rust-lang/crates.io-index" 2427 | checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" 2428 | dependencies = [ 2429 | "idna 0.1.5", 2430 | "matches", 2431 | "percent-encoding 1.0.1", 2432 | ] 2433 | 2434 | [[package]] 2435 | name = "url" 2436 | version = "2.1.0" 2437 | source = "registry+https://github.com/rust-lang/crates.io-index" 2438 | checksum = "75b414f6c464c879d7f9babf951f23bc3743fb7313c081b2e6ca719067ea9d61" 2439 | dependencies = [ 2440 | "idna 0.2.0", 2441 | "matches", 2442 | "percent-encoding 2.1.0", 2443 | ] 2444 | 2445 | [[package]] 2446 | name = "v_escape" 2447 | version = "0.7.4" 2448 | source = "registry+https://github.com/rust-lang/crates.io-index" 2449 | checksum = "660b101c07b5d0863deb9e7fb3138777e858d6d2a79f9e6049a27d1cc77c6da6" 2450 | dependencies = [ 2451 | "v_escape_derive", 2452 | ] 2453 | 2454 | [[package]] 2455 | name = "v_escape_derive" 2456 | version = "0.5.6" 2457 | source = "registry+https://github.com/rust-lang/crates.io-index" 2458 | checksum = "c2ca2a14bc3fc5b64d188b087a7d3a927df87b152e941ccfbc66672e20c467ae" 2459 | dependencies = [ 2460 | "nom", 2461 | "proc-macro2 1.0.5", 2462 | "quote 1.0.2", 2463 | "syn 1.0.5", 2464 | ] 2465 | 2466 | [[package]] 2467 | name = "v_htmlescape" 2468 | version = "0.4.5" 2469 | source = "registry+https://github.com/rust-lang/crates.io-index" 2470 | checksum = "e33e939c0d8cf047514fb6ba7d5aac78bc56677a6938b2ee67000b91f2e97e41" 2471 | dependencies = [ 2472 | "cfg-if", 2473 | "v_escape", 2474 | ] 2475 | 2476 | [[package]] 2477 | name = "vec_map" 2478 | version = "0.8.1" 2479 | source = "registry+https://github.com/rust-lang/crates.io-index" 2480 | checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" 2481 | 2482 | [[package]] 2483 | name = "version_check" 2484 | version = "0.1.5" 2485 | source = "registry+https://github.com/rust-lang/crates.io-index" 2486 | checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" 2487 | 2488 | [[package]] 2489 | name = "walkdir" 2490 | version = "0.1.8" 2491 | source = "registry+https://github.com/rust-lang/crates.io-index" 2492 | checksum = "c66c0b9792f0a765345452775f3adbd28dde9d33f30d13e5dcc5ae17cf6f3780" 2493 | dependencies = [ 2494 | "kernel32-sys", 2495 | "winapi 0.2.8", 2496 | ] 2497 | 2498 | [[package]] 2499 | name = "walkdir" 2500 | version = "2.3.1" 2501 | source = "registry+https://github.com/rust-lang/crates.io-index" 2502 | checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" 2503 | dependencies = [ 2504 | "same-file", 2505 | "winapi 0.3.8", 2506 | "winapi-util", 2507 | ] 2508 | 2509 | [[package]] 2510 | name = "wasi" 2511 | version = "0.7.0" 2512 | source = "registry+https://github.com/rust-lang/crates.io-index" 2513 | checksum = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d" 2514 | 2515 | [[package]] 2516 | name = "widestring" 2517 | version = "0.4.0" 2518 | source = "registry+https://github.com/rust-lang/crates.io-index" 2519 | checksum = "effc0e4ff8085673ea7b9b2e3c73f6bd4d118810c9009ed8f1e16bd96c331db6" 2520 | 2521 | [[package]] 2522 | name = "winapi" 2523 | version = "0.2.8" 2524 | source = "registry+https://github.com/rust-lang/crates.io-index" 2525 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 2526 | 2527 | [[package]] 2528 | name = "winapi" 2529 | version = "0.3.8" 2530 | source = "registry+https://github.com/rust-lang/crates.io-index" 2531 | checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 2532 | dependencies = [ 2533 | "winapi-i686-pc-windows-gnu", 2534 | "winapi-x86_64-pc-windows-gnu", 2535 | ] 2536 | 2537 | [[package]] 2538 | name = "winapi-build" 2539 | version = "0.1.1" 2540 | source = "registry+https://github.com/rust-lang/crates.io-index" 2541 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 2542 | 2543 | [[package]] 2544 | name = "winapi-i686-pc-windows-gnu" 2545 | version = "0.4.0" 2546 | source = "registry+https://github.com/rust-lang/crates.io-index" 2547 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2548 | 2549 | [[package]] 2550 | name = "winapi-util" 2551 | version = "0.1.5" 2552 | source = "registry+https://github.com/rust-lang/crates.io-index" 2553 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 2554 | dependencies = [ 2555 | "winapi 0.3.8", 2556 | ] 2557 | 2558 | [[package]] 2559 | name = "winapi-x86_64-pc-windows-gnu" 2560 | version = "0.4.0" 2561 | source = "registry+https://github.com/rust-lang/crates.io-index" 2562 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2563 | 2564 | [[package]] 2565 | name = "winreg" 2566 | version = "0.6.2" 2567 | source = "registry+https://github.com/rust-lang/crates.io-index" 2568 | checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" 2569 | dependencies = [ 2570 | "winapi 0.3.8", 2571 | ] 2572 | 2573 | [[package]] 2574 | name = "winutil" 2575 | version = "0.1.1" 2576 | source = "registry+https://github.com/rust-lang/crates.io-index" 2577 | checksum = "7daf138b6b14196e3830a588acf1e86966c694d3e8fb026fb105b8b5dca07e6e" 2578 | dependencies = [ 2579 | "winapi 0.3.8", 2580 | ] 2581 | 2582 | [[package]] 2583 | name = "ws2_32-sys" 2584 | version = "0.2.1" 2585 | source = "registry+https://github.com/rust-lang/crates.io-index" 2586 | checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 2587 | dependencies = [ 2588 | "winapi 0.2.8", 2589 | "winapi-build", 2590 | ] 2591 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "staple" 3 | description = "powerful static site generator" 4 | version = "0.3.2" 5 | authors = ["Kilerd Chan "] 6 | license = "MIT" 7 | edition = "2018" 8 | readme= "readme.md" 9 | repository = "https://github.com/Kilerd/staple" 10 | 11 | 12 | [dependencies] 13 | structopt = "0.3.3" 14 | tera = "1.5.0" 15 | serde = "1.0.101" 16 | serde_derive = "1.0.101" 17 | toml = "0.5.6" 18 | thiserror = "1.0.2" 19 | pest = "2.1.2" 20 | pest_derive = "2.1.0" 21 | chrono = { version = "0.4.9", features = ["serde"] } 22 | log = "0.4.8" 23 | notify = "4.0.14" 24 | actix-web = "1.0.8" 25 | actix-files = "0.1.6" 26 | actix = "0.8.3" 27 | actix-web-actors = "1.0.2" 28 | copy_dir = "0.1.2" 29 | console = "0.9.1" 30 | pulldown-cmark = { version = "0.6.1", default-features = false } 31 | url = "2.1.0" 32 | regex = "1.3.9" 33 | itertools = "0.9.0" 34 | serde_json = "1.0.56" 35 | env_logger = "0.7.1" 36 | fs2 = "0.4.3" 37 | walkdir = "2.3.1" 38 | colored = "2.0.0" 39 | 40 | [dev-dependencies] 41 | tempfile = "3.1.0" 42 | -------------------------------------------------------------------------------- /check.sh: -------------------------------------------------------------------------------- 1 | cargo fmt --all -- --check 2 | cargo clippy --all-targets -- -D warnings -------------------------------------------------------------------------------- /data/live_reload.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /homepage/CNAME: -------------------------------------------------------------------------------- 1 | staple.3min.work -------------------------------------------------------------------------------- /homepage/Staple.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kilerd/staple/d7b00327430f77ca45b9d5fa94f94ff54a555e2f/homepage/Staple.lock -------------------------------------------------------------------------------- /homepage/Staple.toml: -------------------------------------------------------------------------------- 1 | [site] 2 | title = "Staple Homepage" 3 | subtitle = "" 4 | description = "" 5 | keywords = [] 6 | author = "" 7 | email = "" 8 | utc_offset = 800 9 | theme = "staple" 10 | domain = "https://kilerd.github.io" 11 | domain_root = "staple" 12 | default_template = "article.html" 13 | 14 | [hook] 15 | before_build = [ 16 | { dir="templates/staple/statics", command="lessc style.less style.css" } 17 | ] 18 | 19 | [watch] 20 | exclusive = [ 21 | "templates/staple/statics/style.css", 22 | ] 23 | 24 | [extra] 25 | version = "0.3.2" -------------------------------------------------------------------------------- /homepage/data/builtin-function.md: -------------------------------------------------------------------------------- 1 | - title = Builtin Function 2 | - url = builtin-function 3 | - datetime = 2020-09-24T21:13:44.049291+08:00 4 | - template = article.html 5 | - draw = false 6 | 7 | -------------------------------------------------------------------------------- /homepage/data/develop-mode.md: -------------------------------------------------------------------------------- 1 | - title = Be powerful when developing 2 | - url = develop-mode 3 | - datetime = 2020-09-24T20:17:34.394508+08:00 4 | - template = article.html 5 | - draw = false 6 | 7 | -------------------------------------------------------------------------------- /homepage/data/get-started.md: -------------------------------------------------------------------------------- 1 | - title = Get Started 2 | - url = get-started 3 | - datetime = 2020-09-24T20:13:17.909933+08:00 4 | - template = article.html 5 | - draw = false 6 | 7 | 8 | ## Installation 9 | currently, staple provide two ways to download its stable binary version. for those who is using rust can download it via Cargo: 10 | ```shell script 11 | cargo install staple 12 | ``` 13 | 14 | or, you can download the latest version in [Github Release](https://github.com/Kilerd/staple/releases). 15 | ### Developer version 16 | if you really need a developer version to taste some new feature which are not stable yet at the first time, you can download the latest staple code and compile it via Cargo: 17 | ```shell script 18 | cargo install --git https://github.com/Kilerd/staple.git 19 | ``` 20 | 21 | ## Usage 22 | `staple` has full cli help text, run `staple --help` to get help text if you don't know how to use it. 23 | 24 | ### Init a staple project 25 | 26 | for now, there are two ways to create a staple project for a folder. 27 | - for existed folder, you can use command `staple init` to initialize current working folder as a new staple project. 28 | - for non-existed folder, using command `staple new TARGET_FOLDER` to create a new folder for staple 29 | -------------------------------------------------------------------------------- /homepage/data/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "/", 3 | "title": "首页", 4 | "template": "index.html", 5 | "datetime": "2015-02-18T23:59:60.234567+05:00", 6 | "data": { 7 | "page": true, 8 | "tags": [ 9 | "test", 10 | "markdown", 11 | "json" 12 | ] 13 | }, 14 | "content": "#hello staple\n here is the content" 15 | } -------------------------------------------------------------------------------- /homepage/data/markdown-ext.md: -------------------------------------------------------------------------------- 1 | - title = Markdown syntax enhance 2 | - url = markdown-ext 3 | - datetime = 2020-09-24T20:17:53.341763+08:00 4 | - template = article.html 5 | - draw = false 6 | 7 | -------------------------------------------------------------------------------- /homepage/data/template.md: -------------------------------------------------------------------------------- 1 | - title = Template 2 | - url = template 3 | - datetime = 2020-09-24T20:17:11.004563+08:00 4 | - template = article.html 5 | - draw = false 6 | 7 | -------------------------------------------------------------------------------- /homepage/templates/staple/article.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block body %} 3 |
4 |

{{page.title}}

5 |
6 | {{ page.content.html | safe }} 7 |
8 | 9 |
10 | 11 | {% endblock body %} -------------------------------------------------------------------------------- /homepage/templates/staple/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {% block head %}{% endblock head %} 9 | {% block title %}Staple{% endblock title %} 10 | 11 | 12 | {% block body %}{% endblock body %} 13 | 14 | {{ develop.live_reload | safe }} 15 | -------------------------------------------------------------------------------- /homepage/templates/staple/cross.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kilerd/staple/d7b00327430f77ca45b9d5fa94f94ff54a555e2f/homepage/templates/staple/cross.html -------------------------------------------------------------------------------- /homepage/templates/staple/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block body %} 3 |
4 |
5 |
6 |

STAPLE{{config.extra.version}}

7 |

powerful static site generator

8 | 9 |
10 |
11 |
12 |
13 | {% for one_page in pages | not_field(attribute="data.page") | reverse %} 14 |
15 | {{ one_page.title }} 17 |
18 | {% endfor %} 19 |
20 | 21 |
22 | 23 |
24 | {% endblock body %} -------------------------------------------------------------------------------- /homepage/templates/staple/page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ article.meta.url }} 6 | 7 | 8 | {{ article.content.html | safe }} 9 | 10 | {{ data | safe | json_encode(pretty=true) }} 11 | 12 | -------------------------------------------------------------------------------- /homepage/templates/staple/statics/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kilerd/staple/d7b00327430f77ca45b9d5fa94f94ff54a555e2f/homepage/templates/staple/statics/bg.png -------------------------------------------------------------------------------- /homepage/templates/staple/statics/prime.js: -------------------------------------------------------------------------------- 1 | /* PrismJS 1.20.0 2 | https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+csp+django+docker+graphql+java+json+less+markdown+markup-templating+plsql+python+jsx+tsx+rust+scss+sql+typescript+yaml&plugins=line-highlight+line-numbers */ 3 | var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(u){var c=/\blang(?:uage)?-([\w-]+)\b/i,n=0,C={manual:u.Prism&&u.Prism.manual,disableWorkerMessageHandler:u.Prism&&u.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof _?new _(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&").replace(/n.length)return;if(!(b instanceof _)){var x=1;if(d&&y!=t.tail.prev){g.lastIndex=k;var w=g.exec(n);if(!w)break;var A=w.index+(h&&w[1]?w[1].length:0),P=w.index+w[0].length,S=k;for(S+=y.value.length;S<=A;)y=y.next,S+=y.value.length;if(S-=y.value.length,k=S,y.value instanceof _)continue;for(var O=y;O!==t.tail&&(S"+a.content+""},!u.document)return u.addEventListener&&(C.disableWorkerMessageHandler||u.addEventListener("message",function(e){var n=JSON.parse(e.data),t=n.language,r=n.code,a=n.immediateClose;u.postMessage(C.highlight(r,C.languages[t],t)),a&&u.close()},!1)),C;var e=C.util.currentScript();function t(){C.manual||C.highlightAll()}if(e&&(C.filename=e.src,e.hasAttribute("data-manual")&&(C.manual=!0)),!C.manual){var r=document.readyState;"loading"===r||"interactive"===r&&e&&e.defer?document.addEventListener("DOMContentLoaded",t):window.requestAnimationFrame?window.requestAnimationFrame(t):window.setTimeout(t,16)}return C}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); 4 | Prism.languages.markup={comment://,prolog:/<\?[\s\S]+?\?>/,doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/,name:/[^\s<>'"]+/}},cdata://i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.languages.markup.doctype.inside["internal-subset"].inside=Prism.languages.markup,Prism.hooks.add("wrap",function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))}),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(a,e){var s={};s["language-"+e]={pattern:/(^$)/i,lookbehind:!0,inside:Prism.languages[e]},s.cdata=/^$/i;var n={"included-cdata":{pattern://i,inside:s}};n["language-"+e]={pattern:/[\s\S]+/,inside:Prism.languages[e]};var t={};t[a]={pattern:RegExp("(<__[^]*?>)(?:))*\\]\\]>|(?!)".replace(/__/g,function(){return a}),"i"),lookbehind:!0,greedy:!0,inside:n},Prism.languages.insertBefore("markup","cdata",t)}}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup,Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.ssml=Prism.languages.xml,Prism.languages.atom=Prism.languages.xml,Prism.languages.rss=Prism.languages.xml; 5 | !function(s){var e=/("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/;s.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:/@[\w-]+[\s\S]*?(?:;|(?=\s*\{))/,inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\((?!\s*\))\s*)(?:[^()]|\((?:[^()]|\([^()]*\))*\))+?(?=\s*\))/,lookbehind:!0,alias:"selector"}}},url:{pattern:RegExp("url\\((?:"+e.source+"|[^\n\r()]*)\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/}},selector:RegExp("[^{}\\s](?:[^{};\"']|"+e.source+")*?(?=\\s*\\{)"),string:{pattern:e,greedy:!0},property:/[-_a-z\xA0-\uFFFF][-\w\xA0-\uFFFF]*(?=\s*:)/i,important:/!important\b/i,function:/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:,]/},s.languages.css.atrule.inside.rest=s.languages.css;var t=s.languages.markup;t&&(t.tag.addInlined("style","css"),s.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/\s*style=("|')(?:\\[\s\S]|(?!\1)[^\\])*\1/i,inside:{"attr-name":{pattern:/^\s*style/i,inside:t.tag.inside},punctuation:/^\s*=\s*['"]|['"]\s*$/,"attr-value":{pattern:/.+/i,inside:s.languages.css}},alias:"language-css"}},t.tag))}(Prism); 6 | Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|interface|extends|implements|trait|instanceof|new)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,boolean:/\b(?:true|false)\b/,function:/\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/}; 7 | Prism.languages.javascript=Prism.languages.extend("clike",{"class-name":[Prism.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])[_$A-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\.(?:prototype|constructor))/,lookbehind:!0}],keyword:[{pattern:/((?:^|})\s*)(?:catch|finally)\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|for|from|function|(?:get|set)(?=\s*[\[$\w\xA0-\uFFFF])|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],number:/\b(?:(?:0[xX](?:[\dA-Fa-f](?:_[\dA-Fa-f])?)+|0[bB](?:[01](?:_[01])?)+|0[oO](?:[0-7](?:_[0-7])?)+)n?|(?:\d(?:_\d)?)+n|NaN|Infinity)\b|(?:\b(?:\d(?:_\d)?)+\.?(?:\d(?:_\d)?)*|\B\.(?:\d(?:_\d)?)+)(?:[Ee][+-]?(?:\d(?:_\d)?)+)?/,function:/#?[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|interface|extends|implements|instanceof|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/((?:^|[^$\w\xA0-\uFFFF."'\])\s])\s*)\/(?:\[(?:[^\]\\\r\n]|\\.)*]|\\.|[^/\\\[\r\n])+\/[gimyus]{0,6}(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/,lookbehind:!0,greedy:!0},"function-variable":{pattern:/#?[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+[_$A-Za-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)?\s*\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*=>)/i,inside:Prism.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\)\s*=>)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:[_$A-Za-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\)\s*\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),Prism.languages.insertBefore("javascript","string",{"template-string":{pattern:/`(?:\\[\s\S]|\${(?:[^{}]|{(?:[^{}]|{[^}]*})*})+}|(?!\${)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\${(?:[^{}]|{(?:[^{}]|{[^}]*})*})+}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\${|}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}}}),Prism.languages.markup&&Prism.languages.markup.tag.addInlined("script","javascript"),Prism.languages.js=Prism.languages.javascript; 8 | Prism.languages.csp={directive:{pattern:/\b(?:(?:base-uri|form-action|frame-ancestors|plugin-types|referrer|reflected-xss|report-to|report-uri|require-sri-for|sandbox) |(?:block-all-mixed-content|disown-opener|upgrade-insecure-requests)(?: |;)|(?:child|connect|default|font|frame|img|manifest|media|object|script|style|worker)-src )/i,alias:"keyword"},safe:{pattern:/'(?:self|none|strict-dynamic|(?:nonce-|sha(?:256|384|512)-)[a-zA-Z\d+=/]+)'/,alias:"selector"},unsafe:{pattern:/(?:'unsafe-inline'|'unsafe-eval'|'unsafe-hashed-attributes'|\*)/,alias:"function"}}; 9 | !function(h){function v(e,n){return"___"+e.toUpperCase()+n+"___"}Object.defineProperties(h.languages["markup-templating"]={},{buildPlaceholders:{value:function(a,r,e,o){if(a.language===r){var c=a.tokenStack=[];a.code=a.code.replace(e,function(e){if("function"==typeof o&&!o(e))return e;for(var n,t=c.length;-1!==a.code.indexOf(n=v(r,t));)++t;return c[t]=e,n}),a.grammar=h.languages.markup}}},tokenizePlaceholders:{value:function(p,k){if(p.language===k&&p.tokenStack){p.grammar=h.languages[k];var m=0,d=Object.keys(p.tokenStack);!function e(n){for(var t=0;t=d.length);t++){var a=n[t];if("string"==typeof a||a.content&&"string"==typeof a.content){var r=d[m],o=p.tokenStack[r],c="string"==typeof a?a:a.content,i=v(k,r),u=c.indexOf(i);if(-1]?|>[=>]?|[&|^~]/,number:/\b\d+(?:\.\d+)?\b/,boolean:/[Tt]rue|[Ff]alse|[Nn]one/,variable:/\b\w+?\b/,punctuation:/[{}[\](),.:;]/};var n=/{{[\s\S]*?}}|{%[\s\S]*?%}|{#[\s\S]*?#}/g,o=e.languages["markup-templating"];e.hooks.add("before-tokenize",function(e){o.buildPlaceholders(e,"django",n)}),e.hooks.add("after-tokenize",function(e){o.tokenizePlaceholders(e,"django")}),e.languages.jinja2=e.languages.django,e.hooks.add("before-tokenize",function(e){o.buildPlaceholders(e,"jinja2",n)}),e.hooks.add("after-tokenize",function(e){o.tokenizePlaceholders(e,"jinja2")})}(Prism); 11 | Prism.languages.docker={keyword:{pattern:/(^\s*)(?:ADD|ARG|CMD|COPY|ENTRYPOINT|ENV|EXPOSE|FROM|HEALTHCHECK|LABEL|MAINTAINER|ONBUILD|RUN|SHELL|STOPSIGNAL|USER|VOLUME|WORKDIR)(?=\s)/im,lookbehind:!0},string:/("|')(?:(?!\1)[^\\\r\n]|\\(?:\r\n|[\s\S]))*\1/,comment:/#.*/,punctuation:/---|\.\.\.|[:[\]{}\-,|>?]/},Prism.languages.dockerfile=Prism.languages.docker; 12 | Prism.languages.graphql={comment:/#.*/,description:{pattern:/(?:"""(?:[^"]|(?!""")")*"""|"(?:\\.|[^\\"\r\n])*")(?=\s*[a-z_])/i,greedy:!0,alias:"string",inside:{"language-markdown":{pattern:/(^"(?:"")?)(?!\1)[\s\S]+(?=\1$)/,lookbehind:!0,inside:Prism.languages.markdown}}},string:{pattern:/"""(?:[^"]|(?!""")")*"""|"(?:\\.|[^\\"\r\n])*"/,greedy:!0},number:/(?:\B-|\b)\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,boolean:/\b(?:true|false)\b/,variable:/\$[a-z_]\w*/i,directive:{pattern:/@[a-z_]\w*/i,alias:"function"},"attr-name":{pattern:/[a-z_]\w*(?=\s*(?:\((?:[^()"]|"(?:\\.|[^\\"\r\n])*")*\))?:)/i,greedy:!0},"class-name":{pattern:/(\b(?:enum|implements|interface|on|scalar|type|union)\s+)[a-zA-Z_]\w*/,lookbehind:!0},fragment:{pattern:/(\bfragment\s+|\.{3}\s*(?!on\b))[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},keyword:/\b(?:enum|fragment|implements|input|interface|mutation|on|query|scalar|schema|type|union)\b/,operator:/[!=|]|\.{3}/,punctuation:/[!(){}\[\]:=,]/,constant:/\b(?!ID\b)[A-Z][A-Z_\d]*\b/}; 13 | !function(e){var t=/\b(?:abstract|assert|boolean|break|byte|case|catch|char|class|const|continue|default|do|double|else|enum|exports|extends|final|finally|float|for|goto|if|implements|import|instanceof|int|interface|long|module|native|new|null|open|opens|package|private|protected|provides|public|record|requires|return|short|static|strictfp|super|switch|synchronized|this|throw|throws|to|transient|transitive|try|uses|var|void|volatile|while|with|yield)\b/,a=/\b[A-Z](?:\w*[a-z]\w*)?\b/;e.languages.java=e.languages.extend("clike",{"class-name":[a,/\b[A-Z]\w*(?=\s+\w+\s*[;,=())])/],keyword:t,function:[e.languages.clike.function,{pattern:/(\:\:)[a-z_]\w*/,lookbehind:!0}],number:/\b0b[01][01_]*L?\b|\b0x[\da-f_]*\.?[\da-f_p+-]+\b|(?:\b\d[\d_]*\.?[\d_]*|\B\.\d[\d_]*)(?:e[+-]?\d[\d_]*)?[dfl]?/i,operator:{pattern:/(^|[^.])(?:<<=?|>>>?=?|->|--|\+\+|&&|\|\||::|[?:~]|[-+*/%&|^!=<>]=?)/m,lookbehind:!0}}),e.languages.insertBefore("java","string",{"triple-quoted-string":{pattern:/"""[ \t]*[\r\n](?:(?:"|"")?(?:\\.|[^"\\]))*"""/,greedy:!0,alias:"string"}}),e.languages.insertBefore("java","class-name",{annotation:{alias:"punctuation",pattern:/(^|[^.])@\w+/,lookbehind:!0},namespace:{pattern:RegExp("(\\b(?:exports|import(?:\\s+static)?|module|open|opens|package|provides|requires|to|transitive|uses|with)\\s+)(?!)[a-z]\\w*(?:\\.[a-z]\\w*)*\\.?".replace(//g,function(){return t.source})),lookbehind:!0,inside:{punctuation:/\./}},generics:{pattern:/<(?:[\w\s,.&?]|<(?:[\w\s,.&?]|<(?:[\w\s,.&?]|<[\w\s,.&?]*>)*>)*>)*>/,inside:{"class-name":a,keyword:t,punctuation:/[<>(),.:]/,operator:/[?&|]/}}})}(Prism); 14 | Prism.languages.json={property:{pattern:/"(?:\\.|[^\\"\r\n])*"(?=\s*:)/,greedy:!0},string:{pattern:/"(?:\\.|[^\\"\r\n])*"(?!\s*:)/,greedy:!0},comment:/\/\/.*|\/\*[\s\S]*?(?:\*\/|$)/,number:/-?\d+\.?\d*(?:e[+-]?\d+)?/i,punctuation:/[{}[\],]/,operator:/:/,boolean:/\b(?:true|false)\b/,null:{pattern:/\bnull\b/,alias:"keyword"}},Prism.languages.webmanifest=Prism.languages.json; 15 | Prism.languages.less=Prism.languages.extend("css",{comment:[/\/\*[\s\S]*?\*\//,{pattern:/(^|[^\\])\/\/.*/,lookbehind:!0}],atrule:{pattern:/@[\w-]+?(?:\((?:[^(){}]|\([^(){}]*\))*\)|[^(){};])*?(?=\s*\{)/,inside:{punctuation:/[:()]/}},selector:{pattern:/(?:@\{[\w-]+\}|[^{};\s@])(?:@\{[\w-]+\}|\((?:[^(){}]|\([^(){}]*\))*\)|[^(){};@])*?(?=\s*\{)/,inside:{variable:/@+[\w-]+/}},property:/(?:@\{[\w-]+\}|[\w-])+(?:\+_?)?(?=\s*:)/i,operator:/[+\-*\/]/}),Prism.languages.insertBefore("less","property",{variable:[{pattern:/@[\w-]+\s*:/,inside:{punctuation:/:/}},/@@?[\w-]+/],"mixin-usage":{pattern:/([{;]\s*)[.#](?!\d)[\w-]+.*?(?=[(;])/,lookbehind:!0,alias:"function"}}); 16 | !function(d){function n(n,e){return n=n.replace(//g,function(){return"(?:\\\\.|[^\\\\\n\r]|(?:\n|\r\n?)(?!\n|\r\n?))"}),e&&(n=n+"|"+n.replace(/_/g,"\\*")),RegExp("((?:^|[^\\\\])(?:\\\\{2})*)(?:"+n+")")}var e="(?:\\\\.|``(?:[^`\r\n]|`(?!`))+``|`[^`\r\n]+`|[^\\\\|\r\n`])+",t="\\|?__(?:\\|__)+\\|?(?:(?:\n|\r\n?)|$)".replace(/__/g,function(){return e}),a="\\|?[ \t]*:?-{3,}:?[ \t]*(?:\\|[ \t]*:?-{3,}:?[ \t]*)+\\|?(?:\n|\r\n?)";d.languages.markdown=d.languages.extend("markup",{}),d.languages.insertBefore("markdown","prolog",{blockquote:{pattern:/^>(?:[\t ]*>)*/m,alias:"punctuation"},table:{pattern:RegExp("^"+t+a+"(?:"+t+")*","m"),inside:{"table-data-rows":{pattern:RegExp("^("+t+a+")(?:"+t+")*$"),lookbehind:!0,inside:{"table-data":{pattern:RegExp(e),inside:d.languages.markdown},punctuation:/\|/}},"table-line":{pattern:RegExp("^("+t+")"+a+"$"),lookbehind:!0,inside:{punctuation:/\||:?-{3,}:?/}},"table-header-row":{pattern:RegExp("^"+t+"$"),inside:{"table-header":{pattern:RegExp(e),alias:"important",inside:d.languages.markdown},punctuation:/\|/}}}},code:[{pattern:/((?:^|\n)[ \t]*\n|(?:^|\r\n?)[ \t]*\r\n?)(?: {4}|\t).+(?:(?:\n|\r\n?)(?: {4}|\t).+)*/,lookbehind:!0,alias:"keyword"},{pattern:/``.+?``|`[^`\r\n]+`/,alias:"keyword"},{pattern:/^```[\s\S]*?^```$/m,greedy:!0,inside:{"code-block":{pattern:/^(```.*(?:\n|\r\n?))[\s\S]+?(?=(?:\n|\r\n?)^```$)/m,lookbehind:!0},"code-language":{pattern:/^(```).+/,lookbehind:!0},punctuation:/```/}}],title:[{pattern:/\S.*(?:\n|\r\n?)(?:==+|--+)(?=[ \t]*$)/m,alias:"important",inside:{punctuation:/==+$|--+$/}},{pattern:/(^\s*)#+.+/m,lookbehind:!0,alias:"important",inside:{punctuation:/^#+|#+$/}}],hr:{pattern:/(^\s*)([*-])(?:[\t ]*\2){2,}(?=\s*$)/m,lookbehind:!0,alias:"punctuation"},list:{pattern:/(^\s*)(?:[*+-]|\d+\.)(?=[\t ].)/m,lookbehind:!0,alias:"punctuation"},"url-reference":{pattern:/!?\[[^\]]+\]:[\t ]+(?:\S+|<(?:\\.|[^>\\])+>)(?:[\t ]+(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\)))?/,inside:{variable:{pattern:/^(!?\[)[^\]]+/,lookbehind:!0},string:/(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\))$/,punctuation:/^[\[\]!:]|[<>]/},alias:"url"},bold:{pattern:n("__(?:(?!_)|_(?:(?!_))+_)+__",!0),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^..)[\s\S]+(?=..$)/,lookbehind:!0,inside:{}},punctuation:/\*\*|__/}},italic:{pattern:n("_(?:(?!_)|__(?:(?!_))+__)+_",!0),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^.)[\s\S]+(?=.$)/,lookbehind:!0,inside:{}},punctuation:/[*_]/}},strike:{pattern:n("(~~?)(?:(?!~))+?\\2",!1),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^~~?)[\s\S]+(?=\1$)/,lookbehind:!0,inside:{}},punctuation:/~~?/}},url:{pattern:n('!?\\[(?:(?!\\]))+\\](?:\\([^\\s)]+(?:[\t ]+"(?:\\\\.|[^"\\\\])*")?\\)| ?\\[(?:(?!\\]))+\\])',!1),lookbehind:!0,greedy:!0,inside:{variable:{pattern:/(\[)[^\]]+(?=\]$)/,lookbehind:!0},content:{pattern:/(^!?\[)[^\]]+(?=\])/,lookbehind:!0,inside:{}},string:{pattern:/"(?:\\.|[^"\\])*"(?=\)$)/}}}}),["url","bold","italic","strike"].forEach(function(e){["url","bold","italic","strike"].forEach(function(n){e!==n&&(d.languages.markdown[e].inside.content.inside[n]=d.languages.markdown[n])})}),d.hooks.add("after-tokenize",function(n){"markdown"!==n.language&&"md"!==n.language||!function n(e){if(e&&"string"!=typeof e)for(var t=0,a=e.length;t?|<|>)?|>[>=]?|\b(?:AND|BETWEEN|IN|LIKE|NOT|OR|IS|DIV|REGEXP|RLIKE|SOUNDS LIKE|XOR)\b/i,punctuation:/[;[\]()`,.]/}; 18 | !function(E){var A=E.languages.plsql=E.languages.extend("sql",{comment:[/\/\*[\s\S]*?\*\//,/--.*/]}),T=A.keyword;Array.isArray(T)||(T=A.keyword=[T]),T.unshift(/\b(?:ACCESS|AGENT|AGGREGATE|ARRAY|ARROW|AT|ATTRIBUTE|AUDIT|AUTHID|BFILE_BASE|BLOB_BASE|BLOCK|BODY|BOTH|BOUND|BYTE|CALLING|CHAR_BASE|CHARSET(?:FORM|ID)|CLOB_BASE|COLAUTH|COLLECT|CLUSTERS?|COMPILED|COMPRESS|CONSTANT|CONSTRUCTOR|CONTEXT|CRASH|CUSTOMDATUM|DANGLING|DATE_BASE|DEFINE|DETERMINISTIC|DURATION|ELEMENT|EMPTY|EXCEPTIONS?|EXCLUSIVE|EXTERNAL|FINAL|FORALL|FORM|FOUND|GENERAL|HEAP|HIDDEN|IDENTIFIED|IMMEDIATE|INCLUDING|INCREMENT|INDICATOR|INDEXES|INDICES|INFINITE|INITIAL|ISOPEN|INSTANTIABLE|INTERFACE|INVALIDATE|JAVA|LARGE|LEADING|LENGTH|LIBRARY|LIKE[24C]|LIMITED|LONG|LOOP|MAP|MAXEXTENTS|MAXLEN|MEMBER|MINUS|MLSLABEL|MULTISET|NAME|NAN|NATIVE|NEW|NOAUDIT|NOCOMPRESS|NOCOPY|NOTFOUND|NOWAIT|NUMBER(?:_BASE)?|OBJECT|OCI(?:COLL|DATE|DATETIME|DURATION|INTERVAL|LOBLOCATOR|NUMBER|RAW|REF|REFCURSOR|ROWID|STRING|TYPE)|OFFLINE|ONLINE|ONLY|OPAQUE|OPERATOR|ORACLE|ORADATA|ORGANIZATION|ORL(?:ANY|VARY)|OTHERS|OVERLAPS|OVERRIDING|PACKAGE|PARALLEL_ENABLE|PARAMETERS?|PASCAL|PCTFREE|PIPE(?:LINED)?|PRAGMA|PRIOR|PRIVATE|RAISE|RANGE|RAW|RECORD|REF|REFERENCE|REM|REMAINDER|RESULT|RESOURCE|RETURNING|REVERSE|ROW(?:ID|NUM|TYPE)|SAMPLE|SB[124]|SEGMENT|SELF|SEPARATE|SEQUENCE|SHORT|SIZE(?:_T)?|SPARSE|SQL(?:CODE|DATA|NAME|STATE)|STANDARD|STATIC|STDDEV|STORED|STRING|STRUCT|STYLE|SUBMULTISET|SUBPARTITION|SUBSTITUTABLE|SUBTYPE|SUCCESSFUL|SYNONYM|SYSDATE|TABAUTH|TDO|THE|TIMEZONE_(?:ABBR|HOUR|MINUTE|REGION)|TRAILING|TRANSAC(?:TIONAL)?|TRUSTED|UB[124]|UID|UNDER|UNTRUSTED|VALIDATE|VALIST|VARCHAR2|VARIABLE|VARIANCE|VARRAY|VIEWS|VOID|WHENEVER|WRAPPED|ZONE)\b/i);var R=A.operator;Array.isArray(R)||(R=A.operator=[R]),R.unshift(/:=/)}(Prism); 19 | Prism.languages.python={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0},"string-interpolation":{pattern:/(?:f|rf|fr)(?:("""|''')[\s\S]*?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^{])(?:{{)*){(?!{)(?:[^{}]|{(?!{)(?:[^{}]|{(?!{)(?:[^{}])+})+})+}/,lookbehind:!0,inside:{"format-spec":{pattern:/(:)[^:(){}]+(?=}$)/,lookbehind:!0},"conversion-option":{pattern:/![sra](?=[:}]$)/,alias:"punctuation"},rest:null}},string:/[\s\S]+/}},"triple-quoted-string":{pattern:/(?:[rub]|rb|br)?("""|''')[\s\S]*?\1/i,greedy:!0,alias:"string"},string:{pattern:/(?:[rub]|rb|br)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,greedy:!0},function:{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)\w+/i,lookbehind:!0},decorator:{pattern:/(^\s*)@\w+(?:\.\w+)*/im,lookbehind:!0,alias:["annotation","punctuation"],inside:{punctuation:/\./}},keyword:/\b(?:and|as|assert|async|await|break|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/,builtin:/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,boolean:/\b(?:True|False|None)\b/,number:/(?:\b(?=\d)|\B(?=\.))(?:0[bo])?(?:(?:\d|0x[\da-f])[\da-f]*\.?\d*|\.\d+)(?:e[+-]?\d+)?j?\b/i,operator:/[-+%=]=?|!=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},Prism.languages.python["string-interpolation"].inside.interpolation.inside.rest=Prism.languages.python,Prism.languages.py=Prism.languages.python; 20 | !function(i){var t=i.util.clone(i.languages.javascript);i.languages.jsx=i.languages.extend("markup",t),i.languages.jsx.tag.pattern=/<\/?(?:[\w.:-]+\s*(?:\s+(?:[\w.:$-]+(?:=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s{'">=]+|\{(?:\{(?:\{[^{}]*\}|[^{}])*\}|[^{}])+\}))?|\{\s*\.{3}\s*[a-z_$][\w$]*(?:\.[a-z_$][\w$]*)*\s*\}))*\s*\/?)?>/i,i.languages.jsx.tag.inside.tag.pattern=/^<\/?[^\s>\/]*/i,i.languages.jsx.tag.inside["attr-value"].pattern=/=(?!\{)(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">]+)/i,i.languages.jsx.tag.inside.tag.inside["class-name"]=/^[A-Z]\w*(?:\.[A-Z]\w*)*$/,i.languages.insertBefore("inside","attr-name",{spread:{pattern:/\{\s*\.{3}\s*[a-z_$][\w$]*(?:\.[a-z_$][\w$]*)*\s*\}/,inside:{punctuation:/\.{3}|[{}.]/,"attr-value":/\w+/}}},i.languages.jsx.tag),i.languages.insertBefore("inside","attr-value",{script:{pattern:/=(?:\{(?:\{(?:\{[^{}]*\}|[^{}])*\}|[^{}])+\})/i,inside:{"script-punctuation":{pattern:/^=(?={)/,alias:"punctuation"},rest:i.languages.jsx},alias:"language-javascript"}},i.languages.jsx.tag);var o=function(t){return t?"string"==typeof t?t:"string"==typeof t.content?t.content:t.content.map(o).join(""):""},p=function(t){for(var n=[],e=0;e"===a.content[a.content.length-1].content||n.push({tagName:o(a.content[0].content[1]),openedBraces:0}):0]|<(?:[^<>]|<[^<>]*>)*>)*>)?/,lookbehind:!0,greedy:!0,inside:null},keyword:/\b(?:abstract|as|asserts|async|await|break|case|catch|class|const|constructor|continue|debugger|declare|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|is|keyof|let|module|namespace|new|null|of|package|private|protected|public|readonly|return|require|set|static|super|switch|this|throw|try|type|typeof|undefined|var|void|while|with|yield)\b/,builtin:/\b(?:string|Function|any|number|boolean|Array|symbol|console|Promise|unknown|never)\b/}),delete e.languages.typescript.parameter;var n=e.languages.extend("typescript",{});delete n["class-name"],e.languages.typescript["class-name"].inside=n,e.languages.insertBefore("typescript","function",{"generic-function":{pattern:/#?[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>(?=\s*\()/,greedy:!0,inside:{function:/^#?[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*/,generic:{pattern:/<[\s\S]+/,alias:"class-name",inside:n}}}}),e.languages.ts=e.languages.typescript}(Prism); 22 | var typescript=Prism.util.clone(Prism.languages.typescript);Prism.languages.tsx=Prism.languages.extend("jsx",typescript); 23 | !function(e){for(var t="/\\*(?:[^*/]|\\*(?!/)|/(?!\\*)|)*\\*/",a=0;a<2;a++)t=t.replace(//g,function(){return t});t=t.replace(//g,function(){return"[^\\s\\S]"}),e.languages.rust={comment:[{pattern:RegExp("(^|[^\\\\])"+t),lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/b?"(?:\\[\s\S]|[^\\"])*"|b?r(#*)"(?:[^"]|"(?!\1))*"\1/,greedy:!0},char:{pattern:/b?'(?:\\(?:x[0-7][\da-fA-F]|u{(?:[\da-fA-F]_*){1,6}|.)|[^\\\r\n\t'])'/,greedy:!0,alias:"string"},attribute:{pattern:/#!?\[[^[\]]*\]/,greedy:!0,alias:"attr-name",inside:{string:null}},"closure-params":{pattern:/([=(,:]\s*|\bmove\s*)\|[^|]*\||\|[^|]*\|(?=\s*(?:\{|->))/,lookbehind:!0,greedy:!0,inside:{"closure-punctuation":{pattern:/^\||\|$/,alias:"punctuation"},rest:null}},"lifetime-annotation":{pattern:/'\w+/,alias:"symbol"},"fragment-specifier":{pattern:/(\$\w+:)[a-z]+/,lookbehind:!0,alias:"punctuation"},variable:/\$\w+/,"function-definition":{pattern:/(\bfn\s*)\w+/,lookbehind:!0,alias:"function"},keyword:[/\b(?:abstract|as|async|await|become|box|break|const|continue|crate|do|dyn|else|enum|extern|final|fn|for|if|impl|in|let|loop|macro|match|mod|move|mut|override|priv|pub|ref|return|self|Self|static|struct|super|trait|try|type|typeof|union|unsafe|unsized|use|virtual|where|while|yield)\b/,/\b(?:[ui](?:8|16|32|64|128|size)|f(?:32|64)|bool|char)\b/],function:/\b[a-z_]\w*(?=\s*(?:::\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>\s*)?\()/,macro:{pattern:/\w+!/,alias:"property"},number:/\b(?:0x[\dA-Fa-f](?:_?[\dA-Fa-f])*|0o[0-7](?:_?[0-7])*|0b[01](?:_?[01])*|(?:\d(?:_?\d)*)?\.?\d(?:_?\d)*(?:[Ee][+-]?\d+)?)(?:_?(?:[iu](?:8|16|32|64|size)?|f32|f64))?\b/,boolean:/\b(?:false|true)\b/,punctuation:/->|\.\.=|\.{1,3}|::|[{}[\];(),:]/,operator:/[-+*\/%!^]=?|=[=>]?|&[&=]?|\|[|=]?|<>?=?|[@?]/},e.languages.rust["closure-params"].inside.rest=e.languages.rust,e.languages.rust.attribute.inside.string=e.languages.rust.string}(Prism); 24 | Prism.languages.scss=Prism.languages.extend("css",{comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|\/\/.*)/,lookbehind:!0},atrule:{pattern:/@[\w-]+(?:\([^()]+\)|[^(])*?(?=\s+[{;])/,inside:{rule:/@[\w-]+/}},url:/(?:[-a-z]+-)?url(?=\()/i,selector:{pattern:/(?=\S)[^@;{}()]?(?:[^@;{}()]|#\{\$[-\w]+\})+(?=\s*\{(?:\}|\s|[^}]+[:{][^}]+))/m,inside:{parent:{pattern:/&/,alias:"important"},placeholder:/%[-\w]+/,variable:/\$[-\w]+|#\{\$[-\w]+\}/}},property:{pattern:/(?:[\w-]|\$[-\w]+|#\{\$[-\w]+\})+(?=\s*:)/,inside:{variable:/\$[-\w]+|#\{\$[-\w]+\}/}}}),Prism.languages.insertBefore("scss","atrule",{keyword:[/@(?:if|else(?: if)?|for|each|while|import|extend|debug|warn|mixin|include|function|return|content)/i,{pattern:/( +)(?:from|through)(?= )/,lookbehind:!0}]}),Prism.languages.insertBefore("scss","important",{variable:/\$[-\w]+|#\{\$[-\w]+\}/}),Prism.languages.insertBefore("scss","function",{placeholder:{pattern:/%[-\w]+/,alias:"selector"},statement:{pattern:/\B!(?:default|optional)\b/i,alias:"keyword"},boolean:/\b(?:true|false)\b/,null:{pattern:/\bnull\b/,alias:"keyword"},operator:{pattern:/(\s)(?:[-+*\/%]|[=!]=|<=?|>=?|and|or|not)(?=\s)/,lookbehind:!0}}),Prism.languages.scss.atrule.inside.rest=Prism.languages.scss; 25 | !function(n){var t=/[*&][^\s[\]{},]+/,e=/!(?:<[\w\-%#;/?:@&=+$,.!~*'()[\]]+>|(?:[a-zA-Z\d-]*!)?[\w\-%#;/?:@&=+$.~*'()]+)?/,r="(?:"+e.source+"(?:[ \t]+"+t.source+")?|"+t.source+"(?:[ \t]+"+e.source+")?)";function a(n,t){t=(t||"").replace(/m/g,"")+"m";var e="([:\\-,[{]\\s*(?:\\s<>[ \t]+)?)(?:<>)(?=[ \t]*(?:$|,|]|}|\\s*#))".replace(/<>/g,function(){return r}).replace(/<>/g,function(){return n});return RegExp(e,t)}n.languages.yaml={scalar:{pattern:RegExp("([\\-:]\\s*(?:\\s<>[ \t]+)?[|>])[ \t]*(?:((?:\r?\n|\r)[ \t]+)[^\r\n]+(?:\\2[^\r\n]+)*)".replace(/<>/g,function(){return r})),lookbehind:!0,alias:"string"},comment:/#.*/,key:{pattern:RegExp("((?:^|[:\\-,[{\r\n?])[ \t]*(?:<>[ \t]+)?)[^\r\n{[\\]},#\\s]+?(?=\\s*:\\s)".replace(/<>/g,function(){return r})),lookbehind:!0,alias:"atrule"},directive:{pattern:/(^[ \t]*)%.+/m,lookbehind:!0,alias:"important"},datetime:{pattern:a("\\d{4}-\\d\\d?-\\d\\d?(?:[tT]|[ \t]+)\\d\\d?:\\d{2}:\\d{2}(?:\\.\\d*)?[ \t]*(?:Z|[-+]\\d\\d?(?::\\d{2})?)?|\\d{4}-\\d{2}-\\d{2}|\\d\\d?:\\d{2}(?::\\d{2}(?:\\.\\d*)?)?"),lookbehind:!0,alias:"number"},boolean:{pattern:a("true|false","i"),lookbehind:!0,alias:"important"},null:{pattern:a("null|~","i"),lookbehind:!0,alias:"important"},string:{pattern:a("(\"|')(?:(?!\\2)[^\\\\\r\n]|\\\\.)*\\2"),lookbehind:!0,greedy:!0},number:{pattern:a("[+-]?(?:0x[\\da-f]+|0o[0-7]+|(?:\\d+\\.?\\d*|\\.?\\d+)(?:e[+-]?\\d+)?|\\.inf|\\.nan)","i"),lookbehind:!0},tag:e,important:t,punctuation:/---|[:[\]{}\-,|>?]|\.\.\./},n.languages.yml=n.languages.yaml}(Prism); 26 | !function(){if("undefined"!=typeof self&&self.Prism&&self.document&&document.querySelector){var t,s=function(){if(void 0===t){var e=document.createElement("div");e.style.fontSize="13px",e.style.lineHeight="1.5",e.style.padding="0",e.style.border="0",e.innerHTML=" 
 ",document.body.appendChild(e),t=38===e.offsetHeight,document.body.removeChild(e)}return t},l=!0,a=0;Prism.hooks.add("before-sanity-check",function(e){var t=e.element.parentNode,n=t&&t.getAttribute("data-line");if(t&&n&&/pre/i.test(t.nodeName)){var i=0;g(".line-highlight",t).forEach(function(e){i+=e.textContent.length,e.parentNode.removeChild(e)}),i&&/^( \n)+$/.test(e.code.slice(-i))&&(e.code=e.code.slice(0,-i))}}),Prism.hooks.add("complete",function e(t){var n=t.element.parentNode,i=n&&n.getAttribute("data-line");if(n&&i&&/pre/i.test(n.nodeName)){clearTimeout(a);var r=Prism.plugins.lineNumbers,o=t.plugins&&t.plugins.lineNumbers;if(b(n,"line-numbers")&&r&&!o)Prism.hooks.add("line-numbers",e);else u(n,i)(),a=setTimeout(c,1)}}),window.addEventListener("hashchange",c),window.addEventListener("resize",function(){g("pre[data-line]").map(function(e){return u(e)}).forEach(v)})}function g(e,t){return Array.prototype.slice.call((t||document).querySelectorAll(e))}function b(e,t){return t=" "+t+" ",-1<(" "+e.className+" ").replace(/[\n\t]/g," ").indexOf(t)}function v(e){e()}function u(u,e,c){var t=(e="string"==typeof e?e:u.getAttribute("data-line")).replace(/\s+/g,"").split(",").filter(Boolean),d=+u.getAttribute("data-line-offset")||0,f=(s()?parseInt:parseFloat)(getComputedStyle(u).lineHeight),m=b(u,"line-numbers"),p=m?u:u.querySelector("code")||u,h=[];t.forEach(function(e){var t=e.split("-"),n=+t[0],i=+t[1]||n,r=u.querySelector('.line-highlight[data-range="'+e+'"]')||document.createElement("div");if(h.push(function(){r.setAttribute("aria-hidden","true"),r.setAttribute("data-range",e),r.className=(c||"")+" line-highlight"}),m&&Prism.plugins.lineNumbers){var o=Prism.plugins.lineNumbers.getLine(u,n),a=Prism.plugins.lineNumbers.getLine(u,i);if(o){var s=o.offsetTop+"px";h.push(function(){r.style.top=s})}if(a){var l=a.offsetTop-o.offsetTop+a.offsetHeight+"px";h.push(function(){r.style.height=l})}}else h.push(function(){r.setAttribute("data-start",n),n span",u).forEach(function(e,t){var n=t+a;e.onclick=function(){var e=i+"."+n;l=!1,location.hash=e,setTimeout(function(){l=!0},1)}})}}return function(){h.forEach(v)}}function c(){var e=location.hash.slice(1);g(".temporary.line-highlight").forEach(function(e){e.parentNode.removeChild(e)});var t=(e.match(/\.([\d,-]+)$/)||[,""])[1];if(t&&!document.getElementById(e)){var n=e.slice(0,e.lastIndexOf(".")),i=document.getElementById(n);if(i)i.hasAttribute("data-line")||i.setAttribute("data-line",""),u(i,t,"temporary ")(),l&&document.querySelector(".temporary.line-highlight").scrollIntoView()}}}(); 27 | !function(){if("undefined"!=typeof self&&self.Prism&&self.document){var l="line-numbers",c=/\n(?!$)/g,m=function(e){var t=a(e)["white-space"];if("pre-wrap"===t||"pre-line"===t){var n=e.querySelector("code"),r=e.querySelector(".line-numbers-rows");if(!n||!r)return;var s=e.querySelector(".line-numbers-sizer"),i=n.textContent.split(c);s||((s=document.createElement("span")).className="line-numbers-sizer",n.appendChild(s)),s.style.display="block",i.forEach(function(e,t){s.textContent=e||"\n";var n=s.getBoundingClientRect().height;r.children[t].style.height=n+"px"}),s.textContent="",s.style.display="none"}},a=function(e){return e?window.getComputedStyle?getComputedStyle(e):e.currentStyle||null:null};window.addEventListener("resize",function(){Array.prototype.forEach.call(document.querySelectorAll("pre."+l),m)}),Prism.hooks.add("complete",function(e){if(e.code){var t=e.element,n=t.parentNode;if(n&&/pre/i.test(n.nodeName)&&!t.querySelector(".line-numbers-rows")){for(var r=!1,s=/(?:^|\s)line-numbers(?:\s|$)/,i=t;i;i=i.parentNode)if(s.test(i.className)){r=!0;break}if(r){t.className=t.className.replace(s," "),s.test(n.className)||(n.className+=" line-numbers");var l,a=e.code.match(c),o=a?a.length+1:1,u=new Array(o+1).join("");(l=document.createElement("span")).setAttribute("aria-hidden","true"),l.className="line-numbers-rows",l.innerHTML=u,n.hasAttribute("data-start")&&(n.style.counterReset="linenumber "+(parseInt(n.getAttribute("data-start"),10)-1)),e.element.appendChild(l),m(n),Prism.hooks.run("line-numbers",e)}}}}),Prism.hooks.add("line-numbers",function(e){e.plugins=e.plugins||{},e.plugins.lineNumbers=!0}),Prism.plugins.lineNumbers={getLine:function(e,t){if("PRE"===e.tagName&&e.classList.contains(l)){var n=e.querySelector(".line-numbers-rows"),r=parseInt(e.getAttribute("data-start"),10)||1,s=r+(n.children.length-1);t 2 |

Staple

3 |

powerful static site generator.

4 | GitHub Workflow Status Crates.io Coverage Status Crates.io (recent) Crates.io 5 | 6 | 7 | ## Installation 8 | currently, staple provide two ways to download its stable binary version. for those who is using rust can download it via Cargo: 9 | ```shell script 10 | cargo install staple 11 | ``` 12 | 13 | or, you can download the latest version in [Github Release](https://github.com/Kilerd/staple/releases). 14 | ### Developer version 15 | if you really need a developer version to taste some new feature which are not stable yet at the first time, you can download the latest staple code and compile it via Cargo: 16 | ```shell script 17 | cargo install --git https://github.com/Kilerd/staple.git 18 | ``` 19 | 20 | ## Usage 21 | `staple` has full cli help text, run `staple --help` to get help text if you don't know how to use it. 22 | 23 | ### Init a staple project 24 | 25 | for now, there are two ways to create a staple project for a folder. 26 | - for existed folder, you can use command `staple init` to initialize current working folder as a new staple project. 27 | - for non-existed folder, using command `staple new TARGET_FOLDER` to create a new folder for staple 28 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | merge_imports=true 2 | report_fixme="Always" -------------------------------------------------------------------------------- /src/app.rs: -------------------------------------------------------------------------------- 1 | use crate::{config::Config, data::PageInfo, error::StapleError, template::Template}; 2 | use walkdir::WalkDir; 3 | 4 | use crate::data::DataFile; 5 | use std::path::{Path, PathBuf}; 6 | 7 | #[derive(Debug)] 8 | pub struct App { 9 | pub(crate) config: Config, 10 | pub(crate) template: Template, 11 | is_develop_mode: bool, 12 | path: PathBuf, 13 | } 14 | 15 | impl App { 16 | pub fn load(path: impl AsRef, develop: bool) -> Result { 17 | let config = Config::load_from_file(&path)?; 18 | debug!("init template"); 19 | let theme = config.get_theme()?; 20 | debug!("theme is {}", theme); 21 | let template = Template::new(&path, theme)?; 22 | Ok(Self { 23 | config, 24 | template, 25 | is_develop_mode: develop, 26 | path: path.as_ref().to_path_buf(), 27 | }) 28 | } 29 | 30 | pub fn render(self) -> Result<(), StapleError> { 31 | for x in &self.config.hook.before_build { 32 | info!("Before-Build Script: {}", x); 33 | 34 | let mut result = std::process::Command::new("sh") 35 | .arg("-c") 36 | .arg(x.to_cmd()) 37 | .current_dir(x.to_dir()) 38 | .spawn() 39 | .expect("cannot spawn process"); 40 | 41 | let status = result.wait()?; 42 | if !status.success() { 43 | return Err(StapleError::HookError(x.to_cmd(), status.code())); 44 | } 45 | } 46 | let vec = self 47 | .load_all_data()? 48 | .into_iter() 49 | .filter(|article| !article.draw) 50 | .collect(); 51 | self.template 52 | .render(vec, &self.config, self.is_develop_mode)?; 53 | 54 | for x in &self.config.hook.after_build { 55 | info!("Before-Build Script: {}", x); 56 | 57 | let mut result = std::process::Command::new("sh") 58 | .arg("-c") 59 | .arg(x.to_cmd()) 60 | .current_dir(x.to_dir()) 61 | .spawn() 62 | .expect("cannot spawn process"); 63 | 64 | let status = result.wait()?; 65 | if !status.success() { 66 | return Err(StapleError::HookError(x.to_cmd(), status.code())); 67 | } 68 | } 69 | 70 | Ok(()) 71 | } 72 | 73 | pub fn load_all_data(&self) -> Result, StapleError> { 74 | let data_path = self.path.join("data"); 75 | let mut articles = vec![]; 76 | let filter = WalkDir::new(data_path) 77 | .into_iter() 78 | .flat_map(|e| e.ok()) 79 | .filter(|de| de.path().is_file()); 80 | 81 | for path in filter { 82 | let file_path = path.path(); 83 | if file_path.is_file() { 84 | let datafile = DataFile::load(file_path)?; 85 | if let Some(data) = datafile { 86 | articles.push(data); 87 | } 88 | } 89 | } 90 | articles.sort_by(|one, other| other.datetime.cmp(&one.datetime)); 91 | Ok(articles) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/command/add.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | app::App, 3 | command::StapleCommand, 4 | data::types::{json::JsonFileData, markdown::MarkdownFileData, CreationOptions, FileType}, 5 | error::StapleError, 6 | }; 7 | use std::path::Path; 8 | use structopt::StructOpt; 9 | 10 | #[derive(StructOpt, Debug)] 11 | pub struct AddOptions { 12 | pub title: String, 13 | #[structopt(long)] 14 | pub url: Option, 15 | #[structopt(long, short)] 16 | pub template: Option, 17 | #[structopt(long)] 18 | pub draw: bool, 19 | #[structopt(long)] 20 | pub data: bool, 21 | } 22 | 23 | pub fn add(path: impl AsRef, options: AddOptions) -> Result<(), StapleError> { 24 | StapleCommand::lock_file(&path)?; 25 | let app = App::load(&path, false)?; 26 | let url = options.url.clone().unwrap_or_else(|| { 27 | options 28 | .title 29 | .clone() 30 | .trim() 31 | .replace(" ", "-") 32 | .replace("_", "-") 33 | }); 34 | let template = options.template.unwrap_or(app.config.site.default_template); 35 | 36 | // new json file 37 | let create_options = CreationOptions { 38 | title: options.title, 39 | url, 40 | template, 41 | draw: options.draw, 42 | }; 43 | if options.data { 44 | JsonFileData::create(path, &create_options) 45 | } else { 46 | MarkdownFileData::create(path, &create_options) 47 | } 48 | } 49 | 50 | #[cfg(test)] 51 | mod test { 52 | use crate::{ 53 | command::add::{add, AddOptions}, 54 | test::setup, 55 | }; 56 | use serde_json::Value; 57 | 58 | #[test] 59 | fn should_add_markdown_file() -> Result<(), Box> { 60 | let dir = setup(); 61 | 62 | crate::command::init::init(&dir)?; 63 | 64 | let options = AddOptions { 65 | title: "test-one".to_owned(), 66 | url: None, 67 | template: None, 68 | draw: false, 69 | data: false, 70 | }; 71 | add(&dir, options)?; 72 | assert!(dir.join("data").join("test-one.md").exists()); 73 | 74 | Ok(()) 75 | } 76 | #[test] 77 | fn should_add_json_file() -> Result<(), Box> { 78 | let dir = setup(); 79 | crate::command::init::init(&dir)?; 80 | let options = AddOptions { 81 | title: "test-one".to_owned(), 82 | url: None, 83 | template: None, 84 | draw: false, 85 | data: true, 86 | }; 87 | add(&dir, options)?; 88 | assert!(dir.join("data").join("test-one.json").exists()); 89 | 90 | Ok(()) 91 | } 92 | 93 | #[test] 94 | fn should_add_json_draw_file() -> Result<(), Box> { 95 | let dir = setup(); 96 | crate::command::init::init(&dir)?; 97 | debug!("staple working in {}", dir.to_str().unwrap()); 98 | let options = AddOptions { 99 | title: "test-one".to_owned(), 100 | url: None, 101 | template: None, 102 | draw: true, 103 | data: true, 104 | }; 105 | add(&dir, options)?; 106 | let buf = dir.join("data").join("test-one.json"); 107 | let string = std::fs::read_to_string(buf)?; 108 | let result: serde_json::Value = serde_json::from_str(&string)?; 109 | 110 | assert_eq!(Some(&Value::Bool(true)), result.get("draw")); 111 | Ok(()) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/command/build.rs: -------------------------------------------------------------------------------- 1 | use crate::{app::App, command::StapleCommand, error::StapleError}; 2 | use std::path::Path; 3 | 4 | pub(crate) fn build(path: impl AsRef, develop: bool) -> Result<(), StapleError> { 5 | StapleCommand::lock_file(&path)?; 6 | App::load(&path, develop)?.render() 7 | } 8 | 9 | #[cfg(test)] 10 | mod test { 11 | use crate::{ 12 | command::{ 13 | add::{add, AddOptions}, 14 | build::build, 15 | }, 16 | test::setup, 17 | }; 18 | 19 | #[test] 20 | fn should_render_article_content() -> Result<(), Box> { 21 | let dir = setup(); 22 | crate::command::init::init(&dir)?; 23 | let options = AddOptions { 24 | title: "test-markdown".to_owned(), 25 | url: None, 26 | template: None, 27 | draw: false, 28 | data: false, 29 | }; 30 | add(&dir, options)?; 31 | 32 | let article = dir.join("data/test-markdown.md"); 33 | let string = std::fs::read_to_string(&article)?; 34 | let string1 = format!("{}\n\n{}", string, "# hello"); 35 | std::fs::write(&article, string1)?; 36 | build(&dir, false)?; 37 | 38 | let x = "

hello

\n"; 39 | assert_eq!( 40 | x, 41 | std::fs::read_to_string(dir.join("public/test-markdown/index.html"))? 42 | ); 43 | 44 | Ok(()) 45 | } 46 | 47 | // todo should_render_json_content 48 | // todo metadata_should_be_rendered 49 | 50 | // todo cross access article data 51 | // todo develop mode rendering 52 | } 53 | -------------------------------------------------------------------------------- /src/command/develop.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | command::StapleCommand, 3 | config::Config, 4 | error::StapleError, 5 | server::{ws::WsEvent, Server}, 6 | }; 7 | use notify::{DebouncedEvent as Event, RecommendedWatcher, RecursiveMode, Watcher}; 8 | use std::{ 9 | path::{Path, PathBuf}, 10 | sync::{ 11 | atomic::{AtomicBool, Ordering}, 12 | Arc, 13 | }, 14 | time::Duration, 15 | }; 16 | 17 | pub(crate) fn develop(path: impl AsRef, port: u16) -> Result<(), StapleError> { 18 | StapleCommand::check_config_file_exist(&path)?; 19 | crate::command::build::build(&path, true)?; 20 | 21 | let has_new_file_event = Arc::new(AtomicBool::new(false)); 22 | let _is_building = Arc::new(AtomicBool::new(false)); 23 | 24 | let (addr, sys) = Server::start(port); 25 | 26 | let file_event_flag_for_watcher = has_new_file_event.clone(); 27 | let _watcher_thread = std::thread::spawn(move || { 28 | let (tx, rx) = std::sync::mpsc::channel(); 29 | let mut result: RecommendedWatcher = 30 | Watcher::new(tx, Duration::from_secs(2)).expect("cannot watch"); 31 | info!("Watching ./data"); 32 | result 33 | .watch("data", RecursiveMode::Recursive) 34 | .expect("cannot watch articles"); 35 | info!("Watching ./templates"); 36 | result 37 | .watch("templates", RecursiveMode::Recursive) 38 | .expect("cannot watch articles"); 39 | info!("Watching ./Staple.toml"); 40 | result 41 | .watch("Staple.toml", RecursiveMode::Recursive) 42 | .expect("cannot watch articles"); 43 | let exclusive_list: Vec = Config::load_from_file("./") 44 | .expect("cannot load config") 45 | .watch 46 | .exclusive 47 | .into_iter() 48 | .map(|p| { 49 | info!("Unwatching {}", &p); 50 | Path::new(&p).canonicalize().expect("invalid unwatch path") 51 | }) 52 | .collect(); 53 | 54 | // 有文件事件来的时候就把 `should_update_flag` 设置为 true 55 | // 循环监听,如果是true 就 build,完成后休眠100ms, build 之前先设置标识为为 false 56 | loop { 57 | match rx.recv() { 58 | Ok(event) => match &event { 59 | Event::Chmod(source) 60 | | Event::Create(source) 61 | | Event::Write(source) 62 | | Event::Rename(source, _) => { 63 | if let Ok(event_path) = source.canonicalize() { 64 | let is_exclusive = exclusive_list 65 | .iter() 66 | .any(|exclusive| event_path.strip_prefix(exclusive).is_ok()); 67 | if is_exclusive { 68 | debug!("Get exclusive file event: {:?}", event); 69 | } else { 70 | info!("get an file event, {:?}", event); 71 | file_event_flag_for_watcher.store(true, Ordering::Relaxed); 72 | } 73 | } else { 74 | warn!("cannot canonicalize event path, skip event"); 75 | } 76 | } 77 | _ => {} 78 | }, 79 | Err(e) => error!("watch error: {:?}", e), 80 | } 81 | } 82 | }); 83 | 84 | let file_event_flag_for_builder = has_new_file_event; 85 | let buf = path.as_ref().to_path_buf(); 86 | let _handle = std::thread::spawn(move || loop { 87 | let need_build = 88 | file_event_flag_for_builder.compare_and_swap(true, false, Ordering::Relaxed); 89 | if need_build { 90 | info!("build stage is triggered by file event."); 91 | let result1 = crate::command::build::build(buf.clone(), true); 92 | match result1 { 93 | Ok(_) => info!("build successfully"), 94 | Err(e) => error!("fail to build due to {}", e), 95 | } 96 | addr.do_send(WsEvent::Refresh); 97 | } 98 | std::thread::sleep(Duration::from_secs(1)); 99 | }); 100 | info!( 101 | "developing server is listening on http://127.0.0.1:{}", 102 | port 103 | ); 104 | sys.run().expect(""); 105 | Ok(()) 106 | } 107 | -------------------------------------------------------------------------------- /src/command/init.rs: -------------------------------------------------------------------------------- 1 | use crate::{config::Config, constants::STAPLE_CONFIG_FILE, error::StapleError}; 2 | use colored::Colorize; 3 | use console::style; 4 | use std::path::Path; 5 | 6 | /// init target folder as staple project structure 7 | /// check whether `Staple.toml` exist or not 8 | /// generate `Staple.toml` config file 9 | /// create folders `articles`, `templates` 10 | /// put default template files 11 | pub(crate) fn init(path: impl AsRef) -> Result<(), StapleError> { 12 | let buf = path.as_ref(); 13 | let check_files = vec![STAPLE_CONFIG_FILE, "data", "templates"]; 14 | for path in check_files { 15 | if buf.join(path).exists() { 16 | error!( 17 | "'{}' existed, please delete it and then continue", 18 | style(path).blue() 19 | ); 20 | return Ok(()); 21 | } 22 | } 23 | info!("Creating file {}", STAPLE_CONFIG_FILE.blue()); 24 | let config = Config::get_default_file(); 25 | let string = toml::to_string(&config).expect("cannot serialize default config struct"); 26 | std::fs::write(buf.join(STAPLE_CONFIG_FILE), string)?; 27 | info!("Creating folder {}", "data".blue()); 28 | std::fs::create_dir(buf.join("data"))?; 29 | info!("Creating folder {}", "template".blue()); 30 | std::fs::create_dir(buf.join("templates"))?; 31 | info!("Creating template {}", "staple".blue()); 32 | std::fs::create_dir(buf.join("templates").join("staple"))?; 33 | info!("Creating template {}", "staple/article".blue()); 34 | std::fs::write( 35 | buf.join("templates").join("staple").join("article.html"), 36 | "{{ page.content.html | safe }}", 37 | )?; 38 | Ok(()) 39 | } 40 | 41 | #[cfg(test)] 42 | mod test { 43 | use crate::{command::init::init, constants::STAPLE_CONFIG_FILE, test::setup}; 44 | 45 | #[test] 46 | fn test_init() -> Result<(), Box> { 47 | let dir = setup(); 48 | init(&dir)?; 49 | 50 | let check_point = vec![ 51 | STAPLE_CONFIG_FILE, 52 | "data", 53 | "templates", 54 | "templates/staple", 55 | "templates/staple/article.html", 56 | ]; 57 | 58 | for point in check_point { 59 | let buf = dir.join(point); 60 | assert!(buf.exists()); 61 | } 62 | Ok(()) 63 | } 64 | 65 | #[test] 66 | fn test_exist() -> Result<(), Box> { 67 | let dir = setup(); 68 | 69 | let buf = dir.join(STAPLE_CONFIG_FILE); 70 | std::fs::write(buf, "exist test")?; 71 | init(&dir)?; 72 | 73 | assert!(!dir.join("data").exists()); 74 | 75 | Ok(()) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/command/list.rs: -------------------------------------------------------------------------------- 1 | use crate::{app::App, command::StapleCommand, error::StapleError}; 2 | use colored::*; 3 | use std::path::Path; 4 | 5 | pub(crate) fn command(path: impl AsRef) -> Result<(), StapleError> { 6 | StapleCommand::lock_file(&path)?; 7 | let app = App::load(path, false)?; 8 | info!("Project Name: {}", app.config.site.title); 9 | let mut pages = app.load_all_data()?; 10 | pages.reverse(); 11 | for page in pages { 12 | let draw = if page.draw { "DRAW" } else { "" }; 13 | 14 | info!( 15 | "{} {:4} {}({})", 16 | page.datetime.format("%b %d, %Y"), 17 | draw.blue(), 18 | page.title.white().magenta(), 19 | page.url 20 | ); 21 | } 22 | Ok(()) 23 | } 24 | 25 | #[cfg(test)] 26 | mod test { 27 | use crate::{ 28 | command::{ 29 | add::{add, AddOptions}, 30 | list::command, 31 | }, 32 | test::setup, 33 | }; 34 | 35 | #[test] 36 | fn test_list() -> Result<(), Box> { 37 | let dir = setup(); 38 | crate::command::init::init(&dir)?; 39 | 40 | command(dir)?; 41 | 42 | Ok(()) 43 | } 44 | 45 | #[test] 46 | fn should_show_article_list() -> Result<(), Box> { 47 | let dir = setup(); 48 | crate::command::init::init(&dir)?; 49 | let options = AddOptions { 50 | title: "test-one".to_owned(), 51 | url: None, 52 | template: None, 53 | draw: true, 54 | data: true, 55 | }; 56 | add(&dir, options)?; 57 | 58 | let options = AddOptions { 59 | title: "test-two".to_owned(), 60 | url: None, 61 | template: None, 62 | draw: false, 63 | data: false, 64 | }; 65 | add(&dir, options)?; 66 | 67 | Ok(command(dir)?) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/command/mod.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use structopt::StructOpt; 4 | 5 | use crate::error::StapleError; 6 | 7 | use crate::{ 8 | command::add::AddOptions, 9 | constants::{STAPLE_CONFIG_FILE, STAPLE_LOCK_FILE}, 10 | util::lock::LockFile, 11 | }; 12 | 13 | pub mod add; 14 | pub mod build; 15 | pub mod develop; 16 | pub mod init; 17 | pub mod list; 18 | pub mod new; 19 | 20 | #[derive(StructOpt, Debug)] 21 | #[structopt(name = "Staple")] 22 | pub enum StapleCommand { 23 | /// create a new folder and then init it as Staple project. 24 | New { 25 | /// folder name 26 | path: String, 27 | /// specific Staple project's title, default is Staple 28 | #[structopt(long)] 29 | title: Option, 30 | /// force to delete exist folder if existed, then create a new one and initialize. 31 | #[structopt(short, long)] 32 | force: bool, 33 | }, 34 | /// init current folder as Staple project. 35 | Init, 36 | /// build 37 | Build, 38 | /// start the develop server listening on local with live-reload 39 | Develop { 40 | /// port of developing server listens on 41 | #[structopt(short, long, default_value = "8000", env = "STAPLE_DEVELOP_PORT")] 42 | port: u16, 43 | }, 44 | /// add new article 45 | Add(AddOptions), 46 | 47 | /// show all information of staple project 48 | List, 49 | } 50 | 51 | impl StapleCommand { 52 | pub fn run(self) -> Result<(), StapleError> { 53 | let path = "."; 54 | match self { 55 | StapleCommand::New { path, title, force } => new::new(path, title, force), 56 | StapleCommand::Init => init::init(&path), 57 | StapleCommand::Build => build::build(path, false), 58 | StapleCommand::Develop { port } => develop::develop(&path, port), 59 | StapleCommand::List => { 60 | StapleCommand::check_config_file_exist(&path)?; 61 | list::command(&path) 62 | } 63 | 64 | StapleCommand::Add(options) => add::add(&path, options), 65 | } 66 | } 67 | 68 | #[inline] 69 | fn config_file_exist(path: impl AsRef) -> bool { 70 | path.as_ref().join(STAPLE_CONFIG_FILE).exists() 71 | } 72 | 73 | fn check_config_file_exist(path: impl AsRef) -> Result<(), StapleError> { 74 | if StapleCommand::config_file_exist(path) { 75 | Ok(()) 76 | } else { 77 | Err(StapleError::ConfigNotFound) 78 | } 79 | } 80 | 81 | fn lock_file(path: impl AsRef) -> Result { 82 | let buf = path.as_ref().join(STAPLE_LOCK_FILE); 83 | let lock_file = LockFile::new(buf)?; 84 | info!("Preparing to lock file..."); 85 | lock_file.lock_file()?; 86 | Ok(lock_file) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/command/new.rs: -------------------------------------------------------------------------------- 1 | use crate::{error::StapleError, template::Template}; 2 | use colored::*; 3 | use console::style; 4 | use std::path::Path; 5 | 6 | pub(crate) fn new( 7 | path: impl AsRef, 8 | _title: Option, 9 | force: bool, 10 | ) -> Result<(), StapleError> { 11 | let buf = path.as_ref(); 12 | let folder = buf.to_str().expect("invalid filename"); 13 | 14 | if force { 15 | // TODO add print 16 | info!( 17 | "Passing `--force` flag, deleting existed folder {}", 18 | folder.blue() 19 | ); 20 | Template::remove_folder(&path)?; 21 | } 22 | if buf.exists() { 23 | let filename = buf.to_str().expect("invalid file name"); 24 | error!( 25 | "folder {} existed, please delete it then run `new` again, or just use `--force` flag (it would delete existed folder and create a new one)", 26 | style(filename).blue() 27 | ); 28 | return Ok(()); 29 | } 30 | info!("Creating staple folder {}", folder.blue()); 31 | std::fs::create_dir(buf)?; 32 | crate::command::init::init(&path) 33 | } 34 | 35 | #[cfg(test)] 36 | mod test { 37 | use crate::{command::new::new, constants::STAPLE_CONFIG_FILE, test::setup}; 38 | 39 | #[test] 40 | fn should_show_error_when_folder_exists() -> Result<(), Box> { 41 | let dir = setup(); 42 | 43 | let existed_folder = dir.join("exists"); 44 | let existed_file = existed_folder.join(STAPLE_CONFIG_FILE); 45 | std::fs::create_dir(&existed_folder)?; 46 | std::fs::write(&existed_file, "invalid config")?; 47 | new(existed_folder, None, false)?; 48 | 49 | let string = std::fs::read_to_string(&existed_file)?; 50 | assert_eq!("invalid config", string); 51 | Ok(()) 52 | } 53 | 54 | #[test] 55 | fn should_delete_existed_folder_when_force_flag_is_true( 56 | ) -> Result<(), Box> { 57 | let dir = setup(); 58 | 59 | let existed_folder = dir.join("exists"); 60 | std::fs::create_dir(&existed_folder)?; 61 | 62 | let existed_file = existed_folder.join(STAPLE_CONFIG_FILE); 63 | std::fs::write(&existed_file, "invalid config")?; 64 | 65 | new(existed_folder, None, true)?; 66 | 67 | let string = std::fs::read_to_string(&existed_file)?; 68 | assert_ne!("invalid config", string); 69 | Ok(()) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, path::Path}; 2 | 3 | use serde_derive::{Deserialize, Serialize}; 4 | use toml::Value; 5 | 6 | use crate::{constants::STAPLE_CONFIG_FILE, error::StapleError}; 7 | use serde::export::Formatter; 8 | use std::fmt::Display; 9 | 10 | #[derive(Serialize, Deserialize, Debug, PartialEq)] 11 | pub struct Config { 12 | pub site: Site, 13 | #[serde(default)] 14 | pub statics: Vec, 15 | #[serde(default)] 16 | pub hook: Hook, 17 | #[serde(default)] 18 | pub watch: Watch, 19 | pub extra: HashMap, 20 | } 21 | 22 | #[derive(Serialize, Deserialize, Debug, PartialEq)] 23 | pub struct Hook { 24 | #[serde(default)] 25 | pub before_build: Vec, 26 | #[serde(default)] 27 | pub after_build: Vec, 28 | } 29 | 30 | impl Default for Hook { 31 | fn default() -> Self { 32 | Hook { 33 | before_build: vec![], 34 | after_build: vec![], 35 | } 36 | } 37 | } 38 | 39 | #[derive(Serialize, Deserialize, Debug, PartialEq)] 40 | #[serde(untagged)] 41 | pub enum HookLine { 42 | Command(String), 43 | TargetDir { dir: String, command: String }, 44 | } 45 | 46 | impl HookLine { 47 | pub fn to_dir(&self) -> String { 48 | match self { 49 | HookLine::Command(_) => String::from("./"), 50 | HookLine::TargetDir { dir, command: _ } => dir.to_string(), 51 | } 52 | } 53 | pub fn to_cmd(&self) -> String { 54 | match self { 55 | HookLine::Command(cmd) => cmd.to_string(), 56 | HookLine::TargetDir { dir: _, command } => command.to_string(), 57 | } 58 | } 59 | } 60 | 61 | impl Display for HookLine { 62 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 63 | write!(f, "[{}] {}", self.to_dir(), self.to_cmd()) 64 | } 65 | } 66 | 67 | #[derive(Serialize, Deserialize, Debug, PartialEq)] 68 | pub struct Watch { 69 | pub exclusive: Vec, 70 | } 71 | 72 | impl Default for Watch { 73 | fn default() -> Self { 74 | Self { exclusive: vec![] } 75 | } 76 | } 77 | 78 | #[derive(Serialize, Deserialize, Debug, PartialEq)] 79 | pub struct ConfigFile { 80 | pub site: Site, 81 | pub statics: Option>, 82 | pub extra: HashMap, 83 | pub hook: Hook, 84 | } 85 | 86 | impl Default for ConfigFile { 87 | fn default() -> Self { 88 | ConfigFile { 89 | site: Default::default(), 90 | statics: None, 91 | extra: Default::default(), 92 | hook: Default::default(), 93 | } 94 | } 95 | } 96 | 97 | impl Config { 98 | pub fn load_from_file(path: impl AsRef) -> Result { 99 | debug!("load config file"); 100 | let config_file_path = path.as_ref().join(STAPLE_CONFIG_FILE); 101 | let config_content = std::fs::read_to_string(config_file_path)?; 102 | let result: Config = toml::from_str(&config_content)?; 103 | Ok(result) 104 | } 105 | 106 | pub fn get_theme(&self) -> Result { 107 | Ok(self.site.theme.clone()) 108 | } 109 | pub fn get_default_file() -> ConfigFile { 110 | ConfigFile::default() 111 | } 112 | } 113 | 114 | impl Default for Config { 115 | fn default() -> Self { 116 | Config { 117 | site: Default::default(), 118 | statics: vec![], 119 | hook: Hook { 120 | before_build: vec![], 121 | after_build: vec![], 122 | }, 123 | watch: Default::default(), 124 | extra: Default::default(), 125 | } 126 | } 127 | } 128 | 129 | #[derive(Serialize, Deserialize, Debug, PartialEq)] 130 | pub struct Site { 131 | pub title: String, 132 | pub subtitle: String, 133 | pub description: String, 134 | pub keywords: Vec, 135 | pub author: String, 136 | pub email: String, 137 | pub utc_offset: i16, 138 | pub theme: String, 139 | pub domain: String, 140 | pub domain_root: String, 141 | pub default_template: String, 142 | } 143 | 144 | impl Default for Site { 145 | fn default() -> Self { 146 | Self { 147 | title: "Staple Site".to_string(), 148 | subtitle: "".to_string(), 149 | description: "".to_string(), 150 | keywords: vec![], 151 | author: "".to_string(), 152 | email: "".to_string(), 153 | utc_offset: 800, 154 | theme: "staple".to_string(), 155 | domain: "".to_string(), 156 | domain_root: "".to_string(), 157 | default_template: "article.html".to_string(), 158 | } 159 | } 160 | } 161 | 162 | #[derive(Serialize, Deserialize, Debug, PartialEq)] 163 | pub struct Statics { 164 | pub from: String, 165 | pub to: String, 166 | } 167 | 168 | #[cfg(test)] 169 | mod test { 170 | use crate::config::{Config, ConfigFile, HookLine}; 171 | 172 | #[test] 173 | fn test_hook_display() { 174 | assert_eq!("[./] ls", HookLine::Command("ls".to_string()).to_string()); 175 | assert_eq!( 176 | "[data] ls", 177 | HookLine::TargetDir { 178 | dir: "data".to_string(), 179 | command: "ls".to_string(), 180 | } 181 | .to_string() 182 | ); 183 | } 184 | 185 | #[test] 186 | fn test_config_file_site_default() { 187 | let config = Config::default(); 188 | let site = config.site; 189 | assert_eq!("Staple Site", site.title); 190 | assert_eq!("", site.author); 191 | assert_eq!("staple", site.theme); 192 | assert_eq!("", site.domain_root); 193 | assert_eq!("article.html", site.default_template); 194 | assert_eq!(800, site.utc_offset); 195 | } 196 | 197 | #[test] 198 | fn test_config_file_hook_default() { 199 | let config = Config::default(); 200 | assert!(config.hook.before_build.is_empty()); 201 | assert!(config.hook.after_build.is_empty()); 202 | } 203 | 204 | #[test] 205 | fn test_config_file_statics_default() { 206 | let config = Config::default(); 207 | assert!(config.statics.is_empty()); 208 | } 209 | 210 | #[test] 211 | fn test_config_file_extra_default() { 212 | let config = Config::default(); 213 | assert!(config.extra.is_empty()); 214 | } 215 | 216 | #[test] 217 | fn test_config_default_generator() { 218 | let config = Config::get_default_file(); 219 | assert_eq!(ConfigFile::default(), config); 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/constants.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | #[cfg(windows)] 4 | pub const LINE_ENDING: &'static str = "\r\n"; 5 | #[cfg(not(windows))] 6 | pub const LINE_ENDING: &str = "\n"; 7 | 8 | pub const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5); 9 | pub const CLIENT_TIMEOUT: Duration = Duration::from_secs(10); 10 | 11 | pub const STAPLE_CONFIG_FILE: &str = "Staple.toml"; 12 | pub const STAPLE_LOCK_FILE: &str = "Staple.lock"; 13 | 14 | pub const RENDER_FOLDER: &str = ".render"; 15 | pub const PUBLIC_FOLDER: &str = "public"; 16 | 17 | pub const DESCRIPTION_SEPARATOR: &str = ""; 18 | 19 | pub const LIVE_RELOAD_CODE: &str = include_str!("../data/live_reload.html"); 20 | -------------------------------------------------------------------------------- /src/data/mod.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, FixedOffset}; 2 | use itertools::Itertools; 3 | use pulldown_cmark::{Event, Options}; 4 | use regex::Regex; 5 | use serde::{Deserialize, Serialize}; 6 | use serde_json::Value; 7 | 8 | use std::{collections::HashMap, path::Path}; 9 | 10 | use crate::{ 11 | data::types::{json::JsonFileData, markdown::MarkdownFileData, FileType}, 12 | error::StapleError, 13 | }; 14 | 15 | pub(crate) mod types; 16 | 17 | #[derive(Serialize, Deserialize, Debug)] 18 | pub struct MarkdownContent { 19 | pub markdown: String, 20 | pub html: String, 21 | } 22 | 23 | impl MarkdownContent { 24 | pub fn new(raw: String) -> Self { 25 | let mut html_output = String::new(); 26 | let options = Options::all(); 27 | let parser = pulldown_cmark::Parser::new_ext(&raw, options).flat_map(|event| match event { 28 | Event::Text(text) => { 29 | let mut text_chars = text.as_bytes().iter(); 30 | let mut events = vec![]; 31 | let re = 32 | Regex::new(r#"\{(?P[^}]+)}\((?P<ruby>[^)]+)\)"#).expect("invalid regex"); 33 | let mut last_end_index = 0; 34 | for captures in re.captures_iter(&text) { 35 | let ruby_group = captures.get(0).unwrap(); 36 | let ruby_name = captures 37 | .name("title") 38 | .expect("invalid title") 39 | .as_str() 40 | .to_string(); 41 | let ruby_description = captures 42 | .name("ruby") 43 | .expect("invalid ruby") 44 | .as_str() 45 | .to_string(); 46 | let ruby_group_start = ruby_group.start(); 47 | 48 | if last_end_index != ruby_group_start { 49 | let ruby_prefix_content: Vec<u8> = text_chars 50 | .by_ref() 51 | .take(ruby_group_start - last_end_index) 52 | .copied() 53 | .collect(); 54 | let string = 55 | String::from_utf8(ruby_prefix_content).expect("invalid utf8 string"); 56 | events.push(Event::Text(string.into())); 57 | } 58 | last_end_index = ruby_group.end(); 59 | text_chars = text_chars.dropping(ruby_group.end() - ruby_group.start()); 60 | 61 | events.push(Event::Html("<ruby>".into())); 62 | events.push(Event::Text(ruby_name.into())); 63 | events.push(Event::Html("<rp>(</rp><rt>".into())); 64 | events.push(Event::Text(ruby_description.into())); 65 | events.push(Event::Html("</rt><rp>)</rp>".into())); 66 | events.push(Event::Html("</ruby>".into())); 67 | } 68 | if last_end_index < text.len() { 69 | let rest: Vec<u8> = text_chars.copied().collect(); 70 | let rest = String::from_utf8(rest).expect("invalid utf8 string"); 71 | events.push(Event::Text(rest.into())); 72 | } 73 | events 74 | } 75 | 76 | _ => vec![event], 77 | }); 78 | pulldown_cmark::html::push_html(&mut html_output, parser); 79 | 80 | Self { 81 | markdown: raw, 82 | html: html_output, 83 | } 84 | } 85 | } 86 | 87 | #[derive(Debug, Serialize, Deserialize)] 88 | #[serde(tag = "types")] 89 | pub enum DataFile { 90 | JsonFile(JsonFileData), 91 | MarkdownFile(MarkdownFileData), 92 | } 93 | 94 | impl DataFile { 95 | pub fn template(&self) -> &str { 96 | match &self { 97 | DataFile::JsonFile(data) => &data.template, 98 | DataFile::MarkdownFile(data) => &data.template, 99 | } 100 | } 101 | 102 | pub fn load(path: impl AsRef<Path>) -> Result<Option<PageInfo>, StapleError> { 103 | let extension = path.as_ref().extension().and_then(|e| e.to_str()); 104 | match extension { 105 | Some("md") => { 106 | MarkdownFileData::load(path.as_ref()).map(|full| Some(full.into_page_info())) 107 | } 108 | Some("json") => { 109 | JsonFileData::load(path.as_ref()).map(|full| Some(full.into_page_info())) 110 | } 111 | _ => Ok(None), 112 | } 113 | } 114 | } 115 | 116 | #[derive(Debug, Serialize, Deserialize)] 117 | pub struct PageInfo { 118 | pub file: String, 119 | pub url: String, 120 | pub title: String, 121 | pub template: String, 122 | #[serde(default)] 123 | pub draw: bool, 124 | pub datetime: DateTime<FixedOffset>, 125 | pub data: HashMap<String, Value>, 126 | pub description: Option<MarkdownContent>, 127 | } 128 | 129 | impl PageInfo { 130 | pub fn to_full_article(&self) -> Result<DataFile, StapleError> { 131 | let path = Path::new(&self.file); 132 | let extension = path.extension().and_then(|e| e.to_str()).unwrap_or(""); 133 | match extension { 134 | "md" => MarkdownFileData::load(path.to_str().expect("invalid file path encoding")) 135 | .map(DataFile::MarkdownFile), 136 | 137 | "json" => JsonFileData::load(path).map(DataFile::JsonFile), 138 | _ => unreachable!(), 139 | } 140 | } 141 | /// 142 | /// 143 | pub fn output_file_name(&self) -> String { 144 | let url = Path::new(&self.url); 145 | let has_extension = url.extension().is_some(); 146 | let start_with_slash = self.url.starts_with('/'); 147 | let end_with_slash = self.url.ends_with('/'); 148 | 149 | format!( 150 | "{}{}", 151 | if start_with_slash { 152 | &self.url[1..self.url.len()] 153 | } else { 154 | &self.url 155 | }, 156 | if has_extension { 157 | "" 158 | } else if end_with_slash { 159 | "index.html" 160 | } else { 161 | "/index.html" 162 | } 163 | ) 164 | } 165 | } 166 | #[cfg(test)] 167 | mod test { 168 | use crate::data::{MarkdownContent, PageInfo}; 169 | use chrono::{FixedOffset, Utc}; 170 | 171 | #[test] 172 | fn should_render_ruby_tag() { 173 | let content = 174 | MarkdownContent::new("**this** is **{ruby}(ruby description) aaa** tag".to_string()); 175 | assert_eq!( 176 | "<p><strong>this</strong> is <strong><ruby>ruby<rp>(</rp><rt>ruby description</rt><rp>)</rp></ruby> aaa</strong> tag</p>\n", 177 | content.html 178 | ); 179 | } 180 | #[test] 181 | fn should_render_ruby_tag_with_utf8() { 182 | let content = MarkdownContent::new("这是一个{RUBY带中文}(中文下标)标签".to_string()); 183 | assert_eq!( 184 | "<p>这是一个<ruby>RUBY带中文<rp>(</rp><rt>中文下标</rt><rp>)</rp></ruby>标签</p>\n", 185 | content.html 186 | ); 187 | } 188 | 189 | #[test] 190 | fn test_page_info_output_file_name() { 191 | fn get_page_info_output_file_name(url: &str) -> String { 192 | PageInfo { 193 | file: "".to_string(), 194 | url: url.to_owned(), 195 | title: "".to_string(), 196 | template: "".to_string(), 197 | draw: false, 198 | datetime: Utc::now().with_timezone(&FixedOffset::east(60 * 60 * 8)), 199 | data: Default::default(), 200 | description: None, 201 | } 202 | .output_file_name() 203 | } 204 | 205 | assert_eq!("index.html", get_page_info_output_file_name("/")); 206 | assert_eq!("rss.xml", get_page_info_output_file_name("/rss.xml")); 207 | assert_eq!("rss.xml", get_page_info_output_file_name("rss.xml")); 208 | assert_eq!("a/index.html", get_page_info_output_file_name("a")); 209 | assert_eq!("a/index.html", get_page_info_output_file_name("/a")); 210 | assert_eq!("a/b/c/index.html", get_page_info_output_file_name("/a/b/c")); 211 | assert_eq!( 212 | "a/b/c/index.html", 213 | get_page_info_output_file_name("/a/b/c/") 214 | ); 215 | assert_eq!("a/b/c.json", get_page_info_output_file_name("/a/b/c.json")); 216 | assert_eq!( 217 | "a/b/what.html", 218 | get_page_info_output_file_name("/a/b/what.html") 219 | ); 220 | assert_eq!( 221 | "a/b/what.html", 222 | get_page_info_output_file_name("a/b/what.html") 223 | ); 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /src/data/types/article.pest: -------------------------------------------------------------------------------- 1 | // 2 | // Created by intellij-pest on 2019-10-17 3 | // article 4 | // Author: Kilerd 5 | // 6 | article = {SOI ~ meta ~ content ~ EOI} 7 | 8 | meta = { "\n"* ~ (inlineMeta ~ "\n"+)+ } 9 | 10 | inlineMeta = { " "* ~ "-" ~ " "* ~ inlineKey ~ " "* ~ "=" ~ " "* ~ inlineValue } 11 | 12 | inlineKey = { (!("\n" | " " | "=") ~ ANY)+ } 13 | inlineValue = { (!("\n") ~ ANY)* } 14 | content = { ANY* } -------------------------------------------------------------------------------- /src/data/types/json.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, FixedOffset, Utc}; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use crate::{ 5 | constants::DESCRIPTION_SEPARATOR, 6 | data::{ 7 | types::{CreationOptions, FileType}, 8 | MarkdownContent, PageInfo, 9 | }, 10 | error::StapleError, 11 | }; 12 | use serde_json::Value; 13 | use std::{collections::HashMap, path::Path}; 14 | 15 | #[derive(Debug, Serialize, Deserialize)] 16 | pub struct JsonFileData { 17 | pub path: String, 18 | pub url: String, 19 | pub title: String, 20 | pub template: String, 21 | #[serde(default)] 22 | pub draw: bool, 23 | pub datetime: DateTime<FixedOffset>, 24 | pub data: HashMap<String, Value>, 25 | pub description: Option<MarkdownContent>, 26 | pub content: MarkdownContent, 27 | } 28 | 29 | #[doc(hidden)] 30 | #[derive(Debug, Serialize, Deserialize)] 31 | struct InnerData { 32 | pub title: String, 33 | pub url: String, 34 | pub template: String, 35 | #[serde(default)] 36 | pub draw: bool, 37 | pub datetime: DateTime<FixedOffset>, 38 | pub data: HashMap<String, Value>, 39 | pub content: String, 40 | } 41 | 42 | impl FileType for JsonFileData { 43 | type Output = JsonFileData; 44 | 45 | fn load(file: impl AsRef<Path>) -> Result<Self::Output, StapleError> { 46 | let file = file.as_ref(); 47 | let data_file_content = std::fs::read_to_string(file)?; 48 | 49 | let data = serde_json::from_str::<InnerData>(&data_file_content)?; 50 | let description = if data.content.contains(DESCRIPTION_SEPARATOR) { 51 | let content_split: Vec<&str> = data.content.splitn(2, DESCRIPTION_SEPARATOR).collect(); 52 | Some(MarkdownContent::new(content_split[0].to_string())) 53 | } else { 54 | None 55 | }; 56 | Ok(Self { 57 | path: file.to_str().unwrap().to_string(), 58 | url: data.url, 59 | title: data.title, 60 | template: data.template, 61 | draw: data.draw, 62 | datetime: data.datetime, 63 | data: data.data, 64 | description, 65 | content: MarkdownContent::new(data.content), 66 | }) 67 | } 68 | 69 | fn create(_file: impl AsRef<Path>, options: &CreationOptions) -> Result<(), StapleError> { 70 | let offset = FixedOffset::east(60 * 60 * 8); 71 | let data = InnerData { 72 | title: options.title.clone(), 73 | url: options.url.clone(), 74 | template: options.template.clone(), 75 | draw: options.draw, 76 | datetime: Utc::now().with_timezone(&offset), 77 | data: HashMap::new(), 78 | content: "".to_string(), 79 | }; 80 | 81 | let string = serde_json::to_string_pretty(&data)?; 82 | 83 | let file_name = options 84 | .title 85 | .clone() 86 | .trim() 87 | .replace(" ", "-") 88 | .replace("_", "-"); 89 | let output_path = _file 90 | .as_ref() 91 | .join("data") 92 | .join(format!("{}.json", &file_name)); 93 | std::fs::write(output_path, string)?; 94 | Ok(()) 95 | } 96 | 97 | fn into_page_info(self) -> PageInfo { 98 | PageInfo { 99 | file: self.path, 100 | url: self.url, 101 | title: self.title, 102 | template: self.template, 103 | draw: self.draw, 104 | datetime: self.datetime, 105 | data: self.data, 106 | description: self.description, 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/data/types/markdown.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use chrono::{DateTime, FixedOffset, Utc}; 4 | use pest::Parser; 5 | use serde_derive::{Deserialize, Serialize}; 6 | 7 | use crate::{ 8 | constants::{DESCRIPTION_SEPARATOR, LINE_ENDING}, 9 | data::{ 10 | types::{CreationOptions, FileType}, 11 | MarkdownContent, PageInfo, 12 | }, 13 | error::StapleError, 14 | }; 15 | use std::path::Path; 16 | 17 | #[derive(Parser)] 18 | #[grammar = "data/types/article.pest"] // relative to src 19 | struct ArticleParser; 20 | 21 | #[derive(Serialize, Deserialize, Debug)] 22 | pub struct ArticleMeta { 23 | pub title: String, 24 | pub url: String, 25 | pub tags: Vec<String>, 26 | pub date: DateTime<FixedOffset>, 27 | pub extra: HashMap<String, String>, 28 | pub description: Option<MarkdownContent>, 29 | } 30 | 31 | #[derive(Serialize, Deserialize, Debug)] 32 | pub struct MarkdownFileData { 33 | pub path: String, 34 | pub url: String, 35 | pub title: String, 36 | pub template: String, 37 | pub datetime: DateTime<FixedOffset>, 38 | #[serde(default)] 39 | pub draw: bool, 40 | pub data: HashMap<String, serde_json::Value>, 41 | pub content: MarkdownContent, 42 | pub description: Option<MarkdownContent>, 43 | } 44 | 45 | impl FileType for MarkdownFileData { 46 | type Output = MarkdownFileData; 47 | 48 | fn load(file: impl AsRef<Path>) -> Result<Self::Output, StapleError> { 49 | let file = file.as_ref().to_str().unwrap(); 50 | debug!("load article {}", &file); 51 | let string = std::fs::read_to_string(file)?; 52 | let mut metas: HashMap<String, String> = HashMap::new(); 53 | let mut content = String::new(); 54 | 55 | let parse1 = ArticleParser::parse(Rule::article, &string); 56 | let x = parse1 57 | .expect("unknown error on parsing markdown") 58 | .next() 59 | .expect("unknown error on parsing markdown"); 60 | for pair in x.into_inner() { 61 | match pair.as_rule() { 62 | Rule::meta => { 63 | for meta in pair.into_inner() { 64 | let mut x1 = meta.into_inner(); 65 | let key: String = x1 66 | .next() 67 | .expect("unknown error on parsing markdown") 68 | .as_str() 69 | .to_string(); 70 | let value: String = x1 71 | .next() 72 | .expect("unknown error on parsing markdown") 73 | .as_str() 74 | .to_string(); 75 | metas.insert(key.to_lowercase(), value); 76 | } 77 | } 78 | Rule::content => { 79 | content.push_str(pair.as_str()); 80 | } 81 | Rule::EOI => (), 82 | _ => unreachable!(), 83 | } 84 | } 85 | 86 | let url = metas.remove("url").ok_or(StapleError::ArticleError { 87 | filename: file.to_string(), 88 | reason: "url does not exist in article's metadata".to_string(), 89 | })?; 90 | let title = metas.remove("title").ok_or(StapleError::ArticleError { 91 | filename: file.to_string(), 92 | reason: "title does not exist in article's metadata".to_string(), 93 | })?; 94 | let template = metas.remove("template").ok_or(StapleError::ArticleError { 95 | filename: file.to_string(), 96 | reason: "template does not exist in article's metadata".to_string(), 97 | })?; 98 | 99 | let draw = metas 100 | .remove("draw") 101 | .map(|value| value.to_lowercase().eq("true")) 102 | .unwrap_or(false); 103 | 104 | let option_date = metas 105 | .remove("datetime") 106 | .ok_or(StapleError::ArticleError { 107 | filename: file.to_string(), 108 | reason: "datetime does not exist in article's metadata".to_string(), 109 | }) 110 | .map(|raw| DateTime::parse_from_rfc3339(&raw))? 111 | .map_err(|e| StapleError::ArticleError { 112 | filename: file.to_string(), 113 | reason: format!("parse date error {}", e), 114 | })?; 115 | 116 | let description = if content.contains(DESCRIPTION_SEPARATOR) { 117 | let content_split: Vec<&str> = content.splitn(2, DESCRIPTION_SEPARATOR).collect(); 118 | Some(MarkdownContent::new(content_split[0].to_string())) 119 | } else { 120 | None 121 | }; 122 | let extra_json_data = metas 123 | .into_iter() 124 | .map(|(key, value)| { 125 | let json_value = match serde_json::from_str::<serde_json::Value>(&value) { 126 | Ok(val) => val, 127 | Err(_) => serde_json::Value::String(value), 128 | }; 129 | (key, json_value) 130 | }) 131 | .collect(); 132 | 133 | Ok(MarkdownFileData { 134 | path: file.to_owned(), 135 | url, 136 | title, 137 | template, 138 | datetime: option_date, 139 | description, 140 | content: MarkdownContent::new(content), 141 | data: extra_json_data, 142 | draw, 143 | }) 144 | } 145 | 146 | fn create(_file: impl AsRef<Path>, options: &CreationOptions) -> Result<(), StapleError> { 147 | let offset = FixedOffset::east(60 * 60 * 8); 148 | let datetime = Utc::now().with_timezone(&offset).to_rfc3339(); 149 | 150 | let mut content = String::new(); 151 | 152 | content.push_str(&format!(" - title = {}{}", &options.title, LINE_ENDING)); 153 | content.push_str(&format!(" - url = {}{}", &options.url, LINE_ENDING)); 154 | content.push_str(&format!(" - datetime = {}{}", datetime, LINE_ENDING)); 155 | content.push_str(&format!( 156 | " - template = {}{}", 157 | options.template, LINE_ENDING 158 | )); 159 | content.push_str(&format!(" - draw = {}{}", options.draw, LINE_ENDING)); 160 | content.push_str(LINE_ENDING); 161 | 162 | let file_name = options.title.trim().replace(" ", "-").replace("_", "-"); 163 | let output_path = _file 164 | .as_ref() 165 | .join("data") 166 | .join(format!("{}.md", &file_name)); 167 | std::fs::write(output_path, content)?; 168 | Ok(()) 169 | } 170 | 171 | fn into_page_info(self) -> PageInfo { 172 | PageInfo { 173 | file: self.path, 174 | url: self.url, 175 | title: self.title, 176 | template: self.template, 177 | draw: self.draw, 178 | datetime: self.datetime, 179 | data: self.data, 180 | description: self.description, 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/data/types/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{data::PageInfo, error::StapleError}; 2 | use std::path::Path; 3 | 4 | pub(crate) mod json; 5 | pub(crate) mod markdown; 6 | 7 | pub struct CreationOptions { 8 | pub title: String, 9 | pub url: String, 10 | pub template: String, 11 | pub draw: bool, 12 | } 13 | 14 | pub trait FileType { 15 | type Output; 16 | fn load(file: impl AsRef<Path>) -> Result<Self::Output, StapleError>; 17 | fn create(file: impl AsRef<Path>, options: &CreationOptions) -> Result<(), StapleError>; 18 | fn into_page_info(self) -> PageInfo; 19 | } 20 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | pub enum StapleError { 5 | #[error("`Staple.toml` does not exist, try to run `staple init` before.")] 6 | ConfigNotFound, 7 | 8 | #[error("io error {:?} {}", .0.kind(), .0.to_string())] 9 | IoError(#[from] std::io::Error), 10 | 11 | #[error("config error {}", .0.to_string())] 12 | ConfigError(#[from] toml::de::Error), 13 | 14 | #[error("render error {}", .0.to_string())] 15 | RenderError(#[from] tera::Error), 16 | 17 | #[error("error on loading article {filename} : {reason}")] 18 | ArticleError { filename: String, reason: String }, 19 | 20 | #[error("error on parse url: {}", .0.to_string())] 21 | UrlParseError(#[from] url::ParseError), 22 | 23 | #[error("cannot serde json file: {0}")] 24 | JsonFileParseError(#[from] serde_json::Error), 25 | 26 | #[error("execute hook `{}` get non-zero exit code: {}", .0, .1.unwrap_or(-1))] 27 | HookError(String, Option<i32>), 28 | } 29 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::dbg_macro)] 2 | #[macro_use] 3 | extern crate pest_derive; 4 | 5 | #[macro_use] 6 | extern crate log; 7 | 8 | use crate::command::StapleCommand; 9 | use env_logger::Env; 10 | use std::{io::Write, process::exit}; 11 | use structopt::StructOpt; 12 | 13 | mod app; 14 | mod command; 15 | mod config; 16 | mod constants; 17 | mod error; 18 | mod server; 19 | mod template; 20 | mod util; 21 | 22 | mod data; 23 | 24 | fn main() -> Result<(), Box<dyn std::error::Error>> { 25 | env_logger::from_env(Env::default().default_filter_or("info")) 26 | .format(|buf, record| { 27 | let level = buf.default_styled_level(record.level()); 28 | writeln!(buf, "{:>5} {}", level, record.args()) 29 | }) 30 | .init(); 31 | 32 | let opt: StapleCommand = StapleCommand::from_args(); 33 | let result = opt.run(); 34 | if let Err(e) = result { 35 | error!("Error: {}", e); 36 | exit(1); 37 | } 38 | Ok(()) 39 | } 40 | 41 | #[cfg(test)] 42 | mod test { 43 | use std::path::PathBuf; 44 | 45 | pub fn setup() -> PathBuf { 46 | let _ = env_logger::builder().is_test(true).try_init().is_ok(); 47 | tempfile::tempdir() 48 | .expect("Cannot init temp dir") 49 | .into_path() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/server/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::server::ws::{MyWebSocket, WSServer, WsEvent}; 2 | use actix::{Actor, Addr, SystemRunner}; 3 | use actix_web::{web, HttpRequest, HttpResponse, HttpServer}; 4 | use std::collections::HashSet; 5 | 6 | pub mod ws; 7 | 8 | fn ws_index( 9 | r: HttpRequest, 10 | stream: web::Payload, 11 | ws_server: web::Data<Addr<WSServer>>, 12 | ) -> Result<HttpResponse, actix_web::error::Error> { 13 | let data1 = ws_server.clone(); 14 | let socket = MyWebSocket::new_with_server(data1.into_inner()); 15 | let (data, res) = actix_web_actors::ws::start_with_addr(socket, &r, stream)?; 16 | debug!("connecting"); 17 | ws_server.get_ref().do_send(WsEvent::Join(data)); 18 | 19 | Ok(res) 20 | } 21 | 22 | pub struct Server {} 23 | 24 | impl Server { 25 | pub fn start(port: u16) -> (Addr<WSServer>, SystemRunner) { 26 | let sys = actix::System::new("staple"); 27 | let server = WSServer { 28 | listeners: HashSet::new(), 29 | } 30 | .start(); 31 | let addr = server.clone(); 32 | 33 | HttpServer::new(move || { 34 | actix_web::App::new() 35 | .data(server.clone()) 36 | .service(web::resource("/notifier").route(web::get().to(ws_index))) 37 | .service(actix_files::Files::new("/", "./public").index_file("index.html")) 38 | }) 39 | .bind(("0.0.0.0", port)) 40 | .expect("") 41 | .system_exit() 42 | .start(); 43 | 44 | (addr, sys) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/server/ws.rs: -------------------------------------------------------------------------------- 1 | use crate::constants::{CLIENT_TIMEOUT, HEARTBEAT_INTERVAL}; 2 | use actix::{prelude::*, Actor, ActorContext, Addr, AsyncContext, Context, Handler, StreamHandler}; 3 | use actix_web_actors::ws; 4 | use std::{collections::HashSet, sync::Arc, time::Instant}; 5 | 6 | #[derive(Message)] 7 | pub enum WsEvent { 8 | Refresh, 9 | Join(Addr<MyWebSocket>), 10 | Stop(Addr<MyWebSocket>), 11 | } 12 | 13 | pub struct WSServer { 14 | pub listeners: HashSet<Addr<MyWebSocket>>, 15 | } 16 | 17 | pub struct MyWebSocket { 18 | hb: Instant, 19 | server: Arc<Addr<WSServer>>, 20 | } 21 | 22 | impl Actor for WSServer { 23 | type Context = Context<Self>; 24 | } 25 | 26 | impl Handler<WsEvent> for WSServer { 27 | type Result = (); 28 | 29 | fn handle(&mut self, msg: WsEvent, _ctx: &mut Self::Context) -> Self::Result { 30 | debug!("server receive msg"); 31 | match msg { 32 | WsEvent::Join(data) => { 33 | debug!("listener join"); 34 | self.listeners.insert(data); 35 | } 36 | 37 | WsEvent::Refresh => { 38 | debug!("do send refresh to listeners"); 39 | for x in &self.listeners { 40 | x.do_send(WsEvent::Refresh) 41 | } 42 | } 43 | WsEvent::Stop(data) => { 44 | debug!("listener stop"); 45 | self.listeners.remove(&data); 46 | debug!("after listener remove: {:?}", self.listeners.len()); 47 | } 48 | } 49 | } 50 | } 51 | 52 | impl MyWebSocket { 53 | pub fn new_with_server(server: Arc<Addr<WSServer>>) -> Self { 54 | Self { 55 | hb: Instant::now(), 56 | server, 57 | } 58 | } 59 | fn hb(&self, ctx: &mut <Self as Actor>::Context) { 60 | ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| { 61 | // check client heartbeats 62 | if Instant::now().duration_since(act.hb) > CLIENT_TIMEOUT { 63 | // heartbeat timed out 64 | info!("Websocket Client heartbeat failed, disconnecting!"); 65 | 66 | // stop actor 67 | ctx.stop(); 68 | 69 | // don't try to send a ping 70 | return; 71 | } 72 | 73 | ctx.ping(""); 74 | }); 75 | } 76 | } 77 | 78 | impl Actor for MyWebSocket { 79 | type Context = ws::WebsocketContext<Self>; 80 | 81 | /// Method is called on actor start. We start the heartbeat process here. 82 | fn started(&mut self, ctx: &mut Self::Context) { 83 | self.hb(ctx); 84 | } 85 | } 86 | 87 | impl Handler<WsEvent> for MyWebSocket { 88 | type Result = (); 89 | 90 | fn handle(&mut self, msg: WsEvent, ctx: &mut Self::Context) -> Self::Result { 91 | if let WsEvent::Refresh = msg { 92 | debug!("listener receive refresh command, send refresh to client"); 93 | ctx.text("refresh"); 94 | } 95 | } 96 | } 97 | 98 | /// Handler for `ws::Message` 99 | impl StreamHandler<ws::Message, ws::ProtocolError> for MyWebSocket { 100 | fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { 101 | // process websocket messages 102 | match msg { 103 | ws::Message::Ping(msg) => { 104 | self.hb = Instant::now(); 105 | ctx.pong(&msg); 106 | } 107 | ws::Message::Pong(_) => { 108 | self.hb = Instant::now(); 109 | } 110 | ws::Message::Text(text) => ctx.text(text), 111 | ws::Message::Binary(bin) => ctx.binary(bin), 112 | ws::Message::Close(_) => { 113 | ctx.stop(); 114 | self.server.do_send(WsEvent::Stop(ctx.address())) 115 | } 116 | ws::Message::Nop => (), 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/template.rs: -------------------------------------------------------------------------------- 1 | use crate::{config::Config, error::StapleError}; 2 | 3 | use std::path::{Path, PathBuf}; 4 | use tera::{Context, Tera}; 5 | 6 | use serde::Serialize; 7 | 8 | use crate::{ 9 | constants::{LIVE_RELOAD_CODE, PUBLIC_FOLDER, RENDER_FOLDER}, 10 | data::{DataFile, PageInfo}, 11 | }; 12 | 13 | #[derive(Debug, Serialize)] 14 | pub struct DevelopData { 15 | live_reload: &'static str, 16 | } 17 | 18 | impl DevelopData { 19 | pub fn new(is_develop: bool) -> Self { 20 | let live_reload = if is_develop { LIVE_RELOAD_CODE } else { "" }; 21 | DevelopData { live_reload } 22 | } 23 | } 24 | 25 | #[derive(Debug, Serialize)] 26 | pub struct RenderData<'a> { 27 | page: DataFile, 28 | config: &'a Config, 29 | develop: &'a DevelopData, 30 | pages: &'a [PageInfo], 31 | } 32 | 33 | impl<'a> RenderData<'a> { 34 | pub fn new( 35 | page: DataFile, 36 | pages: &'a [PageInfo], 37 | config: &'a Config, 38 | develop: &'a DevelopData, 39 | ) -> Self { 40 | RenderData { 41 | page, 42 | pages, 43 | config, 44 | develop, 45 | } 46 | } 47 | } 48 | 49 | #[derive(Debug)] 50 | pub struct Template { 51 | working_path: PathBuf, 52 | name: String, 53 | tera: Tera, 54 | } 55 | 56 | impl Template { 57 | pub fn new(path: impl AsRef<Path>, name: String) -> Result<Self, StapleError> { 58 | let buf = path 59 | .as_ref() 60 | .canonicalize() 61 | .expect("cannot canoicalize path"); 62 | let root = buf.to_str().expect("invalid file path"); 63 | let theme_folder = format!("{}/templates/{}/*", root, name); 64 | debug!("theme folder is {}", theme_folder); 65 | let mut tera = Tera::new(&theme_folder)?; 66 | tera.register_filter("not_field", crate::util::filter::not_field); 67 | tera.register_filter("markdown", crate::util::filter::markdown); 68 | tera.register_function("page_detail", crate::util::filter::page_detail); 69 | Ok(Template { 70 | working_path: path.as_ref().to_path_buf(), 71 | name, 72 | tera, 73 | }) 74 | } 75 | 76 | pub fn render( 77 | self, 78 | articles: Vec<PageInfo>, 79 | config: &Config, 80 | is_develop_mode: bool, 81 | ) -> Result<(), StapleError> { 82 | Template::remove_folder(self.working_path.join(RENDER_FOLDER))?; 83 | std::fs::create_dir(self.working_path.join(RENDER_FOLDER))?; 84 | 85 | // todo can be parallel rendering 86 | for article in articles.iter() { 87 | self.render_article(config, article, &articles, is_develop_mode)?; 88 | } 89 | 90 | self.copy_statics_folder(config)?; 91 | self.copy_statics(config)?; 92 | 93 | Template::remove_folder(self.working_path.join(PUBLIC_FOLDER))?; 94 | std::fs::rename( 95 | self.working_path.join(RENDER_FOLDER), 96 | self.working_path.join(PUBLIC_FOLDER), 97 | )?; 98 | Ok(()) 99 | } 100 | 101 | pub fn render_article<'a>( 102 | &self, 103 | config: &Config, 104 | article: &PageInfo, 105 | articles: &'a [PageInfo], 106 | is_develop_mode: bool, 107 | ) -> Result<(), StapleError> { 108 | info!("rendering article {}({})", &article.title, &article.url); 109 | let debug_data = DevelopData::new(is_develop_mode); 110 | 111 | let full_article = article.to_full_article()?; 112 | 113 | let data = RenderData::new(full_article, articles, config, &debug_data); 114 | let context = Context::from_serialize(&data).expect("cannot serialize"); 115 | let result = self.tera.render(data.page.template(), &context)?; 116 | let url = article.output_file_name(); 117 | let output_file = self.working_path.join(RENDER_FOLDER).join(url); 118 | 119 | if let Some(p) = output_file.parent() { 120 | if !p.exists() { 121 | std::fs::create_dir_all(p)?; 122 | } 123 | } 124 | std::fs::write(output_file, result.as_bytes()).map_err(StapleError::IoError) 125 | } 126 | 127 | fn copy_statics_folder(&self, config: &Config) -> Result<(), StapleError> { 128 | info!("copy template static folder"); 129 | let statics_folder = self 130 | .working_path 131 | .join("templates") 132 | .join(&config.site.theme) 133 | .join("statics"); 134 | if statics_folder.exists() { 135 | debug!("statics folder exist, copy to render folder"); 136 | copy_dir::copy_dir( 137 | statics_folder, 138 | self.working_path.join(RENDER_FOLDER).join("statics"), 139 | )?; 140 | } 141 | Ok(()) 142 | } 143 | 144 | pub fn remove_folder(path: impl AsRef<Path>) -> Result<(), StapleError> { 145 | let buf = path.as_ref(); 146 | if buf.exists() { 147 | debug!("remove folder {}", buf.to_str().expect("invalid file name")); 148 | std::fs::remove_dir_all(buf).map_err(StapleError::IoError) 149 | } else { 150 | Ok(()) 151 | } 152 | } 153 | 154 | pub fn copy_statics(&self, config: &Config) -> Result<(), StapleError> { 155 | let render_folder = self.working_path.join(RENDER_FOLDER); 156 | for statics in &config.statics { 157 | let from = self.working_path.join(&statics.from); 158 | let to = render_folder.join(&statics.to); 159 | info!("coping statics from {} to {}", &statics.from, &statics.to); 160 | std::fs::copy(from, to)?; 161 | } 162 | Ok(()) 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/util/filter.rs: -------------------------------------------------------------------------------- 1 | use crate::data::{MarkdownContent, PageInfo}; 2 | use chrono::{FixedOffset, Utc}; 3 | use std::collections::HashMap; 4 | use tera::{Error, Value}; 5 | 6 | pub fn get_json_pointer(key: &str) -> String { 7 | ["/", &key.replace(".", "/")].join("") 8 | } 9 | 10 | /// item of array contains false bool field or do not contains field. 11 | pub fn not_field(value: &Value, attributes: &HashMap<String, Value>) -> Result<Value, tera::Error> { 12 | let arr = tera::try_get_value!("filter", "value", Vec<Value>, value); 13 | 14 | let key = match attributes.get("attribute") { 15 | Some(val) => tera::try_get_value!("filter", "attribute", String, val), 16 | None => { 17 | return Err(Error::msg( 18 | "The `not_field` filter has to have an `attribute` argument", 19 | )); 20 | } 21 | }; 22 | let result = arr 23 | .into_iter() 24 | .filter(|item| { 25 | let field = item 26 | .pointer(&get_json_pointer(&key)) 27 | .unwrap_or(&Value::Null); 28 | if let Value::String(content) = field { 29 | content.to_uppercase().eq("FALSE") 30 | } else { 31 | field.is_null() || field.eq(&Value::Bool(false)) 32 | } 33 | }) 34 | .collect(); 35 | 36 | Ok(result) 37 | } 38 | 39 | /// loading page detail of specific article while rendering. 40 | /// using this to add avalibility and flexibility to render cross-articles page like rss page or those need at least 2 articles full content. 41 | pub fn page_detail(args: &HashMap<String, Value>) -> Result<Value, tera::Error> { 42 | let file = match args.get("file") { 43 | Some(val) => match tera::from_value::<String>(val.clone()) { 44 | Ok(parsed_val) => parsed_val, 45 | Err(_) => { 46 | return Err(Error::msg(format!( 47 | "Function `page_detail` receive file={} but `file` can only be a string", 48 | val 49 | ))); 50 | } 51 | }, 52 | None => { 53 | return Err(Error::msg( 54 | "Function `page_detail` was called without argument `file`", 55 | )); 56 | } 57 | }; 58 | 59 | info!("├── cross accessing page detail {}", &file); 60 | let full_article = PageInfo { 61 | file, 62 | url: "".to_string(), 63 | title: "".to_string(), 64 | template: "".to_string(), 65 | draw: false, 66 | datetime: Utc::now().with_timezone(&FixedOffset::east(60 * 60 * 8)), 67 | data: HashMap::new(), 68 | description: None, 69 | } 70 | .to_full_article(); 71 | let data = match full_article { 72 | Ok(data) => data, 73 | Err(e) => return Err(Error::msg(format!("Error on loading page detail: {}", e))), 74 | }; 75 | serde_json::to_value(data).map_err(|_| Error::msg("Error on serializing page data into json")) 76 | } 77 | 78 | /// render text as markdown 79 | pub fn markdown(value: &Value, _attributes: &HashMap<String, Value>) -> Result<Value, tera::Error> { 80 | if let Value::String(content) = value { 81 | let html_content = MarkdownContent::new(content.to_owned()).html; 82 | Ok(Value::String(html_content)) 83 | } else { 84 | Ok(value.to_owned()) 85 | } 86 | } 87 | 88 | #[cfg(test)] 89 | mod test { 90 | use crate::util::filter::{get_json_pointer, markdown, not_field}; 91 | use serde_json::{Map, Value}; 92 | use std::collections::HashMap; 93 | 94 | #[test] 95 | fn test_get_json_pointer() { 96 | assert_eq!("/a", get_json_pointer("a")); 97 | assert_eq!("/a/b", get_json_pointer("a.b")); 98 | } 99 | 100 | #[test] 101 | fn should_raise_error_when_attribute_is_empty_in_not_field() { 102 | let mut map = Map::new(); 103 | map.insert("ok".to_owned(), Value::Bool(true)); 104 | let value = Value::Array(vec![Value::Object(map)]); 105 | let result = not_field(&value, &HashMap::new()); 106 | assert!(result.is_err()); 107 | } 108 | #[test] 109 | fn test_not_field1() { 110 | let mut map = Map::new(); 111 | map.insert("ok".to_owned(), Value::Bool(true)); 112 | let value = Value::Array(vec![Value::Object(map)]); 113 | 114 | let mut attribute = HashMap::new(); 115 | attribute.insert("attribute".to_owned(), Value::String("ok".to_owned())); 116 | let result = not_field(&value, &attribute).unwrap(); 117 | assert_eq!(Value::Array(vec![]), result); 118 | } 119 | #[test] 120 | fn test_not_field2() { 121 | let mut map1 = Map::new(); 122 | map1.insert("ok".to_owned(), Value::Bool(false)); 123 | 124 | let mut map2 = Map::new(); 125 | map2.insert("ok".to_owned(), Value::Null); 126 | 127 | let mut map3 = Map::new(); 128 | map3.insert("ok".to_owned(), Value::String("false".to_owned())); 129 | 130 | let mut map4 = Map::new(); 131 | map4.insert("ok".to_owned(), Value::String("False".to_owned())); 132 | 133 | let mut map5 = Map::new(); 134 | map5.insert("ok".to_owned(), Value::String("FALSE".to_owned())); 135 | 136 | let value = Value::Array(vec![ 137 | Value::Object(map1), 138 | Value::Object(map2), 139 | Value::Object(map3), 140 | Value::Object(map4), 141 | Value::Object(map5), 142 | ]); 143 | 144 | let mut attribute = HashMap::new(); 145 | attribute.insert("attribute".to_owned(), Value::String("ok".to_owned())); 146 | let result = not_field(&value, &attribute).unwrap(); 147 | assert_eq!(5, result.as_array().unwrap().len()); 148 | } 149 | 150 | #[test] 151 | fn should_render_text_into_markdown() { 152 | let value = Value::String("hello".to_owned()); 153 | let result = markdown(&value, &HashMap::new()).expect("is not a ok"); 154 | assert_eq!(Value::String("<p>hello</p>\n".to_owned()), result); 155 | } 156 | 157 | #[test] 158 | fn should_return_the_same_if_value_is_not_text() { 159 | let value = Value::Bool(true); 160 | let result = markdown(&value, &HashMap::new()).expect("is not a ok"); 161 | assert_eq!(Value::Bool(true), result); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/util/lock.rs: -------------------------------------------------------------------------------- 1 | use crate::error::StapleError; 2 | use fs2::FileExt; 3 | use std::{ 4 | fs::{File, OpenOptions}, 5 | ops::Deref, 6 | path::Path, 7 | }; 8 | 9 | pub struct LockFile(File); 10 | 11 | impl Deref for LockFile { 12 | type Target = File; 13 | 14 | fn deref(&self) -> &Self::Target { 15 | &self.0 16 | } 17 | } 18 | 19 | impl LockFile { 20 | pub fn new(path: impl AsRef<Path>) -> Result<LockFile, StapleError> { 21 | let file = OpenOptions::new() 22 | .read(false) 23 | .write(true) 24 | .create(true) 25 | .open(path.as_ref())?; 26 | Ok(LockFile(file)) 27 | } 28 | pub fn lock_file(&self) -> Result<(), StapleError> { 29 | Ok(self.0.lock_exclusive()?) 30 | } 31 | } 32 | 33 | impl Drop for LockFile { 34 | fn drop(&mut self) { 35 | let _result = self.0.unlock(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/util/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod filter; 2 | pub mod lock; 3 | -------------------------------------------------------------------------------- /update_local_new_version.sh: -------------------------------------------------------------------------------- 1 | rm -rf ~/.cargo/bin/staple 2 | cargo test -- --test-threads 1 3 | cargo build 4 | cp target/debug/staple ~/.cargo/bin/staple --------------------------------------------------------------------------------