diff --git a/.gitignore b/.gitignore
index ea8c4bf..8d1d5b2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,4 @@
/target
+/build
+/node_modules
+/content/css/syntax.css
diff --git a/Cargo.lock b/Cargo.lock
index 3dd57be..bdce8fc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2,6 +2,30 @@
# It is not intended for manual editing.
version = 4
+[[package]]
+name = "adler2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "anstream"
version = "0.6.21"
@@ -58,24 +82,195 @@ version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
+[[package]]
+name = "approx"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "arraydeque"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236"
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
+[[package]]
+name = "autocfg"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+
+[[package]]
+name = "axum"
+version = "0.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8"
+dependencies = [
+ "axum-core",
+ "bytes",
+ "form_urlencoded",
+ "futures-util",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-util",
+ "itoa",
+ "matchit",
+ "memchr",
+ "mime",
+ "percent-encoding",
+ "pin-project-lite",
+ "serde_core",
+ "serde_json",
+ "serde_path_to_error",
+ "serde_urlencoded",
+ "sync_wrapper",
+ "tokio",
+ "tower",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "axum-core"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "http-body-util",
+ "mime",
+ "pin-project-lite",
+ "sync_wrapper",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
[[package]]
name = "bitflags"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "bstr"
+version = "1.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab"
+dependencies = [
+ "memchr",
+ "serde",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.19.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
+
[[package]]
name = "bytes"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
+[[package]]
+name = "cc"
+version = "1.2.56"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
+dependencies = [
+ "find-msvc-tools",
+ "shlex",
+]
+
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+[[package]]
+name = "chrono"
+version = "0.4.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118"
+dependencies = [
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "serde",
+ "wasm-bindgen",
+ "windows-link",
+]
+
+[[package]]
+name = "chrono-tz"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb"
+dependencies = [
+ "chrono",
+ "chrono-tz-build",
+ "phf",
+]
+
+[[package]]
+name = "chrono-tz-build"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1"
+dependencies = [
+ "parse-zoneinfo",
+ "phf",
+ "phf_codegen",
+]
+
[[package]]
name = "clap"
version = "4.5.59"
@@ -122,6 +317,105 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+
+[[package]]
+name = "crypto-common"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "deranged"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4"
+dependencies = [
+ "powerfmt",
+]
+
+[[package]]
+name = "deunicode"
+version = "1.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04"
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+]
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
[[package]]
name = "errno"
version = "0.3.14"
@@ -132,24 +426,467 @@ dependencies = [
"windows-sys 0.61.2",
]
+[[package]]
+name = "filetime"
+version = "0.2.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "libredox",
+]
+
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
+
+[[package]]
+name = "flate2"
+version = "1.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foldhash"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "fs_extra"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
+
+[[package]]
+name = "fsevent-sys"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
+
+[[package]]
+name = "futures-sink"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"
+
+[[package]]
+name = "futures-task"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
+
+[[package]]
+name = "futures-util"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "pin-project-lite",
+ "slab",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "geo-types"
+version = "0.7.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24f8647af4005fa11da47cd56252c6ef030be8fa97bdbf355e7dfb6348f0a82c"
+dependencies = [
+ "approx",
+ "num-traits",
+ "serde",
+]
+
+[[package]]
+name = "geoutils"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36d244a08113319b5ebcabad2b8b7925732d15eec46d7e7ac3c11734f3b7a6ad"
+
+[[package]]
+name = "getopts"
+version = "0.2.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df"
+dependencies = [
+ "unicode-width",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "globset"
+version = "0.4.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3"
+dependencies = [
+ "aho-corasick",
+ "bstr",
+ "log",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "globwalk"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757"
+dependencies = [
+ "bitflags 2.11.0",
+ "ignore",
+ "walkdir",
+]
+
+[[package]]
+name = "gpx"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfabaf0e8a17a6fb7977fac3bd5846488462edb9f8b246605835483a5501e698"
+dependencies = [
+ "geo-types",
+ "thiserror 1.0.69",
+ "time",
+ "xml-rs",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
+dependencies = [
+ "foldhash",
+]
+
+[[package]]
+name = "hashlink"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea0b22561a9c04a7cb1a302c013e0259cd3b4bb619f145b32f72b8b4bcbed230"
+dependencies = [
+ "hashbrown",
+]
+
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+[[package]]
+name = "http"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
+dependencies = [
+ "bytes",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
+dependencies = [
+ "bytes",
+ "http",
+]
+
+[[package]]
+name = "http-body-util"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "http-range-header"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c"
+
+[[package]]
+name = "httparse"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
+
+[[package]]
+name = "httpdate"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
+
+[[package]]
+name = "humansize"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7"
+dependencies = [
+ "libm",
+]
+
+[[package]]
+name = "hyper"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11"
+dependencies = [
+ "atomic-waker",
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "http",
+ "http-body",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project-lite",
+ "pin-utils",
+ "smallvec",
+ "tokio",
+]
+
+[[package]]
+name = "hyper-util"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
+dependencies = [
+ "bytes",
+ "http",
+ "http-body",
+ "hyper",
+ "pin-project-lite",
+ "tokio",
+ "tower-service",
+]
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.65"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "log",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "ignore"
+version = "0.4.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a"
+dependencies = [
+ "crossbeam-deque",
+ "globset",
+ "log",
+ "memchr",
+ "regex-automata",
+ "same-file",
+ "walkdir",
+ "winapi-util",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
+[[package]]
+name = "inotify"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fdd168d97690d0b8c412d6b6c10360277f4d7ee495c5d0d5d5fe0854923255cc"
+dependencies = [
+ "bitflags 1.3.2",
+ "inotify-sys",
+ "libc",
+]
+
+[[package]]
+name = "inotify-sys"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
+dependencies = [
+ "cfg-if",
+]
+
[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
+[[package]]
+name = "itoa"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
+
+[[package]]
+name = "js-sys"
+version = "0.3.85"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "kqueue"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a"
+dependencies = [
+ "kqueue-sys",
+ "libc",
+]
+
+[[package]]
+name = "kqueue-sys"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b"
+dependencies = [
+ "bitflags 1.3.2",
+ "libc",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
[[package]]
name = "libc"
version = "0.2.182"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
+[[package]]
+name = "libm"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
+
+[[package]]
+name = "libredox"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
+dependencies = [
+ "bitflags 2.11.0",
+ "libc",
+ "redox_syscall 0.7.1",
+]
+
+[[package]]
+name = "linked-hash-map"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
+
[[package]]
name = "lock_api"
version = "0.4.14"
@@ -159,6 +896,50 @@ dependencies = [
"scopeguard",
]
+[[package]]
+name = "log"
+version = "0.4.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
+
+[[package]]
+name = "matchit"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
+
+[[package]]
+name = "memchr"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
+
+[[package]]
+name = "mime"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+
+[[package]]
+name = "mime_guess"
+version = "2.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
+dependencies = [
+ "mime",
+ "unicase",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
+dependencies = [
+ "adler2",
+ "simd-adler32",
+]
+
[[package]]
name = "mio"
version = "1.1.1"
@@ -166,25 +947,111 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
dependencies = [
"libc",
+ "log",
"wasi",
"windows-sys 0.61.2",
]
+[[package]]
+name = "notify"
+version = "7.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c533b4c39709f9ba5005d8002048266593c1cfaf3c5f0739d5b8ab0c6c504009"
+dependencies = [
+ "bitflags 2.11.0",
+ "filetime",
+ "fsevent-sys",
+ "inotify",
+ "kqueue",
+ "libc",
+ "log",
+ "mio",
+ "notify-types",
+ "walkdir",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "notify-types"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "585d3cb5e12e01aed9e8a1f70d5c6b5e86fe2a6e48fc8cd0b3e0b8df6f6eb174"
+dependencies = [
+ "instant",
+]
+
+[[package]]
+name = "num-conv"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+ "libm",
+]
+
[[package]]
name = "olla"
version = "0.1.0"
dependencies = [
"anyhow",
+ "axum",
+ "chrono",
"clap",
+ "fs_extra",
+ "geoutils",
+ "gpx",
+ "notify",
+ "pulldown-cmark",
+ "regex",
+ "serde",
+ "syntect",
+ "tera",
"tokio",
+ "tower-http",
+ "yaml-rust2",
]
+[[package]]
+name = "once_cell"
+version = "1.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+
[[package]]
name = "once_cell_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
+[[package]]
+name = "onig"
+version = "6.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0"
+dependencies = [
+ "bitflags 2.11.0",
+ "libc",
+ "once_cell",
+ "onig_sys",
+]
+
+[[package]]
+name = "onig_sys"
+version = "69.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7f86c6eef3d6df15f23bcfb6af487cbd2fed4e5581d58d5bf1f5f8b7f6727dc"
+dependencies = [
+ "cc",
+ "pkg-config",
+]
+
[[package]]
name = "parking_lot"
version = "0.12.5"
@@ -203,17 +1070,153 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"cfg-if",
"libc",
- "redox_syscall",
+ "redox_syscall 0.5.18",
"smallvec",
"windows-link",
]
+[[package]]
+name = "parse-zoneinfo"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24"
+dependencies = [
+ "regex",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
+
+[[package]]
+name = "pest"
+version = "2.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662"
+dependencies = [
+ "memchr",
+ "ucd-trie",
+]
+
+[[package]]
+name = "pest_derive"
+version = "2.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77"
+dependencies = [
+ "pest",
+ "pest_generator",
+]
+
+[[package]]
+name = "pest_generator"
+version = "2.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f"
+dependencies = [
+ "pest",
+ "pest_meta",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pest_meta"
+version = "2.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220"
+dependencies = [
+ "pest",
+ "sha2",
+]
+
+[[package]]
+name = "phf"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
+dependencies = [
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
+dependencies = [
+ "phf_shared",
+ "rand",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
+dependencies = [
+ "siphasher",
+]
+
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
+
+[[package]]
+name = "plist"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07"
+dependencies = [
+ "base64",
+ "indexmap",
+ "quick-xml",
+ "serde",
+ "time",
+]
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+dependencies = [
+ "zerocopy",
+]
+
[[package]]
name = "proc-macro2"
version = "1.0.106"
@@ -223,6 +1226,34 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "pulldown-cmark"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e8bbe1a966bd2f362681a44f6edce3c2310ac21e4d5067a6e7ec396297a6ea0"
+dependencies = [
+ "bitflags 2.11.0",
+ "getopts",
+ "memchr",
+ "pulldown-cmark-escape",
+ "unicase",
+]
+
+[[package]]
+name = "pulldown-cmark-escape"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae"
+
+[[package]]
+name = "quick-xml"
+version = "0.38.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c"
+dependencies = [
+ "memchr",
+]
+
[[package]]
name = "quote"
version = "1.0.44"
@@ -232,13 +1263,102 @@ dependencies = [
"proc-macro2",
]
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
[[package]]
name = "redox_syscall"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
- "bitflags",
+ "bitflags 2.11.0",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35985aa610addc02e24fc232012c86fd11f14111180f902b67e2d5331f8ebf2b"
+dependencies = [
+ "bitflags 2.11.0",
+]
+
+[[package]]
+name = "regex"
+version = "1.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c"
+
+[[package]]
+name = "rustversion"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+
+[[package]]
+name = "ryu"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
]
[[package]]
@@ -247,6 +1367,89 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+[[package]]
+name = "serde"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.149"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
+dependencies = [
+ "itoa",
+ "memchr",
+ "serde",
+ "serde_core",
+ "zmij",
+]
+
+[[package]]
+name = "serde_path_to_error"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457"
+dependencies = [
+ "itoa",
+ "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
[[package]]
name = "signal-hook-registry"
version = "1.4.8"
@@ -257,6 +1460,34 @@ dependencies = [
"libc",
]
+[[package]]
+name = "simd-adler32"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
+
+[[package]]
+name = "siphasher"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
+
+[[package]]
+name = "slab"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
+
+[[package]]
+name = "slug"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724"
+dependencies = [
+ "deunicode",
+ "wasm-bindgen",
+]
+
[[package]]
name = "smallvec"
version = "1.15.1"
@@ -290,6 +1521,126 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "sync_wrapper"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
+
+[[package]]
+name = "syntect"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "656b45c05d95a5704399aeef6bd0ddec7b2b3531b7c9e900abbf7c4d2190c925"
+dependencies = [
+ "bincode",
+ "flate2",
+ "fnv",
+ "once_cell",
+ "onig",
+ "plist",
+ "regex-syntax",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "thiserror 2.0.18",
+ "walkdir",
+ "yaml-rust",
+]
+
+[[package]]
+name = "tera"
+version = "1.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8004bca281f2d32df3bacd59bc67b312cb4c70cea46cbd79dbe8ac5ed206722"
+dependencies = [
+ "chrono",
+ "chrono-tz",
+ "globwalk",
+ "humansize",
+ "lazy_static",
+ "percent-encoding",
+ "pest",
+ "pest_derive",
+ "rand",
+ "regex",
+ "serde",
+ "serde_json",
+ "slug",
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl 1.0.69",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
+dependencies = [
+ "thiserror-impl 2.0.18",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "time"
+version = "0.3.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
+dependencies = [
+ "deranged",
+ "itoa",
+ "num-conv",
+ "powerfmt",
+ "serde_core",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
+
+[[package]]
+name = "time-macros"
+version = "0.2.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
+dependencies = [
+ "num-conv",
+ "time-core",
+]
+
[[package]]
name = "tokio"
version = "1.49.0"
@@ -318,37 +1669,286 @@ dependencies = [
"syn",
]
+[[package]]
+name = "tokio-util"
+version = "0.7.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tower"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "pin-project-lite",
+ "sync_wrapper",
+ "tokio",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tower-http"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
+dependencies = [
+ "bitflags 2.11.0",
+ "bytes",
+ "futures-core",
+ "futures-util",
+ "http",
+ "http-body",
+ "http-body-util",
+ "http-range-header",
+ "httpdate",
+ "mime",
+ "mime_guess",
+ "percent-encoding",
+ "pin-project-lite",
+ "tokio",
+ "tokio-util",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
+
+[[package]]
+name = "tower-service"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
+
+[[package]]
+name = "tracing"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
+dependencies = [
+ "log",
+ "pin-project-lite",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "typenum"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
+
+[[package]]
+name = "ucd-trie"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
+
+[[package]]
+name = "unicase"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
+
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+[[package]]
+name = "unicode-segmentation"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
+
+[[package]]
+name = "unicode-width"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
+
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.108"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.108"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.108"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55"
+dependencies = [
+ "bumpalo",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.108"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "winapi-util"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.62.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.59.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+[[package]]
+name = "windows-result"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
- "windows-targets",
+ "windows-targets 0.53.5",
]
[[package]]
@@ -360,6 +1960,22 @@ dependencies = [
"windows-link",
]
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm 0.52.6",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
+]
+
[[package]]
name = "windows-targets"
version = "0.53.5"
@@ -367,60 +1983,160 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [
"windows-link",
- "windows_aarch64_gnullvm",
- "windows_aarch64_msvc",
- "windows_i686_gnu",
- "windows_i686_gnullvm",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_gnullvm",
- "windows_x86_64_msvc",
+ "windows_aarch64_gnullvm 0.53.1",
+ "windows_aarch64_msvc 0.53.1",
+ "windows_i686_gnu 0.53.1",
+ "windows_i686_gnullvm 0.53.1",
+ "windows_i686_msvc 0.53.1",
+ "windows_x86_64_gnu 0.53.1",
+ "windows_x86_64_gnullvm 0.53.1",
+ "windows_x86_64_msvc 0.53.1",
]
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
[[package]]
name = "windows_i686_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
[[package]]
name = "windows_i686_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
+
+[[package]]
+name = "xml-rs"
+version = "0.8.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f"
+
+[[package]]
+name = "yaml-rust"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
+dependencies = [
+ "linked-hash-map",
+]
+
+[[package]]
+name = "yaml-rust2"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "631a50d867fafb7093e709d75aaee9e0e0d5deb934021fcea25ac2fe09edc51e"
+dependencies = [
+ "arraydeque",
+ "encoding_rs",
+ "hashlink",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.8.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "zmij"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
diff --git a/Cargo.toml b/Cargo.toml
index 4898e2b..90291ec 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,3 +7,18 @@ edition = "2024"
clap = { version = "4", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
anyhow = "1"
+
+tera = "1"
+pulldown-cmark = "0.13"
+serde = { version = "1", features = ["derive"] }
+yaml-rust2 = "0.11"
+syntect = "5"
+regex = "1"
+chrono = { version = "0.4", features = ["serde"] }
+gpx = "0.10"
+geoutils = "0.5"
+fs_extra = "1"
+
+notify = "7"
+axum = "0.8"
+tower-http = { version = "0.6", features = ["fs"] }
diff --git a/content/css/Ascetic White.tmTheme b/content/css/Ascetic White.tmTheme
new file mode 100644
index 0000000..116ffa1
--- /dev/null
+++ b/content/css/Ascetic White.tmTheme
@@ -0,0 +1,157 @@
+
+
+IPv6, HTTPS & HTTP/2.0
+Welcome 2016!
+
+Thanks [Aeris](https://twitter.com/aeris22) for your help, check [his french blog post](https://blog.imirhil.fr/2015/09/02/cryptcheck-verifiez-implementations-tls.html) about SSL security.
diff --git a/content/posts/2016-12-07-laravel-foreign-relations.md b/content/posts/2016-12-07-laravel-foreign-relations.md
new file mode 100644
index 0000000..08b0478
--- /dev/null
+++ b/content/posts/2016-12-07-laravel-foreign-relations.md
@@ -0,0 +1,167 @@
+---
+title: "Laravel Foreign Relations"
+image: /images/stripe.png
+thumbnail: /images/thumbnail-stripe.png
+code: https://framagit.org/ThibaudDauce/laravel-foreign-relations
+description: In this article, I'll create a proof of concept using Laravel's Eloquent relations to fetch a foreign data source as Stripe.
+lang: en
+website: dev
+---
+
+The Laravel community is a huge fan of [Stripe](https://stripe.com). As Laravel, Stripe provides a great API to interact with the product. In this article, I'll create a proof of concept using Laravel's Eloquent relations to fetch a foreign data source as Stripe. The same idea could be applied to other foreign data providers.
+
+
+
+### Laravel's relation mecanism
+
+As you may know, you can [define relations](https://laravel.com/docs/5.3/eloquent-relationships) with Laravel. In your model, `hasMany`, `belongsTo` or `belongsToMany` are already-provided helpers to create links between your models.
+
+```php
+hasMany(Comment::class);
+ }
+}
+```
+
+But behind the scene, these methods returns children of the `Relation` object. These children could be `HasMany`, `BelongsTo`, `BelongsToMany`, etc. The `Relation` object is an abstract class you need to extend to build a new type of relations. This class is tightly tied with the Eloquent Builder class, which is a big problem. Laravel assumes Eloquent models in both sides of our relation. But in our case, it's one Eloquent model and one `Stripe\Customer` object.
+
+### Building the `StripeRelation`
+
+`StripeRelation` is my child for the `Relation` object. It will fetch and hydrate my models with the customers from Stripe.
+
+#### Constructor
+
+In order to build a `Relation` object, Laravel needs a builder from the foreign class and the instance of the main model. In our example, the foreign class is `Stripe\Customer` and the main model is `App\User`.
+
+First problem: I don't have an Eloquent builder for Stripe customers. I'll need [to create my own](#building-the-stripequerybuilder).
+
+```php
+public function __construct(Model $model)
+{
+ parent::__construct(app(StripeQueryBuilder::class), $model);
+}
+```
+
+#### `addConstraints` and `addEagerConstraints`
+
+The main goal of these methods is to set a `where` clause on the query builder with "id = stripe_id" or "id in [stripe_id_1, stripe_id_2, stripe_id_3]". Since my query builder is a special one, and I know it will be a custom `StripeQueryBuilder`, I can set two public properties `id` for when I need to fetch one Stripe customer, `ids` when I need to fetch all Stripe customers for a collection of users. We will see in the next part how we use these information to fetch the customers from Stripe.
+
+```php
+public function addConstraints()
+{
+ $this->query->id = $this->parent->{$this->localKey};
+}
+
+public function addEagerConstraints(array $models)
+{
+ $this->query->ids = $this->getKeys($models, $this->localKey);
+}
+```
+
+In these methods `$this->localKey` is the name of the attribute of the user model which contains the Stripe ID (default to `stripe_id`).
+
+#### `initRelation`
+
+This method sets the default foreign model for all the users. I don't have a default Stripe model so I just return the unchanged array of models.
+
+#### `getResults`
+
+The name of this method is misleading, its goal is just to fetch one foreign model (one Stripe customer) for one parent model (one user) when you simply type `$user->stripe`. I can delegate the API call to my `StripeQueryBuilder` class. The `stripe_id` of the user has already been set in the `StripeQueryBuilder` by the parent `Relation` class with `addConstraints`.
+
+```php
+public function getResults()
+{
+ return $this->query->first();
+}
+```
+
+#### `match`
+
+This method is called when you try to load the relation on a collection to avoid the [N + 1 problem](https://laravel.com/docs/5.3/eloquent-relationships#eager-loading). It takes three arguments:
+
+- the models — an array of users which needs to be populated with the customers from Stripe
+- the results — an Eloquent Collection from the query builder (basicaly a call to `$query->addEagerConstraints()->get()`) which contains for each Stripe ID the Stripe customer associated
+- the relation — the name of the current relation being processed
+
+The logic is simple, loop over the models and set the relation to whatever is in the `$results` collection:
+
+```php
+public function match(array $models, Collection $results, $relation)
+{
+ foreach ($models as $model) {
+ $model->setRelation($relation, $results[$model->{$this->localKey}]);
+ }
+
+ return $models;
+}
+```
+
+### Building the `StripeQueryBuilder`
+
+Now we need to build our `StripeQueryBuilder` which will call the Stripe API to fetch the requested customers. My class needs to extend the Eloquent Builder and override some methods. Most of the methods will not be changed so it will be impossible to call more *intelligent* methods like `$user->stripe()->update(['delinquent' => true])`.
+
+#### Constructor
+
+The parent model needs a Query. I will simply mock it.
+
+```php
+public function __construct()
+{
+ parent::__construct(Mockery::mock(QueryBuilder::class));
+}
+```
+
+#### `first`
+
+The `first` method fetch one customer based on the ID previously set in the `$id` attribute of the Query Builder.
+
+```php
+public function first($columns = ['*'])
+{
+ if ($this->id) {
+ return Customer::retrieve($this->id);
+ }
+
+ throw new \Exception("'first' method called with no ID provided.");
+}
+```
+
+#### `get`
+
+It's impossible to ask Stripe for all customers with IDs in an array (a simple `WHERE IN` with SQL). I need to fetch all customers and then filter them with PHP. The maximum number of customers for each request is 100. So I need to ask for 100 customers until Stripe tells me that there is no more or all my IDs have been found.
+
+```php
+public function get($columns = ['*'])
+{
+ $models = new Collection;
+
+ if ($this->ids) {
+ $totalCount = count($this->ids);
+
+ foreach ($this->fetchAllStripeCustomers() as $customer) {
+ if (in_array($customer->id, $this->ids)) {
+ $models[$customer->id] = $customer;
+ }
+
+ if ($totalCount === $models->count()) {
+ // All models were found, no need to continue to fetch the customers.
+ break;
+ }
+ }
+ }
+
+ return $models;
+}
+```
+
+### Conclusion
+
+This project is a very basic implementation. It's probably possible to do more. But it shows how you can keep a simple API for your models even with foreign relations. The working final project is available on [https://framagit.org/ThibaudDauce/laravel-foreign-relations](https://framagit.org/ThibaudDauce/laravel-foreign-relations).
+
+I may put this work in a package if some people are interested.
+
+Eventually, for french artisan, I offer Laravel trainings, just visit [https://www.formations-laravel.fr](https://www.formations-laravel.fr) and send me a message!
diff --git a/content/posts/2016-12-15-laravel-recursive-migrations.md b/content/posts/2016-12-15-laravel-recursive-migrations.md
new file mode 100644
index 0000000..712a6a5
--- /dev/null
+++ b/content/posts/2016-12-15-laravel-recursive-migrations.md
@@ -0,0 +1,189 @@
+---
+title: "Laravel Recursive Migrations"
+image: /images/migrations-subdirectories.png
+thumbnail: /images/thumbnail-migrations-subdirectories.png
+code: https://framagit.org/ThibaudDauce/laravel-recursive-migrations
+description: This package allows to put Laravel migrations into sub-directories. Let's build it together!
+lang: en
+website: dev
+---
+
+I'm currently developing the [Quantic Telecom](https://www.quantic-telecom.net) website with Laravel. My `app` folder is well-organized as my `views` and my `resources` but not my migrations because the Laravel `php artisan migrate` command doesn't run nested migrations. My package [`thibaud-dauce/laravel-recursive-migrations`](https://framagit.org/ThibaudDauce/laravel-recursive-migrations) will allow us to put migrations into sub-directories.
+
+In this blog post, I will go through the development of this package. For information, I found two links speaking about this particular problem: on StackOverflow ["Laravel running migrations on “app/database/migrations” folder recursively"](http://stackoverflow.com/questions/21641606/laravel-running-migrations-on-app-database-migrations-folder-recursively) and this issue on Github ["[Proposal/Enhancement] Artisan CLI Migrate command does not go through all sub folders of the migrations folder."](https://github.com/laravel/framework/issues/2561)
+
+The goal of my package is not to create new commands like `submigrate`, `submigrate:rollback`… I want to keep the original names `migrate`, `migrate:rollback` and add an option `--recursive` which will look into all sub-directories.
+
+
+
+When thinking about this problem, several solutions came into my mind:
+
+- Extending [`Illuminate\Database\Migrations\Migrator`](https://github.com/laravel/framework/blob/5.3/src/Illuminate/Database/Migrations/Migrator.php)
+- Extending all commands (migrate, refresh, reset…)
+
+### Extending [`Illuminate\Database\Migrations\Migrator`](https://github.com/laravel/framework/blob/5.3/src/Illuminate/Database/Migrations/Migrator.php)
+
+The easiest approach is to extend the [`Illuminate\Database\Migrations\Migrator`](https://github.com/laravel/framework/blob/5.3/src/Illuminate/Database/Migrations/Migrator.php) and override the [`getMigrationFiles()`](https://github.com/laravel/framework/blob/5.3/src/Illuminate/Database/Migrations/Migrator.php#L298-L307) method. This method currently use the [`glob()`](https://github.com/laravel/framework/blob/5.3/src/Illuminate/Filesystem/Filesystem.php#L364-L367) method of the filesystem to get all files in the migration directory following the "*_*.php" pattern. Using the [`allFiles()`](https://github.com/laravel/framework/blob/5.3/src/Illuminate/Filesystem/Filesystem.php#L398-L401) recursive method instead should do the trick.
+
+The main benefit of this approach is to act on all commands (migrate, rollback, reset…) with only one change because the [`Migrator`](https://github.com/laravel/framework/blob/5.3/src/Illuminate/Database/Migrations/Migrator.php) is used by all of them. But the drawback is that the [`Migrator`](https://github.com/laravel/framework/blob/5.3/src/Illuminate/Database/Migrations/Migrator.php) is a low level class, so it can't access the command's flags. So I will be forced to apply migrations recursively all the time.
+
+### Extending all commands
+
+Extending the commands' classes will allow me to override some of their methods and modify their behavior. It seems to be a good option for me. The only disadvantage is the fact that I need to create a new class for each of the migrations' commands: [`MigrateCommand`](https://github.com/laravel/framework/blob/5.3/src/Illuminate/Database/Console/Migrations/MigrateCommand.php), [`RefreshCommand`](https://github.com/laravel/framework/blob/5.3/src/Illuminate/Database/Console/Migrations/RefreshCommand.php), [`ResetCommand`](https://github.com/laravel/framework/blob/5.3/src/Illuminate/Database/Console/Migrations/ResetCommand.php), [`RollbackCommand`](https://github.com/laravel/framework/blob/5.3/src/Illuminate/Database/Console/Migrations/RollbackCommand.php) and [`StatusCommand`](https://github.com/laravel/framework/blob/5.3/src/Illuminate/Database/Console/Migrations/StatusCommand.php). But I can manage the duplicate code with some traits.
+
+So, I will split the work into two traits. The first one will be in charged of fetching subdirectories and will be general (pure and not linked to Laravel or the migration system). The second one, the ugly one, will need to be add to a child of the [`Illuminate\Database\Console\Migrations\BaseCommand`](https://github.com/laravel/framework/blob/5.3/src/Illuminate/Database/Console/Migrations/BaseCommand.php) and will override the [`getMigrationPaths()`](https://github.com/laravel/framework/blob/5.3/src/Illuminate/Database/Console/Migrations/BaseCommand.php#L24-L36), [`getOptions()`](https://github.com/laravel/framework/blob/5.3/src/Illuminate/Database/Console/Migrations/MigrateCommand.php#L104-L119) and [`call()`](https://github.com/laravel/framework/blob/5.3/src/Illuminate/Console/Command.php#L179-L186) methods. I think it's often a bad choice to force a trait into an inheritance tree, but I couldn't find another solution…
+
+#### My first trait: [`Subdirectories`](https://framagit.org/ThibaudDauce/laravel-recursive-migrations/blob/master/src/Subdirectories.php)
+
+This trait will be pure. It will fetch the sub-directories for a path. I could have done a service class injected by the container but I thought it will be over complicated for some business logic that will never change (sub-directories will always be sub-directories). A service class would have given more flexibility to the end user by allowing him to change or extend my implementation.
+
+The first method of this trait will fetch all sub-directories in a path with the help of the [`Finder`](https://github.com/symfony/symfony/blob/3.2/src/Symfony/Component/Finder/Finder.php) component:
+
+```php
+/**
+ * Fetch all subdirectories from a path.
+ * The original path is included in the array.
+ *
+ * @param string $path
+ * @return string[]
+ */
+protected function subdirectories($path)
+{
+ return Collection::make(Finder::create()->in($path)->directories())
+ ->map(function (SplFileInfo $directory) {
+ return $directory->getPathname();
+ })
+ ->prepend($path)
+ ->values()
+ ->toArray();
+}
+```
+
+The second method will flatMap the result of the first method for multiple base paths. flatMap is a function which map all element in an array to an array (you get an array of array) and then merge all arrays into one.
+
+```php
+/**
+ * Fetch all subdirectories from an array of base paths.
+ * The original paths are included in the array.
+ *
+ * @param string[] $paths
+ * @return string[]
+ */
+protected function allSubdirectories($paths)
+{
+ return Collection::make($paths)
+ ->flatMap(function ($path) {
+ return $this->subdirectories($path);
+ })
+ ->toArray();
+}
+```
+
+#### My second trait: [`RecursiveMigrationCommand`](https://framagit.org/ThibaudDauce/laravel-recursive-migrations/blob/master/src/RecursiveMigrationCommand.php)
+
+The goal of this trait is to provide new methods for the children of [`Illuminate\Database\Console\Migrations\BaseCommand`](https://github.com/laravel/framework/blob/5.3/src/Illuminate/Database/Console/Migrations/BaseCommand.php). It will use the previously seen [`Subdirectories`](https://framagit.org/ThibaudDauce/laravel-recursive-migrations/blob/master/src/Subdirectories.php) trait.
+
+First, it will add the `--recursive` option. Nothing special here to the [`getOptions()`](https://github.com/laravel/framework/blob/5.3/src/Illuminate/Database/Console/Migrations/MigrateCommand.php#L104-L119) method.
+
+```php
+/**
+ * Get the console command options.
+ *
+ * @return array
+ */
+protected function getOptions()
+{
+ return array_merge(parent::getOptions(), [
+ ['recursive', 'r', InputOption::VALUE_NONE, 'Indicates if the migrations should be run recursively (nested directories).']
+ ]);
+}
+```
+
+Then, it will add the sub-directories to the migration paths if the flag is set. The [`allSubdirectories()`](https://framagit.org/ThibaudDauce/laravel-recursive-migrations/blob/master/src/Subdirectories.php#L36-43) method comes from the [`Subdirectories`](https://framagit.org/ThibaudDauce/laravel-recursive-migrations/blob/master/src/Subdirectories.php) trait.
+
+```php
+/**
+ * Get all of the migration paths.
+ *
+ * @return array
+ */
+protected function getMigrationPaths()
+{
+ $paths = parent::getMigrationPaths();
+ return $this->option('recursive') ? $this->allSubdirectories($paths) : $paths;
+}
+```
+
+And finally, the last one, `call()`, is only required for the [`RefreshCommand`](https://github.com/laravel/framework/blob/5.3/src/Illuminate/Database/Console/Migrations/RefreshCommand.php). [`RefreshCommand`](https://github.com/laravel/framework/blob/5.3/src/Illuminate/Database/Console/Migrations/RefreshCommand.php) does not contain any logic. It's only a wrapper around the [`ResetCommand`](https://github.com/laravel/framework/blob/5.3/src/Illuminate/Database/Console/Migrations/ResetCommand.php) and the [`MigrateCommand`](https://github.com/laravel/framework/blob/5.3/src/Illuminate/Database/Console/Migrations/MigrateCommand.php). Internally, it calls the others commands with the [`call()`](https://github.com/laravel/framework/blob/5.3/src/Illuminate/Database/Console/Migrations/RefreshCommand.php#L50-L66) method, passing its arguments like "step", "database" or "path"… But in this implementation, it will not pass the "recursive" flag to the other commands. I need to set it if the command called is a *migrate* command:
+
+```php
+/**
+ * Call another console command.
+ *
+ * @param string $command
+ * @param array $arguments
+ * @return int
+ */
+public function call($command, array $arguments = [])
+{
+ if (starts_with($command, 'migrate')) {
+ $arguments['--recursive'] = $this->option('recursive');
+ }
+
+ parent::call($command, $arguments);
+}
+```
+
+#### New Commands
+
+I also need to create my own commands for the [`MigrateCommand`](https://framagit.org/ThibaudDauce/laravel-recursive-migrations/blob/master/src/Commands/MigrateCommand.php), [`RefreshCommand`](https://framagit.org/ThibaudDauce/laravel-recursive-migrations/blob/master/src/Commands/RefreshCommand.php), [`ResetCommand`](https://framagit.org/ThibaudDauce/laravel-recursive-migrations/blob/master/src/Commands/ResetCommand.php), [`RollbackCommand`](https://framagit.org/ThibaudDauce/laravel-recursive-migrations/blob/master/src/Commands/RollbackCommand.php) and [`StatusCommand`](https://framagit.org/ThibaudDauce/laravel-recursive-migrations/blob/master/src/Commands/StatusCommand.php). All the implementations are the same thanks to the [`RecursiveMigrationCommand`](https://framagit.org/ThibaudDauce/laravel-recursive-migrations/blob/master/src/RecursiveMigrationCommand.php) trait. For example, my [`MigrateCommand`](https://framagit.org/ThibaudDauce/laravel-recursive-migrations/blob/master/src/Commands/MigrateCommand.php):
+
+```php
+app->extend('command.migrate', function () {
+ return new MigrateCommand($this->app['migrator']);
+});
+
+$this->app->extend('command.migrate.rollback', function () {
+ return new RollbackCommand($this->app['migrator']);
+});
+
+$this->app->extend('command.migrate.refresh', function () {
+ return new RefreshCommand($this->app['migrator']);
+});
+
+$this->app->extend('command.migrate.reset', function () {
+ return new ResetCommand($this->app['migrator']);
+});
+
+$this->app->extend('command.migrate.status', function () {
+ return new StatusCommand($this->app['migrator']);
+});
+```
+
+I didn't found a way to override the first binding with `bind()` because the [`MigrationServiceProvider`](https://github.com/laravel/framework/blob/5.3/src/Illuminate/Database/MigrationServiceProvider.php) is deferred by the [`ConsoleSupportServiceProvider`](https://github.com/laravel/framework/blob/5.3/src/Illuminate/Foundation/Providers/ConsoleSupportServiceProvider.php) so it will register his binding after mines. If I also defer my service provider, I get a "Provider already exists!" exception.
+
+So, I need to use the `extend()` method which is not really the correct one I think. The extend method will first resolve the previous binding and give it to my closure. In my service provider, I don't care of the previous instance of the command since I create a new one (a new child) from scratch.
+
+### Conclusion
+
+The project is missing PHPUnit tests even if I already try the commands by hand in a fresh Laravel project. But it's available now on [Packagist](https://packagist.org/packages/thibaud-dauce/laravel-recursive-migrations) and on [Git](https://framagit.org/ThibaudDauce/laravel-recursive-migrations). Please open an issue if you find anything wrong in it.
+
+I discover a lot about the migration system in Laravel during this little project, and I encourage you to dig in the source code of the framework: you'll learn a lot!
diff --git a/content/posts/2017-02-13-pattern-matching-in-php.md b/content/posts/2017-02-13-pattern-matching-in-php.md
new file mode 100644
index 0000000..97ce465
--- /dev/null
+++ b/content/posts/2017-02-13-pattern-matching-in-php.md
@@ -0,0 +1,161 @@
+---
+title: "Pattern Matching in PHP"
+image: /images/pattern-matching.png
+thumbnail: /images/thumbnail-pattern-matching.png
+code: https://gitlab.com/thibauddauce/pattern-matching
+description: How to build a pattern matching library in PHP?
+lang: en
+website: dev
+---
+
+I'm a big fan of Haskell and one of my favorite feature of this awesome language is pattern matching. If you don't know what's pattern matching, it's this:
+
+```hs
+data Customer = Student | Individual
+
+getPrice :: Customer -> Int
+getPrice Student = 10
+getPrice Individual = 30
+```
+
+The main advantage is, if I add a possibility in my type, for example `data Customer = Student | Individual | Company`, the Haskell compiler will tell me that my function is missing a pattern. I will need to provide a `getPrice Company = ?`.
+
+Of course, in PHP we don't have a compiler but we can still have some checks…
+
+
+
+### The problem
+
+Imagine I have a class for my customer:
+
+```php
+type === self::STUDENT) {
+ return 10;
+ } elseif ($this->type === self::INDIVIDUAL) {
+ return 30;
+ } else {
+ throw new InvalidArgumentException('Neither student nor individual.');
+ }
+ }
+}
+```
+
+By the way, pay attention of how 4 lines of code became 18 lines. But, of course, Haskell is a incomprehensible language ;-)
+
+If I have my unit tests like these:
+
+```php
+$customer = new Customer; // in real life, I would use a name constructor…
+$customer->type = Customer::STUDENT;
+
+$this->assertEquals(10, $customer->getPrice());
+```
+
+```php
+$customer = new Customer;
+$customer->type = Customer::INDIVIDUAL;
+
+$this->assertEquals(30, $customer->getPrice());
+```
+
+If latter, I add a new type `const COMPANY = 'company'` in my class, my tests will remain green. If I use this `if() {} elseif () {} else {}` in a lot of places, it will be difficult to catch all occurrences.
+
+### The solution
+
+Use [my small pattern matching library](https://gitlab.com/thibauddauce/pattern-matching) (or build your own, it's only a mater of hours…):
+
+```php
+match($this->type, $actions);
+ }
+
+ public function getPrice()
+ {
+ $this->patternMatchOnType([
+ self::STUDENT => 10,
+ self::INDIVIDUAL => 30,
+ ]);
+ }
+}
+```
+
+If I add a new type and I change my enumerate definition in one place `new Pattern([self::STUDENT, self::INDIVIDUAL, self::COMPANY])`, my previous tests are now failing with a new exception:
+
+```html
+MissingPatternsDuringMatch: 'company' was missing during the match.
+Expected patterns were 'student', 'individual', 'company' and received patterns were 'student', 'individual'.
+```
+
+Even if, I never wrote a test to check if the price for a company is correct.
+
+### Extra features of my library
+
+You can check [the test file](https://gitlab.com/thibauddauce/pattern-matching/blob/master/tests/PatternTest.php) for all features.
+
+#### Checks
+
+My `match()` function is three simple checks happening every time. It means, it raises an exception even if the pattern could be resolved.
+
+- there is no missing pattern in the array (all cases must have a response)
+- there is no extra pattern in the array (if a type is removed, I want to remove all the occurrences of the dead code)
+- the value provided is in the pattern list
+
+#### Callbacks
+
+If your application is doing expensive work for each pattern (more expensive than returning "10" or "30"), you can wrap the result in a callback:
+
+```php
+$this->patternMatchOnType([
+ self::STUDENT => function() {
+ return StudentPrice::fetchFromFile();
+ },
+ self::INDIVIDUAL => function() {
+ return Price::where('type', self::INDIVIDUAL)->first()->value;
+ },
+]);
+```
+
+#### Callbacks arguments
+
+And you can also give arguments to your callbacks with `with`:
+
+```php
+public function patternMatchOnType(array $actions)
+{
+ return (new Pattern([self::STUDENT, self::INDIVIDUAL]))
+ ->with($this) // The callbacks will receive the instance of the customer
+ ->match($this->type, $actions);
+}
+
+public function getPrice()
+{
+ $this->patternMatchOnType([
+ self::STUDENT => 10,
+ self::INDIVIDUAL => function(Customer $customer) {
+ return IndividualPrice::where('age', '>', $customer->age)->first()->value;
+ },
+ ]);
+}
+```
+
+Even if, in this case, you could simply use `$this->age` inside the callback.
diff --git a/content/posts/2017-07-18-your-app-doesnt-have-users.md b/content/posts/2017-07-18-your-app-doesnt-have-users.md
new file mode 100644
index 0000000..f71d738
--- /dev/null
+++ b/content/posts/2017-07-18-your-app-doesnt-have-users.md
@@ -0,0 +1,56 @@
+---
+title: "Your app doesn't have \"users\""
+image: /images/your-app-doesnt-have-users.png
+thumbnail: /images/thumbnail-your-app-doesnt-have-users.png
+description: The most important part of your code is how you're naming things. Don't forget your first class!
+lang: en
+website: dev
+---
+
+I'm a big fan of Laravel. The thing I like the most about the community is the focus on understandable code. Not patterns, not architectures, but how to choose the best API for a task so the result is readable as plain English.
+
+But today, I want to talk about one name that Laravel's developers often get wrong.
+
+
+
+---
+
+The default installation of Laravel comes with a `User` class and a migration to create a `users` table. Why? Because, obviously, every application has users.
+
+*That's not true.*
+
+Adam Wathan is a Laravel developer who provides an awesome course to learn how to build great applications: ["Test Driven Laravel"](https://adamwathan.me/test-driven-laravel/).
+
+
+
+TicketBeast, the Test Driven Laravel sample application, is about promoters and concerts, but in the application there is a `User` class and a `Concert` class but no `Promoter` class.
+
+He is also producing free lives, twice a week, on his [YouTube channel](https://www.youtube.com/channel/UCy1H38XrN7hi7wHSClfXPqQ/videos) where he builds [KiteTail](https://kitetail.co/). Adam put a lot of thought into the name of his users and eventually chose **"makers"** after a few videos. But once again, there is no `Maker` class in KiteTail.
+
+If you watch (and you should) the new series at Laracasts ["Let's Build A Forum with Laravel and TDD"](https://laracasts.com/series/lets-build-a-forum-with-laravel/), you will notice that Jeffrey also uses the `User` class. But this time, he really uses the term "user" when speaking and writing descriptions for his tests.
+
+On the other hand, in the second video, he used the term **"creator"** for "a thread has a creator" and he renamed his relation from `user()` to `creator()`. That's much more readable than `user()`, because what is the user of a thread anyway? If the default doesn't seem right for you, don't use it!
+
+When you don't use the default, Laravel will look for a "creator_id" column in the table instead of a "user_id" one. Jeffrey chose to override the default with `return $this->belongsTo(User::class, 'user_id');`, but I think it could have been better to embrace the new term and rename the table's column from "user_id" to "creator_id". If you need to speak about this table, you could then say "I store the ID of the creator with the body of the thread" instead of "I store the ID of the user with the body of the thread". The first one seems much more natural to me.
+
+---
+
+Thread **creator**, concert **promoter**, product **maker**, it's only three examples among many.
+
+The most important thing to keep in mind is that you should name your classes as it feels natural to **you**. For example, I would use **"members"** to designate the users of a forum but Jeffrey seems to use **"users"** naturally. There is no right or wrong here (as often in development), you only need to choose the correct one so you can speak about your code without translating anything.
+
+So next time you run `laravel new my-project`, don't be lazy, take 5 minutes to think about your product and if you need to rename your `User` class into something else. It's always easier when it's done on a fresh project :-)
+
+I choose to speak about `User` because I think it's often forgotten but it's also true in most applications for "status", "data", "items", "result", "payload"… But don't forget that in a `Collection` class, "items" is perfectly natural, and in the Guzzle HTTP library, "payload" is fine too.
+
+If you want to learn more about it:
+
+- [https://murze.be/2017/06/the-status-antipattern/](https://murze.be/2017/06/the-status-antipattern/)
+- [http://twentypercent.fm/naming-things](http://twentypercent.fm/naming-things)
+
+And if you want to learn how to improve your code:
+
+- [https://adamwathan.me/test-driven-laravel/](https://adamwathan.me/test-driven-laravel/)
+- [Adam's YouTube channel](https://www.youtube.com/channel/UCy1H38XrN7hi7wHSClfXPqQ/videos)
+- [Laracasts](https://laracasts.com/)
+- For french speakers: [Formations Laravel](https://www.formations-laravel.fr/)
diff --git a/content/posts/2017-07-26-improvements-of-the-new-Responsable-interface-in-Laravel.md b/content/posts/2017-07-26-improvements-of-the-new-Responsable-interface-in-Laravel.md
new file mode 100644
index 0000000..0ee9267
--- /dev/null
+++ b/content/posts/2017-07-26-improvements-of-the-new-Responsable-interface-in-Laravel.md
@@ -0,0 +1,198 @@
+---
+title: "Improvements of the new Responsable interface in Laravel"
+image: /images/responsable.png
+thumbnail: /images/thumbnail-responsable.png
+description: "Laravel 5.5 will ship with a new feature: the Responsable interface. Why not add some sugar to it?"
+lang: en
+website: dev
+---
+
+The new `Responsable` interface in Laravel is really awesome, it allows us to simplify our controllers with custom responses objects.
+
+But one of the complexity with responses is the content negotiation and [the new feature](https://github.com/laravel/framework/commit/c0c89fd73cebf9ed56e6c5e69ad35106df03d9db) doesn't help us on this. How could we solve this problem?
+
+
+
+---
+
+### The new feature
+
+First, let's look at how Laravel uses the `Responsable` interface in the Router.
+
+```php
+if ($response instanceof Responsable) {
+ $response = $response->toResponse();
+}
+
+if ($response instanceof PsrResponseInterface) {
+ $response = (new HttpFoundationFactory)->createResponse($response);
+} elseif (! $response instanceof SymfonyResponse &&
+ ($response instanceof Arrayable ||
+ $response instanceof Jsonable ||
+ $response instanceof ArrayObject ||
+ $response instanceof JsonSerializable ||
+ is_array($response))) {
+ $response = new JsonResponse($response);
+} elseif (! $response instanceof SymfonyResponse) {
+ $response = new Response($response);
+}
+```
+
+The only method on the `Respondable` interface is the `toResponse` method. Then, if the response is `Jsonable` or something similar, Laravel will use the `JsonResponse` which `json_encode` all the data.
+
+### Adding Content Negotiation
+
+There is no sign of Content-Negotiation in this method. So if I need to build a response in JSON or in HTML depending of the type the request, I will have some conditions in my controller:
+
+```php
+public function index()
+{
+ $users = User::all();
+
+ if (request()->wantsJson()) {
+ return new UsersJsonResponse($users);
+ }
+
+ return new UsersHtmlResponse($users);
+}
+```
+
+Not too bad, thanks to the awesome `wantsJson()` method, but it could be better (especially if my controller is not a simple database query):
+
+```php
+public function index()
+{
+ $users = User::all();
+
+ return UsersResponse($users);
+}
+```
+
+Better! Now let's dive in the `UsersResponse`:
+
+```php
+class UsersResponse implements Responsable
+{
+ public $users;
+
+ public function __construct($users)
+ {
+ $this->users = $users;
+ }
+
+ public function toResponse()
+ {
+ if (request()->wantsJson()) {
+ return $this->users;
+ }
+
+ return view('users.index', ['users' => $this->users]);
+ }
+}
+```
+
+### Avoiding conditionals with polymorphism
+
+It's often a good thing to replace conditionals by polymorphism. I would rather prefer this response with a new `NegociateContent` trait:
+
+```php
+class UsersResponse implements Responsable
+{
+ use NegociateContent;
+
+ public $users;
+
+ public function __construct($users)
+ {
+ $this->users = $users;
+ }
+
+ public function toJsonResponse()
+ {
+ return $this->users;
+ }
+
+ public function toHtmlResponse()
+ {
+ return view('users.index', ['users' => $this->users]);
+ }
+}
+```
+
+Or this one which extends a new `BaseResponse`:
+
+```php
+class UsersResponse extends BaseResponse
+{
+ public $users;
+
+ public function __construct($users)
+ {
+ $this->users = $users;
+ }
+
+ public function toJsonResponse()
+ {
+ return $this->users;
+ }
+
+ public function toHtmlResponse()
+ {
+ return view('users.index', ['users' => $this->users]);
+ }
+}
+```
+
+The `NegociateContent` or the `BaseResponse` implementations are really straightforward. We can add more checks and content type if needed of course.
+
+```php
+class BaseResponse implements Respondable
+{
+ public function toResponse()
+ {
+ if (request()->wantsJson()) {
+ return $this->toJsonResponse();
+ }
+
+ return $this->toHtmlResponse();
+ }
+}
+```
+
+### Let's try to build the same `view` helper as in mailables
+
+We can even simplify the `toHtmlResponse` method with the same concept as in `Mailable`. As you may know, in mailables, all public properties are directly accessible in the mail view.
+
+```php
+public $users;
+
+public function toHtmlResponse()
+{
+ return $this->view('users.index');
+}
+```
+
+I stole this code from the [`Illuminate\Mail\Mailable`](https://github.com/laravel/framework/blob/master/src/Illuminate/Mail/Mailable.php#L223) class in the framework (I just refactored it to collections).
+
+```php
+public function view($view, $overrides = [])
+{
+ return collect((new ReflectionClass($this))->getProperties(ReflectionProperty::IS_PUBLIC))
+ ->reject(function($property) {
+ return $property->getDeclaringClass()->getName() == self::class;
+ })
+ ->mapWithKeys(function($property) {
+ return [$property->getName(), $property->getValue($this)];
+ })
+ ->merge($overrides)
+ ->pipe(function($data) use($view) {
+ return view($view, $data);
+ });
+}
+```
+
+### Conclusion
+
+I think these few lines of code really improve the `Responsable` interface. I really like the `Mailable` reflection as it feels really natural to me.
+
+I may try to pull request the framework to add these features if people like them so don't hesitate to send me a message [on Twitter \@ThibaudDauce](https://twitter.com/ThibaudDauce) or [by mail (thibaud@dauce.fr)](mailto:thibaud@dauce.fr) to tell me what you think!
diff --git a/content/posts/2019-01-24-what-to-learn-in-2019.md b/content/posts/2019-01-24-what-to-learn-in-2019.md
new file mode 100644
index 0000000..9a50df0
--- /dev/null
+++ b/content/posts/2019-01-24-what-to-learn-in-2019.md
@@ -0,0 +1,56 @@
+---
+title: "What to learn in 2019?"
+description: "In 2019, I want to challenge the way I build applications by exploring other interesting technologies."
+lang: en
+website: dev
+---
+
+Computer science is evolving at a rapid pace. There are new developers bringing new ideas. And there are many ancient discoveries forgotten or underused today. The fact is: there is a lot to learn in computer science in 2019.
+
+This year, I want to go out of my comfort zone and try new stuff. Don't get me wrong, I still love the PHP ecosystem, and Laravel is still an amazing framework to work with (I even [teach Laravel](https://www.formation-laravel.fr/) if you're interested).
+
+So, why switch? To begin with, I don't want to switch but only becoming productive in other areas of computed sciences. Secondly, PHP, as a language, is not that great and the recent features added are not really interesting. I have the feeling PHP is trying to copy Java or other mainstream languages without the correctness of the compilation. I don't consider my program crashing at runtime because of a type annotation is a real improvement over my program crashing at runtime because the method is called on the wrong object.
+
+### What are the goals? Productivity, correctness and performances.
+
+**Productivity** is the most meaningful one. I'm paid to deliver value to a customer and not to do research around programming languages. But productivity is also the easiest metric to improve, I'm sure you can be productive in any language if you practice enough.
+
+**Correctness** is about writing a program and have some guaranties that the result will work as expected. PHP is especially bad at this, but other languages can have different levels of verification with more or less strict type systems.
+
+And for **performances**, even though PHP 7 brought a significant speed boost, the language is still slow. The web is also a really slow platform, and I would like experimenting unfamiliar things like high performance CLI apps and native GUIs.
+
+### Who are the challengers?
+
+[Haskell](https://haskell-lang.org/) is a strictly typed, purely functional, "if it compiles, it works" kind of language. I'm afraid that the accent on absolute correctness and the bad documentation will slow me down on some manageable problems.
+
+★★☆☆ Productivity |
+★★★★ Correctness |
+★★☆☆ Performances
+
+[Elixir](https://elixir-lang.org/) is a functional language based on the Erlang VM with the Ruby syntax. I think I can become highly productive with this kind of language. The correctness is a little bit behind because the language is dynamically typed but the compilation can still identify some issues before going into production. And last but not least, the performance are amazing: less than a millisecond to render a simple HTML webpage with [Phoenix](https://phoenixframework.org/).
+
+★★★☆ Productivity |
+★★☆☆ Correctness |
+★★★☆ Performances
+
+[Rust](https://www.rust-lang.org/) is a low-level programming language with C-like performances. Memory management lower the productivity, but the documentation is really good. And the type system can detect the majority of silly errors at compile-time.
+
+★☆☆☆ Productivity |
+★★★☆ Correctness |
+★★★★ Performances
+
+[PHP](https://secure.php.net/), because this is the tool I provide the most value with. The ecosystem is amazing, but I will not discover anything new soon. And I don't like that.
+
+★★★★ Productivity |
+★☆☆☆ Correctness |
+★☆☆☆ Performances
+
+### So? What will I learn in 2019?
+
+My first choice is Rust. It's the language where I have the most to learn.
+
+For a few weeks I tried building a text editor with Rust, and it was very hard. How to render a character on the screen? How to create a blinking cursor? How to add words in the middle of a large string? I think this project is overly complex for me now but it was a good first try.
+
+I also built a small utility (a few lines of code) to print statistics about my websites. I don't use standard analytics, so I just grabbed the last few days of Nginx's logs and printed the visits for each day and each URI.
+
+Thanks for reading, it's been a while (more than one year) between my previous post and this one, and my written english is a little bit Rusty, please forgive me :-) You can subscribe to my RSS feed to receive my future posts if you want.
\ No newline at end of file
diff --git a/content/posts/2019-07-01-I-dont-like-camel-case.md b/content/posts/2019-07-01-I-dont-like-camel-case.md
new file mode 100644
index 0000000..c049ba4
--- /dev/null
+++ b/content/posts/2019-07-01-I-dont-like-camel-case.md
@@ -0,0 +1,22 @@
+---
+title: "I don't like camelCase"
+description: "Snake case, camel case… Why different languages made different choices?"
+lang: en
+website: dev
+---
+
+Which one is the most readable?
+
+```php
+function getFirstUserFrom(SiretNumber $siretNumber): User {}
+```
+
+```php
+function get_first_user_from(Siret_Number $siret_number): User {}
+```
+
+I think the snake case version is much more readable than the camel case.
+
+How we name our functions is not a technical choice, it's a convention. Nobody can say that the snake case version is worse than the camel case one. The people from the PSR committee decided [a long time ago](https://www.php-fig.org/psr/psr-1/) that, in PHP, methods will be named in camel case and classes will be named in studly case. But in Rust, or in Python, a different choice was made: it's snake case for functions and methods.
+
+Don't let other people decide what works best for you. Recently, I started using snake case for all my functions, methods and variables. And I think I'll switch to the `Siret_Number` approach for all my classes.
\ No newline at end of file
diff --git a/content/posts/2020-10-07-taylor-otwell-a-propos-de-jetstream-et-ma-chaine-youtube.md b/content/posts/2020-10-07-taylor-otwell-a-propos-de-jetstream-et-ma-chaine-youtube.md
new file mode 100644
index 0000000..f702bc5
--- /dev/null
+++ b/content/posts/2020-10-07-taylor-otwell-a-propos-de-jetstream-et-ma-chaine-youtube.md
@@ -0,0 +1,25 @@
+---
+title: "Taylor Otwell à propos de Jetstream. Le futur de ma chaîne YouTube."
+description: "Taylor explique dans une récente vidéo la différence entre open-source et produits commerciaux ce qui me fait penser à mes prochains projets sur YouTube"
+website: dev
+---
+
+Dans [une récente vidéo](https://www.youtube.com/watch?v=krn39HjxPTs), Taylor Otwell revient sur les critiques apportées à [Laravel Jetstream](https://jetstream.laravel.com/1.x/introduction.html). Il y explique que lorsqu'il développe des applications open-source, il le fait pour lui. C'est pour cela que Jetsream ne propose « que » deux options : Livewire ou Inertia. Taylor n'utilisant ni Bootstrap, ni Vue Router, ni Blade seul… c'est normal pour lui ne pas développer ces options. Mais il laisse libre la communauté de créer ses propres bibliothèques open-source si des personnes sont intéressées (d'après ce que j'en ai compris, [Laravel Fortify](https://github.com/laravel/fortify) est l'interface sans aucun visuel permettant de créer ses propres options).
+
+Il explique également que, contrairement à ses travaux open-source où il fait ce qu'il veut (et il a bien raison), lorsqu'il développe des produits commerciaux il se plie aux demandes de ses clients. Je comprends parfaitement cette réflexion pour en avoir un aperçu avec [la chaîne YouTube](https://www.youtube.com/user/tdauce/) lorsque des personnes me demandent pourquoi je ne fais pas une nouvelle série sur Laravel, pourquoi je n'ai pas terminé la série sur Nova, pourquoi je ne fais pas plus de VueJS, etc. Lorsque je fais des vidéos gratuitement sur Internet, je me permets de ne parler que des sujets qui m'intéressent, sans aucune autre obligation. Cela ne m'empêche pas de continuer à faire des formations et de l'accompagnement sur Laravel [pour mes clients](https://www.formation-laravel.fr/).
+
+Pour ceux qui l'avaient remarqué, ce blog était en anglais mais j'ai récemment décidé de reprendre l'écriture car ma situation actuelle m'empêche de faire des vidéos (le format écrit est plus simple à produire). Or, écrire en anglais me demande plus d'efforts, et ma communauté étant principale francophone, j'ai décidé de le reprendre en français.
+
+Pour ceux intéressés par la suite, voici les sujets dont j'aimerais parler dans le futur sur ce blog, mais aussi en vidéo quand ma situation sera plus calme :
+- Des astuces que j'utilise sur mes applications Laravel
+- La transformation de [ce site statique](https://github.com/ThibaudDauce/thibaud.dauce.fr) d'Hakyll vers Rust
+- Un retour d'expérience sur le développement d'une SPA avec [Elm](https://elm-lang.org/)
+- Un retour d'expérience sur le développement d'une application Android avec [Flutter](https://flutter.dev/)
+- Intégration d'[AlpineJS](https://github.com/alpinejs/alpine/) dans mon projet [Têtes de Coton](https://www.youtube.com/playlist?list=PLMWEEzYqZ0em0jJa4LmQPjDqJNaApqKan)
+- Développement d'un blog avec [Rust](https://www.rust-lang.org/) et [Rocket](https://rocket.rs/) (avec plus de choses que [ma précédente expérimentation](https://www.youtube.com/playlist?list=PLMWEEzYqZ0ekOG6_G4q_GXPpVHWrIH--x) : connexion admin, WYSIWYG, envoi de fichiers…)
+- Développement d'un composant [DataTables](https://datatables.net/) en VueJS v3
+- Expérimentation d'une application native multiplateformec en C++ avec [WxWidgets](https://www.wxwidgets.org/)
+
+Et sans doute d'autres choses autour de la programmation mais pas que ! Si vous voulez commenter, discuter, n'hésitez pas à rejoindre [mon Discord](https://discordapp.com/invite/tPtVM9V). Si vous souhaitez être notifié des prochains articles de ce blog, le plus sûr est de s'abonner à [mon flux RSS](https://thibaud.dauce.fr/feed.xml), vous pouvez aussi me suivre [sur Twitter](https://twitter.com/ThibaudDauce/) même si j'y suis très peu actif ces derniers temps.
+
+PS : je reviendrai dans un futur article sur mon avis à propos Jetsream et autres…
\ No newline at end of file
diff --git a/content/posts/2020-10-13-projet-windows.md b/content/posts/2020-10-13-projet-windows.md
new file mode 100644
index 0000000..5c9346f
--- /dev/null
+++ b/content/posts/2020-10-13-projet-windows.md
@@ -0,0 +1,48 @@
+---
+title: "Projet Windows"
+description: "Un développeur Linux depuis plus de 10 ans peut-il passer à Windows ?"
+website: dev
+---
+
+J'ai reçu hier un Dell XPS 17 2020. Après avoir consulté quelques forums, je me suis rendu compte que [les pilotes Linux ne semblent pas tout à fait à jour](https://wiki.archlinux.org/index.php/Dell_XPS_17) pour cette machine et je risque fort d'avoir, au minimum, des problèmes de son. C'est l'occasion rêvée de faire une petite expérimentation : que donne Windows aujourd'hui pour développer ?
+
+Je n'ai personnellement jamais utilisé Windows pour développer, j'ai commencé avec un CD d'Ubuntu (envoyé gratuitement par la poste à l'époque !) il y une dizaine d'années, puis, il y a maintenant plus de 6 ans, je suis passé à ArchLinux pour bénéficier des dernières versions logicielles.
+
+## Objectifs lors de la réception
+
+Mon objectif est donc de tester Windows pendant quelques semaines/mois le temps que le noyau Linux se mette à jour et ensuite d'évaluer l'expérience. Je compte y aller à fond : Windows, mais aussi les applications Mail et Calendar intégrées, Edge en tant que navigateur principal, le nouveau Windows Terminal…
+
+Pour référence, voici les principaux points qui peuvent potentiellement m'embêter par ordre de priorité :
+
+1. Je ne sais pas du tout comment fonctionne les outils de développement du nouveau Edge (est-ce qu'il est compatible avec les extensions Chrome ? Est-ce que toutes les infos sont présentes dans l'explorateur ?)
+1. Les bureaux multiples semblent simplistes sous Windows : j'utilise actuellement 4 bureaux virtuels en carré avec des raccourcis pour se déplacer dans l'espace (vers le haut, gauche, droite, bas…) et des catégories de programmes automatiquement ouvertes sur chaque bureau.
+1. Des problèmes insolvables : sous Linux j'ai toujours la possibilité d'aller assez loin dans le fonctionnement du système en ligne de commande pour trouver une solution.
+1. Un environnement de bureau plus lourd : difficile de faire moins lourd que mon [bspwm](https://wiki.archlinux.org/index.php/Bspwm) actuel.
+
+Mais je pense aussi que Windows va m'apporter quelques avantages :
+
+1. Une meilleure autonomie : en particulier via une meilleure gestion de la puce GPU intégrée d'Intel et de la carte graphique Nvidia dédiée.
+1. Une meilleure gestion des périphériques USB (télépone Android, liseuse, imprimante…).
+1. Moins de temps passé à configurer des choses, réparer des problèmes, installer des mises à jour et plus de temps à développer.
+1. La possibilité d'utiliser Windows Hello et le capteur d'empreintes pour déverrouiller l'ordinateur.
+1. La possibilité d'installer des jeux vidéos
+1. Des meilleurs débuggeurs si je me lance vraiment dans le développement Rust/C++
+
+## Première mise à jour suite à une journée de développement
+
+Après avoir installé mon ordinateur et développé une journée avec, voici mes premiers retours.
+
+Au niveau des problèmes rencontrés :
+- Certains texte de Windows lors des redémarrages pour les mises à jour sont en anglais, incompréhensible… La date et l'heure étaient également en format anglais alors que ma configuration était en français.
+- La configuration de mon clavier Bépo n'était pas la même que sous Linux, mais [j'ai pu corriger ce problème via quelques recherches](/posts/2020-10-14-bépo-sur-windows.html)
+- Redis n'existe pas en version Windows (du moins pas depuis la version 3)
+- Je n'ai pas trouvé d'alternative à `rsync` pour le moment
+- L'application Mail n'arrive pas à se connecter à ProtonMail Bridge et ne propose visiblement pas d'identités multiples pour pouvoir envoyer des mails avec différentes adresses via le même compte SMTP. J'ai installé Thunderbird.
+- Je n'ai pas encore trouvé d'alternative à `inotifywait` pour compiler mon code dès qu'il y a une modification (je peux utiliser `cargo watch` dans l'écosystème Rust, cela doit exister dans l'écosystème d'Elm mais je n'ai pas encore cherché).
+- Beaucoup de logiciels inutiles pré-installés, dont certains néfastes. J'ai passé un certain temps à rechercher pourquoi mon micro enregistrait avec un écho avant de réaliser que le logiciel Maxx Audio Pro essayait de modifier le son en sortie de mon casque pour lui donner un effet 3D (personne n'a jamais dû tester ce logiciel avant de l'installer par défaut sur tous les ordinateurs Dell vu que ça ne fonctionne pas du tout…).
+
+Mais aussi des bonnes surprises :
+- J'adore Windows Hello, c'est très agréable à utiliser et très efficace pour déverrouiller son ordinateur.
+- Toutes mes applications à l'exception de Redis/`rsync` tournent sans problème sur Windows. Pour référence j'ai installé : PHP, Composer, Node/NPM, MariaDB, PostgreSQL, FFMPEG, youtube-dl, Git, Github Desktop, VS Code, OpenVPN, Flutter/Dart, Rust/Rustup/Cargo et Elm. Redis fonctionne sans problème via WSL.
+- Les extensions PHP sont très faciles à installer, pas besoin de compiler l'extension SSH2 par exemple, il suffit de télécharger le DLL depuis [PECL](https://windows.php.net/downloads/pecl/releases/) et de le déplacer dans mon dossier PHP.
+- Les extensions Google Chrome sont compatibles avec Microsoft Edge donc aucun problème à ce niveau là. Je n'ai pas encore eu à faire de l'inspecteur d'élément pour le moment.
\ No newline at end of file
diff --git a/content/posts/2020-10-14-bépo-sur-windows.md b/content/posts/2020-10-14-bépo-sur-windows.md
new file mode 100644
index 0000000..7eafb86
--- /dev/null
+++ b/content/posts/2020-10-14-bépo-sur-windows.md
@@ -0,0 +1,36 @@
+---
+title: "Bépo sur Windows"
+description: "Comment installer la disposition clavier spéciale français sur Windows ?"
+website: dev
+---
+
+J'utilise depuis plusieurs années [Bépo](https://bepo.fr/wiki/Accueil) sur mes ordinateurs à la place du traditionnel Azerty. Bépo est une disposition spécialement conçue pour le français. Son principal avantage est que les touches sont placées de manière à faciliter l'écriture du français (lettres les plus communes sur la ligne de repos, alternance entre les lettres main gauche/main droite, lettres rares sur les doigts les plus faibles…). Les positions des touches sur un clavier Azerty ont été choisies afin d'éviter les contraintes mécaniques des machines à écrire, et ne sont donc pas du tout adaptées à l'écriture sur ordinateur. Bépo permet également d'utiliser facilement des caractères typographiques comme les espaces insécables fines, les guillemets français, les majuscules accentuées, etc.
+
+Je n'ai jamais eu aucun souci avec ma disposition sur Linux, mais après l'installation sur Windows je me suis rendu compte de deux problèmes :
+- Sur Linux je tapais deux fois sur la touche accent grave afin d'écrire un « \` » (très utile pour le Markdown). Sur Windows cette technique affiche un caractère étrange « ̏` ». La seule technique que j'ai trouvée était de taper un accent grave puis une espace : beaucoup moins pratique en plus d'être une habitude difficile à changer.
+- Deuxième problème, [le pilote Bépo Windows officiel](https://bepo.fr/wiki/Windows) remplace la touche « apostrophe » par des apostrophes typographiques « ’ ». C'est typographiquement le bon caractère pour de la rédaction en français mais inutilisable pour un développeur qui a besoin de l'apostrophe classique dans son code.
+
+J'ai donc décidé de modifier la disposition clavier classique via le logiciel [Microsoft Keyboard Layout Creator](https://www.microsoft.com/en-us/download/details.aspx?id=102134). Ce logiciel fonctionne très bien : j'ai pu importer la disposition officielle et résoudre mes deux problèmes.
+- Retrait de l'option « Touche morte » sur la touche de l'accent grave. L'option « Touche morte », d'après ce que j'en ai compris, permet, en tapant une fois sur l'accent grave puis sur une lettre classique, de mettre cet accent sur la lettre. Je n'utilise jamais cette fonctionnalité car la disposition Bépo fournit déjà des touches pour toutes les lettres avec des accents graves. En retirant cette option, je n'ai maintenant qu'à appuyer une fois sur la touche pour voir s'afficher un accent grave seul. Ce changement va me demander un petit temps d'adaptation mais il sera à terme bien plus pratique que sur Linux.
+- Remplacer l'apostrophe typographique par une apostrophe normale.
+
+Mais lors de l'export, le logiciel refuse de créer la disposition clavier.
+```
+ERROR: 'VK_SPACE' in Shift State 'Ctl+Alt' must be made up of white space character(s), but is defined as '_' (U+005f) instead.
+```
+
+En Bépo, le « _ » est lié à la touche `Shift+Espace` et le logiciel est configuré pour lancer une erreur si un caractère autre qu'une espace est défini sur la touche espace.
+
+Je me suis donc demandé comment la version officielle de la disposition avait pu être compilée avec cette « erreur » et je suis tombé sur ce forum : « [Utiliser Microsoft Keyboard Layout Creator avec le fichier bepo.klc](https://forum.bepo.fr/viewtopic.php?id=1306) » expliquant comment modifier l'exécutable de Microsoft Keyboard Layout Creator pour supprimer l'erreur.
+
+Je recopie ici les explications au cas où le sujet du forum disparaîtrait :
+- Faire une copie de l'exécutable par sécurité
+- Ouvrir l'exécutable avec un éditeur hexadécimal (j'ai téléchargé [HxD](https://mh-nexus.de/en/hxd/))
+- Rechercher `2D 05 04 2D 02 17 2A 17 0A` (en faisant bien attention d'être en mode recherche hexadécimale)
+- Remplacer `0A` par `2A`
+
+Je trouve ça incroyable qu'une modification aussi simple dans l'exécutable permette de corriger ce problème sans poser de plantage autre part. Je remercie les personnes qui ont décompilé le programme et trouvé cette solution à mon problème !
+
+Il me reste encore un problème avec la touche accent circonflexe « ^ » qui, comme pour l'accent grave, m'affiche « ôô » lors d'une répétition, alors que sous Linux cela me permettait de créer le smiley « ^^ ». Mais vu que Windows possède un clavier de smiley accessible via `Windows+.` peut-être que je n'aurais pas trop à utiliser cette touche 😊. Je ne peux pas faire la même modification que pour l'accent circonflexe (retirer l'option « Touche morte ») car je l'utilise pour écrire « ô », « î » ou « û » (Bépo n'a pas de touche dédiée à ces lettres).
+
+Si cela vous intéresse, j'écrirai prochainement un article sur mon changement d'Azerty à Bépo, les techniques que j'ai utilisées, le temps que cela m'a pris mais ce que je peux déjà vous dire c'est qu'en 6 ans je n'ai jamais regretté le changement !
\ No newline at end of file
diff --git a/content/posts/2020-10-27-j-ai-change-d-avis-sur-Firefox.md b/content/posts/2020-10-27-j-ai-change-d-avis-sur-Firefox.md
new file mode 100644
index 0000000..c4c3fe2
--- /dev/null
+++ b/content/posts/2020-10-27-j-ai-change-d-avis-sur-Firefox.md
@@ -0,0 +1,15 @@
+---
+title: "J'ai changé d'avis sur Firefox"
+description: "Pendant longtemps j'ai promu Firefox et débattu pour mettre fin au monopole de Chrome. J'avais tort."
+website: dev
+---
+
+Pendant longtemps j'ai débattu pour mettre fin au monopole de Chrome et encourager les personnes autour de moi à utiliser un navigateur autre tel que Firefox : « Le web doit rester ouvert, si tout le monde utilise Chrome, les développeurs de Google auront la possibilité de contrôler les spécifications ». Cette affirmation reste correcte aujourd'hui mais je ne pense pas que ça soit une mauvaise chose.
+
+Beaucoup de personnes pensent que le web est le seul écosystème à permettre un développement multi-plateforme ce qui est complètement faux. Le contre-exemple le plus connu est Java, mais tous les langages basés sur une machine virtuelle qui elle-même est développée sur plusieurs plateformes le permette. Lorsque j'ai commencé à considérer le web comme une machine virtuelle, mon avis a changé.
+
+L'avantage d'une machine virtuelle est d'avoir des spécifications précises pour permettre justement un développement unique, aujourd'hui le web est sous-optimal car les spécifications sont mal respectées par les implémentations et ces dernières doivent être mises à jour individuellement. C'est comme si les développeurs Java devaient attendre que 4 JVM différentes implémentent la nouvelle spécification pour l'utiliser, ou encore qu'une méthode d'affichage GUI donne des résultats un peu différents en fonction de la JVM du client (oui je pense à toi le CSS…).
+
+En observant le web sous cet angle, il serait largement préférable d'avoir un seul moteur de rendu HTML/CSS et un seul interpréteur JavaScript utilisé par tous. Il serait préférable que ce moteur soit un moteur totalement open-source contrôlé par une fondation plutôt que par une entreprise mais ce n'est pas le cas aujourd'hui. Je pense que le web a déjà assez de problèmes (je pourrais en parler plus tard sur le blog…) pour rajouter en plus de ça une contrainte inutile supplémentaire.
+
+Cette réflexion est toute récente, j'ai peut-être manqué certains avantages de cette approche, n'hésitez pas à passer sur [mon Discord](https://discordapp.com/invite/tPtVM9V) si vous voulez discuter. Aussi, je ne connais pas d'autre machine virtuelle basée sur le même modèle que le web (avec plusieurs implémentations principales), si vous en connaissez, ça m'intéresse !
diff --git a/content/posts/3000-01-01-je-suis-accro.md b/content/posts/3000-01-01-je-suis-accro.md
new file mode 100644
index 0000000..8255247
--- /dev/null
+++ b/content/posts/3000-01-01-je-suis-accro.md
@@ -0,0 +1,5 @@
+---
+title: "Je suis accro"
+description: "La plupart des services gratuits que nous utilisons au quotidien sont développés pour nous rendre addicts, comment y rémédier ?"
+website: dev
+---
diff --git a/content/posts/3000-01-01-laravel-authentification.md b/content/posts/3000-01-01-laravel-authentification.md
new file mode 100644
index 0000000..f2f961e
--- /dev/null
+++ b/content/posts/3000-01-01-laravel-authentification.md
@@ -0,0 +1,15 @@
+---
+title: "Laravel autentification : Laravel UI, Jetsream, Fortify, Sanctum, Passport…"
+description: "Pourquoi je n'utilise pas le système ou les systèmes d'authentification proposés par Laravel ?"
+website: dev
+---
+
+Ces dernières années, les différents systèmes d'autentification ont émergé dans l'écosystème Laravel : que des bibliothèques officiellement supportées par le Framework !
+
+Tout a commencé avec Laravel UI qui permettait via `php artisan make:auth` de créer les vues et les contrôleurs pour l'inscription, la connexion, la réinitialisation du mot de passe, etc. Je n'ai jamais utilisé ce système car il était conçu pour Bootstrap, lié à une classe `User` (et je vous rappelle que [votre application n'a pas d'utilisateurs](/posts/2017-07-18-your-app-doesnt-have-users.html)), etc. Vu que le code généré ne contenait que quelques classes et quelques bouts de HTML, il était aussi plus rapide pour moi de réécrire cette partie là afin de la maîtriser de A à Z. Je travaille rarement sur des projets courts, mon principal projet Laravel dure depuis des années et les quelques heures de gagnées en utilisant `php artisan make:auth` auraient été perdues 10 fois durant ces années de maintenance à rechercher comment personnaliser tel ou tel élément.
+
+Ensuite, nous avons eu Passport. Passport implémente le protocole OAuth 2 qui est un protocole extrêmement complexe, inutile dans la plupart des cas. Le protocol OAuth 2 est utile dans un ménage à trois : un visiteur qui veut s'authentifier (moi par exemple), un site qui ne veut pas gérer l'authentification (votre application Laravel) et un site connu où le visiteur a déjà un compte (Facebook). Attention, contrairement à ce que l'on pourrait croire, Passport ne permet pas de gérer la partie connexion avec Facebook (c'est le rôle d'une autre bibliothèque officielle : Socialite), Passport permet de créer le système d'authentification de Facebook. Ce qui, vous y conviendrez, n'est pas très utile. L'OAuth 2 fonctionne bien lorsque vous êtes un site connu mais pour des petites applications, il n'y a pas beaucoup d'intérêt. Je pense que la plupart des développeurs qui utilisent Passport l'utilise de manière détournée en étant le client et le serveur, dans ce cas là l'OAuth 2 ajoute une complexité énorme pour un gain nul.
+
+Avec Laravel 7, nous avons eu Sanctum qui est une version bien plus intéressante que Passport pour gérer ses API (application mobile, SPA…) mais encore trop complexe. Sanctum fonctionne avec des clés API à générer et à stocker en base de données avant de pouvoir les utiliser. Le principal problème c'est que ce système oblige l'utilisateur a générer ses clés sur son compte afin de les utiliser, quelque chose qui n'est pas adapté au commun des mortels. Gitub possède ce système mais c'est un site à destination des développeurs qui connaissent l'utilité d'une clé API et qui peuvent sans problème en créer une pour la renseigner à Composer par exemple.
+
+Avec Laravel 8 nous avons deux nouvelle bibliothèque : Fortify et Jetstream.
\ No newline at end of file
diff --git a/content/posts/3000-01-01-plusieurs-bureaux-virtuels-pour-travailler.md b/content/posts/3000-01-01-plusieurs-bureaux-virtuels-pour-travailler.md
new file mode 100644
index 0000000..7272ecf
--- /dev/null
+++ b/content/posts/3000-01-01-plusieurs-bureaux-virtuels-pour-travailler.md
@@ -0,0 +1,26 @@
+---
+title: "Plusieurs bureaux virtuels pour travailler ?"
+description: "J'ai longtemps travaillé avec plusieurs bureaux virtuels, comment travailler avec un seul espace de travail ?"
+website: dev
+---
+
+Je ne travaille jamais avec plusieurs écrans, j'utilisais donc la fonctionnalité des bureaux multiples. J'en utilisais 4, disposés en carré :
+
+```
+Firefox Thunderbird
+Terminal Éditeur
+```
+
+L'avantage de ces 4 espaces de travail, c'est que je pouvais me rendre en deux touches de clavier, de façon déterministe vers n'importe quelle application. Mes raccourcis étaient configurés pour émuler le déplacement dans Vim, donc réduire les mouvements des mains sur le clavier : « Alt + h » pour aller à gauche, « Alt + j » pour aller en bas, « Alt + k » pour aller en haut et « Alt + l » pour aller à droite. Où que je sois, je savais que le raccourcis « Alt + h, Alt + k » m'emmenaient à Firefox, « Alt + h, Alt + j » au terminal, etc.
+
+Je pouvais également avoir une deuxième application sur chaque bureau en fonction des besoins (régulièrement Discord avec Firefox, et Github Desktop avec mon éditeur). Dans ce cas, j'utilisais « Alt + Tab » sur le bureau en question pour alterner les deux fenêtres.
+
+J'ai trouvé cette organisation très efficace pendant plusieurs années (avec [KDE](https://kde.org/), puis [bspwm](https://github.com/baskerville/bspwm)) jusqu'à récemment où j'ai dû passer à Windows à la place de mon ArchLinux. C'était d'ailleurs dans [mes problèmes possibles de mon premier article sur Windows](/posts/2020-10-13-projet-windows.html).
+
+Effectivement, comme je le pensais, Windows possède des bureaux virtuels limités en ligne horizontale, et même si j'aurais pu trouver une application tierce ou un menu dans les paramètres pour configurer tout ça, j'ai préféré expérimenter avec un seul bureau.
+
+Avec un seul bureau, j'utilise principalement « Alt + Tab » pour changer d'application. Le problème de cette méthode c'est qu'elle n'est pas déterministe. Lorsque vous avez deux applications ouvertes, vous pouvez être sûr de la prochaine application après un « Alt + Tab ». Avec 3 fenêtres, il faut retenir l'ordre dans lequel vous avez visité les applications la dernière fois pour savoir où un (ou deux) « Alt + Tab » vont vous mener. À partir de 4 applications ça me semble presque impossible de retenir où un, deux et trois « Alt + Tab » vont rediriger.
+
+Au quotidien, je trouve cette organisation peu efficace car elle me limite à 3 applications ouvertes mais c'est également un point très positif, si je dois choisir trois applications ça sera : Microsoft Edge, Windows Terminal et Visual Studio Code. Sous Windows, je n'ai donc pas la place d'ouvrir Discord ou Thunderbird et je me rends compte à la fin de la journée que je regarde beaucoup moins mes mails ou mes notifications en travaillant.
+
+Pour conclure, je suis assez satisfait de cette organisation avec 3 applications ouvertes, sous réserve qu'après plusieurs semaines de travail je devienne un peu plus conscient de mes déplacements via « Alt + Tab ». Aujourd'hui encore, il m'arrive de perdre un peu de temps lorsque je souhaite me rendre sur une application spécifique et que je ne sais plus trop où je me trouve de mon historique d'« Alt + Tab ».
\ No newline at end of file
diff --git a/content/talks/2015-02-20-bases-de-donnees-aujourdhui-et-bases-de-donnees-no-sql.md b/content/talks/2015-02-20-bases-de-donnees-aujourdhui-et-bases-de-donnees-no-sql.md
new file mode 100644
index 0000000..10fd820
--- /dev/null
+++ b/content/talks/2015-02-20-bases-de-donnees-aujourdhui-et-bases-de-donnees-no-sql.md
@@ -0,0 +1,10 @@
+---
+title: Bases de données aujourd'hui et bases de données NoSQL
+thumbnail: /images/insa/thumbnail-nosql.png
+slides: /talks/insa-nosql/slides.pdf
+video: https://www.youtube.com/watch?v=oIpjcqHyx2M
+---
+
+Cours d'1h30 devant les étudiants [ASI](http://asi.insa-rouen.fr/) de 3e année à l'[INSA de Rouen](http://www.insa-rouen.fr/) le 20 février 2015. Nous avons redonné la même présentation mise à jour le 4 mars 2016.
+
+La formation en bases de données de l'INSA est orientée bases de données relationnelles. Avec [Antoine Augusti](https://www.antoine-augusti.fr/), nous avons choisi un Projet Approfondissement et d'Ouverture (option encadrée par un professeur à l'INSA) sur le NoSQL. Nous avons travaillé un an sur un état de l'art des bases de données NoSQL et un prototype d'application. Pour clôturer notre projet, nous avons donné une introduction au NoSQL aux étudiants de 3e année lors de leur dernier cours de bases de données. L'intégralité de notre projet est disponible [sur Github](https://github.com/AntoineAugusti/NoSQL-etatart).
diff --git a/content/talks/2015-11-26-injection-de-dependance-ou-comment-decoupler-ses-objets.md b/content/talks/2015-11-26-injection-de-dependance-ou-comment-decoupler-ses-objets.md
new file mode 100644
index 0000000..0fa1eeb
--- /dev/null
+++ b/content/talks/2015-11-26-injection-de-dependance-ou-comment-decoupler-ses-objets.md
@@ -0,0 +1,9 @@
+---
+title: L'injection de dépendance ou comment découpler ses objets
+thumbnail: /images/codeurs-en-seine/thumbnail-injection-dependance.png
+slides: /talks/codeurs-en-seine-injection-dependance/slides.pdf
+---
+
+Quickie de 15 minutes le 26 novembre 2015 à la fac de Rouen lors de [Codeurs en Seine](http://www.codeursenseine.com/).
+
+Ce petit quickie parlera de l'injection de dépendance : un mécanisme permettant de construire des objets totalement découplés les uns des autres mais facilement utilisable entre eux afin de créer des applications plus pérennes. Vos objets seront plus simples, les tester deviendra un vrai plaisir et la maintenance sera beaucoup plus facile.
diff --git a/content/talks/2016-02-23-event-store.md b/content/talks/2016-02-23-event-store.md
new file mode 100644
index 0000000..e474094
--- /dev/null
+++ b/content/talks/2016-02-23-event-store.md
@@ -0,0 +1,11 @@
+---
+title: Event Store
+iframe: /talks/jug-event-store/index.html
+slides: /talks/jug-event-store/index.html
+---
+
+Quickie de 15 minutes le 23 février 2016 à Seine Innopolis lors du [Normandy Java User Group](http://www.normandyjug.org/).
+
+Le CQRS tout le monde en parle, mais comment le mettre en place. L’event sourcing est une des solutions, et Event Store est une des bases de données NoSQL la plus intéressante dans ce domaine, voyons ce que l’on peut faire avec.
+
+Les slides sont également disponibles [sur Github](https://github.com/ThibaudDauce/jug-event-store).
diff --git a/content/talks/2016-02-23-qu-est-ce-qu-internet.md b/content/talks/2016-02-23-qu-est-ce-qu-internet.md
new file mode 100644
index 0000000..0aad44a
--- /dev/null
+++ b/content/talks/2016-02-23-qu-est-ce-qu-internet.md
@@ -0,0 +1,11 @@
+---
+title: Qu'est-ce qu'Internet ?
+iframe: /talks/jug-internet/index.html
+slides: /talks/jug-internet/index.html
+---
+
+Quickie de 15 minutes le 23 février 2016 à Seine Innopolis lors du [Normandy Java User Group](http://www.normandyjug.org/).
+
+Vous l’utilisez tous les jours, toutes les heures, voir même chaque minute. Et pourtant, savez-vous vraiment ce qu’est Internet ? Comment fonctionne Internet ?
+
+Les slides sont également disponibles [sur Github](https://github.com/ThibaudDauce/jug-internet).
diff --git a/content/talks/2017-01-31-l-event-sourcing-en-pratique-ca-donne-quoi.md b/content/talks/2017-01-31-l-event-sourcing-en-pratique-ca-donne-quoi.md
new file mode 100644
index 0000000..09c8c2b
--- /dev/null
+++ b/content/talks/2017-01-31-l-event-sourcing-en-pratique-ca-donne-quoi.md
@@ -0,0 +1,10 @@
+---
+title: L'Event Sourcing en pratique, ça donne quoi ?
+iframe: /talks/orleans-tech-event-sourcing/index.html
+slides: /talks/orleans-tech-event-sourcing/index.html
+tweet: https://twitter.com/orleans_tech/status/826505597159890945
+---
+
+Conférence d'une trentaine de minutes lors du meetup [Orléans Tech](https://orleans-tech.com/) le 31 janvier 2017.
+
+L'Event Sourcing en théorie c'est génial, on en parle partout, mais en pratique, l'important c'est de savoir quand l'utiliser et comment le mettre en place correctement.
diff --git a/content/talks/2017-06-08-decouverte-de-laravel.md b/content/talks/2017-06-08-decouverte-de-laravel.md
new file mode 100644
index 0000000..6c4b298
--- /dev/null
+++ b/content/talks/2017-06-08-decouverte-de-laravel.md
@@ -0,0 +1,10 @@
+---
+title: Découverte de Laravel
+iframe: /talks/wild-code-school-decouverte-laravel/index.html
+slides: /talks/wild-code-school-decouverte-laravel/index.html
+tweet: https://twitter.com/WildSchool_Orls/status/872867406087106560
+---
+
+Découverte de Laravel pendant une après-midi avec les élèves de la [Wild Code School](https://wildcodeschool.fr/) d'Orléans le 8 juin 2017.
+
+Présentation pendant une heure de quelques différences de développement et d'architecture entre Symfony (que les élèves étudient à temps plein) et Laravel. Puis accompagnement des élèves sur un exercice pratique de découverte de Laravel le reste de l'après midi.
diff --git a/content/talks/2017-08-29-orleans-tech-programmation-fonctionnelle.md b/content/talks/2017-08-29-orleans-tech-programmation-fonctionnelle.md
new file mode 100644
index 0000000..0727842
--- /dev/null
+++ b/content/talks/2017-08-29-orleans-tech-programmation-fonctionnelle.md
@@ -0,0 +1,10 @@
+---
+title: La programmation fonctionnelle pour les développeurs web
+iframe: /talks/orleans-tech-programmation-fonctionnelle/index.html
+slides: /talks/orleans-tech-programmation-fonctionnelle/index.html
+tweet: https://twitter.com/orleans_tech/status/897084855112126465
+---
+
+Conférence d'une trentaine de minutes lors du meetup [Orléans Tech](https://orleans-tech.com/) le 29 août 2017.
+
+Nous pensons tous faire de la programmation fonctionnelle lorsque nous écrivons nos fonctions PHP mais la programmation fonctionnelle c'est bien plus que ça ! Dans ce talk je montre comment en mettre en place de manière très simple et naturelle dans notre code de tous les jours.
diff --git a/content/talks/2019-04-18-afup-tours-programmation-fonctionnelle.md b/content/talks/2019-04-18-afup-tours-programmation-fonctionnelle.md
new file mode 100644
index 0000000..c9f4c6f
--- /dev/null
+++ b/content/talks/2019-04-18-afup-tours-programmation-fonctionnelle.md
@@ -0,0 +1,10 @@
+---
+title: La programmation fonctionnelle pour les développeurs web v2
+iframe: /talks/afup-tours-programmation-fonctionnelle/index.html
+slides: /talks/afup-tours-programmation-fonctionnelle/index.html
+tweet: https://twitter.com/AFUP_Tours/status/1118932291458412546
+---
+
+Conférence de 45 minutes lors du meetup de l'[AFUP Tours](https://tours.afup.org) le 18 avril 2019.
+
+Tous les développeurs savent ce qu'est une fonction, mais elles se font souvent voler la vedette par les objets en programmation moderne. Dans ce talk, je présente de nouveaux concepts faciles autour des fonctions permettant de simplifier votre code dans vos projets du quotidien.
diff --git a/content/talks/2019-09-17-orleans-tech-conf-informatique-complexite-ecologie.md b/content/talks/2019-09-17-orleans-tech-conf-informatique-complexite-ecologie.md
new file mode 100644
index 0000000..a5c1f90
--- /dev/null
+++ b/content/talks/2019-09-17-orleans-tech-conf-informatique-complexite-ecologie.md
@@ -0,0 +1,10 @@
+---
+title: Informatique, complexité et écologie
+iframe: /talks/orleans-tech-conf-informatique-complexite-ecologie/index.html
+slides: /talks/orleans-tech-conf-informatique-complexite-ecologie/index.html
+tweet: https://twitter.com/orleans_tech/status/1173935000812736513
+---
+
+Conférence de 30 minutes lors de la première conférence [Orléans Tech Conf](https://orleans-tech-conf.com/) le 17 septembre 2019.
+
+L’objectif de ce talk est de faire réfléchir les participants à l’augmentation de la complexité dans nos applications. Aujourd’hui, la moindre application web demande l’exécution de millions de lignes de code invisibles provenant de divers projets open-source pour fonctionner. Cette complexité fait que les développeurs ne sont plus aussi efficaces qu’ils le pourraient, elle entraîne également l’augmentation des bugs dans les logiciels et la diminution des performances avec un impact écologique certain.
\ No newline at end of file
diff --git a/content/talks/afup-tours-programmation-fonctionnelle/.gitignore b/content/talks/afup-tours-programmation-fonctionnelle/.gitignore
new file mode 100644
index 0000000..6b7f95d
--- /dev/null
+++ b/content/talks/afup-tours-programmation-fonctionnelle/.gitignore
@@ -0,0 +1,14 @@
+.idea/
+*.iml
+*.iws
+*.eml
+out/
+.DS_Store
+.svn
+log/*.log
+tmp/**
+node_modules/
+.sass-cache
+css/reveal.min.css
+js/reveal.min.js
+elm-stuff/
\ No newline at end of file
diff --git a/content/talks/afup-tours-programmation-fonctionnelle/.travis.yml b/content/talks/afup-tours-programmation-fonctionnelle/.travis.yml
new file mode 100644
index 0000000..e65e0df
--- /dev/null
+++ b/content/talks/afup-tours-programmation-fonctionnelle/.travis.yml
@@ -0,0 +1,5 @@
+language: node_js
+node_js:
+ - 11
+after_script:
+ - npm run build -- retire
diff --git a/content/talks/afup-tours-programmation-fonctionnelle/CONTRIBUTING.md b/content/talks/afup-tours-programmation-fonctionnelle/CONTRIBUTING.md
new file mode 100644
index 0000000..c2091e8
--- /dev/null
+++ b/content/talks/afup-tours-programmation-fonctionnelle/CONTRIBUTING.md
@@ -0,0 +1,23 @@
+## Contributing
+
+Please keep the [issue tracker](http://github.com/hakimel/reveal.js/issues) limited to **bug reports**, **feature requests** and **pull requests**.
+
+
+### Personal Support
+If you have personal support or setup questions the best place to ask those are [StackOverflow](http://stackoverflow.com/questions/tagged/reveal.js).
+
+
+### Bug Reports
+When reporting a bug make sure to include information about which browser and operating system you are on as well as the necessary steps to reproduce the issue. If possible please include a link to a sample presentation where the bug can be tested.
+
+
+### Pull Requests
+- Should follow the coding style of the file you work in, most importantly:
+ - Tabs to indent
+ - Single-quoted strings
+- Should be made towards the **dev branch**
+- Should be submitted from a feature/topic branch (not your master)
+
+
+### Plugins
+Please do not submit plugins as pull requests. They should be maintained in their own separate repository. More information here: https://github.com/hakimel/reveal.js/wiki/Plugin-Guidelines
diff --git a/content/talks/afup-tours-programmation-fonctionnelle/LICENSE b/content/talks/afup-tours-programmation-fonctionnelle/LICENSE
new file mode 100644
index 0000000..697d156
--- /dev/null
+++ b/content/talks/afup-tours-programmation-fonctionnelle/LICENSE
@@ -0,0 +1,19 @@
+Copyright (C) 2019 Hakim El Hattab, http://hakim.se, and reveal.js contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff --git a/content/talks/afup-tours-programmation-fonctionnelle/Main.elm b/content/talks/afup-tours-programmation-fonctionnelle/Main.elm
new file mode 100644
index 0000000..9f09dd7
--- /dev/null
+++ b/content/talks/afup-tours-programmation-fonctionnelle/Main.elm
@@ -0,0 +1,26 @@
+import Browser
+import Html exposing (Html, Attribute, button, div, text, input)
+import Html.Attributes exposing (..)
+import Html.Events exposing (onClick, onInput)
+
+main =
+ Browser.sandbox { init = init, update = update, view = view }
+
+type alias Model = String
+type Event = NewName String-- | Reverse
+
+init : Model
+init = "Thibaud"
+
+update : Event -> Model -> Model
+update event oldName = case event of
+ NewName newName -> newName
+ -- Reverse -> String.reverse oldName
+
+view : Model -> Html Event
+view name =
+ div []
+ [ input [ placeholder "New Name", onInput NewName, value name ] []
+ , button [ onClick Reverse ] [ text "Reverse Name" ]
+ , div [] [ text name ]
+ ]
\ No newline at end of file
diff --git a/content/talks/afup-tours-programmation-fonctionnelle/README.md b/content/talks/afup-tours-programmation-fonctionnelle/README.md
new file mode 100644
index 0000000..e5a1911
--- /dev/null
+++ b/content/talks/afup-tours-programmation-fonctionnelle/README.md
@@ -0,0 +1,1388 @@
+# reveal.js [](https://travis-ci.org/hakimel/reveal.js)
+
+A framework for easily creating beautiful presentations using HTML. [Check out the live demo](http://revealjs.com/).
+
+reveal.js comes with a broad range of features including [nested slides](https://github.com/hakimel/reveal.js#markup), [Markdown contents](https://github.com/hakimel/reveal.js#markdown), [PDF export](https://github.com/hakimel/reveal.js#pdf-export), [speaker notes](https://github.com/hakimel/reveal.js#speaker-notes) and a [JavaScript API](https://github.com/hakimel/reveal.js#api). There's also a fully featured visual editor and platform for sharing reveal.js presentations at [slides.com](https://slides.com?ref=github).
+
+
+## Table of contents
+
+- [Online Editor](#online-editor)
+- [Installation](#installation)
+ - [Basic setup](#basic-setup)
+ - [Full setup](#full-setup)
+ - [Folder Structure](#folder-structure)
+- [Instructions](#instructions)
+ - [Markup](#markup)
+ - [Markdown](#markdown)
+ - [Element Attributes](#element-attributes)
+ - [Slide Attributes](#slide-attributes)
+- [Configuration](#configuration)
+- [Presentation Size](#presentation-size)
+- [Dependencies](#dependencies)
+- [Ready Event](#ready-event)
+- [Auto-sliding](#auto-sliding)
+- [Keyboard Bindings](#keyboard-bindings)
+- [Vertical Slide Navigation](#vertical-slide-navigation)
+- [Touch Navigation](#touch-navigation)
+- [Lazy Loading](#lazy-loading)
+- [API](#api)
+ - [Slide Changed Event](#slide-changed-event)
+ - [Presentation State](#presentation-state)
+ - [Slide States](#slide-states)
+ - [Slide Backgrounds](#slide-backgrounds)
+ - [Parallax Background](#parallax-background)
+ - [Slide Transitions](#slide-transitions)
+ - [Internal links](#internal-links)
+ - [Fragments](#fragments)
+ - [Fragment events](#fragment-events)
+ - [Code syntax highlighting](#code-syntax-highlighting)
+ - [Slide number](#slide-number)
+ - [Overview mode](#overview-mode)
+ - [Fullscreen mode](#fullscreen-mode)
+ - [Embedded media](#embedded-media)
+ - [Stretching elements](#stretching-elements)
+ - [Resize Event](#resize-event)
+ - [postMessage API](#postmessage-api)
+- [PDF Export](#pdf-export)
+- [Theming](#theming)
+- [Speaker Notes](#speaker-notes)
+ - [Share and Print Speaker Notes](#share-and-print-speaker-notes)
+ - [Server Side Speaker Notes](#server-side-speaker-notes)
+- [Multiplexing](#multiplexing)
+ - [Master presentation](#master-presentation)
+ - [Client presentation](#client-presentation)
+ - [Socket.io server](#socketio-server)
+- [MathJax](#mathjax)
+- [License](#license)
+
+#### More reading
+
+- [Changelog](https://github.com/hakimel/reveal.js/releases): Up-to-date version history.
+- [Examples](https://github.com/hakimel/reveal.js/wiki/Example-Presentations): Presentations created with reveal.js, add your own!
+- [Browser Support](https://github.com/hakimel/reveal.js/wiki/Browser-Support): Explanation of browser support and fallbacks.
+- [Plugins](https://github.com/hakimel/reveal.js/wiki/Plugins,-Tools-and-Hardware): A list of plugins that can be used to extend reveal.js.
+
+
+## Online Editor
+
+Presentations are written using HTML or Markdown but there's also an online editor for those of you who prefer a graphical interface. Give it a try at [https://slides.com](https://slides.com?ref=github).
+
+
+## Installation
+
+The **basic setup** is for authoring presentations only. The **full setup** gives you access to all reveal.js features and plugins such as speaker notes as well as the development tasks needed to make changes to the source.
+
+### Basic setup
+
+The core of reveal.js is very easy to install. You'll simply need to download a copy of this repository and open the index.html file directly in your browser.
+
+1. Download the latest version of reveal.js from
"+u(e.message+"",!0)+"";throw e}}f.exec=f,m.options=m.setOptions=function(e){return d(m.defaults,e),m},m.getDefaults=function(){return{baseUrl:null,breaks:!1,gfm:!0,headerIds:!0,headerPrefix:"",highlight:null,langPrefix:"language-",mangle:!0,pedantic:!1,renderer:new r,sanitize:!1,sanitizer:null,silent:!1,smartLists:!1,smartypants:!1,tables:!0,xhtml:!1}},m.defaults=m.getDefaults(),m.Parser=p,m.parser=p.parse,m.Renderer=r,m.TextRenderer=s,m.Lexer=a,m.lexer=a.lex,m.InlineLexer=h,m.inlineLexer=h.output,m.Slugger=t,m.parse=m,"undefined"!=typeof module&&"object"==typeof exports?module.exports=m:"function"==typeof define&&define.amd?define(function(){return m}):e.marked=m}(this||("undefined"!=typeof window?window:global)); \ No newline at end of file diff --git a/content/talks/afup-tours-programmation-fonctionnelle/plugin/math/math.js b/content/talks/afup-tours-programmation-fonctionnelle/plugin/math/math.js new file mode 100755 index 0000000..d76c9dd --- /dev/null +++ b/content/talks/afup-tours-programmation-fonctionnelle/plugin/math/math.js @@ -0,0 +1,92 @@ +/** + * A plugin which enables rendering of math equations inside + * of reveal.js slides. Essentially a thin wrapper for MathJax. + * + * @author Hakim El Hattab + */ +var RevealMath = window.RevealMath || (function(){ + + var options = Reveal.getConfig().math || {}; + var mathjax = options.mathjax || 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js'; + var config = options.config || 'TeX-AMS_HTML-full'; + var url = mathjax + '?config=' + config; + + var defaultOptions = { + messageStyle: 'none', + tex2jax: { + inlineMath: [ [ '$', '$' ], [ '\\(', '\\)' ] ], + skipTags: [ 'script', 'noscript', 'style', 'textarea', 'pre' ] + }, + skipStartupTypeset: true + }; + + function defaults( options, defaultOptions ) { + + for ( var i in defaultOptions ) { + if ( !options.hasOwnProperty( i ) ) { + options[i] = defaultOptions[i]; + } + } + + } + + function loadScript( url, callback ) { + + var head = document.querySelector( 'head' ); + var script = document.createElement( 'script' ); + script.type = 'text/javascript'; + script.src = url; + + // Wrapper for callback to make sure it only fires once + var finish = function() { + if( typeof callback === 'function' ) { + callback.call(); + callback = null; + } + } + + script.onload = finish; + + // IE + script.onreadystatechange = function() { + if ( this.readyState === 'loaded' ) { + finish(); + } + } + + // Normal browsers + head.appendChild( script ); + + } + + return { + init: function() { + + defaults( options, defaultOptions ); + defaults( options.tex2jax, defaultOptions.tex2jax ); + options.mathjax = options.config = null; + + loadScript( url, function() { + + MathJax.Hub.Config( options ); + + // Typeset followed by an immediate reveal.js layout since + // the typesetting process could affect slide height + MathJax.Hub.Queue( [ 'Typeset', MathJax.Hub ] ); + MathJax.Hub.Queue( Reveal.layout ); + + // Reprocess equations in slides when they turn visible + Reveal.addEventListener( 'slidechanged', function( event ) { + + MathJax.Hub.Queue( [ 'Typeset', MathJax.Hub, event.currentSlide ] ); + + } ); + + } ); + + } + } + +})(); + +Reveal.registerPlugin( 'math', RevealMath ); diff --git a/content/talks/afup-tours-programmation-fonctionnelle/plugin/multiplex/client.js b/content/talks/afup-tours-programmation-fonctionnelle/plugin/multiplex/client.js new file mode 100644 index 0000000..3ffd1e0 --- /dev/null +++ b/content/talks/afup-tours-programmation-fonctionnelle/plugin/multiplex/client.js @@ -0,0 +1,13 @@ +(function() { + var multiplex = Reveal.getConfig().multiplex; + var socketId = multiplex.id; + var socket = io.connect(multiplex.url); + + socket.on(multiplex.id, function(data) { + // ignore data from sockets that aren't ours + if (data.socketId !== socketId) { return; } + if( window.location.host === 'localhost:1947' ) return; + + Reveal.setState(data.state); + }); +}()); diff --git a/content/talks/afup-tours-programmation-fonctionnelle/plugin/multiplex/index.js b/content/talks/afup-tours-programmation-fonctionnelle/plugin/multiplex/index.js new file mode 100644 index 0000000..8195f04 --- /dev/null +++ b/content/talks/afup-tours-programmation-fonctionnelle/plugin/multiplex/index.js @@ -0,0 +1,64 @@ +var http = require('http'); +var express = require('express'); +var fs = require('fs'); +var io = require('socket.io'); +var crypto = require('crypto'); + +var app = express(); +var staticDir = express.static; +var server = http.createServer(app); + +io = io(server); + +var opts = { + port: process.env.PORT || 1948, + baseDir : __dirname + '/../../' +}; + +io.on( 'connection', function( socket ) { + socket.on('multiplex-statechanged', function(data) { + if (typeof data.secret == 'undefined' || data.secret == null || data.secret === '') return; + if (createHash(data.secret) === data.socketId) { + data.secret = null; + socket.broadcast.emit(data.socketId, data); + }; + }); +}); + +[ 'css', 'js', 'plugin', 'lib' ].forEach(function(dir) { + app.use('/' + dir, staticDir(opts.baseDir + dir)); +}); + +app.get("/", function(req, res) { + res.writeHead(200, {'Content-Type': 'text/html'}); + + var stream = fs.createReadStream(opts.baseDir + '/index.html'); + stream.on('error', function( error ) { + res.write('
| Expected: | " + escapeText(expected) + " |
|---|---|
| Result: | " + escapeText(actual) + " |
| Diff: | " + diff + " |
| Source: | " + escapeText(details.source) + " |
| Source: | " + escapeText(details.source) + " |
|---|