add static builder

This commit is contained in:
Thibaud Dauce
2026-02-18 17:23:24 +01:00
parent e60f150611
commit 1561c07432
155 changed files with 161211 additions and 12 deletions

3
.gitignore vendored
View File

@@ -1 +1,4 @@
/target
/build
/node_modules
/content/css/syntax.css

21
.gitmodules vendored Normal file
View File

@@ -0,0 +1,21 @@
[submodule "content/talks/afup-tours-programmation-fonctionnelle"]
path = content/talks/afup-tours-programmation-fonctionnelle
url = git@gitlab.com:thibauddauce/afup-tours-programmation-fonctionnelle.git
[submodule "content/talks/jug-event-store"]
path = content/talks/jug-event-store
url = git@github.com:ThibaudDauce/jug-event-store.git
[submodule "content/talks/jug-internet"]
path = content/talks/jug-internet
url = git@github.com:ThibaudDauce/jug-internet.git
[submodule "content/talks/orleans-tech-conf-informatique-complexite-ecologie"]
path = content/talks/orleans-tech-conf-informatique-complexite-ecologie
url = git@gitlab.com:thibauddauce/orleans-tech-conf-informatique-complexite-ecologie.git
[submodule "content/talks/orleans-tech-event-sourcing"]
path = content/talks/orleans-tech-event-sourcing
url = git@framagit.org:ThibaudDauce/orleans-tech-event-sourcing.git
[submodule "content/talks/orleans-tech-programmation-fonctionnelle"]
path = content/talks/orleans-tech-programmation-fonctionnelle
url = git@framagit.org:ThibaudDauce/orleans-tech-programmation-fonctionnelle
[submodule "content/talks/wild-code-school-decouverte-laravel"]
path = content/talks/wild-code-school-decouverte-laravel
url = git@framagit.org:ThibaudDauce/wild-code-school-decouverte-laravel.git

1738
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -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"] }

View File

@@ -0,0 +1,157 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>name</key>
<string>Ascetic White</string>
<key>settings</key>
<array>
<dict>
<key>settings</key>
<dict>
<key>background</key>
<string>#FFFFFF</string>
<key>caret</key>
<string>#202020</string>
<key>foreground</key>
<string>#202020</string>
<key>invisibles</key>
<string>#D0D0D0</string>
<key>lineHighlight</key>
<string>#D0D0D0</string>
<key>selection</key>
<string>#C0C0C0</string>
<key>findHighlight</key>
<string>#FFE792</string>
<key>findHighlightForeground</key>
<string>#000000</string>
<key>selectionBorder</key>
<string>#CCCCCC</string>
<key>activeGuide</key>
<string>#9D550FB0</string>
<key>gutterForeground</key>
<string>#303030</string>
<key>bracketsForeground</key>
<string>#F8F8F2A5</string>
<key>bracketsOptions</key>
<string>underline</string>
<key>bracketContentsForeground</key>
<string>#F8F8F2A5</string>
<key>bracketContentsOptions</key>
<string>underline</string>
<key>tagsOptions</key>
<string>stippled_underline</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Comment</string>
<key>scope</key>
<string>comment</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string></string>
<key>foreground</key>
<string>#BBBBBB</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>String</string>
<key>scope</key>
<string>string</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#707070</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Number</string>
<key>scope</key>
<string>constant.numeric</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#707070</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>diff.header</string>
<key>scope</key>
<string>meta.diff, meta.diff.header</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#707070</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>diff.deleted</string>
<key>scope</key>
<string>markup.deleted</string>
<key>settings</key>
<dict>
<key>background</key>
<string>#FF8888</string>
<key>foreground</key>
<string></string>
</dict>
</dict>
<dict>
<key>name</key>
<string>diff.inserted</string>
<key>scope</key>
<string>markup.inserted</string>
<key>settings</key>
<dict>
<key>background</key>
<string>#99CC99</string>
<key>foreground</key>
<string></string>
</dict>
</dict>
<dict>
<key>name</key>
<string>diff.changed</string>
<key>scope</key>
<string>markup.changed</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#E6DB74</string>
</dict>
</dict>
<dict>
<key>scope</key>
<string>constant.numeric.line-number.find-in-files - match</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#202020</string>
</dict>
</dict>
<dict>
<key>scope</key>
<string>entity.name.filename.find-in-files</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#707070</string>
</dict>
</dict>
</array>
<key>uuid</key>
<string>F01C6DC0-0977-11E3-8FFD-0800200C9A66</string>
</dict>
</plist>

40
content/css/app.css Normal file
View File

@@ -0,0 +1,40 @@
@import "tailwindcss";
@import "./syntax.css";
@import "typeface-merriweather";
@plugin "@tailwindcss/typography";
@theme {
--font-serif: "Merriweather", ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
--color-solarized: #fdf6e3;
}
.prose {
--tw-prose-links: var(--color-indigo-600);
--tw-prose-pre-bg: var(--color-solarized);
--tw-prose-pre-code: inherit;
}
.prose a {
font-style: italic;
}
.prose pre code::after {
content: none !important;
}
.prose pre {
width: 100vw;
margin-left: calc(-1 * max(50vw - 450px + 1rem, 1rem));
padding-left: max(50vw - 450px + 1rem, 1rem);
padding-right: 1rem;
border-radius: 0;
overflow-x: visible;
}
@utility container {
max-width: 900px;
width: 100%;
margin-inline: auto;
padding-inline: 1rem;
}

View File

@@ -0,0 +1,386 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>name</key>
<string>Verdandi</string>
<key>settings</key>
<array>
<dict>
<key>name</key>
<string>Variable and parameter name</string>
<key>scope</key>
<string>support.variable,meta.definition.variable.name,variable</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string />
<key>foreground</key>
<string>#51575e</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Object keys, TS grammar specific</string>
<key>scope</key>
<string>meta.object-literal.key entity.name.function,meta.object-literal.key</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string />
<key>foreground</key>
<string>#51575e</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Comment</string>
<key>scope</key>
<string>punctuation.definition.comment,punctuation.comment,comment</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string />
<key>foreground</key>
<string>#939ba4</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Operator</string>
<key>scope</key>
<string>keyword.operator</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>bold</string>
<key>foreground</key>
<string>#212529</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Punctuation</string>
<key>scope</key>
<string>meta.brace,string.link.md,punctuation.definition.metadata.md,meta.property-value punctuation.separator.key-value,tag.xml,tag.html,punctuation.tag,delimiter.tag,paren,brace,bracket,delimiter,punctuation</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string />
<key>foreground</key>
<string>#727981</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>JavaScript string interpolation ${}</string>
<key>scope</key>
<string>punctuation.section.embedded.end.metatag.php,punctuation.section.embedded.begin.metatag.php,punctuation.definition.template-expression.end.js,punctuation.definition.template-expression.end.ts,punctuation.definition.template-expression.begin.ts,punctuation.definition.template-expression.begin.js</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string />
<key>foreground</key>
<string>#212529</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>String</string>
<key>scope</key>
<string>meta.preprocessor string,meta.structure.dictionary.json string.quoted.double.json,meta.structure.dictionary.value.json string.quoted.double.json,support.constant.property-value.string,meta.property-value.string,string</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>italic</string>
<key>foreground</key>
<string>#727981</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Primitive Literals</string>
<key>scope</key>
<array>
<string>constant.numeric</string>
<string>meta.property-value.numeric</string>
<string>support.constant.property-value.numeric</string>
<string>meta.property-value.color</string>
<string>support.constant.property-value.color</string>
<string>constant.language</string>
</array>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string />
<key>foreground</key>
<string>#212529</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>User names</string>
<key>scope</key>
<array>
<string>constant.character</string>
<string>constant.other</string>
<string>entity.name.function</string>
<string>entity.name.class</string>
<string>entity.other.inherited-class</string>
<string>entity.other.attribute-name</string>
<string>entity.name</string>
<string>entity.other.attribute-name</string>
<string>entity.other.attribute-name.html</string>
<string>support.type.property-name</string>
<string>entity.name.tag.table</string>
<string>meta.structure.dictionary.json string.quoted.double.json</string>
</array>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string />
<key>foreground</key>
<string>#212529</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Keyword</string>
<key>scope</key>
<array>
<string>keyword</string>
<string>meta.property-value.keyword</string>
<string>support.constant.property-value.keyword</string>
<string>meta.preprocessor.keyword</string>
<string>keyword.other.use</string>
<string>keyword.other.function.use</string>
<string>keyword.other.namespace</string>
<string>keyword.other.new</string>
<string>keyword.other.special-method</string>
<string>keyword.other.unit</string>
<string>keyword.other.use-as</string>
</array>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>bold</string>
<key>foreground</key>
<string>#212529</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Storage</string>
<key>scope</key>
<array>
<string>storage</string>
<string>storage.type</string>
<string>storage.type.ts</string>
<string>storage.type.var.ts</string>
<string>storage.type.js</string>
<string>storage.type.var.js</string>
<string>storage.type.const.ts</string>
<string>storage.type.let.ts</string>
<string>storage.type.let.js</string>
<string>storage.type.const.js</string>
<string>entity.name.tag</string>
</array>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>bold</string>
<key>foreground</key>
<string>#212529</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Pointer, access, etc</string>
<key>scope</key>
<array>
<string>meta.ptr</string>
<string>meta.pointer</string>
<string>meta.address</string>
<string>meta.array.cxx</string>
</array>
<key>settings</key>
<array />
</dict>
<dict>
<key>name</key>
<string>Preprocessor</string>
<key>scope</key>
<string>meta.preprocessor</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string />
<key>foreground</key>
<string>#212529</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Library</string>
<key>scope</key>
<array>
<string>support.type</string>
<string>support.class</string>
<string>support.function</string>
<string>support.constant</string>
</array>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string />
<key>foreground</key>
<string>#212529</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Invalid</string>
<key>scope</key>
<string>invalid</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#e24747</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Invalid deprecated</string>
<key>scope</key>
<string>invalid.deprecated</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#e24747</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markdown Title Hash</string>
<key>scope</key>
<string>beginning.punctuation,entity.name.type.md,punctuation.definition.heading.md</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string />
<key>foreground</key>
<string>#212529</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markdown titles</string>
<key>scope</key>
<string>entity.name.section,markup.heading</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>bold</string>
<key>foreground</key>
<string>#212529</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markdown Raw</string>
<key>scope</key>
<string>markup.fenced_code,markup.fenced,markup.inline.raw,markup.raw</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>italic</string>
<key>foreground</key>
<string>#727981</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markdown link</string>
<key>scope</key>
<string>meta.image.inline,meta.link.inline,markup.link,string.other.link.title,string.other.link.description</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string />
<key>foreground</key>
<string>#212529</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Makefile Variables</string>
<key>scope</key>
<string>variable.language.makefile,variable.other.makefile</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string />
<key>foreground</key>
<string>#212529</string>
</dict>
</dict>
<dict>
<key>scope</key>
<string>markup.italic</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>italic</string>
</dict>
</dict>
<dict>
<key>scope</key>
<string>markup.bold</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>bold</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>CSS Class</string>
<key>scope</key>
<string>entity.other.attribute-name.class.css</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string />
<key>foreground</key>
<string>#212529</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>CSS Tag name</string>
<key>scope</key>
<string>entity.name.tag.css</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>bold</string>
<key>foreground</key>
<string>#212529</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>CSS Property</string>
<key>scope</key>
<string>meta.property-name.css</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string />
<key>foreground</key>
<string>#212529</string>
</dict>
</dict>
</array>
</dict>
</plist>

BIN
content/images/adludio.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

BIN
content/images/ansible.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

BIN
content/images/bepo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

BIN
content/images/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
content/images/freshrss.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

BIN
content/images/freshrss.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

BIN
content/images/guard.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 KiB

BIN
content/images/hacker.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 KiB

View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="173.206" viewbox="0 0 100 173.206" style="opacity: 0.2">
<polyline points="-1,36.603 43.301,61.603 43.301,111.603 -1,136.603" fill="#F2F1EE" stroke="#3D44A0" stroke-width="2"/>
<polyline points="101,36.603 56.699,61.603 56.699,111.603 101,136.603" fill="#F2F1EE" stroke="#3D44A0" stroke-width="2"/>
<polyline points="6.699,0 6.699,25 50,50 93.301,25 93.301,0" fill="#F2F1EE" stroke="#3D44A0" stroke-width="2"/>
<polyline points="6.699,173.206 6.699,148.206 50,123.206 93.301,148.206 93.301,173.206" fill="#F2F1EE" stroke="#3D44A0" stroke-width="2"/>
</svg>

After

Width:  |  Height:  |  Size: 633 B

BIN
content/images/html5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
content/images/https.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
content/images/ipv6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

BIN
content/images/lambda.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
content/images/latex.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

BIN
content/images/libon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

33
content/images/nginx.svg Normal file
View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="115.11875" height="24.187275" id="svg2" version="1.1" inkscape:version="0.48.3.1 r9886" sodipodi:docname="nginx_logo_release.svg">
<defs id="defs4"/>
<sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="2.8284271" inkscape:cx="23.971822" inkscape:cy="48.217226" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="false" inkscape:window-width="1920" inkscape:window-height="1003" inkscape:window-x="0" inkscape:window-y="24" inkscape:window-maximized="1" fit-margin-top="0" fit-margin-left="0" fit-margin-right="0" fit-margin-bottom="0"/>
<metadata id="metadata7">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:title/>
</cc:Work>
</rdf:RDF>
</metadata>
<g inkscape:groupmode="layer" id="layer2" inkscape:label="Typography" style="display:none" transform="translate(-11.56225,-11.12422)">
<g id="g3910">
<path inkscape:connector-curvature="0" id="path3872" d="m 3.7946429,10.710395 156.7857171,0" style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"/>
<path inkscape:connector-curvature="0" id="path3872-6" d="m 3.6552959,35.734302 156.7857141,0" style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"/>
<path inkscape:connector-curvature="0" id="path3872-6-8" d="m -10.35714,23.222461 156.78571,0" style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"/>
</g>
</g>
<g inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1" transform="translate(-11.56225,-11.12422)">
<path style="fill:none;stroke:#009900;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" d="m 14.047838,32.727592 0,-19.064695 19.062499,19.064695 0,-19.064695" id="path2996" inkscape:connector-curvature="0" sodipodi:nodetypes="cccc"/>
<path style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#009900;fill-opacity:1;stroke:none;stroke-width:5;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans" d="M 44.6875,11.1875 44,12.46875 38.6875,22.125 38,23.34375 38.6875,24.5625 44,33.90625 l 0.71875,1.28125 1.46875,0 10.875,0 1.5625,0 0.6875,-1.40625 3.96875,-8 1.78125,-3.625 -4.03125,0 L 50.875,22.1875 c -1.320782,-0.01868 -2.535605,1.179086 -2.535605,2.5 0,1.320914 1.214823,2.518679 2.535605,2.5 L 57,27.15625 l -1.5,3.03125 -7.875,0 -3.90625,-6.875 3.9375,-7.125 8.377221,0 1.953125,4.007812 5.03125,0 -3.171875,-7.601562 -0.6875,-1.40625 -1.5625,0 -11.408471,0 z" id="path2996-0" inkscape:connector-curvature="0" sodipodi:nodetypes="cccccccccccccccscccccccccccccc"/>
<path style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#009900;fill-opacity:1;stroke:none;stroke-width:5;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans" d="m 70.013782,11.15625 c -1.308989,0.01639 -2.485084,1.222261 -2.46875,2.53125 l 0,6.514509 5,0 0,-6.514509 c 0.01659,-1.329821 -1.201429,-2.547843 -2.53125,-2.53125 z" id="path2996-0-5" inkscape:connector-curvature="0" sodipodi:nodetypes="cccccc"/>
<use x="0" y="0" xlink:href="#path2996" id="use3820" transform="matrix(-1,0,0,1,111.13905,0.04841623)" width="744.09448" height="1052.3622"/>
<g id="g3920" transform="translate(0.10586251,0.33010228)">
<path sodipodi:nodetypes="cc" inkscape:connector-curvature="0" id="path2996-1" d="m 104.90935,13.374209 19.08481,19.017856" style="fill:none;stroke:#009900;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"/>
<use height="1052.3622" width="744.09448" transform="matrix(-1,0,0,1,228.92583,0)" id="use3851" xlink:href="#path2996-1" y="0" x="0"/>
</g>
<path style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#009900;fill-opacity:1;stroke:none;stroke-width:5;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans" d="m 70.013782,35.204069 c -1.308989,-0.01639 -2.485084,-1.222261 -2.46875,-2.53125 l 0,-10.464363 5,0 0,10.464363 c 0.01659,1.329821 -1.201429,2.547843 -2.53125,2.53125 z" id="path2996-0-5-1" inkscape:connector-curvature="0" sodipodi:nodetypes="cccccc"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 960 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 971 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
content/images/rss.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

BIN
content/images/stripe.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

74
content/images/www.svg Normal file
View File

@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="svg2"
sodipodi:version="0.32"
inkscape:version="0.46"
width="600.62299"
height="441.90601"
version="1.0"
sodipodi:docname="WWWlogo.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape">
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs5">
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 526.18109 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="744.09448 : 526.18109 : 1"
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
id="perspective9" />
</defs>
<sodipodi:namedview
inkscape:window-height="719"
inkscape:window-width="1024"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
guidetolerance="10.0"
gridtolerance="10.0"
objecttolerance="10.0"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#ffffff"
id="base"
showgrid="false"
inkscape:zoom="1"
inkscape:cx="109.623"
inkscape:cy="273.93708"
inkscape:window-x="-4"
inkscape:window-y="-4"
inkscape:current-layer="svg2" />
<path
style="opacity:1;fill:#00844a;fill-opacity:1;stroke:#000000;stroke-width:5.02096605;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 92.865304,2.531739 L 177.91464,2.517892 L 247.95637,263.44016 L 337.39638,2.584956 L 389.1409,2.558694 L 473.94127,263.23721 L 553.2417,2.514274 L 598.11252,2.510483 L 476.04666,358.98499 L 425.87196,359.04212 L 342.46466,102.69054 L 250.07507,358.96957 L 199.90132,358.99984 L 92.865304,2.531739 z"
id="path3233"
sodipodi:nodetypes="cccccccccccccc" />
<path
style="opacity:1;fill:#cee7ce;fill-opacity:1;stroke:#000000;stroke-width:5.02096605;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 47.777314,42.748408 L 132.82665,42.734572 L 202.86837,303.65683 L 292.30839,42.801624 L 344.05292,42.775363 L 428.85329,303.45387 L 508.15374,42.730942 L 553.02454,42.727162 L 430.95869,399.20165 L 380.78398,399.25879 L 297.37668,142.90721 L 204.98708,399.18624 L 154.81332,399.21652 L 47.777314,42.748408 z"
id="path3231"
sodipodi:nodetypes="cccccccccccccc" />
<path
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:5.02096605;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 2.510483,82.885159 L 87.559826,82.8713 L 157.60156,343.79356 L 247.04156,82.938364 L 298.78609,82.912102 L 383.58649,343.59061 L 462.88689,82.867682 L 507.75772,82.86389 L 385.69186,439.33837 L 335.51714,439.39551 L 252.10986,183.04395 L 159.72025,439.32296 L 109.5465,439.35325 L 2.510483,82.885159 z"
id="rect2388"
sodipodi:nodetypes="cccccccccccccc" />
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -0,0 +1,52 @@
---
title: Welcome to my new static website!
image: /images/guard.jpg
thumbnail: /images/thumbnail-guard.jpg
lang: en
website: dev
---
I just rebuilt my website with Hakyll, a library for generating static sites. No major visual changes: the design remains almost the same except I'm now using the newest [KNACSS](http://knacss.com/) version and I dropped [Geomicons](http://geomicons.com/) for [Font awesome](http://fontawesome.io/). I also took advantage of this change to add a new blog to my website, I'll try to keep it updated weekly (even if we all know it's impossible ^^).
Why switch from PHP and [Laravel](https://laravel.com) to just HTML files? Why a static website generator?
<!--more-->
### Why a static website generator?
Because, to quote [the Hakyll website](http://jaspervdj.be/hakyll/):
> Static sites are fast, secure, easy to deploy, and manageable using version control.
![A static, fast, secure and easy to deploy Buckingham's guard](/images/guard.jpg)
I like the idea of using only an Apache web server (~~yeah, no Nginx for me, I'm old school~~ not anymore, only fools never change their minds <i class="fa fa-smile-o"></i>) and deploy my application easily with ~~`scp`~~ `rsync`. And, of course, no security breach, it's not a Wordpress!
But, if HTML files are so cool, why don't just use HTML files? Generators brings a lot of possibilities for a blog.
- Use of layouts or templates for your pages. Don't repeat yourself: don't copy / paste the `doctype` and the `header` for each post.
- Create a dynamic index for all your posts (with a summary), an archive page and a RSS / Atom feed.
- Write in [Markdown](https://daringfireball.net/projects/markdown/) instead of HTML, it's a bit easier and it's always possible to add HTML tags in your posts for specific information.
### How is it working?
As I said, I'm using [Hakyll](http://jaspervdj.be/hakyll/). It's develop with [Haskell](https://www.haskell.org/) but every language has a static site generator available: [Pelican](https://github.com/getpelican/pelican/) in Python, [Jekyll](http://jekyllrb.com/) in Ruby, [Sculpin](https://sculpin.io/) in PHP…
I don't think Hakyll is the best one, but I'm currently in functional programming and I really wanted to try to do some Haskell. Hopefully, there is not a lot of Haskell to write and thanks to [Yann Esposito's article](http://yannesposito.com/Scratch/en/blog/Hakyll-setup/), the Haskell part was really easy to do.
After developing the main Haskell file, I just run `stack build` once to compile my program. Then, to write this blog post, I run `stack exec thibaud watch`, open `http://localhost:8000` and start writing my post with [my editor](https://atom.io). Each time I save the file, a rebuild is triggered, and I can see the changes directly in my browser. Really efficient workflow!
I write Markdown compiled in HTML thanks to the great [Pandoc](http://pandoc.org/) library. I use an HTML comment `<!--more-->` to create a « teaser » field. And I also can use HTML to add font awesome character for example like `<i class="fa fa-smile-o"></i>`. All of the code is on [Github](https://github.com/ThibaudDauce/thibaud-dauce), and I'm open to contributions <i class="fa fa-smile-o"></i>.
### What's remaining to do?
This is a first quick version of my Hakyll website, a lot is remaining:
- Remove the `.html` extensions in the URL;
- Add a RSS feed;
- Clean some SASS files;
- Remove external calls to Google fonts and Font Awesome CDN;
- Concatenate CSS files;
- Add texts about my current work in Spark / Scala and my summer internship with NodeJS and Amazon Kinesis.
Thanks for reading me! My English is far from perfect, so I'm open to all remarks, comments and improvements either with Github pull requests or on [Twitter](https://twitter.com/ThibaudDauce).

View File

@@ -0,0 +1,61 @@
---
title: Live without Flash
image: /images/html5.png
thumbnail: /images/thumbnail-html5.png
lang: en
website: dev
---
Adobe Flash is awful. There are a lot of bugs and security problems with this software. Today, HTML5 is a good alternative but it requires a bit of setup. Some websites still have video players in Flash and it's impossible to watch without.
What can we do?
<!--more-->
![The future is HTML5](/images/html5.png)
### First, Firefox configuration
Firefox is the best browser, not because its performances are better, but because Gecko is an alternative to Webkit (the layout engine software component for rendering web pages in Chrome, Chromium, Safari and Opera). If we don't want Google to be the new Internet Explorer, we need to fight for diversity in rendering engines.
How run HTML5 videos in Firefox? First, we need to check some configuration. YouTube has a perfect web page for this task: [Youtube HTML5](https://www.youtube.com/html5).
To enable all the possibilities of HTML5, you need to use the default HTML5 player. Then to be able to watch 480p and 1080p videos, there are a few steps.
* [Media Source Extensions.](http://www.ghacks.net/2014/05/10/enable-media-source-extensions-firefox/) tldr; go to `about:config` and switch to true `media.mediasource.enabled`.
* [MSE & H.264.](http://www.ghacks.net/2014/07/25/enable-mse-h2-64-support-youtube-firefox-right-now/) tldr; go to `about:config` and switch to true `media.fragmented-mp4.*` except `media.fragmented-mp4.use-blank-decoder`.
* [MSE & WebM VP9](https://www.youtube.com/watch?v=R4No4kv3TA8) tldr; go to `about:config` and switch to true `media.mediasource.webm.enabled`.
With Arch Linux, I think you should install `gst-plugins-good` and `gst-libav` too.
Now you can watch HTML5 videos with Firefox in every website.
### Then some trick for old Flash websites
I use two CLI tools to watch Flash videos: `youtube-dl` and `livestreamer`.
#### youtube-dl
To install `youtube-dl`, visit [their website](https://rg3.github.io/youtube-dl/). Or if you're using Arch Linux, just run `yaourt -S youtube-dl`. This little piece of software allows you to download YouTube videos but not only. You can list all available websites with `youtube-dl --list-extractors`. To use it:
```bash
youtube-dl link-to-your-video
```
You can also download YouTube play lists, pages, etc. There are a lot of options like `--write-all-thumbnails` or `--write-subs`. Just check the man page, everything is explained.
#### Livestreamer
Same as `youtube-dl`, for the installation, just go to [their website](https://github.com/chrippa/livestreamer). With Arch Linux, run `yaourt -S livestreamer`. The problem with `youtube-dl` is you can't watch the video being downloaded because it downloads the video and the audio separately. With `livestreamer` you can stream a video from a lot of websites (`livestreamer --plugins`). To use it:
```bash
livestreamer -p vlc link-to-your-video best
```
I used a lot `livestreamer` with my old computer because VLC is way better than HTML5 to decode 1080p videos. I also use it with Twitch streams.
#### Last trick
I often watch AlloCiné, a french website which provides information on French cinema. They have a Flash video player and neither `youtube-dl` nor `livestreamer` support it. With the plugin Greasemonkey on Firefox, I use the script [AlloCine_Zap](http://userscripts-mirror.org/scripts/show/59373) to watch videos as HTML5 (and remove adds ^^).
----
**Edit: thanks to Christophe Cluizel (Awesome guy! Go follow him [\@CCluizel](https://twitter.com/CCluizel) for some Scala, Spark and Machine Learning stuff), there's now a solution for "MSE & WebM VP9". It's not working on my computer but it may work for you. Thanks for his contribution.**

View File

@@ -0,0 +1,65 @@
---
title: An RSS Feed with Hakyll
image: /images/rss.png
thumbnail: /images/thumbnail-rss.png
lang: en
website: dev
---
Twitter is a really cool social network. I daily use it and I discover a lot of new stuff with it. Unfortunately, it's a company, and they need to earn some money. More and more often, Twitter shows me unwanted content like *tweets you could like*, *accounts to follow* or *what happens when you're away*.
I don't want all these features, I just want the tweets of people I follow in chronological order. What I want is an RSS feed.
<!--more-->
![RSS, the perfect alternative to Twitter](/images/rss.png)
### Add an RSS feed in Hakyll
It's a really simple operation, everything is explained [here](http://jaspervdj.be/hakyll/tutorials/05-snapshots-feeds.html). In the tutorial, they explain how to render either an Atom or an RSS feed. I decided to provide both. To do that, I've created a small function named `createFeed`.
```hs
createFeed :: Identifier -> RenderingFunction -> Rules ()
```
Where `RenderingFunction` is the signature provided by Hakyll for `renderAtom` and `renderRss`.
```hs
type RenderingFunction = FeedConfiguration
-> Context String
-> [Item String]
-> Compiler (Item String)
```
Then, my function is just a copy paste from the function in the tutorial.
```hs
createFeed name renderingFunction = create [name] $ do
route idRoute
compile $ do
posts <- fmap (take 10) . recentFirst =<<
loadAllSnapshots "posts/*" "content"
renderingFunction myFeedConfiguration feedCtx posts
```
Eventually, I just need to call my new function for `renderAtom` and `renderRss`.
```hs
createFeed "feed.xml" renderRss
createFeed "atom.xml" renderAtom
```
### The HTML part
To make my feeds work with most of the aggregators, I need to add two `links` to my default template.
```html
<link rel="alternate" type="application/rss+xml" title="Thibaud Dauce's blog" href="./feed.xml">
<link rel="alternate" type="application/atom+xml" title="Thibaud Dauce's blog" href="./atom.xml">
```
### Which reader
I personally use [FreshRSS](http://freshrss.org/). It's a simple, self-hostable aggregator under the AGPL license. The installation is really simple and there is a lot of useful features (like keyboard shortcuts and integration with [Wallabag](https://www.wallabag.org/))
![The clear interface of FreshRSS](/images/freshrss.jpg)

View File

@@ -0,0 +1,67 @@
---
title: 4 lines to break a password in Haskell
image: /images/hacker.jpg
thumbnail: /images/thumbnail-hacker.jpg
lang: en
website: dev
---
I'm relatively new in functional programming but I really enjoy trying to code some piece of software in Haskell. It's a pure, strongly type and efficient language. I tried to develop [a little program](https://github.com/ThibaudDauce/habreaker) to break SHA1 hashes and the code in only 4 lines long.
<!--more-->
![A random hacker with the crucial black mask and really useful ski gloves to type on a keyboard :-)](/images/hacker.jpg)
### Generate the list of all the possible strings
I found [a Stack Overflow answer](http://stackoverflow.com/questions/9542313/how-to-generate-a-list-of-all-possible-strings-from-shortest-to-longest) where someone explains how to use the laziness of Haskell to build an infinite list of all the possible strings. The one line function is :
```hs
allStrings :: [String]
allStrings = [ c : s | s <- "" : allStrings, c <- ['a'..'z'] ++ ['0'..'9'] ]
```
`allStrings` is a list of strings defined as the concatenation of one character `c` in the list `['a'..'z'] ++ ['0'..'9']` and one string of `allStrings`. The definition is here recursive and we get an infinite list.
### Compute the SHA1 hash
First, I decided to use the `sha1` library but it wasn't really efficient. I decided to switch to the `cryptohash` library which is really faster.
| | `sha1` | `cryptohash` |
|-----------------------|--------|--------------|
| 4 characters password | 4,74s | 0,56s |
We need to transform the `String` in `ByteString` before computing the hash so I needed one line to code my SHA1 function:
```hs
sha1 :: String -> Digest SHA1
sha1 = hash . B.pack
```
### Check the password hash
Now we need to compute the hash to check if it matches the hash provided by the user. Here again it's a really simple operation in Haskell:
```hs
checkPassword :: Digest SHA1 -> String -> Bool
checkPassword hash string = (sha1 string) == hash
```
The function takes the hash we're looking for and a string, compute the SHA1 of the string and compare the two results.
### Find the password
Eventually, we need to find the correct password in the list of all possible strings.
```hs
findPassword :: Digest SHA1 -> String
findPassword passwordHash = (head . filter (checkPassword passwordHash)) allStrings
```
First we build the filtering function `checkPassword passwordHash`, then we build a function that filter a list and take the head `head . filter (checkPassword passwordHash)` and finally, we call this function on our infinite list of strings.
Here we take advantage of the lazy evaluation of Haskell. The compiler is smart enough to stop the computation of the infinite list as soon as one element match the filter function because we're only looking for the head of the computed list.
### Haskell in parallel
I tried to parallelize this algorithm but I didn't find a way to do it with my infinite list. I need to dig further in the parallel strategies in Haskell. Maybe for a future blog post. I leave here my first attempt where the program never stop because Haskell can't figure out that we only need the head of the list and I ask for the evaluation of all the elements of the infinite list.
```hs
parFilter :: (S.NFData a) => (a -> Bool) -> [a] -> [a]
parFilter p = S.withStrategy (S.evalBuffer 1000 S.rseq) . filter p
```

View File

@@ -0,0 +1,92 @@
---
title: Write well-formed documents with LaTeX
image: /images/latex.png
thumbnail: /images/thumbnail-latex.png
lang: en
website: dev
---
[LaTeX](http://www.latex-project.org/) is an awesome tool. It allows writing well-formed documents with no efforts. You can manage your references, your bibliography, your acronyms and a lot more.
<!--more-->
![It's really hard to illustrate LaTeX, try to find out why by searching "latex" in Google image :-)](/images/no-image-available.png)
### LaTeX installation
There are two different ways to install LaTeX. First, the basic installation with nothing more than the few main packages. LaTeX is a modular system, each functionality is in a package and you can install them separately. But it's a lot of pain to fix compilation issues when some package is missing and it's really time-consuming. On the other hand you can install the full LaTeX pack. It's the LaTeX compiler with every well-known packages so it's really big (a few Go). The choice of one method over the other is just about the size of your disk.
### Functionalities
#### Language specific typography
LaTeX will take care of adding typographical spaces where needed. It's really useful, especially in language like French where you need to add thin non-breaking spaces before a lot of punctuation signs.
#### Images
LaTeX can be really useful with images, except the verbosity of the code.
```tex
\begin{figure}
\centering
\includegraphics[width=0.5\textwidth]{image.png}
\caption{Title of the image.}
\label{label-of-the-image}
\end{figure}
```
To include an image, you need to use the `\includegraphics` command with some options and the name of your file. To provide a title and a label (useful for references), you need to wrap the image in the `figure` environment. Finally, you need to specify the `\centering` command because it's not the default in LaTeX.
One thing to know with LaTeX is you don't have a lot of control about where your image will be rendered: it could be just after the text, just before or two-page away. It's an advantage because you don't have to bother about the placement of your pictures and it forces you to use the good practice of referencing all your pictures in your text (use "figure 2.1" instead of "below" or "right after"). The main problem here is when you have highly visual documents with figures not only here to help the comprehension of your text but also with a real meaning. It can be annoying to switch between pages to read the document. In these cases, LaTeX may not be the most suitable tool to write your report.
#### References
One of the most useful feature of LaTeX is the references. It allows you to add labels in your text and use them to link to some parts of your document. LaTeX will replace the command `\ref{a label}` with the according number of the chapter, section or figure. If a label is missing, you can easily find it out looking for `??` in your PDF (or in the ugly LaTeX logs ^^). Of course, every reference is a real link in the PDF to jump to the referring section. If you add a chapter, every reference stay up-to-date without any modification.
#### Bibliography and glossary
Who never run into this problem of unused abbreviation or bibliography? LaTeX can help you in these cases. For the bibliography, you just need to specify all your sources and then use them in the text. [BibTeX](https://en.wikibooks.org/wiki/LaTeX/Bibliography_Management) will take care of everything else.
```tex
@Misc{documents-with-latex,
author = {Thibaud Dauce},
title = {Write well-formed documents with LaTeX},
howpublished = {\url{https://thibaud.dauce.fr/posts/2015-12-06-write-well-formed-documents-with-latex.html}},
month = {December},
year = {2015},
}
```
This is an example of entry in a BibTex file. You can use this entry in your document with the `\cite` command, for example:
```tex
Thibaud thinks \LaTeX is awesome \cite{documents-with-latex}.
```
If one of the entry of your bibliography is not used anymore in your document, it will not be printed at the end. The same concept works with [the glossary](https://en.wikibooks.org/wiki/LaTeX/Glossary). You just need to define all your acronyms and definitions:
```tex
\newacronym[longplural={Frames per Second}]{fpsLabel}{FPS}{Frame per Second}
```
and use it:
```tex
My camera is recording at 30 \glspl{fpsLabel}.
```
If it's the first use of the acronym, LaTeX will show the full name and after that he'll use the acronym only in the document. A lot of options and configuration are available in the [documentation](https://en.wikibooks.org/wiki/LaTeX/Glossary).
#### Syntax highlighting
You can use the Python package `pygments` and the LaTeX package `minted` to add syntax highlighting in LaTeX. To use it, you need to install `pygments`:
```bash
yaourt -S python-pygments
```
Then you can compile your document with the `-shell-escape` flag to allow the LaTeX compiler to run system commands and that's it!
### LaTeX generator
I recently discover there was a [Yeoman](http://yeoman.io/) generator for LaTeX named `generator-latex` [available on Github](https://github.com/LeoColomb/generator-latex). The source code is a bit old and I didn't use it yet on a real project but it seems promising. The creation of the LaTeX boilerplate can be really long and I often copy paste old projects. This generator could be really awesome. It includes `grunt`, a NodeJS build tool to compile the project each time there is a modification on files and a live-reload of the PDF in the browser.
### Conclusion
It was a really just an introduction to all the features available with LaTeX. Write documents with LaTeX is harder than in Markdown or in LibreOffice but it brings a lot of very useful features.
The compilation logs of LaTex are awful but the documentation is really great with a lot of examples and explanations.

View File

@@ -0,0 +1,46 @@
---
title: "UX fails: moving elements"
video: /videos/ux-fails-twitter.ogv
lang: en
website: dev
---
What's UX? UX stands for User Experience. It refers to "a person's emotions and attitudes about using a particular product, system or service" according to [Wikipedia](https://en.wikipedia.org/wiki/User_experience). I'm starting today a series of blog posts about UX and some fails I ran into on software I use every day.
<!--more-->
### Twitter
I'll start with [Twitter](https://twitter.com/ThibaudDauce). First of all, I'm a big fan of the service and I use it a lot. Especially for *favorites* (now *likes*). I follow a lot of people and I favorite a lot of articles as a read-it-later. I don't tweet much, and most of the time, I just scroll my timeline and favorite everything seems interesting.
What happens when I scroll my timeline and click on some tweets. More information are shown under the text. Then, I continue scrolling and sometimes, I just click on the background. Twitter folds all previous opened tweets and I lose my position in my timeline. Just check the video below, I need to scroll up to find where I was.
<video controls src="/videos/ux-fails-twitter.ogv">
Your browser doesn't support HTML5 videos, go check <a href="/posts/2015-11-07-live-without-flash.html">my blog post</a>
</video>
### DuckDuckGo
I'm using [DuckDuckGo](https://duckduckgo.com/) every
day and I almost never fallback to Google for my research online. When I try to use Google, it's just to discover that the result are not better than DuckDuckGo and I will not find my answer on the Internet. What's my favorite feature of DuckDuckGo? Bangs. I can type `!w Neil Patrick Harris` to go straight on [a Wikipedia page](https://fr.wikipedia.org/wiki/Neil_Patrick_Harris) or `!php date` to find the parameters of [a PHP function](http://php.net/manual/en/function.date.php).
Do you know *Instant answers* from Google? DuckDuckGo has that too. But instead of showing a card in the right of your screen, DuckDuckGo put it before the first result. When I'm really fast and I want to click on the first result, DuckDuckGo often replaces the first official website by the Wikipedia page and I'm redirected even if I didn't want that in the first place. Check the video below.
<video controls src="/videos/ux-fails-duckduckgo.ogv">
Your browser doesn't support HTML5 videos, go check <a href="/posts/2015-11-07-live-without-flash.html">my blog post</a>
</video>
### Online.net
[Online.net](https://www.online.net/en) is a famous hosting company in France. They tried to improve their website with a modern look. But they just created an awful user experience.
The *Starting at €5.99 / month* text looks really like a button. But when I want to click on it, it just disappears, replaced quickly by a clickable table of options. Check the video below.
<video controls src="/videos/ux-fails-online.ogv">
Your browser doesn't support HTML5 videos, go check <a href="/posts/2015-11-07-live-without-flash.html">my blog post</a>
</video>
### Conclusion
If you don't want to confuse your users, please don't move elements of your page. In case of an asynchronous request, save the space right at the beginning. And if you really have to move an element, make the animation not too fast for the user to understand what's happening.

View File

@@ -0,0 +1,60 @@
---
title: Maybe it's now an option to avoid NullPointerException?
image: /images/schroedinger.jpg
thumbnail: /images/thumbnail-schroedinger.jpg
lang: en
website: dev
---
`NullPointerException` is an obvious runtime problem in most Java applications. But it's not limited to Java, in PHP for example we could get a "Trying to get property of non-object". But today, there's a compilation alternative to this runtime problem.
<!--more-->
![Maybe Cat | Option[Cat] | Optional\<Cat\>](/images/schroedinger.jpg)
Scala is a modern functional (and Object Oriented) language based on the JVM. I did a lot of Scala during the past year, mostly with the Spark framework for distributed computing. In Java, if you try to access a element in a `Map<String, Cat>` for example, [the signature](http://docs.oracle.com/javase/7/docs/api/java/util/Map.html#get%28java.lang.Object%29) will look like this:
```java
/**
* Returns the value to which the specified key is mapped, or null if this map contains no mapping for the key.
*
* @return Cat or null
*/
public Cat get(String key);
```
And it's correct (for the compiler) to write:
```java
this.get("Schroedinger").miaou();
```
If the `Cat` is here, it's fine. If he's not, the program crash with a `NullPointerException`. The compiler will not help you to catch this kind of bugs.
In Scala [the signature](http://www.scala-lang.org/api/current/index.html#scala.collection.Map) for a type `Map[String, Cat]` is:
```scala
def get(key: String): Option[Cat]
```
So you cannot write something like before. The compiler will not allow it because the object `Option[Cat]` doesn't have a `miaou` method. So, how did we manage this? We use pattern matching to check the `None` option. Indeed, the `Option[Map]` type is divided into two types: `Some[Cat]` and `None`.
```scala
this.get("Schroedinger") match {
Some(cat) => cat.miaou()
None => // do something
}
```
Scala is not a strict language so it's possible to get the content of an option or a `null` with the `get` method on `Option[Cat]`:
```scala
// throw a NullPointerException
this.get("Schroedinger").get.miaou()
```
This concept of optional type come from functional programming. The last version of Java introduces a lot of functional programming stuff, it's now possible to use [the new type](http://docs.oracle.com/javase/8/docs/api/java/util/Optional.html): `Optional<Cat>`.
And, last but not least, it's not a new hype feature from 2015, some great (and old) languages like Haskell has [the `Maybe` type](http://haddock.stackage.org/lts-3.18/base-4.8.1.0/Prelude.html#t:Maybe) for a long time. `Maybe Cat` could be `Just Cat` or `Nothing`.
----
In conclusion, if you want to know more about this amazing functionality:
* Scala explanations of `Option` type: [http://www.scala-lang.org/api/2.8.1/scala/Option.html](http://www.scala-lang.org/api/2.8.1/scala/Option.html)
* Haskell explanations of `Maybe` type: [http://learnyouahaskell.com/a-fistful-of-monads#getting-our-feet-wet-with-maybe](http://learnyouahaskell.com/a-fistful-of-monads#getting-our-feet-wet-with-maybe)
* Rust explanations of `Option` type: [https://doc.rust-lang.org/std/option/index.html](https://doc.rust-lang.org/std/option/index.html)

View File

@@ -0,0 +1,58 @@
---
title: Automate deployment with Ansible
image: /images/ansible.png
thumbnail: /images/thumbnail-ansible.png
lang: en
website: dev
---
It's been a while since I wrote my last blog post. Today, I want to share with you my small experience with [Ansible](http://www.ansible.com/). Ansible is an open source project for application deployment. I heard of it last year but never took the time to dig in. Last week, I gave it a new try for [Quantic Telecom](https://www.quantic-telecom.net) to deploy new containers easily and I really enjoy it!
<!--more-->
### Why using Ansible?
![Ansible is simple IT automation](/images/ansible.png)
There is a lot of reasons to use Ansible for your deployments or any other tool like [Puppet](https://puppetlabs.com/) or [Chef](https://www.chef.io/chef/). In my specific use case, I needed to install a lot of new services, each one on a private container: [Piwik Analytics](https://piwik.org/), [OwnCloud](https://owncloud.org/), [Cachet HQ](https://cachethq.io/), [Gitlab](https://about.gitlab.com/), etc (yes, I'm a big fan of open-source software!). At first sight, nothing in common with all these services, there are Ruby and PHP, Nginx and Apache, Debian packages and manual installs. But in fact, for all these containers, I needed to configure the timezone, secure SSH, add some users and private keys, configure ZSH…
In fact, as soon as you start having a documentation explaining how to create and configure new machines with some code to copy paste, I think you need to look into some deployment tools like Ansible.
### Why I like Ansible
Ansible is state-driven, it means that everything in your scripts will be link to state (if you do your job correctly). For example, instead of saying: "I want to install texlive-full", you will say: "I want the package texlive-full to be installed". It changes everything, this feature allows you to run your scripts multiple times without to worry about bad changes. The first time Ansible will check if texlive-full is installed and will install it if it's not. Then, the next times, it will show you that everything is already good to go. Moreover, it's really fast to run.
![latex-full is already installed, two "ok"s, no "changed"](/images/ansible-ok.jpg)
The second thing I like about Ansible is the big number of [Core Modules](http://docs.ansible.com/ansible/modules_core.html). Core Modules are really easy to use and can do almost every simple system administration task with a cool syntax, sometimes they're even easier to remember than the Linux commands.
```yaml
---
- name: be sure user thibaud exists
user: name=thibaud shell=/bin/zsh state=present
- name: be sure thibaud's public key is in authorized_keys
authorized_key:
user: "thibaud"
key: "{{ lookup('file', 'thibaud.pub') }}"
state: present
```
In this example, I use the "user" Core Module and the "authorized_key" Core Module. The module documentation is really clear and you have access to every parameters and a lot of examples to understand how to use it. If I change one single thing manually on a server, Ansible will correct the problem. It's important to name things, and with this capture, I realize that my naming for the first task is not really accurate and should be: "be sure user thibaud exists and use ZSH".
![thibaud is using bash for the staging server, one "ok", one "changed"](/images/ansible-changed.jpg)
Last thing important with Ansible, it's only text so you can easily use Git to keep version control. We use a lot Proxmox and templates for containers and it's hard to keep track of the changes for several hundreds MBs images. With Ansible, everything can be saved within a few kBs.
### What I dislike with Ansible
The first time I open the documentation, I had trouble finding a good way to start. By default, Ansible read a weird `/etc/ansible` for the roles. I don't know who is using this and why but, for me, it shouldn't be the default. Everyone should have an ansible directory with their roles and their hosts and Ansible should throw an error if it can't detect any of this.
I also try to use roles build by the community, and I found two kinds of roles : the easy ones and the over engineered ones. Due to the lack of package manager, I preferred to code the easy ones from scratch, because it's easier to manage than to have to add in the documentation to download these roles. And for the complex ones, I had troubles make them work with my system so I just develop my owns.
### Conclusion
Go get [Ansible](http://www.ansible.com/)! :-)
```bash
pacman -S ansible
```

View File

@@ -0,0 +1,100 @@
---
title: Tips & Tricks with LaTeX
image: /images/latex.png
thumbnail: /images/thumbnail-latex.png
lang: en
website: dev
---
In [my previous blog post](/posts/2015-12-06-write-well-formed-documents-with-latex.html), I talked about how LaTeX is a fantastic tool and I concluded with some generators that could be interesting. I had to write some documents with LaTeX and I wanted to improve my work flow. I will share with you some tips & tricks I discovered.
<!--more-->
### Title page
Create a title page with LaTeX could scare you but I realized that with the awesome LaTeX documentation, it was really easy to customize the title page. Just check [this wiki page](https://en.wikibooks.org/wiki/LaTeX/Title_Creation) and it should explain everything to you.
### White spaces!
![An ugly and compact document with a lot of text](/images/latex-compact.jpg)
I don't like reading documents with a lot of text and no spaces. By default, paragraphs in LaTeX don't have margins, so I've got into the habit of adding `\\` after every paragraph to add a new line. I didn't know but this practice was the reason of all my LaTeX warning, complaining about *Underfull \\hbox (badness 10000) in paragraph at lines 11--51*. I knew it wasn't the best solution but I didn't take the time to look deeper.
After just a few searches, I found the solution. And it's really straight forward, just add in your main file:
```tex
\setlength{\parskip}{\baselineskip}
```
The main drawback of this solution is that the table of content is using paragraph to print the titles. I ended up with big skip after every line in my table of content. For my use case it was really cool and the result is in fact nicer (because I don't have a lot of chapters and I think it's clearer) but for the majority, it's gonna be awful. I'm sorry I don't have a solution right now but as soon as I need to write another document, I will check a solution for this problem.
![A document with a lot of text but more white spaces. Better!](/images/latex-blank.jpg)
### Automatic compilation work flow
* Save file
* Switch desktop to Okular
* Alt-Tab to switch to Konsole
* Up arrow to get the `make`
* Enter to execute the command
* Alt-Tab to go back to Okular
* Look the result
This was my previous work flow. Not optimized at all, so I looked for some file watcher tool and I found the most basic one: *inotify-tools*. With Arch Linux it's just:
```bash
pacman -S inotify-tools
```
And then I wrote a simple bash script I named `watch.sh` watching recursively for changes in my current folder and running the `make` command each time a file was saved.
```bash
while true; do
change=$(inotifywait -re close_write,moved_to,create .)
make && make clean
done
```
And here is my new work flow:
* Save file
* Switch desktop to Okular
* Look the result
Much better!
### GitLab CI
I'm a big fan of GitLab, did you know that you can have as many private repositories as you want in [GitLab.com](https://gitlab.com/users/sign_in)?
GitLab is an open-source alternative to Github, the interface and the features are awesome! GitLab comes with a Continuous Integration tool called GitLab CI. As with Travis, you can write a `.gitlab-ci.yml` and run the tests of your project after each `git push`. What's the point for LaTeX documents? I don't have tests. But I have an artifact. An artifact is a compiled file you want to access after your build (a binary to run your software, a documentation to deploy or a PDF document for example). Here is my `.gitlab-ci.yml` file:
```yml
---
pdflatex:
script: make
artifacts:
```
After your first build, you should see a new button in the GitLab UI.
![Access your artifacts right from GitLab](/images/artifacts_button.png)
And browse all the files.
![Your PDF file should be listed here](/images/artifacts_browser.png)
Of course, to use GitLab CI you need to deploy a GitLab CI runner with LaTeX installed. But it's really [with Ansible](/posts/2016-01-24-automate-deployment-with-ansible.html) :-)
```yml
---
- name: be sure Tex Live is present
apt: name=texlive-full state=latest
```
### A few more tips
Use `\graphicspath{{images/}}` to set the root folder of all your graphics.
If you use a french keyboard like me and manually write all you non-breakable spaces, insert this `\DeclareUnicodeCharacter{00A0}{~}`
If you want a font size bigger than 12pt, `\usepackage{extsizes}` and define your document as a `\documentclass[14pt,a4paper]{extreport}`.
And if you want two words to always be one (no hyphenation breaks), define `\hyphenation{Quantic Telecom}`.

View File

@@ -0,0 +1,133 @@
---
title: "Welcome 2016: IPv6, HTTPS & HTTP/2.0"
image: /images/https.png
thumbnail: /images/thumbnail-https.png
lang: en
website: dev
---
It's been a few weeks since a set a AAAA record for my domain name and allow IPv6 to visit my website. Same with SSL and Let's Encrypt. Today I switched from Apache 2 to Nginx and enabled HTTP/2.0 on my server.
<!--more-->
### IPv6
I'm the co-founder of Quantic Telecom, an operator and ISP for student in Rouen, France and I can tell you, IPv4 is dead! We really need IPv6, and not 3 years from now, today! So, change your VPS provider if you don't have an IPv6, set your AAAA DNS records and listen it:
```
listen [::]:443;
```
![IPv6 is the most recent version of the Internet Protocol](/images/ipv6.png)
### HTTPS everywhere
#### Generate certificates
With [Let's Encrypt](https://letsencrypt.org/), certificates are now free for everyone. So no excuse, just set up HTTPS (and HTTPS only). I first use the Let's Encrypt Python script but today I switch to an unofficial bash implementation of the free (as in free speech) Let's Encrypt protocol: [Neilpang/le](https://github.com/Neilpang/le). If you want to set up HTTPS on your server, just type:
```bash
git clone https://github.com/Neilpang/le.git
cd le
./le.sh install
# reset terminal
le issue /var/www/html/ thibaud.dauce.fr thibaud-dauce.fr,www.thibaud-dauce.fr ec-384
le installcert thibaud.dauce.fr /etc/nginx/cert.pem /etc/nginx/cert.key /etc/nginx/ca.crt "cp /etc/nginx/cert.pem /etc/nginx/fullchain.pem && cat /etc/nginx/ca.crt >> /etc/nginx/fullchain.pem && service nginx reload"
```
#### Use them with Nginx
![Nging webserver](/images/nginx.svg)
```bash
ssl_certificate /etc/nginx/cert.pem;
ssl_certificate_key /etc/nginx/cert.key;
ssl_client_certificate /etc/nginx/ca.crt;
```
#### Secure SSL
```bash
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+AES:+AES128:+AES256:+SHA";
# HSTS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
```
#### Why did I switch from Apache?
![Apache is losing the web to Nginx ([source](http://www.nextplatform.com/2016/02/24/how-apache-is-losing-the-web-to-nginx/))](/images/nginx-apache.jpg)
The length of the configuration files are similar but I prefer the Nginx JSON-like syntax over Apache XML. I'm sure there is a lot of errors in my configuration files (I'm new to Nginx) so, if you find something to change, please ping me on [Twitter](https://twitter.com/ThibaudDauce).
```bash
# /etc/nginx/conf.d/default.conf
server {
listen 80;
listen [::]:80;
server_name thibaud.dauce.fr;
return 301 https://$server_name$request_uri;
}
```
```bash
# /etc/nginx/conf.d/default-ssl.conf
server {
ssl on;
ssl_certificate /etc/nginx/fullchain.pem;
ssl_certificate_key /etc/nginx/cert.key;
# SSL security
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers "EECDH+AES:+AES128:+AES256:+SHA";
ssl_dhparam /etc/ssl/certs/dhparam.pem;
ssl_ecdh_curve secp384r1;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
# HSTS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;
server_name thibaud.dauce.fr;
location / {
root /var/www/html;
}
}
```
To use this config file, you need to generate a stronger DHE parameter (it's gonna take a while ^^):
```bash
cd /etc/ssl/certs
openssl dhparam -out dhparam.pem 4096
```
But then you should get an A+ [on every existing SSL test](https://tls.imirhil.fr/https/thibaud.dauce.fr)!
![CryptCheck](/images/cryptcheck.png)
### HTTP/2.0
As you can see in the previous config files, I simply add `http2` at the end of my `listen` line. It's really just that with Nginx 1.9. If you run a Debian 8 as I do, add these deb repositories to get the last version:
```bash
# /etc/apt/sources.list.d/nginx.list
deb http://nginx.org/packages/mainline/debian/ jessie nginx
deb-src http://nginx.org/packages/mainline/debian/ jessie nginx
```
And then:
```bash
wget http://nginx.org/keys/nginx_signing.key
apt-key add nginx_signing.key
apt-get update
apt-get upgrade
```
<h2 style="text-align: center;">IPv6, HTTPS & HTTP/2.0</h2>
<h2 style="text-align: center;">Welcome 2016!</h2>
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.

View File

@@ -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.
<!--more-->
### 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
<?php
class User {
public function comments()
{
return $this->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!

View File

@@ -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.
<!--more-->
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
<?php
namespace ThibaudDauce\LaravelRecursiveMigrations\Commands;
use ThibaudDauce\LaravelRecursiveMigrations\RecursiveMigrationCommand;
use Illuminate\Database\Console\Migrations\MigrateCommand as BaseMigrateCommand;
class MigrateCommand extends BaseMigrateCommand
{
use RecursiveMigrationCommand;
}
```
### Laravel integration
Last but not least, I need to create [a service provider](https://framagit.org/ThibaudDauce/laravel-recursive-migrations/blob/master/src/LaravelRecursiveMigrationsServiceProvider.php) to bind the new commands in the container. The names of the commands in the container are simple, and I only need to extend the binding.
```php
$this->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!

View File

@@ -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…
<!--more-->
### The problem
Imagine I have a class for my customer:
```php
<?php
class Customer {
const STUDENT = 'student';
const INDIVIDUAL = 'individual';
public $type;
public function getPrice()
{
if ($this->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
<?php
class Customer {
const STUDENT = 'student';
const INDIVIDUAL = 'individual';
public $type;
public function patternMatchOnType(array $actions)
{
return (new Pattern([self::STUDENT, self::INDIVIDUAL]))
->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.

View File

@@ -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.
<!--more-->
---
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/).
![Test Driven Laravel course](/images/ticket-beast-promoters.png)
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/)

View File

@@ -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?
<!--more-->
---
### 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!

View File

@@ -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.
<br class="inline-block mb-2">
<span class="text-orange-light">★★</span><span class="text-orange-darker">☆☆</span> Productivity |
<span class="text-orange-light">★★★★</span><span class="text-orange-darker"></span> Correctness |
<span class="text-orange-light">★★</span><span class="text-orange-darker">☆☆</span> 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/).
<br class="inline-block mb-2">
<span class="text-orange-light">★★★</span><span class="text-orange-darker">☆</span> Productivity |
<span class="text-orange-light">★★</span><span class="text-orange-darker">☆☆</span> Correctness |
<span class="text-orange-light">★★★</span><span class="text-orange-darker">☆</span> 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.
<br class="inline-block mb-2">
<span class="text-orange-light">★</span><span class="text-orange-darker">☆☆☆</span> Productivity |
<span class="text-orange-light">★★★</span><span class="text-orange-darker">☆</span> Correctness |
<span class="text-orange-light">★★★★</span><span class="text-orange-darker"></span> 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.
<br class="inline-block mb-2">
<span class="text-orange-light">★★★★</span><span class="text-orange-darker"></span> Productivity |
<span class="text-orange-light">★</span><span class="text-orange-darker">☆☆☆</span> Correctness |
<span class="text-orange-light">★</span><span class="text-orange-darker">☆☆☆</span> 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.

View File

@@ -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.

View File

@@ -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…

View File

@@ -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.

View File

@@ -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!

View File

@@ -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!

View File

@@ -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
---

View File

@@ -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.

View File

@@ -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».

View File

@@ -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).

View File

@@ -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.

View File

@@ -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. Levent 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 lon peut faire avec.
Les slides sont également disponibles [sur Github](https://github.com/ThibaudDauce/jug-event-store).

View File

@@ -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 lutilisez tous les jours, toutes les heures, voir même chaque minute. Et pourtant, savez-vous vraiment ce quest Internet ? Comment fonctionne Internet ?
Les slides sont également disponibles [sur Github](https://github.com/ThibaudDauce/jug-internet).

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.
Lobjectif de ce talk est de faire réfléchir les participants à laugmentation de la complexité dans nos applications. Aujourdhui, la moindre application web demande lexé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 quils le pourraient, elle entraîne également laugmentation des bugs dans les logiciels et la diminution des performances avec un impact écologique certain.

Binary file not shown.

View File

@@ -0,0 +1,33 @@
{% extends "default.html" %}
{% block content %}
<main role="main" class="container space-y-12">
<div class="space-y-3">
<h2 class="text-xl font-semibold text-gray-800 leading-none">
Travail…
</h2>
<p class="prose">
Je suis actuellement freelance <a href="https://laravel.com">Laravel</a> (développement et formation).<br>
Je travaille pour la plateforme publique <a href="https://data.gouv.fr">data.gouv.fr</a> en Python.<br>
J'enseigne également la programmation <a href="https://www.youtube.com/user/tdauce">sur YouTube</a> et <a href="https://www.formation-laravel.fr">en entreprise</a>.<br>
J'ai co-fondé <a href="https://www.quantic-telecom.net">Quantic Telecom</a>, un fournisseur d'accès à Internet.<br>
</p>
</div>
<div class="space-y-3">
<h2 class="text-xl font-semibold text-gray-800 leading-none">
Lectures…
</h2>
<p class="prose">
<a href="https://fr.wikipedia.org/wiki/La_Roue_du_temps">La Roue du temps</a> de Robert Jordan sur ma <a href="https://www.fnac.com/Liseuse-numerique-Kobo-by-Fnac-Libra-H2O-7-8-Go-H2O-Noir/a13681417/w-4">Kobo Libra H2O</a>.
</p>
</div>
<div class="space-y-3">
<h2 class="text-xl font-semibold text-gray-800 leading-none">
Autres…
</h2>
<p class="prose">
Je lance actuellement <a href="https://moulin.olla.fr">un espace de coworking et une salle de bloc</a> dans mon village.
</p>
</div>
</main>
{% endblock content %}

View File

@@ -0,0 +1,58 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>{{ title }} — Thibaud Ollagnier</title>
<link rel="stylesheet" href="/css/app.css"/>
<link rel="alternate" type="application/rss+xml" title="Thibaud Dauce" href="/feed.xml">
<link rel="shortcut icon" href="/images/favicon.png">
<meta property="og:title" content="{{ title }}">
<meta name="twitter:card" content="summary">
<meta name="twitter:site" content="@ThibaudDauce" />
{% if description %}
<meta property="og:description" content="{{ description }}">
{% endif %}
</head>
<body class="overflow-x-hidden bg-gray-100 text-black font-serif leading-normal min-h-screen flex flex-col py-6 sm:py-12">
<header class="container mb-12 space-y-4">
<div>
<h2 class="sm:text-lg font-normal flex items-center gap-2">
<a href="/" class="text-gray-900">Thibaud Ollagnier</a>
<a href="https://discordapp.com/invite/tPtVM9V">
<svg xmlns="http://www.w3.org/2000/svg" class="fill-current text-gray-700 size-5" viewBox="0 0 245 240"><path d="M104.4 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1.1-6.1-4.5-11.1-10.2-11.1zM140.9 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1s-4.5-11.1-10.2-11.1z"/><path d="M189.5 20h-134C44.2 20 35 29.2 35 40.6v135.2c0 11.4 9.2 20.6 20.5 20.6h113.4l-5.3-18.5 12.8 11.9 12.1 11.2 21.5 19V40.6c0-11.4-9.2-20.6-20.5-20.6zm-38.6 130.6s-3.6-4.3-6.6-8.1c13.1-3.7 18.1-11.9 18.1-11.9-4.1 2.7-8 4.6-11.5 5.9-5 2.1-9.8 3.5-14.5 4.3-9.6 1.8-18.4 1.3-25.9-.1-5.7-1.1-10.6-2.7-14.7-4.3-2.3-.9-4.8-2-7.3-3.4-.3-.2-.6-.3-.9-.5-.2-.1-.3-.2-.4-.3-1.8-1-2.8-1.7-2.8-1.7s4.8 8 17.5 11.8c-3 3.8-6.7 8.3-6.7 8.3-22.1-.7-30.5-15.2-30.5-15.2 0-32.2 14.4-58.3 14.4-58.3 14.4-10.8 28.1-10.5 28.1-10.5l1 1.2c-18 5.2-26.3 13.1-26.3 13.1s2.2-1.2 5.9-2.9c10.7-4.7 19.2-6 22.7-6.3.6-.1 1.1-.2 1.7-.2 6.1-.8 13-1 20.2-.2 9.5 1.1 19.7 3.9 30.1 9.6 0 0-7.9-7.5-24.9-12.7l1.4-1.6s13.7-.3 28.1 10.5c0 0 14.4 26.1 14.4 58.3 0 0-8.5 14.5-30.6 15.2z"/></svg>
</a>
</h2>
<h1 class="text-xl sm:text-2xl font-normal">{{ title }}</h1>
</div>
<nav class="flex items-center gap-3 text-sm sm:text-base leading-none">
<a href="/" class="font-semibold {% if blog %} text-black {% else %} text-gray-600 hover:text-black {% endif %}">Blog</a>
<span class="hidden sm:inline size-1 rounded-full bg-gray-500"></span>
<a href="/talks.html" class="font-semibold {% if talks %} text-black {% else %} text-gray-600 hover:text-black {% endif %}">Conférences</a>
<span class="hidden sm:inline size-1 rounded-full bg-gray-500"></span>
<a href="https://www.youtube.com/user/tdauce/" class="font-semibold text-gray-600 hover:text-black">Vidéos</a>
<span class="hidden sm:inline size-1 rounded-full bg-gray-500"></span>
<a href="/about.html" class="font-semibold {% if about %} text-black {% else %} text-gray-600 hover:text-black {% endif %}">À propos</a>
</nav>
</header>
{% block content %}
{% endblock content %}
<footer class="container py-12 mt-12 text-sm text-gray-800">
<p>
Ce blog est généré statiquement en Rust. Si vous trouvez une faute ou un problème, <a href="https://github.com/ThibaudDauce/thibaud.dauce.fr" class="text-indigo-600 italic underline">vous pouvez contribuer via Github</a>.
</p>
</footer>
</body>
</html>

View File

@@ -0,0 +1,46 @@
{% extends "default.html" %}
{% block content %}
<div id="map" class="w-full" style="height: 50rem"></div>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/leaflet.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/leaflet.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet-gpx/1.7.0/gpx.min.js"></script>
<script>
var map = L.map('map').setView(new L.LatLng(46.498, 2.197), 7);
var googleSat = L.tileLayer('https://{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}', {
maxZoom: 20,
subdomains: ['mt0', 'mt1', 'mt2', 'mt3']
}).addTo(map);
function loadGPX(trace, color, popup) {
new L.GPX('/traces/' + trace, {
async: true,
marker_options: {
iconSize: [11, 15],
shadowSize: [16.6, 16.6],
iconAnchor: [5.3, 15],
shadowAnchor: [5.3, 15.6],
startIconUrl: '/images/pin-icon-start.png',
endIconUrl: '/images/pin-icon-end.png',
shadowUrl: '/images/pin-shadow.png',
},
polyline_options: {
color: color,
opacity: 1,
weight: 4,
lineCap: 'round'
}
}).on('addpoint', function(e) {
e.target.bindPopup(popup)
}).addTo(map)
}
{% for trip in trips %}
{% for trace in trip.traces %}
loadGPX('{{ trace.path | safe }}', '{{ trip.color }}', "<b>{{ trip.type_as_string }} {{ trip.name }} de {{ trip.date }}</b><br>Distance : {{ trip.distance_format }}<br>Dénivelé: +{{ trip.elevation_positive_format }} | -{{ trip.elevation_negative_format }}<br><br><b>Segment {{ trace.name }}</b><br>Distance : {{ trace.distance_format }}<br>Dénivelé: +{{ trace.elevation_positive_format }} | -{{ trace.elevation_negative_format }}");
{% endfor %}
{% endfor %}
</script>
{% endblock content %}

View File

@@ -0,0 +1,30 @@
{% extends "default.html" %}
{% block content %}
<main role="main" class="container space-y-12">
{% for post in posts | reverse %}
<div class="space-y-2">
<h2 class="flex flex-col gap-2">
<span class="text-sm font-semibold text-gray-600 leading-none whitespace-nowrap">
{% if post.lang == "en" %}
{{ post.date_en }}
{% else %}
Le {{ post.date_fr }}
{% endif %}
</span>
<a href="{{ post.url }}" class="text-xl font-semibold text-gray-800 leading-none hover:underline">
{{ post.title }}
</a>
</h2>
{% if loop.index < 20 %}
<div class="prose">
{% if post.description %}
{{ post.description }}
{% endif %}
</div>
{% endif %}
</div>
{% endfor %}
</main>
{% endblock content %}

View File

@@ -0,0 +1,19 @@
{% extends "default.html" %}
{% block content %}
<main role="main" class="container space-y-6">
<p class="text-sm text-gray-600 italic">
{% if post.lang == "en" %}
Published {{ post.date_en }}
{% else %}
Publié le {{ post.date_fr }}
{% endif %}
</p>
<div class="prose">
{{ post.html | safe }}
</div>
</main>
{% endblock content %}

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>
<title>Thibaud Dauce</title>
<link>https://thibaud.dauce.fr</link>
<description><![CDATA[All Thibaud Dauce's articles]]></description>
<atom:link href="https://thibaud.dauce.fr/feed.xml" rel="self" type="application/rss+xml" />
<lastBuildDate>{{ latest_post.date_rss }}</lastBuildDate>
{% for post in posts | reverse %}
<item>
<title><![CDATA[{{ post.title|safe }}]]></title>
<link>https://thibaud.dauce.fr{{ post.url|safe }}</link>
<description><![CDATA[{{ post.html|safe }}]]></description>
<pubDate>{{ post.date_rss }}</pubDate>
<guid>https://thibaud.dauce.fr{{ post.url|safe }}</guid>
<dc:creator>Thibaud Dauce</dc:creator>
</item>
{% endfor %}
</channel>
</rss>

Some files were not shown because too many files have changed in this diff Show More