home

My NixOS systems configurations.
Log | Files | Refs | LICENSE

commit 1f735085886a78adf7da898489551d335ffd8d64
parent 4fabf8433ad7f6d39bee73307baefc863840b6ad
Author: Vincent Demeester <vincent@sbr.pm>
Date:   Sat,  7 May 2022 10:27:40 +0200

www/vincent.demeester.fr: moving articles to legacy

Signed-off-by: Vincent Demeester <vincent@sbr.pm>

Diffstat:
Mwww/vincent.demeester.fr/Makefile | 2+-
Dwww/vincent.demeester.fr/content/articles/meta_publishing_this_website.org | 607-------------------------------------------------------------------------------
Dwww/vincent.demeester.fr/content/articles/sitemap.org | 145-------------------------------------------------------------------------------
Rwww/vincent.demeester.fr/content/articles/.setup.org -> www/vincent.demeester.fr/content/legacy/articles/.setup.org | 0
Rwww/vincent.demeester.fr/content/articles/2021-10.private.html -> www/vincent.demeester.fr/content/legacy/articles/2021-10.private.html | 0
Rwww/vincent.demeester.fr/content/articles/Makefile -> www/vincent.demeester.fr/content/legacy/articles/Makefile | 0
Rwww/vincent.demeester.fr/content/articles/a_philosophy_of_project_governance_drew_devault_s_blog.org -> www/vincent.demeester.fr/content/legacy/articles/a_philosophy_of_project_governance_drew_devault_s_blog.org | 0
Rwww/vincent.demeester.fr/content/articles/add_a_contrib_directory_to_your_projects_drew_devault_s_blog.org -> www/vincent.demeester.fr/content/legacy/articles/add_a_contrib_directory_to_your_projects_drew_devault_s_blog.org | 0
Rwww/vincent.demeester.fr/content/articles/apis.org -> www/vincent.demeester.fr/content/legacy/articles/apis.org | 0
Rwww/vincent.demeester.fr/content/articles/architect.org -> www/vincent.demeester.fr/content/legacy/articles/architect.org | 0
Rwww/vincent.demeester.fr/content/articles/bash.org -> www/vincent.demeester.fr/content/legacy/articles/bash.org | 0
Rwww/vincent.demeester.fr/content/articles/blog.org -> www/vincent.demeester.fr/content/legacy/articles/blog.org | 0
Rwww/vincent.demeester.fr/content/articles/book_a_practical_approach_to_large_scale_agile_development.org -> www/vincent.demeester.fr/content/legacy/articles/book_a_practical_approach_to_large_scale_agile_development.org | 0
Rwww/vincent.demeester.fr/content/articles/book_a_practical_guide_to_distributed_scrum.org -> www/vincent.demeester.fr/content/legacy/articles/book_a_practical_guide_to_distributed_scrum.org | 0
Rwww/vincent.demeester.fr/content/articles/book_accelerate.org -> www/vincent.demeester.fr/content/legacy/articles/book_accelerate.org | 0
Rwww/vincent.demeester.fr/content/articles/book_debugging_teams.org -> www/vincent.demeester.fr/content/legacy/articles/book_debugging_teams.org | 0
Rwww/vincent.demeester.fr/content/articles/book_kanban_workbook.org -> www/vincent.demeester.fr/content/legacy/articles/book_kanban_workbook.org | 0
Rwww/vincent.demeester.fr/content/articles/book_living_documentation.org -> www/vincent.demeester.fr/content/legacy/articles/book_living_documentation.org | 0
Rwww/vincent.demeester.fr/content/articles/book_make_time.org -> www/vincent.demeester.fr/content/legacy/articles/book_make_time.org | 0
Rwww/vincent.demeester.fr/content/articles/book_managing_humans.org -> www/vincent.demeester.fr/content/legacy/articles/book_managing_humans.org | 0
Rwww/vincent.demeester.fr/content/articles/book_sleep_smarter.org -> www/vincent.demeester.fr/content/legacy/articles/book_sleep_smarter.org | 0
Rwww/vincent.demeester.fr/content/articles/book_sprint.org -> www/vincent.demeester.fr/content/legacy/articles/book_sprint.org | 0
Rwww/vincent.demeester.fr/content/articles/book_the_checklist_manifesto.org -> www/vincent.demeester.fr/content/legacy/articles/book_the_checklist_manifesto.org | 0
Rwww/vincent.demeester.fr/content/articles/book_the_flinch.org -> www/vincent.demeester.fr/content/legacy/articles/book_the_flinch.org | 0
Rwww/vincent.demeester.fr/content/articles/book_the_holloway_guide_to_equity_compensation.org -> www/vincent.demeester.fr/content/legacy/articles/book_the_holloway_guide_to_equity_compensation.org | 0
Rwww/vincent.demeester.fr/content/articles/book_the_holloway_guide_to_remote_work.org -> www/vincent.demeester.fr/content/legacy/articles/book_the_holloway_guide_to_remote_work.org | 0
Rwww/vincent.demeester.fr/content/articles/book_the_manager_s_path.org -> www/vincent.demeester.fr/content/legacy/articles/book_the_manager_s_path.org | 0
Rwww/vincent.demeester.fr/content/articles/book_the_now_habit.org -> www/vincent.demeester.fr/content/legacy/articles/book_the_now_habit.org | 0
Rwww/vincent.demeester.fr/content/articles/book_the_ultimate_guide_to_remote_work.org -> www/vincent.demeester.fr/content/legacy/articles/book_the_ultimate_guide_to_remote_work.org | 0
Rwww/vincent.demeester.fr/content/articles/book_time_management.org -> www/vincent.demeester.fr/content/legacy/articles/book_time_management.org | 0
Rwww/vincent.demeester.fr/content/articles/book_work_clean.org -> www/vincent.demeester.fr/content/legacy/articles/book_work_clean.org | 0
Rwww/vincent.demeester.fr/content/articles/building_interactive_ssh_applications_drew_devault_s_blog.org -> www/vincent.demeester.fr/content/legacy/articles/building_interactive_ssh_applications_drew_devault_s_blog.org | 0
Rwww/vincent.demeester.fr/content/articles/clojure.org -> www/vincent.demeester.fr/content/legacy/articles/clojure.org | 0
Rwww/vincent.demeester.fr/content/articles/config_configurations.org -> www/vincent.demeester.fr/content/legacy/articles/config_configurations.org | 0
Rwww/vincent.demeester.fr/content/articles/config_email_configuration.org -> www/vincent.demeester.fr/content/legacy/articles/config_email_configuration.org | 0
Rwww/vincent.demeester.fr/content/articles/containers.org -> www/vincent.demeester.fr/content/legacy/articles/containers.org | 0
Rwww/vincent.demeester.fr/content/articles/continuous_deployment.org -> www/vincent.demeester.fr/content/legacy/articles/continuous_deployment.org | 0
Rwww/vincent.demeester.fr/content/articles/continuous_integration.org -> www/vincent.demeester.fr/content/legacy/articles/continuous_integration.org | 0
Rwww/vincent.demeester.fr/content/articles/css.org -> www/vincent.demeester.fr/content/legacy/articles/css.org | 0
Rwww/vincent.demeester.fr/content/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/david/2020/02/14/index.html -> www/vincent.demeester.fr/content/legacy/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/david/2020/02/14/index.html | 0
Rwww/vincent.demeester.fr/content/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/fonts/FiraCode-Retina.woff -> www/vincent.demeester.fr/content/legacy/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/fonts/FiraCode-Retina.woff | 0
Rwww/vincent.demeester.fr/content/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/fonts/FiraCode-Retina.woff2 -> www/vincent.demeester.fr/content/legacy/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/fonts/FiraCode-Retina.woff2 | 0
Rwww/vincent.demeester.fr/content/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/fonts/triplicate_t4_poly_bold.woff -> www/vincent.demeester.fr/content/legacy/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/fonts/triplicate_t4_poly_bold.woff | 0
Rwww/vincent.demeester.fr/content/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/fonts/triplicate_t4_poly_bold.woff2 -> www/vincent.demeester.fr/content/legacy/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/fonts/triplicate_t4_poly_bold.woff2 | 0
Rwww/vincent.demeester.fr/content/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/fonts/triplicate_t4_poly_italic.woff -> www/vincent.demeester.fr/content/legacy/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/fonts/triplicate_t4_poly_italic.woff | 0
Rwww/vincent.demeester.fr/content/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/fonts/triplicate_t4_poly_italic.woff2 -> www/vincent.demeester.fr/content/legacy/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/fonts/triplicate_t4_poly_italic.woff2 | 0
Rwww/vincent.demeester.fr/content/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/fonts/triplicate_t4_poly_regular.woff -> www/vincent.demeester.fr/content/legacy/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/fonts/triplicate_t4_poly_regular.woff | 0
Rwww/vincent.demeester.fr/content/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/fonts/triplicate_t4_poly_regular.woff2 -> www/vincent.demeester.fr/content/legacy/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/fonts/triplicate_t4_poly_regular.woff2 | 0
Rwww/vincent.demeester.fr/content/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/style_2020-01-24.css -> www/vincent.demeester.fr/content/legacy/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/style_2020-01-24.css | 0
Rwww/vincent.demeester.fr/content/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/icons2/favicon.ico -> www/vincent.demeester.fr/content/legacy/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/icons2/favicon.ico | 0
Rwww/vincent.demeester.fr/content/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/js/instantpage-3.0.0.min.js -> www/vincent.demeester.fr/content/legacy/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/js/instantpage-3.0.0.min.js | 0
Rwww/vincent.demeester.fr/content/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/org-board-7af3b3f4-a1f0-4adf-80f5-833d55ca7f86.log -> www/vincent.demeester.fr/content/legacy/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/org-board-7af3b3f4-a1f0-4adf-80f5-833d55ca7f86.log | 0
Rwww/vincent.demeester.fr/content/articles/data/89/96fae4-8792-4fcb-a18c-f625d63881e0/2013-11-12-Get-started-with-Ledger.png -> www/vincent.demeester.fr/content/legacy/articles/data/89/96fae4-8792-4fcb-a18c-f625d63881e0/2013-11-12-Get-started-with-Ledger.png | 0
Rwww/vincent.demeester.fr/content/articles/data/89/96fae4-8792-4fcb-a18c-f625d63881e0/elbank-ynab.html -> www/vincent.demeester.fr/content/legacy/articles/data/89/96fae4-8792-4fcb-a18c-f625d63881e0/elbank-ynab.html | 0
Rwww/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/org-board-ac212115-e0c6-448c-9344-17c10aeb9694.log -> www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/org-board-ac212115-e0c6-448c-9344-17c10aeb9694.log | 0
Rwww/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/articles/emacs-org-mode-generate-ids.html -> www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/articles/emacs-org-mode-generate-ids.html | 0
Rwww/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/articles/images/org-export1.png -> www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/articles/images/org-export1.png | 0
Rwww/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/articles/images/org-export2.png -> www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/articles/images/org-export2.png | 0
Rwww/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-bold-line-figures/et-book-bold-line-figures.eot -> www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-bold-line-figures/et-book-bold-line-figures.eot | 0
Rwww/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-bold-line-figures/et-book-bold-line-figures.svg -> www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-bold-line-figures/et-book-bold-line-figures.svg | 0
Rwww/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-bold-line-figures/et-book-bold-line-figures.ttf -> www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-bold-line-figures/et-book-bold-line-figures.ttf | 0
Rwww/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-bold-line-figures/et-book-bold-line-figures.woff -> www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-bold-line-figures/et-book-bold-line-figures.woff | 0
Rwww/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.eot -> www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.eot | 0
Rwww/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.svg -> www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.svg | 0
Rwww/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.ttf -> www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.ttf | 0
Rwww/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.woff -> www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.woff | 0
Rwww/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-roman-line-figures/et-book-roman-line-figures.eot -> www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-roman-line-figures/et-book-roman-line-figures.eot | 0
Rwww/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-roman-line-figures/et-book-roman-line-figures.svg -> www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-roman-line-figures/et-book-roman-line-figures.svg | 0
Rwww/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-roman-line-figures/et-book-roman-line-figures.ttf -> www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-roman-line-figures/et-book-roman-line-figures.ttf | 0
Rwww/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-roman-line-figures/et-book-roman-line-figures.woff -> www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-roman-line-figures/et-book-roman-line-figures.woff | 0
Rwww/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.eot -> www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.eot | 0
Rwww/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.svg -> www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.svg | 0
Rwww/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.ttf -> www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.ttf | 0
Rwww/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.woff -> www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.woff | 0
Rwww/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/tufte.css -> www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/tufte.css | 0
Rwww/vincent.demeester.fr/content/articles/data/b9/059b7e-29c0-4e7f-a1c1-901d812109bf/architecture.png -> www/vincent.demeester.fr/content/legacy/articles/data/b9/059b7e-29c0-4e7f-a1c1-901d812109bf/architecture.png | 0
Rwww/vincent.demeester.fr/content/articles/data/d4/868e4e-61bc-4904-b683-f165569f8baf/lifecycle.png -> www/vincent.demeester.fr/content/legacy/articles/data/d4/868e4e-61bc-4904-b683-f165569f8baf/lifecycle.png | 0
Rwww/vincent.demeester.fr/content/articles/development-machines.org -> www/vincent.demeester.fr/content/legacy/articles/development-machines.org | 0
Rwww/vincent.demeester.fr/content/articles/documentation.org -> www/vincent.demeester.fr/content/legacy/articles/documentation.org | 0
Rwww/vincent.demeester.fr/content/articles/dogfooding.org -> www/vincent.demeester.fr/content/legacy/articles/dogfooding.org | 0
Rwww/vincent.demeester.fr/content/articles/emacs.old.org -> www/vincent.demeester.fr/content/legacy/articles/emacs.old.org | 0
Rwww/vincent.demeester.fr/content/articles/emacs.org -> www/vincent.demeester.fr/content/legacy/articles/emacs.org | 0
Rwww/vincent.demeester.fr/content/articles/emacs_appearance.org -> www/vincent.demeester.fr/content/legacy/articles/emacs_appearance.org | 0
Rwww/vincent.demeester.fr/content/articles/emacs_keep_it_clean.org -> www/vincent.demeester.fr/content/legacy/articles/emacs_keep_it_clean.org | 0
Rwww/vincent.demeester.fr/content/articles/emacs_lisp.org -> www/vincent.demeester.fr/content/legacy/articles/emacs_lisp.org | 0
Rwww/vincent.demeester.fr/content/articles/emacs_projects.org -> www/vincent.demeester.fr/content/legacy/articles/emacs_projects.org | 0
Rwww/vincent.demeester.fr/content/articles/email.org -> www/vincent.demeester.fr/content/legacy/articles/email.org | 0
Rwww/vincent.demeester.fr/content/articles/empathy_online.org -> www/vincent.demeester.fr/content/legacy/articles/empathy_online.org | 0
Rwww/vincent.demeester.fr/content/articles/eshell.org -> www/vincent.demeester.fr/content/legacy/articles/eshell.org | 0
Rwww/vincent.demeester.fr/content/articles/fedora-silverblue.org -> www/vincent.demeester.fr/content/legacy/articles/fedora-silverblue.org | 0
Rwww/vincent.demeester.fr/content/articles/fedora.org -> www/vincent.demeester.fr/content/legacy/articles/fedora.org | 0
Rwww/vincent.demeester.fr/content/articles/feeds.org -> www/vincent.demeester.fr/content/legacy/articles/feeds.org | 0
Rwww/vincent.demeester.fr/content/articles/firefox.org -> www/vincent.demeester.fr/content/legacy/articles/firefox.org | 0
Rwww/vincent.demeester.fr/content/articles/fish.org -> www/vincent.demeester.fr/content/legacy/articles/fish.org | 0
Rwww/vincent.demeester.fr/content/articles/foo.gif -> www/vincent.demeester.fr/content/legacy/articles/foo.gif | 0
Rwww/vincent.demeester.fr/content/articles/generics_aren_t_ready_for_go_drew_devault_s_blog.org -> www/vincent.demeester.fr/content/legacy/articles/generics_aren_t_ready_for_go_drew_devault_s_blog.org | 0
Rwww/vincent.demeester.fr/content/articles/getting_boxes_done.org -> www/vincent.demeester.fr/content/legacy/articles/getting_boxes_done.org | 0
Rwww/vincent.demeester.fr/content/articles/getting_boxes_done_the_code.org -> www/vincent.demeester.fr/content/legacy/articles/getting_boxes_done_the_code.org | 0
Rwww/vincent.demeester.fr/content/articles/getting_started_with_qemu_drew_devault_s_blog.org -> www/vincent.demeester.fr/content/legacy/articles/getting_started_with_qemu_drew_devault_s_blog.org | 0
Rwww/vincent.demeester.fr/content/articles/git.org -> www/vincent.demeester.fr/content/legacy/articles/git.org | 0
Rwww/vincent.demeester.fr/content/articles/git_annex.org -> www/vincent.demeester.fr/content/legacy/articles/git_annex.org | 0
Rwww/vincent.demeester.fr/content/articles/git_sr_ht.org -> www/vincent.demeester.fr/content/legacy/articles/git_sr_ht.org | 0
Rwww/vincent.demeester.fr/content/articles/github.org -> www/vincent.demeester.fr/content/legacy/articles/github.org | 0
Rwww/vincent.demeester.fr/content/articles/gitlab.org -> www/vincent.demeester.fr/content/legacy/articles/gitlab.org | 0
Rwww/vincent.demeester.fr/content/articles/gitops.org -> www/vincent.demeester.fr/content/legacy/articles/gitops.org | 0
Rwww/vincent.demeester.fr/content/articles/gnupg.org -> www/vincent.demeester.fr/content/legacy/articles/gnupg.org | 0
Rwww/vincent.demeester.fr/content/articles/gnus.org -> www/vincent.demeester.fr/content/legacy/articles/gnus.org | 0
Rwww/vincent.demeester.fr/content/articles/go.org -> www/vincent.demeester.fr/content/legacy/articles/go.org | 0
Rwww/vincent.demeester.fr/content/articles/haskell.org -> www/vincent.demeester.fr/content/legacy/articles/haskell.org | 0
Rwww/vincent.demeester.fr/content/articles/how_i_decide_between_many_programming_languages_drew_devault_s_blog.org -> www/vincent.demeester.fr/content/legacy/articles/how_i_decide_between_many_programming_languages_drew_devault_s_blog.org | 0
Rwww/vincent.demeester.fr/content/articles/how_to_drive_upstream_project.org -> www/vincent.demeester.fr/content/legacy/articles/how_to_drive_upstream_project.org | 0
Rwww/vincent.demeester.fr/content/articles/images/documentation/overview.png -> www/vincent.demeester.fr/content/legacy/articles/images/documentation/overview.png | 0
Rwww/vincent.demeester.fr/content/articles/images/sandbox/long-img.png -> www/vincent.demeester.fr/content/legacy/articles/images/sandbox/long-img.png | 0
Rwww/vincent.demeester.fr/content/articles/images/sandbox/pic-demo.png -> www/vincent.demeester.fr/content/legacy/articles/images/sandbox/pic-demo.png | 0
Rwww/vincent.demeester.fr/content/articles/images/sandbox/some_filename.png -> www/vincent.demeester.fr/content/legacy/articles/images/sandbox/some_filename.png | 0
Rwww/vincent.demeester.fr/content/articles/images/tekton/canary-pipeline.png -> www/vincent.demeester.fr/content/legacy/articles/images/tekton/canary-pipeline.png | 0
Rwww/vincent.demeester.fr/content/articles/images/tekton/tekton-horizontal-color.png -> www/vincent.demeester.fr/content/legacy/articles/images/tekton/tekton-horizontal-color.png | 0
Rwww/vincent.demeester.fr/content/articles/index.org -> www/vincent.demeester.fr/content/legacy/articles/index.org | 0
Rwww/vincent.demeester.fr/content/articles/individual_contributors.org -> www/vincent.demeester.fr/content/legacy/articles/individual_contributors.org | 0
Rwww/vincent.demeester.fr/content/articles/infrastructure.org -> www/vincent.demeester.fr/content/legacy/articles/infrastructure.org | 0
Rwww/vincent.demeester.fr/content/articles/internet_of_things.org -> www/vincent.demeester.fr/content/legacy/articles/internet_of_things.org | 0
Rwww/vincent.demeester.fr/content/articles/ipfs.org -> www/vincent.demeester.fr/content/legacy/articles/ipfs.org | 0
Rwww/vincent.demeester.fr/content/articles/keyboard.org -> www/vincent.demeester.fr/content/legacy/articles/keyboard.org | 0
Rwww/vincent.demeester.fr/content/articles/kind.org -> www/vincent.demeester.fr/content/legacy/articles/kind.org | 0
Rwww/vincent.demeester.fr/content/articles/knative.org -> www/vincent.demeester.fr/content/legacy/articles/knative.org | 0
Rwww/vincent.demeester.fr/content/articles/kubernetes.org -> www/vincent.demeester.fr/content/legacy/articles/kubernetes.org | 0
Rwww/vincent.demeester.fr/content/articles/kubernetes_on_nixos.org -> www/vincent.demeester.fr/content/legacy/articles/kubernetes_on_nixos.org | 0
Rwww/vincent.demeester.fr/content/articles/kubernix.org -> www/vincent.demeester.fr/content/legacy/articles/kubernix.org | 0
Rwww/vincent.demeester.fr/content/articles/leadership.org -> www/vincent.demeester.fr/content/legacy/articles/leadership.org | 0
Rwww/vincent.demeester.fr/content/articles/libvirt.org -> www/vincent.demeester.fr/content/legacy/articles/libvirt.org | 0
Rwww/vincent.demeester.fr/content/articles/linux.org -> www/vincent.demeester.fr/content/legacy/articles/linux.org | 0
Rwww/vincent.demeester.fr/content/articles/lisp.org -> www/vincent.demeester.fr/content/legacy/articles/lisp.org | 0
Rwww/vincent.demeester.fr/content/articles/mac.org -> www/vincent.demeester.fr/content/legacy/articles/mac.org | 0
Rwww/vincent.demeester.fr/content/articles/make.org -> www/vincent.demeester.fr/content/legacy/articles/make.org | 0
Rwww/vincent.demeester.fr/content/articles/meta_meta.org -> www/vincent.demeester.fr/content/legacy/articles/meta_meta.org | 0
Awww/vincent.demeester.fr/content/legacy/articles/meta_publishing_this_website.org | 610++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rwww/vincent.demeester.fr/content/articles/minikube.org -> www/vincent.demeester.fr/content/legacy/articles/minikube.org | 0
Rwww/vincent.demeester.fr/content/articles/moby_project.org -> www/vincent.demeester.fr/content/legacy/articles/moby_project.org | 0
Rwww/vincent.demeester.fr/content/articles/my_organizational_workflow.org -> www/vincent.demeester.fr/content/legacy/articles/my_organizational_workflow.org | 0
Rwww/vincent.demeester.fr/content/articles/my_personal_journey_from_mit_to_gpl_drew_devault_s_blog.org -> www/vincent.demeester.fr/content/legacy/articles/my_personal_journey_from_mit_to_gpl_drew_devault_s_blog.org | 0
Rwww/vincent.demeester.fr/content/articles/nginx.org -> www/vincent.demeester.fr/content/legacy/articles/nginx.org | 0
Rwww/vincent.demeester.fr/content/articles/nix.org -> www/vincent.demeester.fr/content/legacy/articles/nix.org | 0
Rwww/vincent.demeester.fr/content/articles/nixos-overlays.org -> www/vincent.demeester.fr/content/legacy/articles/nixos-overlays.org | 0
Rwww/vincent.demeester.fr/content/articles/nixos.org -> www/vincent.demeester.fr/content/legacy/articles/nixos.org | 0
Rwww/vincent.demeester.fr/content/articles/notmuch.org -> www/vincent.demeester.fr/content/legacy/articles/notmuch.org | 0
Rwww/vincent.demeester.fr/content/articles/ocp-bootstrap.xml -> www/vincent.demeester.fr/content/legacy/articles/ocp-bootstrap.xml | 0
Rwww/vincent.demeester.fr/content/articles/open_container_initiative.org -> www/vincent.demeester.fr/content/legacy/articles/open_container_initiative.org | 0
Rwww/vincent.demeester.fr/content/articles/openbsd.org -> www/vincent.demeester.fr/content/legacy/articles/openbsd.org | 0
Rwww/vincent.demeester.fr/content/articles/opencontainers_artifacts_oci_artifacts.org -> www/vincent.demeester.fr/content/legacy/articles/opencontainers_artifacts_oci_artifacts.org | 0
Rwww/vincent.demeester.fr/content/articles/opencontainers_distribution_spec_oci_distribution_specification.org -> www/vincent.demeester.fr/content/legacy/articles/opencontainers_distribution_spec_oci_distribution_specification.org | 0
Rwww/vincent.demeester.fr/content/articles/opencontainers_image_spec_oci_image_format.org -> www/vincent.demeester.fr/content/legacy/articles/opencontainers_image_spec_oci_image_format.org | 0
Rwww/vincent.demeester.fr/content/articles/opencontainers_runtime_spec_oci_runtime_specification.org -> www/vincent.demeester.fr/content/legacy/articles/opencontainers_runtime_spec_oci_runtime_specification.org | 0
Rwww/vincent.demeester.fr/content/articles/opendatahub_opendatahub.org -> www/vincent.demeester.fr/content/legacy/articles/opendatahub_opendatahub.org | 0
Rwww/vincent.demeester.fr/content/articles/openshift-commons.org -> www/vincent.demeester.fr/content/legacy/articles/openshift-commons.org | 0
Rwww/vincent.demeester.fr/content/articles/openshift-commons/app.yaml -> www/vincent.demeester.fr/content/legacy/articles/openshift-commons/app.yaml | 0
Rwww/vincent.demeester.fr/content/articles/openshift-commons/pipeline-petclinic-deploy.yaml -> www/vincent.demeester.fr/content/legacy/articles/openshift-commons/pipeline-petclinic-deploy.yaml | 0
Rwww/vincent.demeester.fr/content/articles/openshift-commons/resources.yaml -> www/vincent.demeester.fr/content/legacy/articles/openshift-commons/resources.yaml | 0
Rwww/vincent.demeester.fr/content/articles/openshift-commons/task-openshift-client.yaml -> www/vincent.demeester.fr/content/legacy/articles/openshift-commons/task-openshift-client.yaml | 0
Rwww/vincent.demeester.fr/content/articles/openshift-commons/task-s2i-java-8.yaml -> www/vincent.demeester.fr/content/legacy/articles/openshift-commons/task-s2i-java-8.yaml | 0
Rwww/vincent.demeester.fr/content/articles/openshift.org -> www/vincent.demeester.fr/content/legacy/articles/openshift.org | 0
Rwww/vincent.demeester.fr/content/articles/openshift_on_vm_bare_metal.org -> www/vincent.demeester.fr/content/legacy/articles/openshift_on_vm_bare_metal.org | 0
Rwww/vincent.demeester.fr/content/articles/openshift_pipeline.org -> www/vincent.demeester.fr/content/legacy/articles/openshift_pipeline.org | 0
Rwww/vincent.demeester.fr/content/articles/org_library_of_babel.org -> www/vincent.demeester.fr/content/legacy/articles/org_library_of_babel.org | 0
Rwww/vincent.demeester.fr/content/articles/org_mode.org -> www/vincent.demeester.fr/content/legacy/articles/org_mode.org | 0
Rwww/vincent.demeester.fr/content/articles/org_roam.org -> www/vincent.demeester.fr/content/legacy/articles/org_roam.org | 0
Rwww/vincent.demeester.fr/content/articles/personal_knowledge_base.org -> www/vincent.demeester.fr/content/legacy/articles/personal_knowledge_base.org | 0
Rwww/vincent.demeester.fr/content/articles/preseed.cfg -> www/vincent.demeester.fr/content/legacy/articles/preseed.cfg | 0
Rwww/vincent.demeester.fr/content/articles/programming.org -> www/vincent.demeester.fr/content/legacy/articles/programming.org | 0
Rwww/vincent.demeester.fr/content/articles/python.org -> www/vincent.demeester.fr/content/legacy/articles/python.org | 0
Rwww/vincent.demeester.fr/content/articles/red_hat.org -> www/vincent.demeester.fr/content/legacy/articles/red_hat.org | 0
Rwww/vincent.demeester.fr/content/articles/refiling_trees_to_files.org -> www/vincent.demeester.fr/content/legacy/articles/refiling_trees_to_files.org | 0
Rwww/vincent.demeester.fr/content/articles/remote-development.org -> www/vincent.demeester.fr/content/legacy/articles/remote-development.org | 0
Rwww/vincent.demeester.fr/content/articles/remote.org -> www/vincent.demeester.fr/content/legacy/articles/remote.org | 0
Rwww/vincent.demeester.fr/content/articles/remote_team.org -> www/vincent.demeester.fr/content/legacy/articles/remote_team.org | 0
Rwww/vincent.demeester.fr/content/articles/roam_research_why_i_love_it_and_how_i_use_it_nat_eliason.org -> www/vincent.demeester.fr/content/legacy/articles/roam_research_why_i_love_it_and_how_i_use_it_nat_eliason.org | 0
Rwww/vincent.demeester.fr/content/articles/rpi.org -> www/vincent.demeester.fr/content/legacy/articles/rpi.org | 0
Rwww/vincent.demeester.fr/content/articles/runc.org -> www/vincent.demeester.fr/content/legacy/articles/runc.org | 0
Rwww/vincent.demeester.fr/content/articles/rust.org -> www/vincent.demeester.fr/content/legacy/articles/rust.org | 0
Rwww/vincent.demeester.fr/content/articles/sandbox.org -> www/vincent.demeester.fr/content/legacy/articles/sandbox.org | 0
Rwww/vincent.demeester.fr/content/articles/serverless.org -> www/vincent.demeester.fr/content/legacy/articles/serverless.org | 0
Rwww/vincent.demeester.fr/content/articles/simple_correct_fast_in_that_order_drew_devault_s_blog.org -> www/vincent.demeester.fr/content/legacy/articles/simple_correct_fast_in_that_order_drew_devault_s_blog.org | 0
Rwww/vincent.demeester.fr/content/articles/ssh.org -> www/vincent.demeester.fr/content/legacy/articles/ssh.org | 0
Rwww/vincent.demeester.fr/content/articles/talks.org -> www/vincent.demeester.fr/content/legacy/articles/talks.org | 0
Rwww/vincent.demeester.fr/content/articles/team_lead.org -> www/vincent.demeester.fr/content/legacy/articles/team_lead.org | 0
Rwww/vincent.demeester.fr/content/articles/tekton-effective.org -> www/vincent.demeester.fr/content/legacy/articles/tekton-effective.org | 0
Rwww/vincent.demeester.fr/content/articles/tekton-migrating-from-jenkins.org -> www/vincent.demeester.fr/content/legacy/articles/tekton-migrating-from-jenkins.org | 0
Rwww/vincent.demeester.fr/content/articles/tekton-pipeline-without-pipeline-resources.org -> www/vincent.demeester.fr/content/legacy/articles/tekton-pipeline-without-pipeline-resources.org | 0
Rwww/vincent.demeester.fr/content/articles/tekton-usage.org -> www/vincent.demeester.fr/content/legacy/articles/tekton-usage.org | 0
Rwww/vincent.demeester.fr/content/articles/tekton.org -> www/vincent.demeester.fr/content/legacy/articles/tekton.org | 0
Rwww/vincent.demeester.fr/content/articles/tekton_dev.org -> www/vincent.demeester.fr/content/legacy/articles/tekton_dev.org | 0
Rwww/vincent.demeester.fr/content/articles/test.resource.yaml -> www/vincent.demeester.fr/content/legacy/articles/test.resource.yaml | 0
Rwww/vincent.demeester.fr/content/articles/test.yaml -> www/vincent.demeester.fr/content/legacy/articles/test.yaml | 0
Rwww/vincent.demeester.fr/content/articles/testing.org -> www/vincent.demeester.fr/content/legacy/articles/testing.org | 0
Rwww/vincent.demeester.fr/content/articles/typography.org -> www/vincent.demeester.fr/content/legacy/articles/typography.org | 0
Rwww/vincent.demeester.fr/content/articles/ubuntu.org -> www/vincent.demeester.fr/content/legacy/articles/ubuntu.org | 0
Rwww/vincent.demeester.fr/content/articles/virtualization.org -> www/vincent.demeester.fr/content/legacy/articles/virtualization.org | 0
Rwww/vincent.demeester.fr/content/articles/vterm.org -> www/vincent.demeester.fr/content/legacy/articles/vterm.org | 0
Rwww/vincent.demeester.fr/content/articles/wireguard.org -> www/vincent.demeester.fr/content/legacy/articles/wireguard.org | 0
Rwww/vincent.demeester.fr/content/articles/writing_technical_book.org -> www/vincent.demeester.fr/content/legacy/articles/writing_technical_book.org | 0
Rwww/vincent.demeester.fr/content/articles/yoga.org -> www/vincent.demeester.fr/content/legacy/articles/yoga.org | 0
Rwww/vincent.demeester.fr/content/articles/yubikey.org -> www/vincent.demeester.fr/content/legacy/articles/yubikey.org | 0
Rwww/vincent.demeester.fr/content/articles/zsh.org -> www/vincent.demeester.fr/content/legacy/articles/zsh.org | 0
Awww/vincent.demeester.fr/content/legacy/posts/2012-05-07-reinit-and-jekyll.org | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awww/vincent.demeester.fr/content/legacy/posts/2012-05-08-gitolite-quick-and-dirty-mirror.org | 126+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awww/vincent.demeester.fr/content/legacy/posts/2012-05-13-jekyll-foreman-guard-bundler.org | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awww/vincent.demeester.fr/content/legacy/posts/2012-07-23-maven-release-gitflow.org | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awww/vincent.demeester.fr/content/legacy/posts/2012-12-16-gollum-comme-wiki-personnel.org | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awww/vincent.demeester.fr/content/legacy/posts/2013-09-08-maven-tmpfs.org | 158+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awww/vincent.demeester.fr/content/legacy/posts/2013-10-12-podcasts.org | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awww/vincent.demeester.fr/content/legacy/posts/2014-03-24-redesign-et-rΓ©solutions.org | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awww/vincent.demeester.fr/content/legacy/posts/2018-07-28-gotest-tools-intro.org | 45+++++++++++++++++++++++++++++++++++++++++++++
Awww/vincent.demeester.fr/content/legacy/posts/2018-08-16-gotest-tools-assertions.org | 340+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awww/vincent.demeester.fr/content/legacy/posts/2018-09-01-gotest-tools-skip.org | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awww/vincent.demeester.fr/content/legacy/posts/2018-09-06-gotest-tools-golden.org | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awww/vincent.demeester.fr/content/legacy/posts/2018-09-14-gotest-tools-fs.org | 187+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awww/vincent.demeester.fr/content/legacy/posts/2018-09-18-gotest-tools-icmd.org | 209+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awww/vincent.demeester.fr/content/legacy/posts/2019-01-20-2018-year-review.org | 151++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awww/vincent.demeester.fr/content/legacy/posts/2019-01-26-nix-run-alias.org | 200+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awww/vincent.demeester.fr/content/legacy/posts/2019-03-23-gotest-tools-poll.org | 136+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awww/vincent.demeester.fr/content/legacy/posts/2020-02-22-digital-minimalism.org | 130+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awww/vincent.demeester.fr/content/legacy/posts/2020-03-22-org-mode-website.org | 562+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awww/vincent.demeester.fr/content/legacy/posts/2020-04-15-emacs-bankruptcy-is-fun.org | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awww/vincent.demeester.fr/content/legacy/posts/2020-06-21-website-update.org | 16++++++++++++++++
Awww/vincent.demeester.fr/content/legacy/posts/2020-07-08-june-status-update.org | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awww/vincent.demeester.fr/content/legacy/posts/2020-12-01-nixify-www.draft | 8++++++++
Awww/vincent.demeester.fr/content/legacy/posts/diving-into-nix.org.draft | 8++++++++
Awww/vincent.demeester.fr/content/legacy/posts/index.org | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awww/vincent.demeester.fr/content/legacy/posts/quick-kubernetes-provisionning.org.draft | 5+++++
Dwww/vincent.demeester.fr/content/posts/2012-05-07-reinit-and-jekyll.org | 61-------------------------------------------------------------
Dwww/vincent.demeester.fr/content/posts/2012-05-08-gitolite-quick-and-dirty-mirror.org | 126-------------------------------------------------------------------------------
Dwww/vincent.demeester.fr/content/posts/2012-05-13-jekyll-foreman-guard-bundler.org | 90-------------------------------------------------------------------------------
Dwww/vincent.demeester.fr/content/posts/2012-07-23-maven-release-gitflow.org | 99-------------------------------------------------------------------------------
Dwww/vincent.demeester.fr/content/posts/2012-12-16-gollum-comme-wiki-personnel.org | 84-------------------------------------------------------------------------------
Dwww/vincent.demeester.fr/content/posts/2013-09-08-maven-tmpfs.org | 158-------------------------------------------------------------------------------
Dwww/vincent.demeester.fr/content/posts/2013-10-12-podcasts.org | 91-------------------------------------------------------------------------------
Dwww/vincent.demeester.fr/content/posts/2014-03-24-redesign-et-rΓ©solutions.org | 76----------------------------------------------------------------------------
Dwww/vincent.demeester.fr/content/posts/2018-07-28-gotest-tools-intro.org | 45---------------------------------------------
Dwww/vincent.demeester.fr/content/posts/2018-08-16-gotest-tools-assertions.org | 340-------------------------------------------------------------------------------
Dwww/vincent.demeester.fr/content/posts/2018-09-01-gotest-tools-skip.org | 59-----------------------------------------------------------
Dwww/vincent.demeester.fr/content/posts/2018-09-06-gotest-tools-golden.org | 75---------------------------------------------------------------------------
Dwww/vincent.demeester.fr/content/posts/2018-09-14-gotest-tools-fs.org | 187-------------------------------------------------------------------------------
Dwww/vincent.demeester.fr/content/posts/2018-09-18-gotest-tools-icmd.org | 209-------------------------------------------------------------------------------
Dwww/vincent.demeester.fr/content/posts/2019-01-20-2018-year-review.org | 151------------------------------------------------------------------------------
Dwww/vincent.demeester.fr/content/posts/2019-01-26-nix-run-alias.org | 200-------------------------------------------------------------------------------
Dwww/vincent.demeester.fr/content/posts/2019-03-23-gotest-tools-poll.org | 136-------------------------------------------------------------------------------
Dwww/vincent.demeester.fr/content/posts/2020-02-22-digital-minimalism.org | 130-------------------------------------------------------------------------------
Dwww/vincent.demeester.fr/content/posts/2020-03-22-org-mode-website.org | 562-------------------------------------------------------------------------------
Dwww/vincent.demeester.fr/content/posts/2020-04-15-emacs-bankruptcy-is-fun.org | 108-------------------------------------------------------------------------------
Dwww/vincent.demeester.fr/content/posts/2020-06-21-website-update.org | 16----------------
Dwww/vincent.demeester.fr/content/posts/2020-07-08-june-status-update.org | 109-------------------------------------------------------------------------------
Dwww/vincent.demeester.fr/content/posts/2020-12-01-nixify-www.draft | 8--------
Dwww/vincent.demeester.fr/content/posts/diving-into-nix.org.draft | 8--------
Dwww/vincent.demeester.fr/content/posts/quick-kubernetes-provisionning.org.draft | 5-----
Awww/vincent.demeester.fr/content/sitemap.org | 15+++++++++++++++
Mwww/vincent.demeester.fr/lib/publish.el | 8++++----
255 files changed, 3854 insertions(+), 3890 deletions(-)

diff --git a/www/vincent.demeester.fr/Makefile b/www/vincent.demeester.fr/Makefile @@ -56,7 +56,7 @@ build: lib/publish.el lib/publish-common.el build-articles .PHONY: build-articles build-articles: $(NOTES) - rsync -arv --delete --copy-links --exclude='*.private.org' --exclude='*.db' $(NOTES)/ content/articles/ + rsync -arv --delete --copy-links --exclude='*.private.org' --exclude='*.db' $(NOTES)/ content/legacy/articles/ $(NOTES): $(error $(NOTES) doesn't exists…) diff --git a/www/vincent.demeester.fr/content/articles/meta_publishing_this_website.org b/www/vincent.demeester.fr/content/articles/meta_publishing_this_website.org @@ -1,607 +0,0 @@ -# -*- mode: org; eval: (add-hook 'after-save-hook (lambda () (org-babel-tangle)) nil t) -*- -#+TITLE: meta: publishing this website -#+ROAM_ALIAS: "publishing this website" - -This is a *always up-to-date* version of my initial [[https://vincent.demeester.fr/posts/2020-03-22-org-mode-website.html][post]] about publishing this website -using [[file:org_mode.org][org-mode]]. This uses the [[https://orgmode.org/manual/Extracting-Source-Code.html][tangle]] feature of =org-mode= and will span from the -=Makefile= of the [[https://git.sr.ht/~vdemeester/www][repository]] to any [[file:elisp.org][emacs-lisp]] code required. - -This is part of the [[file:meta_meta.org][Meta]] entry. - -#+TOC: headlines 3 - -* Parts of the website - -Let's look at the different part of the website and for each where I do get the -information I want to publish. - -- =/= :: this is just a file (=index.org=) that I maintain manually. -- =/posts= :: this is what we call /blog/ these days : short to medium article that are valid - at a point of time, as may contain /deprecated/ content, or content that do not reflect - my views at a later point in time. -- =/articles= :: medium to long article about a topic. Those should be up-to-date or - explicitly mark as deprecated or invalid. This is my *ready for the public* knowledge - database, a bit like [[https://braindump.jethro.dev/][Jethro's Braindump]]. It is managed using [[file:org_roam.org][org-roam]] and I just need to - get the latests somewhere to publish it. -- =/configurations= :: medium to long article about my configurations. Those are base - sources for my ~home~ /configuration/ mono-repository, and usually follow literate - programming principles. The are managed using [[file:org_roam.org][org-roam]] with the =config:= prefix. -- =/files= :: a dump of random files, it is actually on another domain name, completely - unmanaged by this. -- =/about= :: an about page about the author of the website (aka [[https://vincent.demeester.fr][me]]), linking external - contributions (GitHub/Gitlab/… profiles, Talks, …). - -In a nutshell, the folder hierarchy is something like : - -#+begin_src shell :tangle no -src/www -|-- about # the about folder (with one index.org) -|-- articles # <- comes from ~/desktop/org/notes~ -|-- css # the css -|-- images # the images -|-- index.org # the index πŸŽ‰ -|-- posts # the posts (format YYYY-MM-DD-{title}.org) -`-- public # the output that get deployed later on -#+end_src - -* Publishing - -** /Make/ it happen -:PROPERTIES: -:header-args: :tangle /etc/nixos/www/vincent.demeester.fr/Makefile -:header-args+: :comments org -:ID: 6191455e-95bc-4abc-a5f5-a62606ab2ea7 -:END: - -In order to publish this website, I am using [[file:make.org][make]]. In a nutshell, I am going to define a -few target to get the content from my notes, export org files into html and copy more or -less everything to the =public= folder. I will also define a clean and a publish target. - -The first part of my =Makefile= will be to define some constants that I want to use later -on. Those are mainly to easily change where to look for the notes or where the emacs -configuration is. - -#+begin_src makefile -EMACS = -ifndef EMACS -EMACS = "emacs" -endif - -DOTEMACS = -ifndef DOTEMACS -DOTEMACS = "~/.config/emacs" -endif - -PUBLISH = -ifndef PUBLISH -PUBLISH = vincent.demeester.fr -endif - -NOTES = ~/desktop/org/notes -#+end_src - -The default target will be name =build=. - -#+begin_src makefile -all: build -#+end_src - -*** Building =public/= and publishing it -:PROPERTIES: -:ID: 853f3d6b-f385-4091-9f9e-b04d17794e5c -:END: - -To build the website, we will be using [[file:emacs.org][Emacs]] in batch mode, with some shared library *and* -the actual [[id:631ced7a-f3f7-4a77-81a8-4a1884a6c4d4][publish]] script. - -#+begin_src makefile -.PHONY: build -build: publish.el publish-common.el build-articles - @echo "Publishing... with current Emacs configurations." - ${EMACS} --debug-init --batch --directory $(DOTEMACS)/lisp/ --directory $(DOTEMACS)/lisp/vorg/ \ - --load publish-common.el --load publish.el \ - --funcall org-publish-all - -.PHONY: build-articles -build-articles: $(NOTES) - rsync -arv --delete --copy-links --exclude='*.private.org' --exclude='*.db' $(NOTES)/ articles/ - -$(NOTES): - $(error $(NOTES) doesn't exists…) -#+end_src - -The =publish= target is gonna be really simple: I just need to copy the content to -=~/desktop/sites= on the current machine, and the rest is automated. - -#+begin_src makefile -#rsync -a --progress --copy-links --delete public/assets/.fancyindex/ ~/desktop/sites/dl.sbr.pm/.fancyindex/ -#rsync -a --progress --copy-links --delete public/ ~/desktop/sites/${PUBLISH}/ -.PHONY: publish -publish: build - rsync -ave ssh --progress --copy-links --delete public/assets/.fancyindex/ kerkouane.vpn:/var/www/dl.sbr.pm/.fancyindex/ - rsync -ave ssh --progress --copy-links --delete public/ kerkouane.vpn:/var/www/${PUBLISH}/ -#+end_src - -*** Local server -:PROPERTIES: -:ID: 0e26a52e-5f66-42c6-934d-b45cfc9745b2 -:END: - -Let's use =miniserve= (using [[file:nix.org][Nix]] with =nix-shell=) to serve the static website locally to -validate my changes. - -#+begin_src makefile -.PHONY: serve -serve: - nix-shell -p miniserve --command "miniserve --port=8181 --index=index.html public/" -#+end_src - -*** Final nits of the =Makefile= -:PROPERTIES: -:ID: 5bec22c8-491b-4e03-855a-4b5f859473cf -:END: - -One of the final step is to install the git hooks if any. I tend to have this target in all my -personal =Makefile= at least. Let's also define a =pre-commit= target that will hold -anything we need to do at =pre-commit=. - -#+begin_src makefile -.PHONY: install-hooks -install-hooks: - if [ -e .git ]; then nix-shell -p git --run 'git config core.hooksPath .githooks'; fi - -.PHONY: pre-commit -pre-commit: README.md -#+end_src - -And the final target is the =clean= one. This will remove any compile emacs-lisp file -(=*.elc=), the =public= folder, and some org-mode metadata. - -#+begin_src makefile -.PHONY: clean -clean: - @echo "Cleaning up.." - @-rm -rvf *.elc - @-rm -rvf public - @-rm -rv ~/.org-timestamps/* -#+end_src - -** The /publish/ scrits -:PROPERTIES: -:ID: 631ced7a-f3f7-4a77-81a8-4a1884a6c4d4 -:END: - -I've imported the script directly in here, I'll slowly split this and document it. - -*** =publish.el= -:PROPERTIES: -:header-args: :tangle /etc/nixos/www/vincent.demeester.fr/publish.el -:ID: bef868db-17f0-4a67-9c64-6dcf64ad6de1 -:END: - -#+begin_src emacs-lisp -;;; publish.el --- Publish www project -*- lexical-binding: t; -*- -;; Author: Vincent Demeester <vincent@sbr.pm> - -;;; Commentary: -;; This script will convert the org-mode files in this directory into -;; html. - -;;; Code: -(require 'package) -(require 'publish-common) - -(setq org-publish-project-alist - `(("posts" - :base-directory "posts" - :base-extension "org" - :recursive t - :publishing-function org-html-publish-to-html - :publishing-directory "./public/posts" - :exclude ,(regexp-opt '("README.org" "draft")) - :auto-sitemap t - :with-footnotes t - :with-toc nil - :with-drawers t - :sitemap-filename "index.org" - :sitemap-title "Posts" - :sitemap-format-entry sbr/org-sitemap-format-entry - :sitemap-style list - :sitemap-sort-files anti-chronologically - :sitemap-function sbr/org-publish-sitemap - :html-head-include-scripts nil - :html-head-include-default-style nil - :html-head ,sbr-website-html-head - :html-preamble sbr-website-html-preamble - :html-postamble ,sbr-website-html-postamble) - ("posts-rss" - :base-directory "posts" - :base-extension "org" - :recursive t - :html-link-home "https://vincent.demeester.fr/" - :rss-link-home "https://vincent.demeester.fr/posts/" - :html-link-use-abs-url t - :rss-extension "xml" - :publishing-directory "./public" - :publishing-function (sbr/org-rss-publish-to-rss) - :section-number nil - :exclude ".*" - :include ("index.org")) - ("articles" - :base-directory "articles" - :base-extension "org" - :recursive t - :publishing-function org-html-publish-to-html - :publishing-directory "./public/articles" - :exclude ,(regexp-opt '("README.org" "draft")) - :auto-sitemap t - :with-footnotes t - :with-toc nil - :with-drawers t - :sitemap-filename "sitemap.org" - :sitemap-title "Articles" - :sitemap-style tree - :sitemap-sort-files anti-chronologically - ;;:sitemap-format-entry sbr/org-sitemap-format-entry - ;;:sitemap-function sbr/org-publish-sitemap - :html-head-include-scripts nil - :html-head-include-default-style nil - :html-head ,sbr-website-html-head - :html-preamble sbr-website-html-preamble - :html-postamble ,sbr-website-html-postamble) - ("articles-assets" - :exclude ,(regexp-opt '("*.org")) - :base-directory "articles" - :base-extension ,site-attachments - :publishing-directory "./public/articles" - :publishing-function org-publish-attachment - :recursive t) - ("about" - :base-directory "about" - :base-extension "org" - :exclude ,(regexp-opt '("README.org" "draft")) - :index-filename "index.org" - :recursive nil - :with-footnotes t - :with-toc nil - :with-drawers t - :publishing-function org-html-publish-to-html - :publishing-directory "./public/about" - :html-head-include-scripts nil - :html-head-include-default-style nil - :html-head ,sbr-website-html-head - :html-preamble sbr-website-html-preamble - :html-postamble ,sbr-website-html-postamble) - ("index" - :base-directory "" - :base-extension "org" - :exclude ,(regexp-opt '("README.org" "draft")) - :index-filename "index.org" - :recursive nil - :with-footnotes t - :with-toc nil - :with-drawers t - :with-title nil - :publishing-function org-html-publish-to-html - :publishing-directory "./public" - :html-head-include-scripts nil - :html-head-include-default-style nil - :html-head ,sbr-website-html-head - :html-preamble sbr-website-html-preamble - :html-postamble ,sbr-website-html-postamble) - ("css" - :base-directory "./css" - :base-extension ,site-attachments - :recursive t - :publishing-directory "./public/css" - :publishing-function org-publish-attachment - :recursive t) - ("images" - :base-directory "./images" - :base-extension ,site-attachments - :publishing-directory "./public/images" - :publishing-function org-publish-attachment - :recursive t) - ("assets" - :base-directory "./assets" - :base-extension ,site-attachments - :publishing-directory "./public/assets" - :publishing-function org-publish-attachment - :recursive t) - ("legacy" - :base-directory "./legacy" - :base-extension ,site-attachments - :publishing-directory "./public/" - :publishing-function org-publish-attachment - :recursive t) - ("all" :components ("posts" "about" "index" "articles" "articles-assets" "css" "images" "assets" "legacy" "posts-rss")))) - -(provide 'publish) -;;; publish.el ends here -#+end_src - -*** =publish-common.el= -:PROPERTIES: -:header-args: :tangle /etc/nixos/www/vincent.demeester.fr/publish-common.el -:ID: bcedf3bd-49ec-4602-b916-daea6695af1e -:END: - -#+begin_src emacs-lisp -;;; publish-common.el --- Commons code for www publishing projects -*- lexical-binding: t; -*- -;; Author: Vincent Demeester <vincent@sbr.pm> - -;;; Commentary: -;; -;;; Code: -;; load org -(require 'org) -(require 'dash) -;; load org export functions -(require 'ox-publish) -(require 'ox-rss) -(require 'ox-html) -;; load org link functions -(require 'ol-man) -(require 'ol-git-link) -;; Those are mine -(require 'ol-github) -(require 'ol-gitlab) -(require 'org-attach) -;; load additional libraries -(require 'go-mode) -(require 'css-mode) -(require 'yaml-mode) -(require 'nix-mode) - -(require 's) - -(setq org-export-use-babel nil) -(setq org-link-abbrev-alist '(("att" . org-attach-expand-link))) - -;; setting to nil, avoids "Author: x" at the bottom -(setq org-export-with-section-numbers nil - org-export-with-smart-quotes t - org-export-with-toc nil) - -(defvar sbr-date-format "%b %d, %Y") - -(setq org-html-divs '((preamble "header" "top") - (content "main" "content") - (postamble "footer" "postamble")) - org-html-container-element "section" - org-html-metadata-timestamp-format sbr-date-format - org-html-checkbox-type 'unicode - org-html-html5-fancy t - org-html-doctype "html5" - org-html-htmlize-output-type 'css - org-html-htmlize-font-prefix "org-" - org-src-fontify-natively t - org-html-coding-system 'utf-8-unix) - -(defun sbr/org-export-format-drawer (name content) - "HTML export of drawer with NAME and CONTENT. -name is the name of the drawer, that will be used as class. -content is the content of the drawer" - (format "<div class='drawer %s'>\n<h6>%s</h6>\n%s</div>" - (downcase name) - (capitalize name) - content)) -(setq org-html-format-drawer-function 'sbr/org-export-format-drawer) - -(defun read-file (filePath) - "Return FILEPATH's file content." - (with-temp-buffer - (insert-file-contents filePath) - (buffer-string))) - -(defvar sbr-website-html-head - "<link rel='icon' type='image/x-icon' href='/images/favicon.ico'/> -<meta name='viewport' content='width=device-width, initial-scale=1'> -<link rel='stylesheet' href='/css/new.css' type='text/css'/> -<link rel='stylesheet' href='/css/syntax.css' type='text/css'/> -<link href='/index.xml' rel='alternate' type='application/rss+xml' title='Vincent Demeester' />") - -(defun sbr-website-html-preamble (plist) - "PLIST: An entry." - ;; Skip adding subtitle to the post if :KEYWORDS don't have 'post' has a - ;; keyword - (when (string-match-p "post" (format "%s" (plist-get plist :keywords))) - (plist-put plist - :subtitle (format "Published on %s by %s." - (org-export-get-date plist sbr-date-format) - (car (plist-get plist :author))))) - - ;; Below content will be added anyways - "<nav> -<img src=\"/images/favicon.ico\" id=\"sitelogo\"/> <a href='/'>home</a> / -<a href='/posts/'>posts</a> (<a href='/index.xml'>rss</a>) / -<a href='/articles/'>articles</a> / -<a href='https://dl.sbr.pm/'>files</a> / -<a href='/about/'>about</a></li> -</nav>") - -(defvar sbr-website-html-postamble - "<footer> - <span class='questions'>Questions, comments ? Please use my <a href=\"https://lists.sr.ht/~vdemeester/public-inbox\">public inbox</a> by sending a plain-text email to <a href=\"mailto:~vdemeester/public-inbox@lists.sr.ht\">~vdemeester/public-inbox@lists.sr.ht</a>.</span> - <span class='opinions'>Opinions stated here are my own and do not express the views of my employer, spouse, children, pets, neighbors, secret crushes, favorite authors, or anyone else who is not me. And maybe not even me, depending on how old this is.</span> - <span class='copyright'> - Content and design by Vincent Demeester - (<a rel='licence' href='http://creativecommons.org/licenses/by-nc-sa/3.0/'>Some rights reserved</a>) - </span><br /> - <span class='engine'> - Powered by <a href='https://www.gnu.org/software/emacs/'>Gnu Emacs</a> and <a href='https://orgmode.org'>orgmode</a> - </span> -</footer>") -(defvar site-attachments - (regexp-opt '("jpg" "jpeg" "gif" "png" "svg" - "ico" "cur" "css" "js" "woff" "html" "pdf" "otf")) - "File types that are published as static files.") - -(defun sbr/org-sitemap-format-entry (entry style project) - "Format posts with author and published data in the index page. - -ENTRY: file-name -STYLE: -PROJECT: `posts in this case." - (cond ((not (directory-name-p entry)) - (format "%s β€” [[file:%s][%s]] - :PROPERTIES: - :PUBDATE: [%s] - :END:" - (format-time-string "%Y-%m-%d" - (org-publish-find-date entry project)) - entry - (org-publish-find-title entry project) - (format-time-string "%Y-%m-%d" - (org-publish-find-date entry project)))) - ((eq style 'tree) (file-name-nondirectory (directory-file-name entry))) - (t entry))) - -(defun sbr/org-publish-sitemap (title list) - "" - (concat "#+TITLE: " title "\n\n" - (org-list-to-subtree list))) - -(defun sbr/org-get-first-paragraph (file) - "Get string content of first paragraph of file." - (ignore-errors - (with-temp-buffer - (insert-file-contents file) - (goto-char (point-min)) - (show-all) - (let ((first-begin (progn - (org-forward-heading-same-level 1) - (next-line) - (point))) - (first-end (progn - (org-next-visible-heading 1) - (point)))) - (buffer-substring first-begin first-end))))) - -(defun sbr/org-rss-publish-to-rss (plist filename pub-dir) - "Prepare rss.org file before exporting." - (let* ((postsdir (plist-get plist :base-directory))) - (with-current-buffer (find-file filename) - (erase-buffer) - (insert "#+TITLE: Posts\n") - (insert "#+AUTHOR: Vincent Demeester\n") - (insert "#+OPTIONS: toc:nil\n") - (let* ((files-all - (reverse (directory-files "." nil - "[0-9-]+.*\\.org$"))) - (files (seq-subseq files-all 0 (min (length files-all) 30)))) - (message (format "foo: %s" filename)) - (dolist (post files) - (let* ((post-file post) - (post-title (org-publish-find-title post-file plist)) - (preview-str (sbr/org-get-first-paragraph post-file)) - (date (replace-regexp-in-string - "\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\)-.*" - "\\1" post))) - (insert (concat "* [[file:" postsdir "/" post "][" post-title "]]\n\n")) - (org-set-property "ID" post) - (org-set-property "RSS_TITLE" post-title) - ;; ox-rss prepends html-link-home to permalink - (org-set-property "RSS_PERMALINK" - (concat postsdir "/" - (file-name-sans-extension post) - ".html")) - (org-set-property - "PUBDATE" - (format-time-string - "<%Y-%m-%d %a %H:%M>" - (org-time-string-to-time - (replace-regexp-in-string - "\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\)-.*" - "\\1" post)))) - (insert preview-str) - (newline 1) - (insert (concat "[[file:" postsdir "/" post "][(Read more)]]\n\n")))) - (save-buffer)))) - (let ((user-mail-address "t") - (org-export-with-broken-links t) - (org-rss-use-entry-url-as-guid nil)) - (org-rss-publish-to-rss plist filename pub-dir))) - -(advice-add #'org-export-get-reference :override #'unpackaged/org-export-get-reference) - -(defun unpackaged/org-export-get-reference (datum info) - "Like `org-export-get-reference', except uses heading titles instead of random numbers." - (let ((cache (plist-get info :internal-references))) - (or (car (rassq datum cache)) - (let* ((crossrefs (plist-get info :crossrefs)) - (cells (org-export-search-cells datum)) - ;; Preserve any pre-existing association between - ;; a search cell and a reference, i.e., when some - ;; previously published document referenced a location - ;; within current file (see - ;; `org-publish-resolve-external-link'). - ;; - ;; However, there is no guarantee that search cells are - ;; unique, e.g., there might be duplicate custom ID or - ;; two headings with the same title in the file. - ;; - ;; As a consequence, before re-using any reference to - ;; an element or object, we check that it doesn't refer - ;; to a previous element or object. - (new (or (cl-some - (lambda (cell) - (let ((stored (cdr (assoc cell crossrefs)))) - (when stored - (let ((old (org-export-format-reference stored))) - (and (not (assoc old cache)) stored))))) - cells) - (when (org-element-property :raw-value datum) - ;; Heading with a title - (unpackaged/org-export-new-title-reference datum cache)) - ;; NOTE: This probably breaks some Org Export - ;; feature, but if it does what I need, fine. - (org-export-format-reference - (org-export-new-reference cache)))) - (reference-string new)) - ;; Cache contains both data already associated to - ;; a reference and in-use internal references, so as to make - ;; unique references. - (dolist (cell cells) (push (cons cell new) cache)) - ;; Retain a direct association between reference string and - ;; DATUM since (1) not every object or element can be given - ;; a search cell (2) it permits quick lookup. - (push (cons reference-string datum) cache) - (plist-put info :internal-references cache) - reference-string)))) - -(defun unpackaged/org-export-new-title-reference (datum cache) - "Return new reference for DATUM that is unique in CACHE." - (cl-macrolet ((inc-suffixf (place) - `(progn - (string-match (rx bos - (minimal-match (group (1+ anything))) - (optional "--" (group (1+ digit))) - eos) - ,place) - ;; HACK: `s1' instead of a gensym. - (-let* (((s1 suffix) (list (match-string 1 ,place) - (match-string 2 ,place))) - (suffix (if suffix - (string-to-number suffix) - 0))) - (setf ,place (format "%s--%s" s1 (cl-incf suffix))))))) - (let* ((title (org-element-property :raw-value datum)) - (ref (url-hexify-string (substring-no-properties title))) - (parent (org-element-property :parent datum))) - (while (--any (equal ref (car it)) - cache) - ;; Title not unique: make it so. - (if parent - ;; Append ancestor title. - (setf title (concat (org-element-property :raw-value parent) - "--" title) - ref (url-hexify-string (substring-no-properties title)) - parent (org-element-property :parent parent)) - ;; No more ancestors: add and increment a number. - (inc-suffixf ref))) - ref))) - -(provide 'publish-common) -;;; publish-common.el ends here -#+end_src diff --git a/www/vincent.demeester.fr/content/articles/sitemap.org b/www/vincent.demeester.fr/content/articles/sitemap.org @@ -1,144 +0,0 @@ -#+TITLE: Articles - -- [[file:feeds.org][My Feeds]] -- [[file:go.org][Go]] -- [[file:emacs.org][My Emacs System]] -- [[file:documentation.org][Documentation]] -- [[file:remote-development.org][Remote developement]] -- [[file:bash.org][bash]] -- [[file:development-machines.org][Development machines made easy]] -- [[file:blog.org][Blog]] -- [[file:programming.org][Programming]] -- [[file:linux.org][Linux]] -- [[file:firefox.org][Firefox]] -- [[file:nixos.org][NixOS]] -- [[file:gnupg.org][gnupg]] -- [[file:css.org][Cascading Style Sheets]] -- [[file:org_mode.org][org-mode]] -- [[file:mac.org][Mac (OSX)]] -- [[file:typography.org][Typography]] -- [[file:testing.org][Testing]] -- [[file:rust.org][Rust]] -- [[file:email.org][email]] -- [[file:nix.org][Nix]] -- [[file:infrastructure.org][Infrastructure]] -- [[file:openbsd.org][openbsd]] -- [[file:lisp.org][Lisp]] -- [[file:remote_team.org][Remote Team]] -- [[file:make.org][make]] -- [[file:apis.org][APIs]] -- [[file:book_the_holloway_guide_to_remote_work.org][Book: The Holloway Guide to Remote Work]] -- [[file:meta_publishing_this_website.org][meta: publishing this website]] -- [[file:tekton-pipeline-without-pipeline-resources.org][Tekton Pipeline : another world without PipelineResources]] -- [[file:tekton-usage.org][Tektoncd usage and examples]] -- [[file:tekton-effective.org][Efficient Pipeline with Tekton]] -- [[file:kubernetes_on_nixos.org][Kubernetes on NixOS]] -- [[file:keyboard.org][Keyboards]] -- [[file:haskell.org][haskell]] -- [[file:tekton.org][Tekton]] -- [[file:containers.org][Containers]] -- [[file:git.org][Git]] -- [[file:org_library_of_babel.org][Library of Babel (org-mode)]] -- [[file:tekton-migrating-from-jenkins.org][Migrating from Jenkins to Tekton]] -- [[file:rpi.org][Raspberry PI]] -- [[file:kubernetes.org][Kubernetes]] -- [[file:gitops.org][GitOps]] -- [[file:emacs.old.org][My Emacs System]] -- [[file:config_configurations.org][Configurations]] -- [[file:clojure.org][Clojure]] -- [[file:remote.org][Remote work]] -- [[file:red_hat.org][Red Hat]] -- [[file:book_the_manager_s_path.org][Book: The Manager's Path]] -- [[file:gitlab.org][Gitlab]] -- [[file:book_debugging_teams.org][Book: Debugging Teams]] -- [[file:gnus.org][Gnus]] -- [[file:ubuntu.org][Ubuntu]] -- [[file:openshift_on_vm_bare_metal.org][OpenShift on VM Bare metal]] -- [[file:knative.org][Knative]] -- [[file:book_the_checklist_manifesto.org][Book: The Checklist Manifesto]] -- [[file:continuous_integration.org][continuous integration]] -- [[file:moby_project.org][Moby Project]] -- [[file:individual_contributors.org][Individual Contributors]] -- [[file:runc.org][runc]] -- [[file:git_sr_ht.org][git.sr.ht]] -- [[file:book_managing_humans.org][Book: Managing Humans]] -- [[file:architect.org][Architect]] -- [[file:leadership.org][Leadership]] -- [[file:book_a_practical_approach_to_large_scale_agile_development.org][Book: A Practical Approach to Large-Scale Agile Development]] -- [[file:book_the_now_habit.org][Book: The Now habit]] -- [[file:book_work_clean.org][Book: Work Clean]] -- [[file:kubernix.org][kubernix]] -- [[file:book_the_flinch.org][Book: The Flinch]] -- [[file:open_container_initiative.org][Open Container Initiative]] -- [[file:book_sprint.org][Book: Sprint]] -- [[file:how_to_drive_upstream_project.org][How to drive upstream project]] -- [[file:book_living_documentation.org][Book: Living Documentation]] -- [[file:continuous_deployment.org][continuous deployment]] -- [[file:vterm.org][vterm]] -- [[file:book_accelerate.org][Book: Accelerate]] -- [[file:yoga.org][Yoga]] -- [[file:team_lead.org][Team Lead]] -- [[file:book_time_management.org][Book: Time Management]] -- [[file:tekton_dev.org][Tekton development environment]] -- [[file:openshift.org][OpenShift]] -- [[file:github.org][GitHub]] -- [[file:openshift_pipeline.org][OpenShift Pipeline]] -- [[file:eshell.org][eshell]] -- [[file:ipfs.org][ipfs]] -- [[file:personal_knowledge_base.org][Personal knowledge base]] -- [[file:nginx.org][nginx]] -- [[file:book_kanban_workbook.org][Book: Kanban Workbook]] -- [[file:git_annex.org][git-annex]] -- [[file:minikube.org][minikube]] -- [[file:book_the_ultimate_guide_to_remote_work.org][Book: The Ultimate Guide To Remote Work]] -- [[file:kind.org][kind]] -- [[file:book_a_practical_guide_to_distributed_scrum.org][Book: A Practical Guide to Distributed Scrum]] -- [[file:book_make_time.org][Book: Make time]] -- [[file:dogfooding.org][dogfooding]] -- [[file:book_sleep_smarter.org][Book: Sleep Smarter]] -- [[file:writing_technical_book.org][Writing a Technical Book]] -- [[file:config_email_configuration.org][config: Email configuration]] -- [[file:internet_of_things.org][Internet of Things]] -- [[file:my_organizational_workflow.org][My Organizational Workflow]] -- [[file:ssh.org][ssh]] -- [[file:meta_meta.org][Meta]] -- [[file:serverless.org][serverless]] -- [[file:python.org][Python]] -- [[file:emacs_appearance.org][emacs: appearance]] -- [[file:notmuch.org][notmuch]] -- [[file:emacs_projects.org][emacs: Managing projects]] -- [[file:emacs_lisp.org][Emacs Lisp]] -- [[file:emacs_keep_it_clean.org][emacs: keep it clean]] -- [[file:zsh.org][zsh]] -- [[file:book_the_holloway_guide_to_equity_compensation.org][Book: The Holloway Guide to Equity Compensation]] -- [[file:sandbox.org][Emacs org-mode sandbox]] -- [[file:org_roam.org][org-roam]] -- [[file:index.org][Articles]] -- [[file:refiling_trees_to_files.org][Refiling Trees to Files]] -- [[file:getting_boxes_done_the_code.org][Getting Boxes Done, the Code]] -- [[file:getting_boxes_done.org][Getting Boxes Done]] -- [[file:fish.org][Fish shell notes]] -- [[file:virtualization.org][Virtualization]] -- [[file:fedora-silverblue.org][Fedora silverblue]] -- [[file:wireguard.org][Wireguard]] -- [[file:yubikey.org][Yubikey setup on Linux]] -- [[file:openshift-commons.org][OpenShift Commons]] -- [[file:fedora.org][Fedora]] -- [[file:libvirt.org][libvirt]] -- [[file:nixos-overlays.org][NixOS overlay notes]] -- [[file:talks.org][Talks]] -- [[file:opendatahub_opendatahub.org][OpenDataHub Β· OpenDataHub]] -- [[file:empathy_online.org][Empathy Online]] -- [[file:generics_aren_t_ready_for_go_drew_devault_s_blog.org][Generics aren’t ready for Go | Drew DeVault’s Blog]] -- [[file:roam_research_why_i_love_it_and_how_i_use_it_nat_eliason.org][Roam Research: Why I Love It and How I Use It - Nat Eliason]] -- [[file:my_personal_journey_from_mit_to_gpl_drew_devault_s_blog.org][My personal journey from MIT to GPL | Drew DeVault’s Blog]] -- [[file:building_interactive_ssh_applications_drew_devault_s_blog.org][Building interactive SSH applications | Drew DeVault’s Blog]] -- [[file:how_i_decide_between_many_programming_languages_drew_devault_s_blog.org][How I decide between many programming languages | Drew DeVault’s Blog]] -- [[file:a_philosophy_of_project_governance_drew_devault_s_blog.org][A philosophy of project governance | Drew DeVault’s Blog]] -- [[file:add_a_contrib_directory_to_your_projects_drew_devault_s_blog.org][Add a β€œcontrib” directory to your projects | Drew DeVault’s Blog]] -- [[file:opencontainers_artifacts_oci_artifacts.org][opencontainers/artifacts: OCI Artifacts]] -- [[file:opencontainers_distribution_spec_oci_distribution_specification.org][opencontainers/distribution-spec: OCI Distribution Specification]] -- [[file:opencontainers_image_spec_oci_image_format.org][opencontainers/image-spec: OCI Image Format]] -- [[file:opencontainers_runtime_spec_oci_runtime_specification.org][opencontainers/runtime-spec: OCI Runtime Specification]] -- [[file:simple_correct_fast_in_that_order_drew_devault_s_blog.org][Simple, correct, fast: in that order | Drew DeVault’s Blog]] -- [[file:getting_started_with_qemu_drew_devault_s_blog.org][Getting started with qemu | Drew DeVault’s Blog]]- \ No newline at end of file diff --git a/www/vincent.demeester.fr/content/articles/.setup.org b/www/vincent.demeester.fr/content/legacy/articles/.setup.org diff --git a/www/vincent.demeester.fr/content/articles/2021-10.private.html b/www/vincent.demeester.fr/content/legacy/articles/2021-10.private.html diff --git a/www/vincent.demeester.fr/content/articles/Makefile b/www/vincent.demeester.fr/content/legacy/articles/Makefile diff --git a/www/vincent.demeester.fr/content/articles/a_philosophy_of_project_governance_drew_devault_s_blog.org b/www/vincent.demeester.fr/content/legacy/articles/a_philosophy_of_project_governance_drew_devault_s_blog.org diff --git a/www/vincent.demeester.fr/content/articles/add_a_contrib_directory_to_your_projects_drew_devault_s_blog.org b/www/vincent.demeester.fr/content/legacy/articles/add_a_contrib_directory_to_your_projects_drew_devault_s_blog.org diff --git a/www/vincent.demeester.fr/content/articles/apis.org b/www/vincent.demeester.fr/content/legacy/articles/apis.org diff --git a/www/vincent.demeester.fr/content/articles/architect.org b/www/vincent.demeester.fr/content/legacy/articles/architect.org diff --git a/www/vincent.demeester.fr/content/articles/bash.org b/www/vincent.demeester.fr/content/legacy/articles/bash.org diff --git a/www/vincent.demeester.fr/content/articles/blog.org b/www/vincent.demeester.fr/content/legacy/articles/blog.org diff --git a/www/vincent.demeester.fr/content/articles/book_a_practical_approach_to_large_scale_agile_development.org b/www/vincent.demeester.fr/content/legacy/articles/book_a_practical_approach_to_large_scale_agile_development.org diff --git a/www/vincent.demeester.fr/content/articles/book_a_practical_guide_to_distributed_scrum.org b/www/vincent.demeester.fr/content/legacy/articles/book_a_practical_guide_to_distributed_scrum.org diff --git a/www/vincent.demeester.fr/content/articles/book_accelerate.org b/www/vincent.demeester.fr/content/legacy/articles/book_accelerate.org diff --git a/www/vincent.demeester.fr/content/articles/book_debugging_teams.org b/www/vincent.demeester.fr/content/legacy/articles/book_debugging_teams.org diff --git a/www/vincent.demeester.fr/content/articles/book_kanban_workbook.org b/www/vincent.demeester.fr/content/legacy/articles/book_kanban_workbook.org diff --git a/www/vincent.demeester.fr/content/articles/book_living_documentation.org b/www/vincent.demeester.fr/content/legacy/articles/book_living_documentation.org diff --git a/www/vincent.demeester.fr/content/articles/book_make_time.org b/www/vincent.demeester.fr/content/legacy/articles/book_make_time.org diff --git a/www/vincent.demeester.fr/content/articles/book_managing_humans.org b/www/vincent.demeester.fr/content/legacy/articles/book_managing_humans.org diff --git a/www/vincent.demeester.fr/content/articles/book_sleep_smarter.org b/www/vincent.demeester.fr/content/legacy/articles/book_sleep_smarter.org diff --git a/www/vincent.demeester.fr/content/articles/book_sprint.org b/www/vincent.demeester.fr/content/legacy/articles/book_sprint.org diff --git a/www/vincent.demeester.fr/content/articles/book_the_checklist_manifesto.org b/www/vincent.demeester.fr/content/legacy/articles/book_the_checklist_manifesto.org diff --git a/www/vincent.demeester.fr/content/articles/book_the_flinch.org b/www/vincent.demeester.fr/content/legacy/articles/book_the_flinch.org diff --git a/www/vincent.demeester.fr/content/articles/book_the_holloway_guide_to_equity_compensation.org b/www/vincent.demeester.fr/content/legacy/articles/book_the_holloway_guide_to_equity_compensation.org diff --git a/www/vincent.demeester.fr/content/articles/book_the_holloway_guide_to_remote_work.org b/www/vincent.demeester.fr/content/legacy/articles/book_the_holloway_guide_to_remote_work.org diff --git a/www/vincent.demeester.fr/content/articles/book_the_manager_s_path.org b/www/vincent.demeester.fr/content/legacy/articles/book_the_manager_s_path.org diff --git a/www/vincent.demeester.fr/content/articles/book_the_now_habit.org b/www/vincent.demeester.fr/content/legacy/articles/book_the_now_habit.org diff --git a/www/vincent.demeester.fr/content/articles/book_the_ultimate_guide_to_remote_work.org b/www/vincent.demeester.fr/content/legacy/articles/book_the_ultimate_guide_to_remote_work.org diff --git a/www/vincent.demeester.fr/content/articles/book_time_management.org b/www/vincent.demeester.fr/content/legacy/articles/book_time_management.org diff --git a/www/vincent.demeester.fr/content/articles/book_work_clean.org b/www/vincent.demeester.fr/content/legacy/articles/book_work_clean.org diff --git a/www/vincent.demeester.fr/content/articles/building_interactive_ssh_applications_drew_devault_s_blog.org b/www/vincent.demeester.fr/content/legacy/articles/building_interactive_ssh_applications_drew_devault_s_blog.org diff --git a/www/vincent.demeester.fr/content/articles/clojure.org b/www/vincent.demeester.fr/content/legacy/articles/clojure.org diff --git a/www/vincent.demeester.fr/content/articles/config_configurations.org b/www/vincent.demeester.fr/content/legacy/articles/config_configurations.org diff --git a/www/vincent.demeester.fr/content/articles/config_email_configuration.org b/www/vincent.demeester.fr/content/legacy/articles/config_email_configuration.org diff --git a/www/vincent.demeester.fr/content/articles/containers.org b/www/vincent.demeester.fr/content/legacy/articles/containers.org diff --git a/www/vincent.demeester.fr/content/articles/continuous_deployment.org b/www/vincent.demeester.fr/content/legacy/articles/continuous_deployment.org diff --git a/www/vincent.demeester.fr/content/articles/continuous_integration.org b/www/vincent.demeester.fr/content/legacy/articles/continuous_integration.org diff --git a/www/vincent.demeester.fr/content/articles/css.org b/www/vincent.demeester.fr/content/legacy/articles/css.org diff --git a/www/vincent.demeester.fr/content/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/david/2020/02/14/index.html b/www/vincent.demeester.fr/content/legacy/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/david/2020/02/14/index.html diff --git a/www/vincent.demeester.fr/content/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/fonts/FiraCode-Retina.woff b/www/vincent.demeester.fr/content/legacy/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/fonts/FiraCode-Retina.woff Binary files differ. diff --git a/www/vincent.demeester.fr/content/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/fonts/FiraCode-Retina.woff2 b/www/vincent.demeester.fr/content/legacy/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/fonts/FiraCode-Retina.woff2 Binary files differ. diff --git a/www/vincent.demeester.fr/content/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/fonts/triplicate_t4_poly_bold.woff b/www/vincent.demeester.fr/content/legacy/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/fonts/triplicate_t4_poly_bold.woff Binary files differ. diff --git a/www/vincent.demeester.fr/content/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/fonts/triplicate_t4_poly_bold.woff2 b/www/vincent.demeester.fr/content/legacy/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/fonts/triplicate_t4_poly_bold.woff2 Binary files differ. diff --git a/www/vincent.demeester.fr/content/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/fonts/triplicate_t4_poly_italic.woff b/www/vincent.demeester.fr/content/legacy/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/fonts/triplicate_t4_poly_italic.woff Binary files differ. diff --git a/www/vincent.demeester.fr/content/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/fonts/triplicate_t4_poly_italic.woff2 b/www/vincent.demeester.fr/content/legacy/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/fonts/triplicate_t4_poly_italic.woff2 Binary files differ. diff --git a/www/vincent.demeester.fr/content/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/fonts/triplicate_t4_poly_regular.woff b/www/vincent.demeester.fr/content/legacy/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/fonts/triplicate_t4_poly_regular.woff Binary files differ. diff --git a/www/vincent.demeester.fr/content/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/fonts/triplicate_t4_poly_regular.woff2 b/www/vincent.demeester.fr/content/legacy/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/fonts/triplicate_t4_poly_regular.woff2 Binary files differ. diff --git a/www/vincent.demeester.fr/content/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/style_2020-01-24.css b/www/vincent.demeester.fr/content/legacy/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/css/style_2020-01-24.css diff --git a/www/vincent.demeester.fr/content/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/icons2/favicon.ico b/www/vincent.demeester.fr/content/legacy/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/icons2/favicon.ico Binary files differ. diff --git a/www/vincent.demeester.fr/content/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/js/instantpage-3.0.0.min.js b/www/vincent.demeester.fr/content/legacy/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/larlet.fr/static/david/js/instantpage-3.0.0.min.js diff --git a/www/vincent.demeester.fr/content/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/org-board-7af3b3f4-a1f0-4adf-80f5-833d55ca7f86.log b/www/vincent.demeester.fr/content/legacy/articles/data/7a/f3b3f4-a1f0-4adf-80f5-833d55ca7f86/2020-02-14T17_19_57_0100/org-board-7af3b3f4-a1f0-4adf-80f5-833d55ca7f86.log diff --git a/www/vincent.demeester.fr/content/articles/data/89/96fae4-8792-4fcb-a18c-f625d63881e0/2013-11-12-Get-started-with-Ledger.png b/www/vincent.demeester.fr/content/legacy/articles/data/89/96fae4-8792-4fcb-a18c-f625d63881e0/2013-11-12-Get-started-with-Ledger.png diff --git a/www/vincent.demeester.fr/content/articles/data/89/96fae4-8792-4fcb-a18c-f625d63881e0/elbank-ynab.html b/www/vincent.demeester.fr/content/legacy/articles/data/89/96fae4-8792-4fcb-a18c-f625d63881e0/elbank-ynab.html diff --git a/www/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/org-board-ac212115-e0c6-448c-9344-17c10aeb9694.log b/www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/org-board-ac212115-e0c6-448c-9344-17c10aeb9694.log diff --git a/www/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/articles/emacs-org-mode-generate-ids.html b/www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/articles/emacs-org-mode-generate-ids.html diff --git a/www/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/articles/images/org-export1.png b/www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/articles/images/org-export1.png Binary files differ. diff --git a/www/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/articles/images/org-export2.png b/www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/articles/images/org-export2.png Binary files differ. diff --git a/www/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-bold-line-figures/et-book-bold-line-figures.eot b/www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-bold-line-figures/et-book-bold-line-figures.eot Binary files differ. diff --git a/www/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-bold-line-figures/et-book-bold-line-figures.svg b/www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-bold-line-figures/et-book-bold-line-figures.svg diff --git a/www/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-bold-line-figures/et-book-bold-line-figures.ttf b/www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-bold-line-figures/et-book-bold-line-figures.ttf Binary files differ. diff --git a/www/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-bold-line-figures/et-book-bold-line-figures.woff b/www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-bold-line-figures/et-book-bold-line-figures.woff Binary files differ. diff --git a/www/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.eot b/www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.eot Binary files differ. diff --git a/www/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.svg b/www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.svg diff --git a/www/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.ttf b/www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.ttf Binary files differ. diff --git a/www/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.woff b/www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.woff Binary files differ. diff --git a/www/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-roman-line-figures/et-book-roman-line-figures.eot b/www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-roman-line-figures/et-book-roman-line-figures.eot Binary files differ. diff --git a/www/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-roman-line-figures/et-book-roman-line-figures.svg b/www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-roman-line-figures/et-book-roman-line-figures.svg diff --git a/www/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-roman-line-figures/et-book-roman-line-figures.ttf b/www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-roman-line-figures/et-book-roman-line-figures.ttf Binary files differ. diff --git a/www/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-roman-line-figures/et-book-roman-line-figures.woff b/www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-roman-line-figures/et-book-roman-line-figures.woff Binary files differ. diff --git a/www/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.eot b/www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.eot Binary files differ. diff --git a/www/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.svg b/www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.svg diff --git a/www/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.ttf b/www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.ttf Binary files differ. diff --git a/www/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.woff b/www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.woff Binary files differ. diff --git a/www/vincent.demeester.fr/content/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/tufte.css b/www/vincent.demeester.fr/content/legacy/articles/data/ac/212115-e0c6-448c-9344-17c10aeb9694/2020-02-14T17_45_26_0100/writequit.org/css/tufte.css diff --git a/www/vincent.demeester.fr/content/articles/data/b9/059b7e-29c0-4e7f-a1c1-901d812109bf/architecture.png b/www/vincent.demeester.fr/content/legacy/articles/data/b9/059b7e-29c0-4e7f-a1c1-901d812109bf/architecture.png Binary files differ. diff --git a/www/vincent.demeester.fr/content/articles/data/d4/868e4e-61bc-4904-b683-f165569f8baf/lifecycle.png b/www/vincent.demeester.fr/content/legacy/articles/data/d4/868e4e-61bc-4904-b683-f165569f8baf/lifecycle.png Binary files differ. diff --git a/www/vincent.demeester.fr/content/articles/development-machines.org b/www/vincent.demeester.fr/content/legacy/articles/development-machines.org diff --git a/www/vincent.demeester.fr/content/articles/documentation.org b/www/vincent.demeester.fr/content/legacy/articles/documentation.org diff --git a/www/vincent.demeester.fr/content/articles/dogfooding.org b/www/vincent.demeester.fr/content/legacy/articles/dogfooding.org diff --git a/www/vincent.demeester.fr/content/articles/emacs.old.org b/www/vincent.demeester.fr/content/legacy/articles/emacs.old.org diff --git a/www/vincent.demeester.fr/content/articles/emacs.org b/www/vincent.demeester.fr/content/legacy/articles/emacs.org diff --git a/www/vincent.demeester.fr/content/articles/emacs_appearance.org b/www/vincent.demeester.fr/content/legacy/articles/emacs_appearance.org diff --git a/www/vincent.demeester.fr/content/articles/emacs_keep_it_clean.org b/www/vincent.demeester.fr/content/legacy/articles/emacs_keep_it_clean.org diff --git a/www/vincent.demeester.fr/content/articles/emacs_lisp.org b/www/vincent.demeester.fr/content/legacy/articles/emacs_lisp.org diff --git a/www/vincent.demeester.fr/content/articles/emacs_projects.org b/www/vincent.demeester.fr/content/legacy/articles/emacs_projects.org diff --git a/www/vincent.demeester.fr/content/articles/email.org b/www/vincent.demeester.fr/content/legacy/articles/email.org diff --git a/www/vincent.demeester.fr/content/articles/empathy_online.org b/www/vincent.demeester.fr/content/legacy/articles/empathy_online.org diff --git a/www/vincent.demeester.fr/content/articles/eshell.org b/www/vincent.demeester.fr/content/legacy/articles/eshell.org diff --git a/www/vincent.demeester.fr/content/articles/fedora-silverblue.org b/www/vincent.demeester.fr/content/legacy/articles/fedora-silverblue.org diff --git a/www/vincent.demeester.fr/content/articles/fedora.org b/www/vincent.demeester.fr/content/legacy/articles/fedora.org diff --git a/www/vincent.demeester.fr/content/articles/feeds.org b/www/vincent.demeester.fr/content/legacy/articles/feeds.org diff --git a/www/vincent.demeester.fr/content/articles/firefox.org b/www/vincent.demeester.fr/content/legacy/articles/firefox.org diff --git a/www/vincent.demeester.fr/content/articles/fish.org b/www/vincent.demeester.fr/content/legacy/articles/fish.org diff --git a/www/vincent.demeester.fr/content/articles/foo.gif b/www/vincent.demeester.fr/content/legacy/articles/foo.gif diff --git a/www/vincent.demeester.fr/content/articles/generics_aren_t_ready_for_go_drew_devault_s_blog.org b/www/vincent.demeester.fr/content/legacy/articles/generics_aren_t_ready_for_go_drew_devault_s_blog.org diff --git a/www/vincent.demeester.fr/content/articles/getting_boxes_done.org b/www/vincent.demeester.fr/content/legacy/articles/getting_boxes_done.org diff --git a/www/vincent.demeester.fr/content/articles/getting_boxes_done_the_code.org b/www/vincent.demeester.fr/content/legacy/articles/getting_boxes_done_the_code.org diff --git a/www/vincent.demeester.fr/content/articles/getting_started_with_qemu_drew_devault_s_blog.org b/www/vincent.demeester.fr/content/legacy/articles/getting_started_with_qemu_drew_devault_s_blog.org diff --git a/www/vincent.demeester.fr/content/articles/git.org b/www/vincent.demeester.fr/content/legacy/articles/git.org diff --git a/www/vincent.demeester.fr/content/articles/git_annex.org b/www/vincent.demeester.fr/content/legacy/articles/git_annex.org diff --git a/www/vincent.demeester.fr/content/articles/git_sr_ht.org b/www/vincent.demeester.fr/content/legacy/articles/git_sr_ht.org diff --git a/www/vincent.demeester.fr/content/articles/github.org b/www/vincent.demeester.fr/content/legacy/articles/github.org diff --git a/www/vincent.demeester.fr/content/articles/gitlab.org b/www/vincent.demeester.fr/content/legacy/articles/gitlab.org diff --git a/www/vincent.demeester.fr/content/articles/gitops.org b/www/vincent.demeester.fr/content/legacy/articles/gitops.org diff --git a/www/vincent.demeester.fr/content/articles/gnupg.org b/www/vincent.demeester.fr/content/legacy/articles/gnupg.org diff --git a/www/vincent.demeester.fr/content/articles/gnus.org b/www/vincent.demeester.fr/content/legacy/articles/gnus.org diff --git a/www/vincent.demeester.fr/content/articles/go.org b/www/vincent.demeester.fr/content/legacy/articles/go.org diff --git a/www/vincent.demeester.fr/content/articles/haskell.org b/www/vincent.demeester.fr/content/legacy/articles/haskell.org diff --git a/www/vincent.demeester.fr/content/articles/how_i_decide_between_many_programming_languages_drew_devault_s_blog.org b/www/vincent.demeester.fr/content/legacy/articles/how_i_decide_between_many_programming_languages_drew_devault_s_blog.org diff --git a/www/vincent.demeester.fr/content/articles/how_to_drive_upstream_project.org b/www/vincent.demeester.fr/content/legacy/articles/how_to_drive_upstream_project.org diff --git a/www/vincent.demeester.fr/content/articles/images/documentation/overview.png b/www/vincent.demeester.fr/content/legacy/articles/images/documentation/overview.png diff --git a/www/vincent.demeester.fr/content/articles/images/sandbox/long-img.png b/www/vincent.demeester.fr/content/legacy/articles/images/sandbox/long-img.png Binary files differ. diff --git a/www/vincent.demeester.fr/content/articles/images/sandbox/pic-demo.png b/www/vincent.demeester.fr/content/legacy/articles/images/sandbox/pic-demo.png Binary files differ. diff --git a/www/vincent.demeester.fr/content/articles/images/sandbox/some_filename.png b/www/vincent.demeester.fr/content/legacy/articles/images/sandbox/some_filename.png Binary files differ. diff --git a/www/vincent.demeester.fr/content/articles/images/tekton/canary-pipeline.png b/www/vincent.demeester.fr/content/legacy/articles/images/tekton/canary-pipeline.png Binary files differ. diff --git a/www/vincent.demeester.fr/content/articles/images/tekton/tekton-horizontal-color.png b/www/vincent.demeester.fr/content/legacy/articles/images/tekton/tekton-horizontal-color.png diff --git a/www/vincent.demeester.fr/content/articles/index.org b/www/vincent.demeester.fr/content/legacy/articles/index.org diff --git a/www/vincent.demeester.fr/content/articles/individual_contributors.org b/www/vincent.demeester.fr/content/legacy/articles/individual_contributors.org diff --git a/www/vincent.demeester.fr/content/articles/infrastructure.org b/www/vincent.demeester.fr/content/legacy/articles/infrastructure.org diff --git a/www/vincent.demeester.fr/content/articles/internet_of_things.org b/www/vincent.demeester.fr/content/legacy/articles/internet_of_things.org diff --git a/www/vincent.demeester.fr/content/articles/ipfs.org b/www/vincent.demeester.fr/content/legacy/articles/ipfs.org diff --git a/www/vincent.demeester.fr/content/articles/keyboard.org b/www/vincent.demeester.fr/content/legacy/articles/keyboard.org diff --git a/www/vincent.demeester.fr/content/articles/kind.org b/www/vincent.demeester.fr/content/legacy/articles/kind.org diff --git a/www/vincent.demeester.fr/content/articles/knative.org b/www/vincent.demeester.fr/content/legacy/articles/knative.org diff --git a/www/vincent.demeester.fr/content/articles/kubernetes.org b/www/vincent.demeester.fr/content/legacy/articles/kubernetes.org diff --git a/www/vincent.demeester.fr/content/articles/kubernetes_on_nixos.org b/www/vincent.demeester.fr/content/legacy/articles/kubernetes_on_nixos.org diff --git a/www/vincent.demeester.fr/content/articles/kubernix.org b/www/vincent.demeester.fr/content/legacy/articles/kubernix.org diff --git a/www/vincent.demeester.fr/content/articles/leadership.org b/www/vincent.demeester.fr/content/legacy/articles/leadership.org diff --git a/www/vincent.demeester.fr/content/articles/libvirt.org b/www/vincent.demeester.fr/content/legacy/articles/libvirt.org diff --git a/www/vincent.demeester.fr/content/articles/linux.org b/www/vincent.demeester.fr/content/legacy/articles/linux.org diff --git a/www/vincent.demeester.fr/content/articles/lisp.org b/www/vincent.demeester.fr/content/legacy/articles/lisp.org diff --git a/www/vincent.demeester.fr/content/articles/mac.org b/www/vincent.demeester.fr/content/legacy/articles/mac.org diff --git a/www/vincent.demeester.fr/content/articles/make.org b/www/vincent.demeester.fr/content/legacy/articles/make.org diff --git a/www/vincent.demeester.fr/content/articles/meta_meta.org b/www/vincent.demeester.fr/content/legacy/articles/meta_meta.org diff --git a/www/vincent.demeester.fr/content/legacy/articles/meta_publishing_this_website.org b/www/vincent.demeester.fr/content/legacy/articles/meta_publishing_this_website.org @@ -0,0 +1,610 @@ +#+TITLE: meta: publishing this website +#+ROAM_ALIAS: "publishing this website" + +This is a *always up-to-date* version of my initial [[https://vincent.demeester.fr/posts/2020-03-22-org-mode-website.html][post]] about publishing this website +using [[file:org_mode.org][org-mode]]. This uses the [[https://orgmode.org/manual/Extracting-Source-Code.html][tangle]] feature of =org-mode= and will span from the +=Makefile= of the [[https://git.sr.ht/~vdemeester/www][repository]] to any [[file:elisp.org][emacs-lisp]] code required. + +This is part of the [[file:meta_meta.org][Meta]] entry. + +#+TOC: headlines 3 + +* Parts of the website + +Let's look at the different part of the website and for each where I do get the +information I want to publish. + +- =/= :: this is just a file (=index.org=) that I maintain manually. +- =/posts= :: this is what we call /blog/ these days : short to medium article that are valid + at a point of time, as may contain /deprecated/ content, or content that do not reflect + my views at a later point in time. +- =/articles= :: medium to long article about a topic. Those should be up-to-date or + explicitly mark as deprecated or invalid. This is my *ready for the public* knowledge + database, a bit like [[https://braindump.jethro.dev/][Jethro's Braindump]]. It is managed using [[file:org_roam.org][org-roam]] and I just need to + get the latests somewhere to publish it. +- =/configurations= :: medium to long article about my configurations. Those are base + sources for my ~home~ /configuration/ mono-repository, and usually follow literate + programming principles. The are managed using [[file:org_roam.org][org-roam]] with the =config:= prefix. +- =/files= :: a dump of random files, it is actually on another domain name, completely + unmanaged by this. +- =/about= :: an about page about the author of the website (aka [[https://vincent.demeester.fr][me]]), linking external + contributions (GitHub/Gitlab/… profiles, Talks, …). + +In a nutshell, the folder hierarchy is something like : + +#+begin_src shell :tangle no +src/www +|-- about # the about folder (with one index.org) +|-- articles # <- comes from ~/desktop/org/notes~ +|-- css # the css +|-- images # the images +|-- index.org # the index πŸŽ‰ +|-- posts # the posts (format YYYY-MM-DD-{title}.org) +`-- public # the output that get deployed later on +#+end_src + +* Publishing + +** /Make/ it happen +:PROPERTIES: +:header-args: :tangle /etc/nixos/www/vincent.demeester.fr/Makefile +:header-args+: :comments org +:ID: 6191455e-95bc-4abc-a5f5-a62606ab2ea7 +:END: + +In order to publish this website, I am using [[file:make.org][make]]. In a nutshell, I am going to define a +few target to get the content from my notes, export org files into html and copy more or +less everything to the =public= folder. I will also define a clean and a publish target. + +The first part of my =Makefile= will be to define some constants that I want to use later +on. Those are mainly to easily change where to look for the notes or where the emacs +configuration is. + +#+begin_src makefile +EMACS = +ifndef EMACS +EMACS = "emacs" +endif + +DOTEMACS = +ifndef DOTEMACS +DOTEMACS = "~/.config/emacs" +endif + +PUBLISH = +ifndef PUBLISH +PUBLISH = vincent.demeester.fr +endif + +NOTES = ~/desktop/org/notes +#+end_src + +The default target will be name =build=. + +#+begin_src makefile +all: build +#+end_src + +*** Building =public/= and publishing it +:PROPERTIES: +:ID: 853f3d6b-f385-4091-9f9e-b04d17794e5c +:END: + +To build the website, we will be using [[file:emacs.org][Emacs]] in batch mode, with some shared library *and* +the actual [[id:631ced7a-f3f7-4a77-81a8-4a1884a6c4d4][publish]] script. + +#+begin_src makefile +.PHONY: build +build: publish.el publish-common.el build-articles + @echo "Publishing... with current Emacs configurations." + ${EMACS} --debug-init --batch --directory $(DOTEMACS)/lisp/ --directory $(DOTEMACS)/lisp/vorg/ \ + --load publish-common.el --load publish.el \ + --funcall org-publish-all + +.PHONY: build-articles +build-articles: $(NOTES) + rsync -arv --delete --copy-links --exclude='*.private.org' --exclude='*.db' $(NOTES)/ articles/ + +$(NOTES): + $(error $(NOTES) doesn't exists…) +#+end_src + +The =publish= target is gonna be really simple: I just need to copy the content to +=~/desktop/sites= on the current machine, and the rest is automated. + +#+begin_src makefile +#rsync -a --progress --copy-links --delete public/assets/.fancyindex/ ~/desktop/sites/dl.sbr.pm/.fancyindex/ +#rsync -a --progress --copy-links --delete public/ ~/desktop/sites/${PUBLISH}/ +.PHONY: publish +publish: build + rsync -ave ssh --progress --copy-links --delete public/assets/.fancyindex/ kerkouane.vpn:/var/www/dl.sbr.pm/.fancyindex/ + rsync -ave ssh --progress --copy-links --delete public/ kerkouane.vpn:/var/www/${PUBLISH}/ +#+end_src + +*** Local server +:PROPERTIES: +:ID: 0e26a52e-5f66-42c6-934d-b45cfc9745b2 +:END: + +Let's use =miniserve= (using [[file:nix.org][Nix]] with =nix-shell=) to serve the static website locally to +validate my changes. + +#+begin_src makefile +.PHONY: serve +serve: + nix-shell -p miniserve --command "miniserve --port=8181 --index=index.html public/" +#+end_src + +*** Final nits of the =Makefile= +:PROPERTIES: +:ID: 5bec22c8-491b-4e03-855a-4b5f859473cf +:END: + +One of the final step is to install the git hooks if any. I tend to have this target in all my +personal =Makefile= at least. Let's also define a =pre-commit= target that will hold +anything we need to do at =pre-commit=. + +#+begin_src makefile +.PHONY: install-hooks +install-hooks: + if [ -e .git ]; then nix-shell -p git --run 'git config core.hooksPath .githooks'; fi + +.PHONY: pre-commit +pre-commit: README.md +#+end_src + +And the final target is the =clean= one. This will remove any compile emacs-lisp file +(=*.elc=), the =public= folder, and some org-mode metadata. + +#+begin_src makefile +.PHONY: clean +clean: + @echo "Cleaning up.." + @-rm -rvf *.elc + @-rm -rvf public + @-rm -rv ~/.org-timestamps/* +#+end_src + +** The /publish/ scrits +:PROPERTIES: +:ID: 631ced7a-f3f7-4a77-81a8-4a1884a6c4d4 +:END: + +I've imported the script directly in here, I'll slowly split this and document it. + +*** =publish.el= +:PROPERTIES: +:header-args: :tangle /etc/nixos/www/vincent.demeester.fr/publish.el +:ID: bef868db-17f0-4a67-9c64-6dcf64ad6de1 +:END: + +#+begin_src emacs-lisp +;;; publish.el --- Publish www project -*- lexical-binding: t; -*- +;; Author: Vincent Demeester <vincent@sbr.pm> + +;;; Commentary: +;; This script will convert the org-mode files in this directory into +;; html. + +;;; Code: +(require 'package) +(require 'publish-common) + +(setq org-publish-project-alist + `(("posts" + :base-directory "posts" + :base-extension "org" + :recursive t + :publishing-function org-html-publish-to-html + :publishing-directory "./public/posts" + :exclude ,(regexp-opt '("README.org" "draft")) + :auto-sitemap t + :with-footnotes t + :with-toc nil + :with-drawers t + :sitemap-filename "index.org" + :sitemap-title "Posts" + :sitemap-format-entry sbr/org-sitemap-format-entry + :sitemap-style list + :sitemap-sort-files anti-chronologically + :sitemap-function sbr/org-publish-sitemap + :html-head-include-scripts nil + :html-head-include-default-style nil + :html-head ,sbr-website-html-head + :html-preamble sbr-website-html-preamble + :html-postamble ,sbr-website-html-postamble) + ("posts-rss" + :base-directory "posts" + :base-extension "org" + :recursive t + :html-link-home "https://vincent.demeester.fr/" + :rss-link-home "https://vincent.demeester.fr/posts/" + :html-link-use-abs-url t + :rss-extension "xml" + :publishing-directory "./public" + :publishing-function (sbr/org-rss-publish-to-rss) + :section-number nil + :exclude ".*" + :include ("index.org")) + ("articles" + :base-directory "articles" + :base-extension "org" + :recursive t + :publishing-function org-html-publish-to-html + :publishing-directory "./public/articles" + :exclude ,(regexp-opt '("README.org" "draft")) + :auto-sitemap t + :with-footnotes t + :with-toc nil + :with-drawers t + :sitemap-filename "sitemap.org" + :sitemap-title "Articles" + :sitemap-style tree + :sitemap-sort-files anti-chronologically + ;;:sitemap-format-entry sbr/org-sitemap-format-entry + ;;:sitemap-function sbr/org-publish-sitemap + :html-head-include-scripts nil + :html-head-include-default-style nil + :html-head ,sbr-website-html-head + :html-preamble sbr-website-html-preamble + :html-postamble ,sbr-website-html-postamble) + ("articles-assets" + :exclude ,(regexp-opt '("*.org")) + :base-directory "articles" + :base-extension ,site-attachments + :publishing-directory "./public/articles" + :publishing-function org-publish-attachment + :recursive t) + ("about" + :base-directory "about" + :base-extension "org" + :exclude ,(regexp-opt '("README.org" "draft")) + :index-filename "index.org" + :recursive nil + :with-footnotes t + :with-toc nil + :with-drawers t + :publishing-function org-html-publish-to-html + :publishing-directory "./public/about" + :html-head-include-scripts nil + :html-head-include-default-style nil + :html-head ,sbr-website-html-head + :html-preamble sbr-website-html-preamble + :html-postamble ,sbr-website-html-postamble) + ("index" + :base-directory "" + :base-extension "org" + :exclude ,(regexp-opt '("README.org" "draft")) + :index-filename "index.org" + :recursive nil + :with-footnotes t + :with-toc nil + :with-drawers t + :with-title nil + :publishing-function org-html-publish-to-html + :publishing-directory "./public" + :html-head-include-scripts nil + :html-head-include-default-style nil + :html-head ,sbr-website-html-head + :html-preamble sbr-website-html-preamble + :html-postamble ,sbr-website-html-postamble) + ("css" + :base-directory "./css" + :base-extension ,site-attachments + :recursive t + :publishing-directory "./public/css" + :publishing-function org-publish-attachment + :recursive t) + ("images" + :base-directory "./images" + :base-extension ,site-attachments + :publishing-directory "./public/images" + :publishing-function org-publish-attachment + :recursive t) + ("assets" + :base-directory "./assets" + :base-extension ,site-attachments + :publishing-directory "./public/assets" + :publishing-function org-publish-attachment + :recursive t) + ("legacy" + :base-directory "./legacy" + :base-extension ,site-attachments + :publishing-directory "./public/" + :publishing-function org-publish-attachment + :recursive t) + ("all" :components ("posts" "about" "index" "articles" "articles-assets" "css" "images" "assets" "legacy" "posts-rss")))) + +(provide 'publish) +;;; publish.el ends here +#+end_src + +*** =publish-common.el= +:PROPERTIES: +:header-args: :tangle /etc/nixos/www/vincent.demeester.fr/publish-common.el +:ID: bcedf3bd-49ec-4602-b916-daea6695af1e +:END: + +#+begin_src emacs-lisp +;;; publish-common.el --- Commons code for www publishing projects -*- lexical-binding: t; -*- +;; Author: Vincent Demeester <vincent@sbr.pm> + +;;; Commentary: +;; +;;; Code: +;; load org +(require 'org) +(require 'dash) +;; load org export functions +(require 'ox-publish) +(require 'ox-rss) +(require 'ox-html) +;; load org link functions +(require 'ol-man) +(require 'ol-git-link) +;; Those are mine +(require 'ol-github) +(require 'ol-gitlab) +(require 'org-attach) +;; load additional libraries +(require 'go-mode) +(require 'css-mode) +(require 'yaml-mode) +(require 'nix-mode) + +(require 's) + +(setq org-export-use-babel nil) +(setq org-link-abbrev-alist '(("att" . org-attach-expand-link))) + +;; setting to nil, avoids "Author: x" at the bottom +(setq org-export-with-section-numbers nil + org-export-with-smart-quotes t + org-export-with-toc nil) + +(defvar sbr-date-format "%b %d, %Y") + +(setq org-html-divs '((preamble "header" "top") + (content "main" "content") + (postamble "footer" "postamble")) + org-html-container-element "section" + org-html-metadata-timestamp-format sbr-date-format + org-html-checkbox-type 'unicode + org-html-html5-fancy t + org-html-doctype "html5" + org-html-htmlize-output-type 'css + org-html-htmlize-font-prefix "org-" + org-src-fontify-natively t + org-html-coding-system 'utf-8-unix) + +(defun sbr/org-export-format-drawer (name content) + "HTML export of drawer with NAME and CONTENT. +name is the name of the drawer, that will be used as class. +content is the content of the drawer" + (format "<div class='drawer %s'>\n<h6>%s</h6>\n%s</div>" + (downcase name) + (capitalize name) + content)) +(setq org-html-format-drawer-function 'sbr/org-export-format-drawer) + +(defun read-file (filePath) + "Return FILEPATH's file content." + (with-temp-buffer + (insert-file-contents filePath) + (buffer-string))) + +(defvar sbr-website-html-head + "<link rel='icon' type='image/x-icon' href='/images/favicon.ico'/> +<meta name='viewport' content='width=device-width, initial-scale=1'> +<link rel='stylesheet' href='/css/new.css' type='text/css'/> +<link rel='stylesheet' href='/css/syntax.css' type='text/css'/> +<link href='/index.xml' rel='alternate' type='application/rss+xml' title='Vincent Demeester' />") + +(defun sbr-website-html-preamble (plist) + "PLIST: An entry." + ;; Skip adding subtitle to the post if :KEYWORDS don't have 'post' has a + ;; keyword + (when (string-match-p "post" (format "%s" (plist-get plist :keywords))) + (plist-put plist + :subtitle (format "Published on %s by %s." + (org-export-get-date plist sbr-date-format) + (car (plist-get plist :author))))) + + ;; Below content will be added anyways + "<nav> +<img src=\"/images/favicon.ico\" id=\"sitelogo\"/> <a href='/'>home</a> / +<a href='/posts/'>posts</a> (<a href='/index.xml'>rss</a>) / +<a href='/articles/'>articles</a> / +<a href='https://dl.sbr.pm/'>files</a> / +<a href='/about/'>about</a></li> +</nav>") + +(defvar sbr-website-html-postamble + "<footer> + <span class='questions'>Questions, comments ? Please use my <a href=\"https://lists.sr.ht/~vdemeester/public-inbox\">public inbox</a> by sending a plain-text email to <a href=\"mailto:~vdemeester/public-inbox@lists.sr.ht\">~vdemeester/public-inbox@lists.sr.ht</a>.</span> + <span class='opinions'>Opinions stated here are my own and do not express the views of my employer, spouse, children, pets, neighbors, secret crushes, favorite authors, or anyone else who is not me. And maybe not even me, depending on how old this is.</span> + <span class='copyright'> + Content and design by Vincent Demeester + (<a rel='licence' href='http://creativecommons.org/licenses/by-nc-sa/3.0/'>Some rights reserved</a>) + </span><br /> + <span class='engine'> + Powered by <a href='https://www.gnu.org/software/emacs/'>Gnu Emacs</a> and <a href='https://orgmode.org'>orgmode</a> + </span> +</footer>") +(defvar site-attachments + (regexp-opt '("jpg" "jpeg" "gif" "png" "svg" + "ico" "cur" "css" "js" "woff" "html" "pdf" "otf")) + "File types that are published as static files.") + +(defun sbr/org-sitemap-format-entry (entry style project) + "Format posts with author and published data in the index page. + +ENTRY: file-name +STYLE: +PROJECT: `posts in this case." + (cond ((not (directory-name-p entry)) + (format "%s β€” [[file:%s][%s]] + :PROPERTIES: + :PUBDATE: [%s] + :END:" + (format-time-string "%Y-%m-%d" + (org-publish-find-date entry project)) + entry + (org-publish-find-title entry project) + (format-time-string "%Y-%m-%d" + (org-publish-find-date entry project)))) + ((eq style 'tree) (file-name-nondirectory (directory-file-name entry))) + (t entry))) + +(defun sbr/org-publish-sitemap (title list) + "" + (concat "#+TITLE: " title "\n\n" + (org-list-to-subtree list))) + +(defun sbr/org-get-first-paragraph (file) + "Get string content of first paragraph of file." + (ignore-errors + (with-temp-buffer + (insert-file-contents file) + (goto-char (point-min)) + (show-all) + (let ((first-begin (progn + (org-forward-heading-same-level 1) + (next-line) + (point))) + (first-end (progn + (org-next-visible-heading 1) + (point)))) + (buffer-substring first-begin first-end))))) + +(defun sbr/org-rss-publish-to-rss (plist filename pub-dir) + "Prepare rss.org file before exporting." + (let* ((postsdir (plist-get plist :base-directory))) + (with-current-buffer (find-file filename) + (erase-buffer) + (insert "#+TITLE: Posts\n") + (insert "#+AUTHOR: Vincent Demeester\n") + (insert "#+OPTIONS: toc:nil\n") + (let* ((files-all + (reverse (directory-files "." nil + "[0-9-]+.*\\.org$"))) + (files (seq-subseq files-all 0 (min (length files-all) 30)))) + (message (format "foo: %s" filename)) + (dolist (post files) + (let* ((post-file post) + (post-title (org-publish-find-title post-file plist)) + (preview-str (sbr/org-get-first-paragraph post-file)) + (date (replace-regexp-in-string + "\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\)-.*" + "\\1" post))) + (insert (concat "* [[file:" postsdir "/" post "][" post-title "]]\n\n")) + (org-set-property "ID" post) + (org-set-property "RSS_TITLE" post-title) + ;; ox-rss prepends html-link-home to permalink + (org-set-property "RSS_PERMALINK" + (concat postsdir "/" + (file-name-sans-extension post) + ".html")) + (org-set-property + "PUBDATE" + (format-time-string + "<%Y-%m-%d %a %H:%M>" + (org-time-string-to-time + (replace-regexp-in-string + "\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\)-.*" + "\\1" post)))) + (insert preview-str) + (newline 1) + (insert (concat "[[file:" postsdir "/" post "][(Read more)]]\n\n")))) + (save-buffer)))) + (let ((user-mail-address "t") + (org-export-with-broken-links t) + (org-rss-use-entry-url-as-guid nil)) + (org-rss-publish-to-rss plist filename pub-dir))) + +(advice-add #'org-export-get-reference :override #'unpackaged/org-export-get-reference) + +(defun unpackaged/org-export-get-reference (datum info) + "Like `org-export-get-reference', except uses heading titles instead of random numbers." + (let ((cache (plist-get info :internal-references))) + (or (car (rassq datum cache)) + (let* ((crossrefs (plist-get info :crossrefs)) + (cells (org-export-search-cells datum)) + ;; Preserve any pre-existing association between + ;; a search cell and a reference, i.e., when some + ;; previously published document referenced a location + ;; within current file (see + ;; `org-publish-resolve-external-link'). + ;; + ;; However, there is no guarantee that search cells are + ;; unique, e.g., there might be duplicate custom ID or + ;; two headings with the same title in the file. + ;; + ;; As a consequence, before re-using any reference to + ;; an element or object, we check that it doesn't refer + ;; to a previous element or object. + (new (or (cl-some + (lambda (cell) + (let ((stored (cdr (assoc cell crossrefs)))) + (when stored + (let ((old (org-export-format-reference stored))) + (and (not (assoc old cache)) stored))))) + cells) + (when (org-element-property :raw-value datum) + ;; Heading with a title + (unpackaged/org-export-new-title-reference datum cache)) + ;; NOTE: This probably breaks some Org Export + ;; feature, but if it does what I need, fine. + (org-export-format-reference + (org-export-new-reference cache)))) + (reference-string new)) + ;; Cache contains both data already associated to + ;; a reference and in-use internal references, so as to make + ;; unique references. + (dolist (cell cells) (push (cons cell new) cache)) + ;; Retain a direct association between reference string and + ;; DATUM since (1) not every object or element can be given + ;; a search cell (2) it permits quick lookup. + (push (cons reference-string datum) cache) + (plist-put info :internal-references cache) + reference-string)))) + +(defun unpackaged/org-export-new-title-reference (datum cache) + "Return new reference for DATUM that is unique in CACHE." + (cl-macrolet ((inc-suffixf (place) + `(progn + (string-match (rx bos + (minimal-match (group (1+ anything))) + (optional "--" (group (1+ digit))) + eos) + ,place) + ;; HACK: `s1' instead of a gensym. + (-let* (((s1 suffix) (list (match-string 1 ,place) + (match-string 2 ,place))) + (suffix (if suffix + (string-to-number suffix) + 0))) + (setf ,place (format "%s--%s" s1 (cl-incf suffix))))))) + (let* ((title (org-element-property :raw-value datum)) + (ref (url-hexify-string (substring-no-properties title))) + (parent (org-element-property :parent datum))) + (while (--any (equal ref (car it)) + cache) + ;; Title not unique: make it so. + (if parent + ;; Append ancestor title. + (setf title (concat (org-element-property :raw-value parent) + "--" title) + ref (url-hexify-string (substring-no-properties title)) + parent (org-element-property :parent parent)) + ;; No more ancestors: add and increment a number. + (inc-suffixf ref))) + ref))) + +(provide 'publish-common) +;;; publish-common.el ends here +#+end_src + +#+begin_src bash +# -*- mode: org; eval: (add-hook 'after-save-hook (lambda () (org-babel-tangle)) nil t) -*- +#+end_src diff --git a/www/vincent.demeester.fr/content/articles/minikube.org b/www/vincent.demeester.fr/content/legacy/articles/minikube.org diff --git a/www/vincent.demeester.fr/content/articles/moby_project.org b/www/vincent.demeester.fr/content/legacy/articles/moby_project.org diff --git a/www/vincent.demeester.fr/content/articles/my_organizational_workflow.org b/www/vincent.demeester.fr/content/legacy/articles/my_organizational_workflow.org diff --git a/www/vincent.demeester.fr/content/articles/my_personal_journey_from_mit_to_gpl_drew_devault_s_blog.org b/www/vincent.demeester.fr/content/legacy/articles/my_personal_journey_from_mit_to_gpl_drew_devault_s_blog.org diff --git a/www/vincent.demeester.fr/content/articles/nginx.org b/www/vincent.demeester.fr/content/legacy/articles/nginx.org diff --git a/www/vincent.demeester.fr/content/articles/nix.org b/www/vincent.demeester.fr/content/legacy/articles/nix.org diff --git a/www/vincent.demeester.fr/content/articles/nixos-overlays.org b/www/vincent.demeester.fr/content/legacy/articles/nixos-overlays.org diff --git a/www/vincent.demeester.fr/content/articles/nixos.org b/www/vincent.demeester.fr/content/legacy/articles/nixos.org diff --git a/www/vincent.demeester.fr/content/articles/notmuch.org b/www/vincent.demeester.fr/content/legacy/articles/notmuch.org diff --git a/www/vincent.demeester.fr/content/articles/ocp-bootstrap.xml b/www/vincent.demeester.fr/content/legacy/articles/ocp-bootstrap.xml diff --git a/www/vincent.demeester.fr/content/articles/open_container_initiative.org b/www/vincent.demeester.fr/content/legacy/articles/open_container_initiative.org diff --git a/www/vincent.demeester.fr/content/articles/openbsd.org b/www/vincent.demeester.fr/content/legacy/articles/openbsd.org diff --git a/www/vincent.demeester.fr/content/articles/opencontainers_artifacts_oci_artifacts.org b/www/vincent.demeester.fr/content/legacy/articles/opencontainers_artifacts_oci_artifacts.org diff --git a/www/vincent.demeester.fr/content/articles/opencontainers_distribution_spec_oci_distribution_specification.org b/www/vincent.demeester.fr/content/legacy/articles/opencontainers_distribution_spec_oci_distribution_specification.org diff --git a/www/vincent.demeester.fr/content/articles/opencontainers_image_spec_oci_image_format.org b/www/vincent.demeester.fr/content/legacy/articles/opencontainers_image_spec_oci_image_format.org diff --git a/www/vincent.demeester.fr/content/articles/opencontainers_runtime_spec_oci_runtime_specification.org b/www/vincent.demeester.fr/content/legacy/articles/opencontainers_runtime_spec_oci_runtime_specification.org diff --git a/www/vincent.demeester.fr/content/articles/opendatahub_opendatahub.org b/www/vincent.demeester.fr/content/legacy/articles/opendatahub_opendatahub.org diff --git a/www/vincent.demeester.fr/content/articles/openshift-commons.org b/www/vincent.demeester.fr/content/legacy/articles/openshift-commons.org diff --git a/www/vincent.demeester.fr/content/articles/openshift-commons/app.yaml b/www/vincent.demeester.fr/content/legacy/articles/openshift-commons/app.yaml diff --git a/www/vincent.demeester.fr/content/articles/openshift-commons/pipeline-petclinic-deploy.yaml b/www/vincent.demeester.fr/content/legacy/articles/openshift-commons/pipeline-petclinic-deploy.yaml diff --git a/www/vincent.demeester.fr/content/articles/openshift-commons/resources.yaml b/www/vincent.demeester.fr/content/legacy/articles/openshift-commons/resources.yaml diff --git a/www/vincent.demeester.fr/content/articles/openshift-commons/task-openshift-client.yaml b/www/vincent.demeester.fr/content/legacy/articles/openshift-commons/task-openshift-client.yaml diff --git a/www/vincent.demeester.fr/content/articles/openshift-commons/task-s2i-java-8.yaml b/www/vincent.demeester.fr/content/legacy/articles/openshift-commons/task-s2i-java-8.yaml diff --git a/www/vincent.demeester.fr/content/articles/openshift.org b/www/vincent.demeester.fr/content/legacy/articles/openshift.org diff --git a/www/vincent.demeester.fr/content/articles/openshift_on_vm_bare_metal.org b/www/vincent.demeester.fr/content/legacy/articles/openshift_on_vm_bare_metal.org diff --git a/www/vincent.demeester.fr/content/articles/openshift_pipeline.org b/www/vincent.demeester.fr/content/legacy/articles/openshift_pipeline.org diff --git a/www/vincent.demeester.fr/content/articles/org_library_of_babel.org b/www/vincent.demeester.fr/content/legacy/articles/org_library_of_babel.org diff --git a/www/vincent.demeester.fr/content/articles/org_mode.org b/www/vincent.demeester.fr/content/legacy/articles/org_mode.org diff --git a/www/vincent.demeester.fr/content/articles/org_roam.org b/www/vincent.demeester.fr/content/legacy/articles/org_roam.org diff --git a/www/vincent.demeester.fr/content/articles/personal_knowledge_base.org b/www/vincent.demeester.fr/content/legacy/articles/personal_knowledge_base.org diff --git a/www/vincent.demeester.fr/content/articles/preseed.cfg b/www/vincent.demeester.fr/content/legacy/articles/preseed.cfg diff --git a/www/vincent.demeester.fr/content/articles/programming.org b/www/vincent.demeester.fr/content/legacy/articles/programming.org diff --git a/www/vincent.demeester.fr/content/articles/python.org b/www/vincent.demeester.fr/content/legacy/articles/python.org diff --git a/www/vincent.demeester.fr/content/articles/red_hat.org b/www/vincent.demeester.fr/content/legacy/articles/red_hat.org diff --git a/www/vincent.demeester.fr/content/articles/refiling_trees_to_files.org b/www/vincent.demeester.fr/content/legacy/articles/refiling_trees_to_files.org diff --git a/www/vincent.demeester.fr/content/articles/remote-development.org b/www/vincent.demeester.fr/content/legacy/articles/remote-development.org diff --git a/www/vincent.demeester.fr/content/articles/remote.org b/www/vincent.demeester.fr/content/legacy/articles/remote.org diff --git a/www/vincent.demeester.fr/content/articles/remote_team.org b/www/vincent.demeester.fr/content/legacy/articles/remote_team.org diff --git a/www/vincent.demeester.fr/content/articles/roam_research_why_i_love_it_and_how_i_use_it_nat_eliason.org b/www/vincent.demeester.fr/content/legacy/articles/roam_research_why_i_love_it_and_how_i_use_it_nat_eliason.org diff --git a/www/vincent.demeester.fr/content/articles/rpi.org b/www/vincent.demeester.fr/content/legacy/articles/rpi.org diff --git a/www/vincent.demeester.fr/content/articles/runc.org b/www/vincent.demeester.fr/content/legacy/articles/runc.org diff --git a/www/vincent.demeester.fr/content/articles/rust.org b/www/vincent.demeester.fr/content/legacy/articles/rust.org diff --git a/www/vincent.demeester.fr/content/articles/sandbox.org b/www/vincent.demeester.fr/content/legacy/articles/sandbox.org diff --git a/www/vincent.demeester.fr/content/articles/serverless.org b/www/vincent.demeester.fr/content/legacy/articles/serverless.org diff --git a/www/vincent.demeester.fr/content/articles/simple_correct_fast_in_that_order_drew_devault_s_blog.org b/www/vincent.demeester.fr/content/legacy/articles/simple_correct_fast_in_that_order_drew_devault_s_blog.org diff --git a/www/vincent.demeester.fr/content/articles/ssh.org b/www/vincent.demeester.fr/content/legacy/articles/ssh.org diff --git a/www/vincent.demeester.fr/content/articles/talks.org b/www/vincent.demeester.fr/content/legacy/articles/talks.org diff --git a/www/vincent.demeester.fr/content/articles/team_lead.org b/www/vincent.demeester.fr/content/legacy/articles/team_lead.org diff --git a/www/vincent.demeester.fr/content/articles/tekton-effective.org b/www/vincent.demeester.fr/content/legacy/articles/tekton-effective.org diff --git a/www/vincent.demeester.fr/content/articles/tekton-migrating-from-jenkins.org b/www/vincent.demeester.fr/content/legacy/articles/tekton-migrating-from-jenkins.org diff --git a/www/vincent.demeester.fr/content/articles/tekton-pipeline-without-pipeline-resources.org b/www/vincent.demeester.fr/content/legacy/articles/tekton-pipeline-without-pipeline-resources.org diff --git a/www/vincent.demeester.fr/content/articles/tekton-usage.org b/www/vincent.demeester.fr/content/legacy/articles/tekton-usage.org diff --git a/www/vincent.demeester.fr/content/articles/tekton.org b/www/vincent.demeester.fr/content/legacy/articles/tekton.org diff --git a/www/vincent.demeester.fr/content/articles/tekton_dev.org b/www/vincent.demeester.fr/content/legacy/articles/tekton_dev.org diff --git a/www/vincent.demeester.fr/content/articles/test.resource.yaml b/www/vincent.demeester.fr/content/legacy/articles/test.resource.yaml diff --git a/www/vincent.demeester.fr/content/articles/test.yaml b/www/vincent.demeester.fr/content/legacy/articles/test.yaml diff --git a/www/vincent.demeester.fr/content/articles/testing.org b/www/vincent.demeester.fr/content/legacy/articles/testing.org diff --git a/www/vincent.demeester.fr/content/articles/typography.org b/www/vincent.demeester.fr/content/legacy/articles/typography.org diff --git a/www/vincent.demeester.fr/content/articles/ubuntu.org b/www/vincent.demeester.fr/content/legacy/articles/ubuntu.org diff --git a/www/vincent.demeester.fr/content/articles/virtualization.org b/www/vincent.demeester.fr/content/legacy/articles/virtualization.org diff --git a/www/vincent.demeester.fr/content/articles/vterm.org b/www/vincent.demeester.fr/content/legacy/articles/vterm.org diff --git a/www/vincent.demeester.fr/content/articles/wireguard.org b/www/vincent.demeester.fr/content/legacy/articles/wireguard.org diff --git a/www/vincent.demeester.fr/content/articles/writing_technical_book.org b/www/vincent.demeester.fr/content/legacy/articles/writing_technical_book.org diff --git a/www/vincent.demeester.fr/content/articles/yoga.org b/www/vincent.demeester.fr/content/legacy/articles/yoga.org diff --git a/www/vincent.demeester.fr/content/articles/yubikey.org b/www/vincent.demeester.fr/content/legacy/articles/yubikey.org diff --git a/www/vincent.demeester.fr/content/articles/zsh.org b/www/vincent.demeester.fr/content/legacy/articles/zsh.org diff --git a/www/vincent.demeester.fr/content/legacy/posts/2012-05-07-reinit-and-jekyll.org b/www/vincent.demeester.fr/content/legacy/posts/2012-05-07-reinit-and-jekyll.org @@ -0,0 +1,61 @@ +#+title: Reinit and Jekyll +#+date: <2012-05-07 Mon> +#+filetags: website jekyll +#+setupfile: ../templates/post.org + +* Introduction + +Two weeks ago, my /online/ personal server has been attacked and, +somehow, died. I'm in the process of re-installation of it but I'm going +to hardened a bit the security on it. Anyway, this crash meant that +every piece of site I maintain has been down. That's why I moved this +/identity site/ on the github pages, using a CNAME ; That way I can +crash as much as I want my server(s), this page should still be up for a +while. + +And I'm switching on Jekyll for this website as It is supported by +Github page, easy to use and easy to deploy elsewhere (if one day I want +to move from Github). + +The rest of the post is going to be used as a /sandbox/ post to test the +site styles. + +#+BEGIN_QUOTE + This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, + consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. + Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. + + Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse + id sem consectetuer libero luctus adipiscing. +#+END_QUOTE + +** Highlight + :PROPERTIES: + :CUSTOM_ID: highlight + :ID: 6e1687a1-6898-4fe1-ac56-53b6e1e1f310 + :END: + +#+begin_src ruby +def foo puts 'foo' end +#+end_src + +Some bash script... + +#+begin_src bash +#!/bin/bash +update_gems() { + echo "Update gems for all versions ? (y/N)" + read UPDATE_GEMS + test -z "${UPDATE_GEMS}" && UPDATE_GEMS="n" + + if test "${UPDATE_GEMS}" = "y"; then + for version in =ls --color=never $HOME/.rbenv/versions=; do + echo "Updating ${version%/}" + RBENV_VERSION="${version%/}" rbenv exec gem update + RBENV_VERSION="${version%/}" rbenv exec gem install bundler + done + fi +} + +update_gems +#+end_src diff --git a/www/vincent.demeester.fr/content/legacy/posts/2012-05-08-gitolite-quick-and-dirty-mirror.org b/www/vincent.demeester.fr/content/legacy/posts/2012-05-08-gitolite-quick-and-dirty-mirror.org @@ -0,0 +1,126 @@ +#+title: Gitolite quick and dirty mirror +#+date: <2012-05-08 Tue> +#+filetags: gitolite git linux mirror github +#+setupfile: ../templates/post.org + +* Introduction + +I'm running a gitolite _instance_ on my personal server to manage my repositories +(personnal, private or public) ; and I am quickly going to share with you how I setup a +_quick and dirty_ mirror feature. + +First, I am using **gitolite 3**. The mirroring we are going to setup is not the +_supported_ [[http://sitaramc.github.com/gitolite/mirroring.html][mirroring *built-in*]]. We are going to implement a simplier way to set mirror +thing : + +1. Write a custom gitolite command ; the idea is to be able to write ~git-config~ stuff. +2. Write a hook that take a specific ~git-config~ (let say ~mirror.url~) and do a simple + mirroring. + +* Gitolite commands + +Gitolite 3 has been rewritten to be more flexible : [[http://sitaramc.github.com/gitolite/g3why.html][Why a completely new version]]. The +rewrite made it really easy to extend gitolite. +I've fork [[https://github.com/vdemeester/gitolite][gitolite]] on github+ I've +created a [[http://github.com/vdemeester/vdemeester-gitolite-local-code][repository git]] to easily add commands to my gitolite instance via _local +code_. The gitolite command I wrote is a quick and dirty script in shell to add ~git +config~. The source should speek for itself ; It _should_ include some way to check if the +given config is not already present in the ~gitolite-admin~ configuration file β€” and so +might be rewritten in ~Perl~. + +The command is ~write-git-config~ because a ~git-config~ command already exists +in the built-in commands. + +#+begin_src bash +#!/bin/sh + +# Usage: ssh git@host write-git-config <repo> <key> <value> +# +# Set git-config value for user-created ("wild") repo. + +die() { echo "$@" >&2; exit 1; } +usage() { perl -lne 'print substr($_, 2) if /^# Usage/../^$/' < $0; exit 1; } +[ -z "$1" ] && [ -z "$2" ] && [ -z "$3" ] && usage +[ "$1" = "-h" ] && usage +[ -z "$GL_USER" ] && die GL_USER not set + +# ---------------------------------------------------------------------- +repo=$1; shift +key=$1; shift +value=$1; shift + +# this shell script takes arguments that are completely under the user's +# control, so make sure you quote those suckers! + +if gitolite query-rc -q WRITER_CAN_UPDATE_DESC +then + gitolite access -q "$repo" $GL_USER W any || die You are not authorised +else + gitolite creator "$repo" $GL_USER || die You are not authorised +fi + +# if it passes, $repo is a valid repo name so it is known to contain only sane +# characters. This is because 'gitolite creator' return true only if there +# *is* a repo of that name and it has a gl-creator file that contains the same +# text as $GL_USER. + +configfile=`gitolite query-rc GL_REPO_BASE`/"$repo".git/config + +git config --file "$configfile" "$key" "$value" +#+end_src + +* Gitolite hooks + +The next step is to write a quick ~post-receive~ hook that check if there is a +certain ~git-config~ entry and run ~git push --mirror~. The file is in +~$HOME/.gitolite/hooks/common/post-receive~ ; you could add a better system to +hooks (to be able to add "dynamic" hooks, …). + +#+begin_src bash + +#!/bin/sh + +# Simple gitolite mirroring + +# flush STDIN coming from git, because gitolite's own post-receive.mirrorpush +# script does the same thing +[ -t 0 ] || cat >/dev/null + +[ -z "$GL_REPO" ] && die GL_REPO not set + +target=`git config --get mirror.url` +[ -z "$target" ] && exit 0 + +# Support a REPO variable for wildcard mirrors +gl_repo_escaped=$(echo $GL_REPO | sed 's/\//\\\//g') +target=$(echo $target | sed -e "s/REPO/$gl_repo_escaped/g") + +# Do the mirror push +git push --mirror $target +#+end_src + +The next, and final step is to run `gitolite compile` to update links to hooks +for every repositories. + +* For real + +And finaly, this is the final step you'll do. + +#+begin_src bash +$ ssh git@host write-git-config vincent/vcsh-home mirror.url git@github.com:vdemeester/vcsh-home.git +$ git push +Counting objects: 5, done. +Delta compression using up to 2 threads. +Compressing objects: 100% (3/3), done. +Writing objects: 100% (3/3), 294 bytes, done. +Total 3 (delta 2), reused 0 (delta 0) +remote: To git@github.com:vdemeester/vcsh-home.git +remote: 65681a8..701c990 master -> master +To git@host:vincent/vcsh-home.git + 65681a8..701c990 master -> master +#+end_src + + +And that should be it ! + +_Update 2012/10/04_ : Moved from gitolite fork to _gitolite local code_ +repository. diff --git a/www/vincent.demeester.fr/content/legacy/posts/2012-05-13-jekyll-foreman-guard-bundler.org b/www/vincent.demeester.fr/content/legacy/posts/2012-05-13-jekyll-foreman-guard-bundler.org @@ -0,0 +1,90 @@ +#+title: Jekyll Forman Guard Bundler +#+date: <2012-05-13 Sun> +#+filetags: jekyll ruby bundler guard foreman +#+setupfile: ../templates/post.org + +* Introduction + +This post is a quick "How did I setup my Jekyll environnement ?". We are +going all the tools that are quite awesome in Ruby. + +* Goal + :PROPERTIES: + :CUSTOM_ID: goal + :END: + +The goal is simple : + +1. I want to be able to install any dependent + [[http://rubygems.org][Gem]] with a /on-liner/ command +2. I want to be able to run a /Jekyll server/ that auto updates. + +We are going to play with : [[http://gembundler.com/][Bundler]], +[[https://github.com/guard/guard][Guard]] and +[[https://github.com/ddollar/foreman][foreman]]. + +* Bundler + :PROPERTIES: + :CUSTOM_ID: bundler + :END: + +Bundler let us run =bundle install= to get all Ruby Gems we will need ; +It use a file name =Gemfile=. The gems we need are simple : =jekyll=, +=guard= and some Guard extensions. + +#+begin_src ruby +source "http://rubygems.org" + +gem 'jekyll' +gem 'guard' +gem 'guard-jekyll2' +gem 'guard-shell' +gem 'guard-bundler' +#+end_src + +* Guard + :PROPERTIES: + :CUSTOM_ID: guard + :END: + +#+BEGIN_QUOTE + Guard is a command line tool to easily handle events on file system + modifications. +#+END_QUOTE + +Guard will be watching file we told him and run action in consequence ; +The file is name =Guardfile=. + +#+begin_src ruby +guard 'jekyll2' do + watch %r{.*} +end + +guard :bundler do + watch('Gemfile') +end +# vim:filetype=ruby +#+end_src + +* Foreman + :PROPERTIES: + :CUSTOM_ID: foreman + :END: + +Finally, foreman will let us declare our processes and will handle the +start, forward the output and handle the shutdown. It can then export +its configuration into more /production-ready/ file (=init=, =upstard=, +...) ; It uses a file named =Procfile=. + +We will tell foreman to run : + +- The jekyll build-in server : =jekyll --server= +- Guard, to handle file changes /in background/. + +#+begin_src bash +web: bundle exec jekyll --server +guard: bundle exec guard +#+end_src + +And that's all folk. Now, you just need to run foreman in the +Jekyll-powered directory and edit your files. diff --git a/www/vincent.demeester.fr/content/legacy/posts/2012-07-23-maven-release-gitflow.org b/www/vincent.demeester.fr/content/legacy/posts/2012-07-23-maven-release-gitflow.org @@ -0,0 +1,99 @@ +#+title: Maven Release Gitflow +#+date: <2012-07-23 Mon> +#+filetags: maven java git gitflow release +#+setupfile: ../templates/post.org + +* Introduction + +I like a lot the [[http://nvie.com/posts/a-successful-git-branching-model/][gitflow]] way of managing project. When working on maven project, there is +few great plugins that helps to get the work done. One of them is [[http://maven.apache.org/plugins/maven-release-plugin][maven-release-plugin]]. + +Inspired on this [[https://gist.github.com/1043970][gist]], I've come +with a cool way of doing things (let say we want to release a 0.1 +version of an artifact) : + +* Prepare the pom.xml. + :PROPERTIES: + :CUSTOM_ID: prepare-the-pom.xml. + :END: + +It needs =<scm>= entries, =<distributionManagement>= entries (to know +where to deploy the release artifact) and few options for the +maven-release-plugin : + +{{< highlight xml >}} +#+begin_src xml +<project> + + <!-- […] --> + <build> + <plugins> + <!-- […] --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-release-plugin</artifactId> + <version>2.3.2</version> + <configuration> + <tagNameFormat>v@{project.version}</tagNameFormat> + <pushChanges>false</pushChanges> + <localCheckout>true</localCheckout> + </configuration> + </plugin> + <!-- […] --> + </plugins> + </build> + <!-- […] --> + +</project> +#+end_src + +Few explanation here : + +- =tagNameFormat= is here to change the default tag name (which is + =${project.artifactId}-${project.version}=) to a better one. +- =pushChanges= set to =false= tells maven-release-plugin not to push + changes (this will become useful) +- =localCheckout= set to =true= tells maven-release-plugin to clone from + local repository (not distant). This is especially useful here because + we didn't push anything (so not setting this option would result in a + failure). + +* The real stuff + :PROPERTIES: + :CUSTOM_ID: the-real-stuff + :END: + +First create a release branch from develop. + +#+begin_src bash +$ git checkout -b release/v0.1 develop +#+end_src + +Then run the maven release stuff. + +#+begin_src bash +$ mvn release:prepare # change the pom, commit and tag version, and + # re-change pom (by incrementing SNAPSHOT version) +$ mvn release:perform # get the tagged version, compile and deploy +#+end_src + +And the real fun begins. + +#+begin_src bash +$ git checkout develop # get back to the develop branch +$ git merge --no-ff release/v0.1 # merge the version back into develop +$ git checkout master # go to the master branch +$ git merge --no-ff release/v0.1~1 # merge the version back into master but + # the tagged version instead of the release/v0.1 HEAD +$ git branch -D release/v0.1 # Removing the release branch +$ git push --all && git push --tags # Finally push everything +#+end_src + +The real magic here is the =git merge --no-ff release/v0.1~1= which will +merge into master the commit before the HEAD of the branch +=release/v0.1=. + +The next step would be to create a helper script that automates this and +verify that the =pom.xml= has the right configuration options. + +*Edit 17:58* : You can take a look [[https://github.com/vdemeester/java-config/blob/master/bin/mvn-release-flow][here]] diff --git a/www/vincent.demeester.fr/content/legacy/posts/2012-12-16-gollum-comme-wiki-personnel.org b/www/vincent.demeester.fr/content/legacy/posts/2012-12-16-gollum-comme-wiki-personnel.org @@ -0,0 +1,84 @@ +#+title: Gollum comme Wiki personnel +#+date: <2012-12-16 Sun> +#+filetags: wiki golum github personnel +#+setupfile: ../templates/post.org + +* Introduction + +Il y a environ 4 mois j'ai eu un accident de vΓ©lo ; un traumatisme +crΓ’nien, des brulures sur la face, quelques points de sutures, un doigt +cassΓ© et une hernie discale m'ont clouΓ© (et me clou encore) plus que +d'habitude sur ma chaise de bureau. Le bon cΓ΄tΓ© des choses, c'est que +cela m'a permit de me poser et de rΓ©flΓ©chir une bonne faΓ§on d'Γͺtre +efficace et organiser, au travail et Γ  la maison :-). + +Une des principales /action/ que j'ai pris est d'utiliser un wiki local +et synchronisΓ© sur /tout/ mes PCs. Le /format/ wiki est assez adaptΓ© Γ  +une prise de note et Γ  la crΓ©ation de contenu plus complet (comme des +[[http://shortbrain.org][articles]] ou de la documentation pour des +projets en cours). Les conditions Γ©taient les suivantes : + +- FacilitΓ© de mise en place. +- Pas de base de donnΓ©es. +- /Merging/ facile ([[http://git-scm.com][git]] /rules my world/). +- [[http://daringfireball.net/projects/markdown/][Markdown]] comme + syntaxe, car utilisΓ© Γ  peu prΓ¨s partout (blogs, articles, READMEs, + documentations). +- Γ‰ditable Γ  partir d'une interface web ou de mon Γ©diteur favoris. + +L'outil qui remplit presque toutes ces conditions s'appelle +[[https://github.com/github/gollum][gollum]]. C'est un moteur wiki, +Γ©cris en ruby, qui se base sur un repository +[[http://git-scm.com][git]]. Il est dΓ©velopper par l'Γ©quipe de +[[http://github.com][Github]] et c'est celui qui est utilisΓ© par les +pages wiki lΓ -bas. Il permet d'utiliser Γ  peu prΓ¨s n'importe quel +syntaxe (dont +[[https://github.com/github/github-flavored-markdown][github-markdown]] +qui est assez proche de celle de +[[http://johnmacfarlane.net/pandoc][pandoc]]). Par ailleurs, comme il se +base sur [[http://git-scm.com][git]], les points /"pas de base de +donnΓ©es"/, /"merging facile"/ et /"Γ©ditable Γ©galement Γ  partir de mon +Γ©diteur favoris"/ sont toutes remplies. + +Avec [[https://github.com/github/gollum][Gollum]] vous avez un wiki +markdown dΓ©centralisΓ©, Γ©ditable via une interface web ou via votre +Γ©diteur favoris. + +* Mise en place + :PROPERTIES: + :CUSTOM_ID: mise-en-place + :END: + +La mise en place est relativement simple ; aprΓ¨s tout dΓ©pend du besoin +que vous avez. L'installation se fait par [[file:rubygems.org][RubyGem]] +ou en clonant le repository. + +{{< highlight bash >}} # Installation de gollum et du format markdown de +github $ gem install gollum gitub-markdown {{< /highlight >}} + +Si vous n'utilisez pas [[https://github.com/sstephenson/rbenv][rbenv]] +ou [[https://rvm.io/][rvm]] il est probable qu'il faille lancer la +commande en root ou utiliser sudo. + +Ensuite, il suffit de lancer +[[https://github.com/github/gollum][Gollum]] dans un dossier qui est un +repository git ; le tour est jouΓ© + +{{< highlight bash >}} # J'ulitise ~/desktop/wiki pour mon wiki $ cd +~/desktop/wiki && gollum {{< /highlight >}} + +L'idΓ©e finale est d'automatiser deux choses : + +1. Le dΓ©marrage de gollum +2. La synchronisation du repository avec les diffΓ©rents autres /remotes/ + +Suivant le systΓ¨me d'exploitation et/ou la distribution utilisΓ©es, il y +a Γ©normΓ©ment de possibilitΓ© d'effectuer cette automatisation. Dans mon +cas, j'ai une [[http://debian.org][Debian]] assez light, avec surtout +plein de scripts. Je dΓ©marre donc +[[https://github.com/github/gollum][Gollum]] au dΓ©marrage de ma session +grΓ’ce Γ  une script qui est lancΓ© dans la foulΓ©e du gestionnaire de +fenΓͺtre. La synchronisation se fait grΓ’ce Γ  une tΓ’che planifiΓ©e /cron/ +qui est "distribuΓ©" sur chacune de mes machines. + +/C'est tout pour le moment/ ;-). diff --git a/www/vincent.demeester.fr/content/legacy/posts/2013-09-08-maven-tmpfs.org b/www/vincent.demeester.fr/content/legacy/posts/2013-09-08-maven-tmpfs.org @@ -0,0 +1,158 @@ +#+title: Maven Tmpfs +#+date: <2013-09-08 Sun> +#+filetags: maven tmpfs ssd +#+setupfile: ../templates/post.org + +* Introduction + +Je suis un utilisateur convaincu de [[http://maven.apache.org/][maven]], malgrΓ© ces dΓ©fauts, le +moto *"Convention over configuration"* me va vraiment bien. Que ce soit +au boulot ou Γ  la maison, j'ai plus d'ordinateurs Γ©quipΓ©s de ssd (ou de +mΓ©moire flash) que de disque traditionnel (mΓ©canique ?). Pour augmenter +un peu la durΓ©e de vie de ces disques SSD, j'ai cherchΓ© Γ  savoir comment +/dΓ©porter/ le /build/ de maven (qui, pour rappel, se passe dans le +dossier =target/=) hors du SSD ; ici ce sera dans le dossier =/tmp/= qui +est montΓ© en mΓ©moire (merci =tmpfs=), mais on peut imaginer dΓ©porter Γ§a +sur un autre disque, etc.. AprΓ¨s quelques recherches j'ai trouvΓ©s +quelques inspirations. + +#+BEGIN_QUOTE + *Limitations* + + Dans la solution prΓ©sentΓ©e ci-dessous les principales limitations sont + les suivantes (que j'essaierais de diminuer au fil du temp ;P) : + + 1. Il est nΓ©cessaire de modifier le pom.xml du projet ; cela ne + s'appliquera donc pas Γ  tous les projets maven sans modification du + pom.xml. + 2. Cela ne fonctionne que sur une plateforme qui support les liens + symboliques (Linux, Mac OS X, et autre UNIX). + 3. Cela ne fonctionne qu'avec Java 7 ou plus. + 4. Si vous utilisez m2e, il va gentillement gueuler et c'est moche ; pour rΓ©soudre le + problΓ¨me, il faut faire un tour vers [[http://wiki.eclipse.org/M2E_plugin_execution_not_covered][M2E plugin execution not covered]]. +#+END_QUOTE + +Pour [[http://maven.apache.org/][maven]], le dossier =target/= vient de la propriΓ©tΓ© +=project.build.directory=. Dans la thΓ©orie, il suffirait de modifier +(dans =$HOME/.m2/settings.xml=) cette propriΓ©tΓ© et le tour serait jouer. +Malheuresement ce n'est pas possible, =project.build.directory= est une +propriΓ©tΓ© interne et n'est, Γ  priori, pas modifiable. + +Notre souhait est le suivant : + +1. Le build doit se faire dans =/tmp/m2/=, ce qui pour un projet se + traduit par =/tmp/m2/${groupId}:${artifactId}=. +2. Le dossier =target/= dans les sources est un lien symbolique vers le + dossier dans =/tmp/m2/= +3. On passe par un *profile* qui n'est *pas actif* par dΓ©faut (pour ne + pas faire chier le monde) mais *activable via une propriΓ©tΓ©* (maven + nous permet de le faire et c'est cool =^_^=). La propriΓ©tΓ© utilisΓ©e + sera =external.build.root=. + +Le code ci-dessous est repris directement de mon inspiration[fn:1]. Il +s'occupe de crΓ©er le dossier =${groupId}:${artifactId}= dans +=external.build.root= et de faire le lien dans le dossier courant. + +#+begin_src xml +<project> + <!-- […] --> + <profiles> + <profile> + <id>external-build-dir</id> + <activation> + <activeByDefault>false</activeByDefault> + <property> + <name>external.build.root</name> + </property> + </activation> + <build> + <plugins> + <plugin> + <groupId>com.alexecollins.maven.plugin</groupId> + <artifactId>script-maven-plugin</artifactId> + <version>1.0.0</version> + <executions> + <execution> + <id>prep-work-tree</id> + <goals> + <goal>execute</goal> + </goals> + <phase>initialize</phase> + <configuration> + <script> + import java.nio.file.* + def dir = + "${external.build.root}/${project.groupId}:${project.artifactId}" + println "using Maven dir ${dir}" + def dirPath = Paths.get(dir) + if (!Files.exists(dirPath)) { + Files.createDirectories(dirPath) + } + def target = Paths.get("${project.build.directory}") + if (!Files.exists(target)) { + Files.createSymbolicLink(target, dirPath) + }</script> + </configuration> + </execution> + <execution> + <id>drop-symlink</id> + <goals> + <goal>execute</goal> + </goals> + <phase>clean</phase> + <configuration> + <script> + import java.nio.file.* + def target = Paths.get("${project.build.directory}") + if (Files.isSymbolicLink(target)) { + Files.delete(target) + } + </script> + </configuration> + </execution> + </executions> + <dependencies> + <dependency> + <groupId>org.codehaus.groovy</groupId> + <artifactId>groovy</artifactId> + <version>1.8.6</version> + </dependency> + </dependencies> + <configuration> + <language>groovy</language> + </configuration> + </plugin> + </plugins> + </build> + </profile> + </profiles> + <!-- […] --> +</project> +#+end_src + +Ainsi, il suffit ensuite d'avoir quelques choses du genre dans son +=$HOME/.m2/settings.xml= pour que les builds qui ont ce profil se +/build/ dans =/tmp/m2/=. On peut aussi ne rien avoir dans +=$HOME/.m2/settings.xml= et utilise =-Dexternal.build.root=/tmp/m2/= +avec la commande =mvn=. + +#+begin_src xml +<settings> + <!-- […] --> + <profiles> + <profile> + <id>build-in-ramfs</id> + <properties> + <external.build.root>/tmp/m2/</external.build.root> + </properties> + </profile> + </profiles> + <activeProfiles> + <activeProfile>build-in-ramfs</activeProfile> + </activeProfiles> + <!-- […] --> +</settings> +#+end_src + + +[fn:1] [[http://elehack.net/writings/programming/maven-target-in-tmpfs][PuttingMaven build directories out-of-tree]] par [[http://elehack.net/][Michal Ekstrand]] diff --git a/www/vincent.demeester.fr/content/legacy/posts/2013-10-12-podcasts.org b/www/vincent.demeester.fr/content/legacy/posts/2013-10-12-podcasts.org @@ -0,0 +1,91 @@ +#+TITLE: Podcasts +#+date: <2013-10-12 Sat> +#+filetags: music podcast +#+setupfile: ../templates/post.org + +* Introduction + +#+BEGIN_QUOTE + Voici un petit billet prΓ©sentant les diffΓ©rents podcast que j'Γ©coute + plus ou moins rΓ©guliΓ¨rement. +#+END_QUOTE + +J'Γ©coute Γ©normΓ©ment de musique et de podcast ; je passe beaucoup de +temps avec des Γ©couteurs sur la tΓͺte ou la chaine hifi en route. Les +podcasts ont une grande place. Voici une liste plus ou moins bien triΓ©s +de ceux auxquels je suis souscrit et/ou que j'Γ©coute en ce moment. Je +tiendrais peut-Γͺtre ce post Γ  jour ou en crΓ©erait un nouveau sinon :-). + +* Geek & co + :PROPERTIES: + :CUSTOM_ID: geek-co + :END: + +Je suis un developpeur, un geek et convaincu des logiciels libres, les +podcasts qui suivent reflΓ¨te assez cette partie lΓ  de mon identitΓ©. + +- [[http://www.captainweb.net/][L'apΓ©ro de Captain (fr)]] : dans le + genre geeky, dΓ©jantΓ© et sauvage, en franΓ§ais, on ne fait pas mieux. + Pas vraiment safe for work, et pas tout Γ  fait "libriste" comme + j'aime, les tranches de rires sont garanties ; mΓͺme si parfois on est + un peu verreux d'avoir Γ©couter jusqu'Γ  la fin et le bien nommΓ© + "wazzuf". +- [[http://www.agencetousgeeks.com/][Agence Tous Geeks (fr)]] : fils + cachΓ© de l'apΓ©ro du captain, on y retrouve des amis (et membres de ce + dernier), mais c'est un peu plus calme. +- [[http://bazingcast.com/about/][Bazingcast (fr)]] : podcast geek, plus + posΓ© que les deux prΓ©cΓ©dents mais avec des dΓ©bats, des trolls et tout + ce que l'on peut attendre de geeks. +- [[http://www.captainposix.net/][Parole de Tux (fr-be)]] : podcast + venant de nos voisin belge, pas trop long et plutΓ΄t sympa ; et si + comme moi vous adorez l'accent belge, c'est le top. +- [[http://faif.us/][Free as in Freedom (en)]] : podcast parlant de + logiciel libre principalement cΓ΄tΓ© license, truc lΓ©gal, etc.. en + anglais, faut parfois s'accrocher. +- [[http://episodes.gitminutes.com/][Git Minutes (en)]] : podcast Γ  + propos de Git et des outils de son Γ©cosystΓ¨me (vcsh, etc..). + +* Radio + :PROPERTIES: + :CUSTOM_ID: radio + :END: + +J'aime bien la radio, bien plus que la tΓ©lΓ©vision (que j'allume si peu +qu'Γ  chaque fois la box se met Γ  jour =;-p=). + +- [[http://www.franceinter.fr/emission-laura-leishman-project][France + Inter - LLP (Laura leishman Project)]] +- [[http://radiofrance-podcast.net/podcast09/rss_12265.xml][Le Mouv' - + Laura Leishman Project]] +- [[http://www.franceinter.fr/emission-interception][France Inter - + Interception]] +- [[http://www.la-bas.org/][France Inter - lΓ -bas si j'y suis]] +- [[http://www.franceculture.fr/podcast/4689840][France Culture - + Pixel]] +- [[http://www.franceculture.fr/podcast/4685228][France Culture - Place + de la toile]] +- [[http://www.franceculture.fr/podcast/4689418][France Culture - + Philippe Meyer]] +- [[http://radiofrance-podcast.net/podcast09/rss_12582.xml][Le Mouv' - + Glitch (sur le Mouv')]] +- [[http://radiofrance-podcast.net/podcast09/rss_12691.xml][Le Mouv' - + Code Source]] +- [[http://radiofrance-podcast.net/podcast09/rss_12190.xml][Le Mouv' - + Suivez le geek]] +- [[http://www.divergence-fm.org/-http-www-divergence-fm-org-ecrire-exec-rubrique-id_rubrique-61-.html][Divergence + Numerique]] + +* Musique + :PROPERTIES: + :CUSTOM_ID: musique + :END: + +Les podcast suivant sont purement musique, tous musique techno ou trance +(que j'aime bien). + +- [[http://podcasts.flaix.fr/corstencountdown][Ferry Corsten - Corsten + Countdown]] +- [[http://www.galexmusic.com/podcast/gareth.xml][Gareth Emery Podcast]] +- [[http://feedproxy.feedburner.com/Tiestos_club_life][Tiesto Club + Life]] +- [[http://oakenfold.libsyn.com/rss][Oakenfold Perfecto Podcast]] diff --git a/www/vincent.demeester.fr/content/legacy/posts/2014-03-24-redesign-et-rΓ©solutions.org b/www/vincent.demeester.fr/content/legacy/posts/2014-03-24-redesign-et-rΓ©solutions.org @@ -0,0 +1,76 @@ +#+title: Redesign et RΓ©solutions +#+date: <2014-03-24 Mon> +#+filetags: jekyll design images redesign +#+setupfile: ../templates/post.org + +* Introduction + +Un /tout petit/ post pour parler rapidement, entre autre, du redesign de +[[http://vincent.demeester.fr][vincent.demeester.fr]] et de mes +rΓ©solutions. + +* Redesign + :PROPERTIES: + :CUSTOM_ID: redesign + :END: + +Les raisons de ce redesign sont assez simple : je change d'employeur et +de ville (retour sur Paris =\o/=). Cela fait pas mal de changement, et +en voulant mettre Γ  jour la page d'accueil (ce que je n'ai toujours pas +fait =;-P=), j'avais envie de /dΓ©mΓ©nager/ le site en quelques sortes. + +Pour l'inspiration c'est assez facile Γ  trouver, regarder +[[http://medium.com][medium]], le site de +[[http://www.viksit.com/][Viksit Gaur]] ou encore celui de +[[http://silent-strength.com/][Michael]] (coucou =:-P=). J'adore ce +genre de site, assez Γ©purΓ© mais avec une partie fixe (Γ  gauche ou Γ  +droite) et avec des images changeantes. + +Les images en fond de l'espace de gauche peuvent changer d'une page Γ  +l'autre. Je suis en train de m'amuser un peu avec +[[http://jekyllrb.com][Jekyll]]. Il faut que je trouve un moyen +d'optimiser un peu les images que j'utilise parce que lΓ  je joue un peu +le bourrin. + +* RΓ©solutions + :PROPERTIES: + :CUSTOM_ID: rΓ©solutions + :END: + +Nouvelle annΓ©e, nouveau boulot, implique nouvelles rΓ©solutions. Il y a +deux aspects Γ  ces /rΓ©solutions/ : sur un plan +informatique/geek/travail/organisation, et sur un plan purement +/physique/. + +Mon retour sur Paris devrait me permettre de participer Γ  un peu plus +d'Γ©vΓ¨nements, notamment du cΓ΄tΓ© des Java User Group, mais aussi FSFE, et +j'en passe. Je compte Γ©galement continuΓ© Γ  jouer le /factotum/ en me +gardant un peu de temps au niveau personnel pour travailler sur des +aspects /geek/ que je n'aurais peut-Γͺtre pas l'occasion de pratiquer au +travail. Au niveau de mes /points d'entrΓ©es/ sur le web, je compte +remettre un peu en route la partie blog de ce site et tourner +shortbrain.org (ou autre) en un site plus "documentation" en me basant +sur mes notes (powered by [[http://org-mode.org][org-mode]]). Je me suis +Γ©galement remis Γ  [[https://www.gnu.org/software/emacs/][Gnu Emacs]] et +/oh god/ qu'est-ce que c'est bon =:-D=. + +Au niveau phyisque, dans la continuitΓ© de la fin 2012, l'annΓ©e 2013 a +probablement Γ©tΓ© la pire de ma vie. Les problΓ¨mes de dos c'est pas +facile tous les jours.. En fΓ©vrier, j'avais peur de ne jamais remonter +sur un vΓ©lo ; en Mai je remontais pour la premiΓ¨re fois sur le vΓ©lo, en +Septembre je faisais 40km par semaine et en dΓ©cembre j'Γ©tais presque +capable de faire 30km en une journΓ©e. MΓ¨me si l'annΓ©e 2014 a mal +commencΓ© (je me suis felΓ© un cΓ΄te en Janvier), mes objectifs pour cette +annΓ©e et les futurs sont nettement plus positif. Le +[[http://fitbit.com][fitbit]] que j'ai acquis en FΓ©vrier me permet +d'avoir un objectif de marche (certes assez modeste) de 10000 pas et +8,05 km par jour. Je compte bien me fixer d'autre objectifs : monter la +barre plus haut (15000 voir plus), faire 50km de vΓ©lo sur une journΓ©e. + +Les objectifs physiques Γ  trΓ¨s long termes sont Γ©galement assez simples +: retrouver ma forme physique et ne plus Γͺtre gΓ©nΓ© Γ  cause du dos. En +gros c'est : Γͺtre capable de faire 120km de vΓ©lo et monter des cols (Col +du Sapenay, Mont Revard) et Γ  long terme, faire des courses de footing +(i.e.Β 10km, Paris-Versaille, Semi-marathon, marathon :D). + +Sur ce, je vais retourner Γ  mes cartons :-P. diff --git a/www/vincent.demeester.fr/content/legacy/posts/2018-07-28-gotest-tools-intro.org b/www/vincent.demeester.fr/content/legacy/posts/2018-07-28-gotest-tools-intro.org @@ -0,0 +1,45 @@ +#+TITLE: Golang testing β€” gotest.tools introduction +#+date: <2018-07-28 Sat> +#+filetags: feature go testing +#+setupfile: ../templates/post.org + +* Introduction + +I already wrote 2 previous posts about golang and testing. It's something I care deeply +about and I wanted to continue writing about it. It took me a bit more time than I +thought, but getting back to it. Since the [[http://vincent.demeester.fr/posts/2017-04-22-golang-testing-golden-file/][last post]], Daniel Nephin and I worked (but +mainly Daniel πŸ€—) on bootstrapping a testing helper library. + +Let me introduce it to you this library : [[https://gotest.tools][=gotest.tools=]]. As described in the [[https://godoc.org/gotest.tools][godoc]] package comment, =gotest.tools= is a +collection of packages to augment =testing= and support common patterns. It's an enhanced and growing version of the +initial helpers we (the docker/moby maintainers) wrote initially in [[https://github.com/docker/docker][=docker/docker=]] repository. We are using in quite some +project here at [[https://github.com][Docker]]. + +There is a bunch of packages that will all have their own post (linked here when available) : + +- [[file:2018-08-16-gotest-tools-assertions.org][=assert=]] (with =assert/cmp= and =assert/opt=) that provides assertions for comparing expected values to actual values. +- =env= that provides functions to test code that read environment variable or the current working directory. +- [[file:2018-09-14-gotest-tools-fs.org][=fs=]] that provides tools for creating temporary files, and testing the contents and structure of a directory. +- [[file:2018-09-06-gotest-tools-golden.org][=golden=]] that provides tools for comparing large multi-line strings. +- [[file:2018-09-18-gotest-tools-icmd.org][=icmd=]] that executes binaries and provides convenient assertions for testing the results. +- [[file:2019-03-23-gotest-tools-poll.org][=poll=]] that provides tools for testing asynchronous code. +- [[file:2018-09-01-gotest-tools-skip.org][=skip=]] that provides functions for skipping a test and printing the source code of the condition used to skip the test. + +There is also experimental package, using the =x= notation (as the golang team uses, for example with =golang.org/x/sync=) : + +- =x/subtest= that provides a =TestContext= to subtests which handles cleanup and provides a =testing.TB= and =context.Context=. + +There is already some good =testing= helpers in the Go ecosystem : [[https://github.com/stretchr/testify][=testify=]], [[http://labix.org/gocheck][=gocheck=]], [[https://github.com/onsi/ginkgo][=ginkgo=]] and a lot more β€” so +why create a new one ? There is multiple reason for it, most of them can be seen in the following [[https://github.com/gotestyourself/gotest.tools/issues/49#issuecomment-362436026][GitHub issue]]. + +[[https://github.com/dnephin/][Daniel]] also wrote a very useful converter if your code base is currently using =testify= : =gty-migrate-from-testify=. + +#+BEGIN_SRC sh +$ go get -u gotest.tools/assert/cmd/gty-migrate-from-testify +# […] +$ go list \ + -f '{{.ImportPath}} {{if .XTestGoFiles}}{{"\n"}}{{.ImportPath}}_test{{end}}' \ + ./... | xargs gty-migrate-from-testify +#+END_SRC + +In the next post, let's dig into the assertion part of the library, package =assert= πŸ‘Ό. diff --git a/www/vincent.demeester.fr/content/legacy/posts/2018-08-16-gotest-tools-assertions.org b/www/vincent.demeester.fr/content/legacy/posts/2018-08-16-gotest-tools-assertions.org @@ -0,0 +1,340 @@ +#+title: Golang testing β€” gotest.tools assertions +#+date: <2018-08-16 Thu> +#+filetags: go testing assert +#+setupfile: ../templates/post.org + +#+TOC: headlines 2 + +* Introduction + +Let's take a closer look at [[https://gotest.tools][=gotest.tools=]] assertions packages. This is mainly about =assert=, =assert/cmp= and +=assert/opt=. + +#+BEGIN_QUOTE +Package assert provides assertions for comparing expected values to actual values. When assertion fails a helpful error +message is printed. +#+END_QUOTE + +There is two main functions (=Assert= and =Check=) and some helpers (like =NilError=, …). They all take a =*testing.T= as +a first argument, pretty common across testing Go libraries. Let's dive into those ! + +* =Assert= and =Check= + +Both those functions accept a =Comparison= (we'll check what it is later on) and fail the test when that comparison +fails. The one difference is that =Assert= will end the test execution at immediately whereas =Check= will fail the test +and proceed with the rest of the test case. This is similar to =FailNow= and =Fail= from the standard library +=testing=. Both have their use cases. + +We'll Use =Assert= for the rest of the section but any example here would work with =Check= too. When we said +=Comparison= above, it's mainly the [[https://godoc.org/gotest.tools/assert#BoolOrComparison][BoolOrComparison]] interface β€” it can either be a boolean expression, or a +[[https://godoc.org/gotest.tools/assert/cmp#Comparison][cmp.Comparison]] type. =Assert= and =Check= code will be /smart/ enough to detect which one it is. + +#+BEGIN_SRC go + assert.Assert(t, ok) + assert.Assert(t, err != nil) + assert.Assert(t, foo.IsBar()) +#+END_SRC + +So far not anything extra-ordinary. Let's first look at some more /helper/ functions in the =assert= package and quickly +dive a bit deeper with =Comparison=. + +* More =assert= helpers + +The additional helper functions are the following + +- =Equal= uses the ==== operator to assert two values are equal. +- =DeepEqual= uses =google/go-cmp= to assert two values are equal (it's /close/ to =reflect.DeepEqual= but not + quite). We'll detail a bit more the /options/ part of this function with =cmp.DeepEqual=. +- =Error= fails if the error is =nil= *or* the error message is not the expected one. +- =ErrorContains= fails if the error is =nil= *or* the error message does not contain the expected substring. +- =ErrorType= fails if the error is =nil= *or* the error type is not the expected type. +- =NilError= fails if the error is not =nil=. + +All those helper functions have a equivalent function in the =cmp= package that returns a =Comparison=. I, personally, +prefer to use =assert.Check= or =assert.Assert= in combination with =cmp.Comparison= as it allows me to write all my +assertions the same way, with built-ins comparison or with my own β€” i.e. =assert.Assert(t, is.Equal(…), "message"= or +=assert.Assert(t, stackIsUp(c, time…), "another message")=. + +* =cmp.Comparison= + +This is where it get really interesting, =gotest.tools= tries to make it as easy as possible for you to create +appropriate comparison β€” making you test readable as much as possible. + +Let's look a bit at the =cmp.Comparison= type. + +#+BEGIN_SRC go +type Comparison func() Result +#+END_SRC + +It's just a function that returns a =cmp.Result=, so let's look at =cmp.Result= definition. + +#+BEGIN_SRC go +type Result interface { + Success() bool +} +#+END_SRC + +Result is an =interface=, thus any /struct/ that provide a function =Success= that returns a =bool= can be used as a +comparison result, making it really easy to use in your code. There is also existing type of result to make it even +quicker to write your own comparison. + +- =ResultSuccess= is a constant which is returned to indicate success. +- =ResultFailure= and =ResultFailureTemplate= return a failed Result with a failure message. +- =ResultFromError= returns =ResultSuccess= if =err= is nil. Otherwise =ResultFailure= is returned with the error + message as the failure message. It works a bit like the =errors.Wrap= function of the [[https://github.com/pkg/errors][=github.com/pkgs/errors=]] + package. + +The =cmp= package comes with a few defined comparison that, we think, should cover a high number of use-cases. Let's +look at them. + +** Equality with =Equal= and =DeepEqual= + +#+BEGIN_QUOTE +Equal uses the == operator to assert two values are equal and fails the test if they are not equal. + +If the comparison fails Equal will use the variable names for x and y as part of the failure message to identify the +actual and expected values. + +If either x or y are a multi-line string the failure message will include a unified diff of the two values. If the +values only differ by whitespace the unified diff will be augmented by replacing whitespace characters with visible +characters to identify the whitespace difference. +#+END_QUOTE + +On the other hand… + +#+BEGIN_QUOTE +DeepEqual uses google/go-cmp (http://bit.do/go-cmp) to assert two values are equal and fails the test if they are not +equal. + +Package https://godoc.org/gotest.tools/assert/opt provides some additional commonly used Options. +#+END_QUOTE + +Using one or the other is as simple as : if you wrote your =if= with ==== then use =Equal=, otherwise use =DeepEqual=. +=DeepEqual= (and usually =reflect.DeepEqual=) is used when you want to compare anything more complex than primitive +types. One advantage of using =cmp.DeepEqual= over =reflect.DeepEqual= (in an if), is that you get a well crafted +message that shows the diff between the expected and the actual structs compared – and you can pass options to it. + +#+BEGIN_SRC go +assert.Assert(t, cmp.DeepEqual([]string{"a", "b"}, []string{"b", "a"})) +// Will print something like +// --- result +// +++ exp +// {[]string}[0]: +// -: "a" +// +: "b" +// {[]string}[1]: +// -: "b" +// +: "a" +foo := &someType(a: "with", b: "value") +bar := &someType(a: "with", b: "value") +// the following will succeed as foo and bar are _DeepEqual_ +assert.Assert(t, cmp.DeepEqual(foo, bar)) +#+END_SRC + +When using =DeepEqual=, you may end up with really weird behavior(s). You may want to ignore some fields, or consider +=nil= slice or map the same as empty ones ; or more common, your /struct/ contains some unexported fields that you +cannot use when comparing (as they are not exported πŸ˜“). In those case, you can use =go-cmp= options. + +Some existing one are : +- [[https://godoc.org/github.com/google/go-cmp/cmp/cmpopts#EquateEmpty][=EquateEmpty=]] returns a Comparer option that determines all maps and slices with a length of zero to be equal, + regardless of whether they are nil. +- [[https://godoc.org/github.com/google/go-cmp/cmp/cmpopts#IgnoreFields][=IgnoreFields=]] returns an Option that ignores exported fields of the given names on a single struct type. The struct + type is specified by passing in a value of that type. +- [[https://godoc.org/github.com/google/go-cmp/cmp/cmpopts#IgnoreUnexported][=IgnoreUnexported=]] returns an Option that only ignores the immediate unexported fields of a struct, including anonymous + fields of unexported types. +- [[https://godoc.org/github.com/google/go-cmp/cmp/cmpopts#SortSlices][=SortSlices=]] returns a Transformer option that sorts all =[]V= +- … and [[https://godoc.org/github.com/google/go-cmp/cmp/cmpopts][more]] πŸ‘Ό + +=gotest.tools= also defines some *and* you can define yours ! As an example, =gotest.tools= defines =TimeWithThreshold= +and =DurationWithThreshold= that allows to not fails if the time (or duration) is not exactly the same but in the +specified threshold we specified. Here is the code for =DurationWithThreshold= for inspiration. + +#+BEGIN_SRC go +// DurationWithThreshold returns a gocmp.Comparer for comparing time.Duration. The +// Comparer returns true if the difference between the two Duration values is +// within the threshold and neither value is zero. +func DurationWithThreshold(threshold time.Duration) gocmp.Option { + return gocmp.Comparer(cmpDuration(threshold)) +} + +func cmpDuration(threshold time.Duration) func(x, y time.Duration) bool { + return func(x, y time.Duration) bool { + if x == 0 || y == 0 { + return false + } + delta := x - y + return delta <= threshold && delta >= -threshold + } +} +#+END_SRC + +Another good example for those options is when you want to skip some field. In [[https://github.com/docker/docker][=docker/docker=]] we want to be able to +easily check for equality between two service specs, but those might have different =CreatedAt= and =UpdatedAt= values +that we usually don't care about – what we want is to make sure it happens in the past 20 seconds. You can easily define +an option for that. + +#+BEGIN_SRC go + func cmpServiceOpts() cmp.Option { + const threshold = 20 * time.Second + + // Apply withinThreshold only for the following fields + metaTimeFields := func(path cmp.Path)bool { + switch path.String() { + case "Meta.CreatedAt", "Meta.UpdatedAt": + return true + } + return false + } + // have a 20s threshold for the time value that will be passed + withinThreshold := cmp.Comparer(func(x, y time.Time) bool { + delta := x.Sub(y) + return delta < threshold && delta > -threshold + }) + + return cmp.FilterPath(metaTimeFields, withinThreshold) + } +#+END_SRC + +I recommend you look at the [[https://godoc.org/gotest.tools/assert/opt][gotest.tools/assert/opt]] documentation to see which one are defined and how to use them. + +** Errors with =Error=, =ErrorContains= and =ErrorType= + +Checking for errors is *very common* in Go, having =Comparison= function for it was a requirement. + +- =Error= fails if the error is =nil= *or* the error message is not the expected one. +- =ErrorContains= fails if the error is =nil= *or* the error message does not contain the expected substring. +- =ErrorType= fails if the error is =nil= *or* the error type is not the expected type. + +Let's first look at the most used : =Error= and =ErrorContains=. + +#+BEGIN_SRC go + var err error + // will fail with : expected an error, got nil + assert.Check(t, cmp.Error(err, "message in a bottle")) + err = errors.Wrap(errors.New("other"), "wrapped") + // will fail with : expected error "other", got "wrapped: other" + assert.Check(t, cmp.Error(err, "other")) + // will succeed + assert.Check(t, cmp.ErrorContains(err, "other")) +#+END_SRC + +As you can see =ErrorContains= is especially useful when working with /wrapped/ errors. +Now let's look at =ErrorType=. + +#+BEGIN_SRC go + var err error + // will fail with : error is nil, not StubError + assert.Check(t, cmp.ErrorType(err, StubError{})) + + err := StubError{"foo"} + // will succeed + assert.Check(t, cmp.ErrorType(err, StubError{})) + + // Note that it also work with a function returning an error + func foo() error {} + assert.Check(t, cmp.ErrorType(foo, StubError{})) +#+END_SRC + +** Bonus with =Panics= + +Sometimes, a code is supposed to /panic/, see [[https://golang.org/doc/effective_go.html#panic][Effective Go (#Panic)]] for more information. And thus, you may want to make +sure you're code panics in such cases. It's always a bit tricky to test a code that panic as you have to use a deferred +function to recover the panic β€” but then if the panic doesn't happen how do you fail the test ? + +This is where =Panics= comes handy. + +#+BEGIN_SRC go + func foo(shouldPanic bool) { + if shouldPanic { + panic("booooooooooh") + } + // don't worry, be happy + } + // will fail with : did not panic + assert.Check(t, cmp.Panics(foo(false))) + // will succeed + assert.Check(t, cmp.Panics(foo(true))) +#+END_SRC + +** Miscellaneous with =Contains=, =Len= and =Nil= + +Those last three /built-in/ =Comparison= are pretty straightforward. + +- =Contains= succeeds if item is in collection. Collection may be a string, map, slice, or array. + + If collection is a string, item must also be a string, and is compared using =strings.Contains()=. If collection is a + Map, contains will succeed if item is a key in the map. If collection is a slice or array, item is compared to each + item in the sequence using ==reflect.DeepEqual()==. +- =Len= succeeds if the sequence has the expected length. +- =Nil= succeeds if obj is a nil interface, pointer, or function. + +#+BEGIN_SRC go + // Contains works on string, map, slice or arrays + assert.Check(t, cmp.Contains("foobar", "foo")) + assert.Check(t, cmp.Contains([]string{"a", "b", "c"}, "b")) + assert.Check(t, cmp.Contains(map[string]int{"a": 1, "b": 2, "c": 4}, "b")) + + // Len also works on string, map, slice or arrays + assert.Check(t, cmp.Len("foobar", 6)) + assert.Check(t, cmp.Len([]string{"a", "b", "c"}, 3)) + assert.Check(t, cmp.Len(map[string]int{"a": 1, "b": 2, "c": 4}, 3)) + + // Nil + var foo *MyStruc + assert.Check(t, cmp.Nil(foo)) + assert.Check(t, cmp.Nil(bar())) +#+END_SRC + +But let's not waste more time and let's see how to write our own =Comparison= ! + +** Write your own =Comparison= + +One of the main aspect of =gotest.tools/assert= is to make it easy for developer to write as less boilerplate code as +possible while writing tests. Writing your own =Comparison= allows you to write a well named function that will be easy +to read and that can be re-used across your tests. + +Let's look back at the =cmp.Comparison= and =cmp.Result= types. + +#+BEGIN_SRC go +type Comparison func() Result + +type Result interface { + Success() bool +} +#+END_SRC + +A =Comparison= for =assert.Check= or =assert.Check= is a function that return a =Result=, it's pretty straightforward to +implement, especially with =cmp.ResultSuccess= and =cmp.ResultFailure(…)= (as seen previously). + +#+BEGIN_SRC go + func regexPattern(value string, pattern string) cmp.Comparison { + return func() cmp.Result { + re := regexp.MustCompile(pattern) + if re.MatchString(value) { + return cmp.ResultSuccess + } + return cmp.ResultFailure( + fmt.Sprintf("%q did not match pattern %q", value, pattern)) + } + } + + // To use it + assert.Check(t, regexPattern("12345.34", `\d+.\d\d`)) +#+END_SRC + +As you can see, it's pretty easy to implement, and you can do quite a lot in there easily. If a function call returns an +error inside of your =Comparison= function, you can use =cmp.ResultFromError= for example. Having something like +=assert.Check(t, isMyServerUp(":8080"))= is way more readable than a 30-line of code to check it. + +* Conclusion… + +… and that's a wrap. We only looked at the =assert= package of [[https://gotest.tools][=gotest.tools=]] so far, but it's already quite a bit to process. + +We've seen : +- the main functions provided by this package : =assert.Assert= and =assert.Check= +- some helper functions like =assert.NilError=, … +- the =assert/cmp=, and =assert/opt= sub-package that allows you to write more custom =Comparison= + +Next time, we'll look at the =skip= package, that is a really simple wrapper on top of =testing.Skip= function. + +** diff --git a/www/vincent.demeester.fr/content/legacy/posts/2018-09-01-gotest-tools-skip.org b/www/vincent.demeester.fr/content/legacy/posts/2018-09-01-gotest-tools-skip.org @@ -0,0 +1,59 @@ +#+title: Golang testing β€” gotest.tools skip +#+date: <2018-09-01 Sat> +#+filetags: go testing skip +#+setupfile: ../templates/post.org + +* Introduction + +Let's continue the [[https://gotest.tools][=gotest.tools=]] serie, this time with the =skip= package. This is a +really simple one so this should be quick. + +#+BEGIN_QUOTE +=skip= provides functions for skipping a test and printing the source code of the +condition used to skip the test. +#+END_QUOTE + +The package consists of only one function : =If=. The idea comes mainly from +[[https://github.com/docker/docker][=docker/docker=]] integration test suite, where we wanted to skip some test (or test suites) +given different context. By context I mean things like the system we are running on +(=Windows=, =Linux=, …) or the capabilities of the running kernel or node (is =apparmor= +available or not on the current machine). + +This =If= method takes a =testing.T= pointer and either a boolean, a function that +returns a boolean, *or* an expression. + +#+BEGIN_SRC go + // boolean + // --- SKIP: TestName (0.00s) + // skip.go:19: MissingFeature + var MissingFeature bool + skip.If(t, MissingFeature) + + // function + // --- SKIP: TestName (0.00s) + // skip.go:19: !IsExperimentalDaemon(dockerClient): daemon is not experimental + skip.If(t, IsExperimentalDaemon(dockerClient), "daemon is not experimental") + + // expression + // --- SKIP: TestName (0.00s) + // skip.go:19: apiVersion < version("v1.24") + skip.If(t, apiVersion < version("v1.24")) +#+END_SRC + +There is few elements to note though : + +- This package (as other parts of the =gotest.tools= packages), will try to look at source + files to display the expression used (same goes for =assert=). This is usually not a + problem because you run tests where the source code is. *However*, in the cases you + generate a test binary to be executed later (Γ -la =kubernetes= or other projects), this + can display a weird error message if the sources are not available… You shouldn't be + worried too much about it, but it's better if you know :) +- The main reason to use =skip.If= is mainly for new contributors to get in quickly, + *reducing potential friction of them running the tests on their environment*. The more + the tests are written in a way they explicitely declare their requirements (and skipped + if the environment does not meet those), the easier it makes contributors run your + tests. *But*, this also means, you should try to measure the skipped tests on your + continuous integration system to make sure you run all of them eventually… otherwise + it's dead code. /But more on that in later posts πŸ˜‰/. + +That's all for today folks, told you that was going to be quick. diff --git a/www/vincent.demeester.fr/content/legacy/posts/2018-09-06-gotest-tools-golden.org b/www/vincent.demeester.fr/content/legacy/posts/2018-09-06-gotest-tools-golden.org @@ -0,0 +1,75 @@ +#+TITLE: Golang testing β€” gotest.tools golden +#+date: <2018-09-06 Thu> +#+filetags: go testing golden +#+setupfile: ../templates/post.org + +#+TOC: headlines 2 + +* Introduction +Let's continue the [[https://gotest.tools][=gotest.tools=]] serie, this time with the =golden= package. This is a +[[/posts/2017-04-22-golang-testing-golden-file/][/quick follow-up/ on a previous =golden= post]], but focused on the =gotest.tools= +implementation. I'm gonna be quicker, please read that one if =golden= files is a new +concept for you. + +#+BEGIN_QUOTE +Package =golden= provides tools for comparing large mutli-line strings. + +Golden files are files in the =./testdata/= sub-directory of the package under test. +#+END_QUOTE + +In the previous article, we described the problem, and how to fix it by writing a small +helper. Well, that small helper is in =gotest.tools/golden= now, and it has a tiny bit +more features. + +One of the difference between the =gotest.tools= implementation and the previous post is +the flag name. In =gotest.tools/golden=, the flag is =-test.update-golden= (was just +=-test.update= before). Just as before, if the =-test.update-golden= flag is set then the +actual content is written to the golden file, before reading it and comparing. + +There is two ways to use the =golden= package: +- on it's own, using =golden.Assert= or =golden.AssertBytes= +- as a =cmp.Comparison=, with =golden.String= or =golden.Bytes= + +* =Assert= and =AssertBytes= + +Using =Assert= functions should be straightforward. Both =Assert= function compares the +actual content to the expected content in the golden file and returns whether the +assertion was successful (true) or not (false). + +- =Assert= uses string. Note that this one *removes carriage return* before comparing to + depend as less as possible of the system (=\n= vs =\r\n= πŸ˜…) +- =AssertBytes= uses raw data (in the form of =[]byte=) + +#+BEGIN_SRC go + golden.Assert(t, "foo", "foo-content.golden") + // Could also be used to check some binary format + golden.AssertBytes(t, []byte("foo"), "foo-content.golden") +#+END_SRC + +* =Bytes= and =String= + +As written in a [[/posts/2018-08-16-gotest-tools-assertions/][previous post (about the =assert= package)]], I prefer to use =cmp.Comparison=. + +#+BEGIN_QUOTE +All those helper functions have a equivalent function in the =cmp= package that returns a +=Comparison=. I, personally, prefer to use =assert.Check= or =assert.Assert= in +combination with =cmp.Comparison= as it allows me to write all my assertions the same way, +with built-ins comparison or with my own β€” i.e. =assert.Assert(t, is.Equal(…), "message"= +or =assert.Assert(t, stackIsUp(c, time…), "another message")=. +#+END_QUOTE + +The =golden= package gives us that too, in the form of =Bytes= and =String=. Using the +=assert.Check= or =assert.Assert= functions with those is equivalent to their /helper/ +counter-part =golden.Assert= and =golden.AssertBytes=. + +#+BEGIN_SRC go + assert.Assert(t, golden.String("foo", "foo-content.golden")) + // Could also be used to check some binary format + assert.Assert(t, golden.Bytes([]byte("foo"), "foo-content.golden")) +#+END_SRC + +* Conclusion… + +… that's a wrap. As for [[/posts/2018-09-01-gotest-tools-skip/][=skip=]], this is a small package, so the post was going to be +quick. =golden= package just solve a specific problem (read [[/posts/2017--04-22-golang-testing-golden-file/][Golang testing β€” golden file]]) +in a simple way. diff --git a/www/vincent.demeester.fr/content/legacy/posts/2018-09-14-gotest-tools-fs.org b/www/vincent.demeester.fr/content/legacy/posts/2018-09-14-gotest-tools-fs.org @@ -0,0 +1,187 @@ +#+TITLE: Golang testing β€” gotest.tools fs +#+date: <2018-09-14 Fri> +#+filetags: go testing fs +#+setupfile: ../templates/post.org + +#+TOC: headlines 2 + +* Introduction + +Let's continue the [[https://gotest.tools][=gotest.tools=]] serie, this time with the =fs= package. + +#+BEGIN_QUOTE +Package fs provides tools for creating temporary files, and testing the contents and structure of a directory. +#+END_QUOTE + +This package is heavily using functional arguments (as we saw in [[/posts/2017-01-01-go-testing-functionnal-builders/][functional arguments for +wonderful builders]]). Functional arguments is, in a nutshell, a combinaison of two Go +features : /variadic/ functions (=...= operation in a function signature) and the fact +that =func= are /first class citizen/. This looks more or less like that. + +#+BEGIN_SRC go + type Config struct {} + + func MyFn(ops ...func(*Config)) *Config { + c := &Config{} // with default values + for _, op := range ops { + op(c) + } + return c + } + + // Calling it + conf := MyFn(withFoo, withBar("baz")) +#+END_SRC + +The =fs= package has too *main* purpose : + +1. create folders and files required for testing in a simple manner +2. compare two folders structure (and content) + +* Create folder structures + +Sometimes, you need to create folder structures (and files) in tests. Doing =i/o= work +takes time so try to limit the number of tests that needs to do that, especially in unit +tests. Doing it in tests adds a bit of boilerplate that could be avoid. As stated [[/posts/2017-01-01-go-testing-functionnal-builders/][before]] : + +#+BEGIN_QUOTE +One of the most important characteristic of a unit test (and any type of test really) is +*readability*. This means it should be easy to read but most importantly it should *clearly +show the intent* of the test. The setup (and cleanup) of the tests should be as small as +possible to avoid the noise. +#+END_QUOTE + +In a test you usually end up using =ioutil= function to create what you need. This looks +somewhat like the following. + +#+BEGIN_SRC go + path, err := ioutil.TempDir("", "bar") + if err != nil { // or using `assert.Assert` + t.Fatal(err) + } + if err := os.Mkdir(filepath.Join(path, "foo"), os.FileMode(0755)); err != nil { + t.Fatal(err) + } + if err := ioutil.WriteFile(filepath.Join(path, "foo", "bar"), []byte("content"), os.FileMode(0777)); err != nil { + t.Fatal(err) + } + defer os.RemoveAll(path) // to clean up at the end of the test +#+END_SRC + +The =fs= package intends to help reduce the noise and comes with a bunch function to create +folder structure : + +- two main function =NewFile= and =NewDir= +- a bunch of /operators/ : =WithFile=, =WithDir=, … + +#+BEGIN_SRC go + func NewDir(t assert.TestingT, prefix string, ops ...PathOp) *Dir { + // … + } + + func NewFile(t assert.TestingT, prefix string, ops ...PathOp) *File { + // … + } +#+END_SRC + +The =With*= function are all satisfying the =PathOp= interface, making =NewFile= and +=NewDir= extremely composable. Let's first see how our above example would look like using +the =fs= package, and then, we'll look a bit more at the main =PathOp= function… + +#+BEGIN_SRC go + dir := fs.NewDir(t, "bar", fs.WithDir("foo", + fs.WithFile("bar", fs.WithContent("content"), fs.WithMode(os.FileMode(0777))), + )) + defer dir.Remove() +#+END_SRC + +It's clean and simple to read. The intent is well described and there is not that much of +noise. =fs= functions tends to have /sane/ and /safe/ defaults value (for =os.FileMode= +for example). Let's list the main, useful, =PathOp= provided by =gotest.tools/fs=. + +- =WithDir= creates a sub-directory in the directory at path. +- =WithFile= creates a file in the directory at path with content. +- =WithSymlink= creates a symlink in the directory which links to target. Target must be a + path relative to the directory. +- =WithHardlink= creates a link in the directory which links to target. Target must be a + path relative to the directory. +- =WithContent= and =WWithBytes= write content to a file at Path (from a =string= or a + =[]byte= slice). +- =WithMode= sets the file mode on the directory or file at path. +- =WithTimestamps= sets the access and modification times of the file system object at + path. +- =FromDir= copies the directory tree from the source path into the new Dir. This is + pretty useful when you have a huge folder structure already present in you =testdata= + folder or elsewhere. +- =AsUser= changes ownership of the file system object at Path. + +Also, note that =PathOp= being an function type, you can provide your own implementation +for specific use-cases. Your function just has to satisfy =PathOp= signature. + +#+BEGIN_SRC go + type PathOp func(path Path) error +#+END_SRC + +* Compare folder structures + +Sometimes, the code you're testing is creating a folder structure, and you would like to +be able to tests that, with the given arguments, it creates the specified structure. =fs= +allows you to do that too. + +The package provides a =Equal= function, which returns a =Comparison=, that the [[/posts/2018-08-16-gotest-tools-assertions/][=assert=]] +package understand. It works by comparing a =Manifest= type provided by the test and a +=Manifest= representation of the specified folder. + +#+BEGIN_QUOTE + Equal compares a directory to the expected structured described by a manifest and returns + success if they match. If they do not match the failure message will contain all the + differences between the directory structure and the expected structure defined by the + Manifest. +#+END_QUOTE + +A =Manifest= stores the expected structure and properties of files and directories in a +file-system. You can create a =Manifest= using either the functions =Expected= or +=ManifestFromDir=. + +We're going to focus on the =Expected= function, as =ManifestFromDir= does pretty much +what you would expected : it takes the specified path, and returns a =Manifest= that +represent this folder. + +#+BEGIN_SRC go + func Expected(t assert.TestingT, ops ...PathOp) Manifest +#+END_SRC + +=Expected= is close to =NewDir= function : it takes the same =PathOp= functional +arguments. This makes creating a =Manifest= straightforward, as it's working the same. Any +function that satisfy =PathOp= can be used for =Manifest= the exact same way you're using +them on =fs.NewDir=. + +There is a few additional functions that are only useful with =Manifest= : + +- =MatchAnyFileContent= updates a Manifest so that the file at path may contain any content. +- =MatchAnyFileMode= updates a Manifest so that the resource at path will match any file mode. +- =MatchContentIgnoreCarriageReturn= ignores cariage return discrepancies. +- =MatchExtraFiles= updates a Manifest to allow a directory to contain unspecified files. + +#+BEGIN_SRC go + path := operationWhichCreatesFiles() + expected := fs.Expected(t, + fs.WithFile("one", "", + fs.WithBytes(golden.Get(t, "one.golden")), + fs.WithMode(0600)), + fs.WithDir("data", + fs.WithFile("config", "", fs.MatchAnyFileContent)), + ) + + assert.Assert(t, fs.Equal(path, expected)) +#+END_SRC + +The following example compares the result of =operationWhichCreatesFiles= to the expected +=Manifest=. As you can see it also integrates well with other part of the =gotest.tools= +library, with the [[/posts/2018-09-06-gotest-tools-golden/][=golden= package]] in this example. + +* Conclusion… + +… that's a wrap. In my opinion, this is one the most useful package provided by +=gotest.tools= after =assert=. It allows to create simple or complex folder structure +without the noise that usually comes with it. diff --git a/www/vincent.demeester.fr/content/legacy/posts/2018-09-18-gotest-tools-icmd.org b/www/vincent.demeester.fr/content/legacy/posts/2018-09-18-gotest-tools-icmd.org @@ -0,0 +1,209 @@ +#+TITLE: Golang testing β€” gotest.tools icmd +#+date: <2018-09-18 Tue> +#+filetags: go testing cmd +#+setupfile: ../templates/post.org + +#+TOC: headlines 2 + +* Introduction +Let's continue the [[https://gotest.tools][=gotest.tools=]] serie, this time with the =icmd= package. + +#+BEGIN_QUOTE +Package icmd executes binaries and provides convenient assertions for testing the results. +#+END_QUOTE + +After file-system operations (seen in [[/posts/2018-09-14-gotest-tools-fs/][=fs=]]), another common use-case in tests is to +*execute a command*. The reasons can be you're testing the =cli= you're currently writing +or you need to setup something using a command line. A classic execution in a test might +lookup like the following. + +#+BEGIN_SRC go + cmd := exec.Command("echo", "foo") + cmd.Stout = &stdout + cmd.Env = env + if err := cmd.Run(); err != nil { + t.Fatal(err) + } + if string(stdout) != "foo" { + t.Fatalf("expected: foo, got %s", string(stdout)) + } +#+END_SRC + +The package =icmd= is there to ease your pain (as usual πŸ˜‰) β€” we used /the name =icmd=/ +instead of =cmd= because it's a pretty common identifier in Go source code, thus would be +really easy to /shadow/ β€” and have some really weird problems going on. + +The usual =icmd= workflow is the following: + +1. Describe the command you want to execute using : type =Cmd=, function =Command= and + =CmdOp= operators. +2. Run it using : function =RunCmd= or =RunCommand= (that does 1. for you). You can also + use =StartCmd= and =WaitOnCmd= if you want more control on the execution workflow. +3. Check the result using the =Assert=, =Equal= or =Compare= methods attached to the + =Result= struct that the command execution return. + +* Create and run a command + +Let's first dig how to create commands. In this part, the assumption here is that the +command is successful, so we'll have =.Assert(t, icmd.Success)= for now β€” we'll learn more +about =Assert= in the next section πŸ‘Ό. + +The simplest way to create and run a command is using =RunCommand=, it has the same +signature as =os/exec.Command=. A simple command execution goes as below. + +#+BEGIN_SRC go + icmd.RunCommand("echo", "foo").Assert(t, icmd.Sucess) +#+END_SRC + +Sometimes, you need to customize the command a bit more, like adding some environment +variable. In those case, you are going to use =RunCmd=, it takes a =Cmd= and operators. +Let's look at those functions. + +#+BEGIN_SRC go + func RunCmd(cmd Cmd, cmdOperators ...CmdOp) *Result + + func Command(command string, args ...string) Cmd + + type Cmd struct { + Command []string + Timeout time.Duration + Stdin io.Reader + Stdout io.Writer + Dir string + Env []string + } +#+END_SRC + +As we've seen [[/posts/2017-01-01-go-testing-functionnal-builders/][multiple]] [[/posts/2018-08-16-gotest-tools-assertions/][times]] [[/posts/2018-09-14-gotest-tools-fs/][before]], it uses the /powerful/ functional arguments. At the +time I wrote this post, the =icmd= package doesn't contains too much =CmdOp= [fn:1], so I'll +propose two version for each example : one with =CmdOpt= present in [[https://github.com/gotestyourself/gotest.tools/pull/122][this PR]] and one +without them. + +#+BEGIN_SRC go + // With + icmd.RunCmd(icmd.Command("sh", "-c", "echo $FOO"), + icmd.WithEnv("FOO=bar", "BAR=baz"), icmd.Dir("/tmp"), + icmd.WithTimeout(10*time.Second), + ).Assert(t, icmd.Success) + + // Without + icmd.RunCmd(icmd.Cmd{ + Command: []string{"sh", "-c", "echo $FOO"}, + Env: []string{"FOO=bar", "BAR=baz"}, + Dir: "/tmp", + Timeout: 10*time.Second, + }).Assert(t, icmd.Success) +#+END_SRC + +As usual, the intent is clear, it's simple to read and composable (with =CmdOp='s). + +[fn:1] The =icmd= package is one of the oldest =gotest.tools= package, that comes from the +[[https://github.com/docker/docker][=docker/docker=]] initially. We introduced these =CmdOp= but implementations were in +=docker/docker= at first and we never really updated them. + +* Assertions + +Let's dig into the assertion part of =icmd=. Running a command returns a struct +=Result=. It has the following methods : + +- =Assert= compares the Result against the Expected struct, and fails the test if any of + the expectations are not met. +- =Compare= compares the result to Expected and return an error if they do not match. +- =Equal= compares the result to Expected. If the result doesn't match expected + returns a formatted failure message with the command, stdout, stderr, exit code, and any + failed expectations. It returns an =assert.Comparison= struct, that can be used by other + =gotest.tools=. +- =Combined= returns the stdout and stderr combined into a single string. +- =Stderr= returns the stderr of the process as a string. +- =Stdout= returns the stdout of the process as a string. + +When you have a result, you, most likely want to do two things : + +- /assert/ that the command succeed or failed with some specific values (exit code, + stderr, stdout) +- use the output β€” most likely =stdout= but maybe =stderr= β€” in the rest of the test. + +As seen above, /asserting/ the command result is using the =Expected= struct. + +#+BEGIN_SRC go + type Expected struct { + ExitCode int // the exit code the command returned + Timeout bool // did it timeout ? + Error string // error returned by the execution (os/exe) + Out string // content of stdout + Err string // content of stderr + } +#+END_SRC + +=Success= is a constant that defines a success β€” it's an exit code of =0=, didn't timeout, +no error. There is also the =None= constant, that should be used for =Out= or =Err=, to +specify that we don't want any content for those standard outputs. + +#+BEGIN_SRC go + icmd.RunCmd(icmd.Command("cat", "/does/not/exist")).Assert(t, icmd.Expected{ + ExitCode: 1, + Err: "cat: /does/not/exist: No such file or directory", + }) + + // In case of success, we may want to do something with the result + result := icmd.RunCommand("cat", "/does/exist") + result.Assert(t, icmd.Success) + // Read the output line by line + scanner := bufio.NewScanner(strings.NewReader(result.Stdout())) + for scanner.Scan() { + // Do something with it + } +#+END_SRC + +If the =Result= doesn't map the =Expected=, a test failure will happen with a useful +message that will contains the executed command and what differs between the result and +the expectation. + +#+BEGIN_SRC go + result := icmd.RunCommand(…) + result.Assert(t, icmd.Expected{ + ExitCode: 101, + Out: "Something else", + Err: None, + }) + // Command: binary arg1 + // ExitCode: 99 (timeout) + // Error: exit code 99 + // Stdout: the output + // Stderr: the stderr + // + // Failures: + // ExitCode was 99 expected 101 + // Expected command to finish, but it hit the timeout + // Expected stdout to contain "Something else" + // Expected stderr to contain "[NOTHING]" + … +#+END_SRC + +Finally, we listed =Equal= above, that returns a =Comparison= struct. This means we can +use it easily with the =assert= package. As written in a [[/posts/2018-08-16-gotest-tools-assertions/][previous post (about the =assert= +package)]], I tend prefer to use =cmp.Comparison=. Let's convert the above examples using +=assert=. + +#+BEGIN_SRC go + result := icmd.RunCmd(icmd.Command("cat", "/does/not/exist")) + assert.Check(t, result.Equal(icmd.Expected{ + ExitCode: 1, + Err: "cat: /does/not/exist: No such file or directory", + })) + + // In case of success, we may want to do something with the result + result := icmd.RunCommand("cat", "/does/exist") + assert.Assert(t, result.Equal(icmd.Success)) + // Read the output line by line + scanner := bufio.NewScanner(strings.NewReader(result.Stdout())) + for scanner.Scan() { + // Do something with it + } +#+END_SRC + +* Conclusion… + +… that's a wrap. The =icmd= package allows to easily run command and describe what result +are expected of the execution, with the least noise possible. We *use this package heavily* +on several =docker/*= projects (the engine, the cli)… diff --git a/www/vincent.demeester.fr/content/legacy/posts/2019-01-20-2018-year-review.org b/www/vincent.demeester.fr/content/legacy/posts/2019-01-20-2018-year-review.org @@ -0,0 +1,151 @@ +#+title: 2018 year review +#+date: <2019-01-20 Sun> +#+filetags: review + +* Introduction + +Here is my review of 2018, the first of its kind, hopefully not the last πŸ‘Ό. I saw +some[fn:1] /2018[fn:2] reviews/[fn:3] articles[fn:4] in my Feedly feed and I thought it +would be a good idea to write my own too. + +I'll try in the next year β€” maybe month if I ever want to do monthly reviews β€” to automate +some of it ; using the beloved =org-mode=. + +[fn:1] [[https://punchagan.muse-amuse.in/blog/2018-in-review/][2018 in Review - Noetic Nought]] +[fn:2] [[https://medium.com/@buster/42-dig-deeper-e2278d1fe015][42 β€” Dig deeper – Buster Benson – Medium]] +[fn:3] [[https://jvns.ca/blog/2018/12/23/2018--year-in-review/][2018: Year in review - Julia Evans]] +[fn:4] [[https://writing.natwelch.com/post/685][Nat? Nat. Nat! | #685 2018 Year in Review]] + +* Work + +The big change this year is : I changed job πŸ‘Ό. I went from Docker Inc. to Red Hat. I +needed a change and 5 month in, I think it was the *best choice I made in my life* so far +πŸ’ƒ. I'm doing open-source for a living and best part, I am working remotely (more on that +later). + +Before that, at Docker Inc., I continued the work I started years before, +a.k.a. maintaining the Moby project and the docker engine, among other Docker project +(both open-source and closed-source). I also helped the work on the compose side, from the +root of =docker/compose-on-kubernetes= (before it got open-sourced), to the =docker/app= +experiments. + +At Red Hat, I started to work upstream in the Kubernetes community, mainly on the Knative +projects. I also work on the Openshift Cloud Function project (and thus team), and those +fellows are awesome ! Digging more into Openshift, and other part of the Red Hat portfolio +is a really good learning experience, and it's just the start ! + +As stated above, I am now working home, full-time. I could work from home from time to +time when I was at Docker inc, but working home full-time is another kind of beast. So far +it is really good, some adjustments were needed but it's for the best. Here is a small +take on "working from home": + +- It's easy to have *no distraction*, thus having *really productive* piece of time +- It's also *really easy to work long day or really long period of time*. It's especially + true if, like me, you work on a distributed team (across multiple timezones). + - I ended up using the Pomodoro technique to make sure I move at least few times a day + - I try to make sure I don't make an habits of checking out work code, email and other + material after a certain hour in the evening. It's ok to do it sometimes, but for your + sanity, you need some rest time. +- It's easy to adapt your day to circumstance. If you got to run errands in the middle of + the day, it's no big deal. You can take the time back later on. +- It's so good to have *no* commmute time. That said I end up /walking or taking the bike/ + early morning to clear my head before work 😝. + +* Personal + +Health wise, it's a mix of good and bad year. The first half was really good, the second +way less. End of august, I felt something weird in the right knee, and well, turns out my +internal meniscus is in a real bad shape. Just as before joining Docker, I'm gonna need a +surgery, on the right knee that time. It's gonna affect 2019 (the first half, I'm not +gonna be able to move around much but.. meh, it's life). + +Now that I work from home, I'm really glad I got a standing desk at the end of 2017. I +tend to work standing most of the time -- except when my knee hurts (and most likely for +few months after the surgery πŸ˜…). I invested on a ultrawide screen, to get the same +experience I had at Docker. And oh boy those screens are good ! + +I also try to clean my desk and it's "neighboorhood". As I get older, I want less messy +stuuf (desk, flat, ...). I'm leaning towards having less stuff, being commputer related or +not. It's not minimalism, but it feel good to have less stuff, but stuff that you actually +use. I still have trouble throwing old computer away, mainly because I fell they can be +useful in some way. + +[[/images/2019/01/desk1.jpg]] + +This year I migrate all of my "infrastructure" computer to NixOS. I learned a lot of Nix, +reworked my configuration multiple time to end up with a [[https://github.com/vdemeester/nixos-configuration.git][system configuration repository]] +that uses modules, and a [[https://github.com/vdemeester/home.git][/home configuration repository/]] (for user configuration). The +[[https://github.com/vdemeester/home.git][home]] repository uses [[https://github.com/rycee/home-manager.git][=home-manager=]] and thus doesn't make any assumption of running on top +of NixOS. This allows me to have an /easy to get/ setup on any system that =nixpkgs= +supports (any Linux distribution, Mac OSX, Windows Subsystem Linux). The current +configuration is not yet optimal but I'm pretty happy about what I got : + +- Custom DNS server @home to make it easier to target local hosts. +- Local proxies and mirrors for docker images, nixpkgs binary package and go modules to + eat less bandwidth. +- Easy to setup VPN using [[https://www.wireguard.com/][WireGuard]]. +- File replication using =syncthing= and automatic backup on my local NAS. +- Automatic system upgrade, thanks to NixOS. I'll probably write an article about that + later on this year. + +I started to use =todoist= in 2017, and boy, oh boy, it helped me quite a lot ! I'm using +it daily to organize my work and quickly get idea, and /todos/ out of my head. The main +problem with it is it's not integrated with another tool I'm using daily : Emacs and +=org-mode=. =org-mode= is a fantastic piece of software and is, on its own, the main +reason for me to invest time in Emacs. I'm taking note in =org-mode=, I write my daily +standup notes in there too. I end up going back and forth between =org-mode= and =todoist= +for those daily standup. I am lazy, I want to automate that. And the best way to do it, is +to also use =org-mode= for task management. I'm in a /transition/ mode right now, but my +goal for 2019 is to use todoist to take quick note/todo(s) on the move (aka on the phone) +and use =org-mode= for the rest. + + +* Reading & Writing + +I used to like reading, but the past years, I didn't really read that much, except some +technical books. 2018 in, that respect, is not an exception, I didn't read too much. Worse +than that, I started some book and stopped at some point, for no apparent reason ; and +now, I need to start back from the beginning, which, well, is not helping me want to read +them again. + +I'm trying two thing to counter that and consume more books for the years to come. + +1. I now have a reading list on my =org-mode= files, where I track which one I read and + when I read them ; and maybe notes too. I have a lot of book on my kindle, that only + wait for one thing, being read.. +2. I subscribed to [[https://www.audible.fr][Audible]] πŸ‘Ό. Working from home, I tend to take a long break after lunch, + where I'm going for a walk, for around an hour. I can't read while walking but I + definitely can listen - that make audio books perfect for these moments. I also + alternate between audio books and non-musical podcasts. + +On the writing side, 2017 was a slow year in terms of writing (only 2 posts), 2018 was a +bit better, 6 posts -- it's a bit cheating, as it was mainly between changing jobs, and +on a series I still need to finish. I'm hoping to write more this year, hence the goals +I've set to myself below. + +* 2019 Goals + +- *Get back on my feet after knee surgery (exercices, …)* πŸƒ +- *Read at least one book per month (be audible, ebook or paper)* πŸ“– +- *Giving at least a talk (on Knative, containers, nixos, ..)* πŸ™Š + + I didn't give too much talk in 2018 (at least less than 2017). I'm gonna try to get back + at it this year. The surgery won't help but it's just few months. + +- *At least 1 video per month* πŸ“Ή + + I want to start recording some video, as I feel it's an easier medium than writing and, + well, I wanna try ! + +- *At least 1 post per month* ✍️ +- *Enhance my emacs skills (aka don't be afraid of the lisp)* ⌨️ + + I'm using Emacs for almost anything that doesn't happen in a web browser. But I still + feel like a newbie. I want to learn more, to write more lisp that help me being even + more lazier (aka achieve more doing less 😝) + +- *Enhance my Nix(OS) skills* 🐧 +- *Learn / master a new language* 🎽 + + I'm working with Go 90% of my time. I want to master and learn more language. On my list + are Emacs Lisp, Rust, Typescript and Haskell. diff --git a/www/vincent.demeester.fr/content/legacy/posts/2019-01-26-nix-run-alias.org b/www/vincent.demeester.fr/content/legacy/posts/2019-01-26-nix-run-alias.org @@ -0,0 +1,200 @@ +#+title: Nix run aliases +#+date: <2019-01-26 Sat> +#+filetags: nixos fish alias nix shell home manager + +* Introduction + +I use [[https://nixos.org/][=NixOS=]] each and every day, everywhere. One really cool feature of =nix= is +=nix-shell= and more recently (with =nix= >= =2.0.0=), =nix run=. + +#+begin_src man +Usage: nix run <FLAGS>... <INSTALLABLES>... + +Summary: run a shell in which the specified packages are available. + +Flags: + --arg <NAME> <EXPR> argument to be passed to Nix functions + --argstr <NAME> <STRING> string-valued argument to be passed to Nix functions + -c, --command <COMMAND> <ARGS> command and arguments to be executed; defaults to 'bash' + -f, --file <FILE> evaluate FILE rather than the default + -i, --ignore-environment clear the entire environment (except those specified with --keep) + -I, --include <PATH> add a path to the list of locations used to look up <...> file names + -k, --keep <NAME> keep specified environment variable + -u, --unset <NAME> unset specified environment variable + +Examples: + + To start a shell providing GNU Hello from NixOS 17.03: + $ nix run -f channel:nixos-17.03 hello + + To start a shell providing youtube-dl from your 'nixpkgs' channel: + $ nix run nixpkgs.youtube-dl + + To run GNU Hello: + $ nix run nixpkgs.hello -c hello --greeting 'Hi everybody!' + + To run GNU Hello in a chroot store: + $ nix run --store ~/my-nix nixpkgs.hello -c hello + +Note: this program is EXPERIMENTAL and subject to change. +#+end_src + +As you can see from the =-h= summary, it makes it really easy to run a shell or a command +with some packages that are not in your main configuration. It will download the +package(s) if there are not available in the Nix store (=/nix/store/=). + +A few month ago I decided it would be a perfect use-case for command I do not run +often. My idea was, let's define =aliases= (in the shell) that would make a simple command +call, like =ncdu=, become =nix run nixpkgs.ncdu -c ndcu=. My /shell of choice/ is [[https://fishshell.com/][fish]], so +I decided to dig into the /language/ in order to implement that. + +The use case is the following : +- When I type =foo=, I want the command =foo= in package =bar= to be executed. +- I want to be able to pin a channel for the package β€” I'm using [[https://matthewbauer.us/][Matthew Bauer]] [[https://matthewbauer.us/blog/channel-changing.html][Channel + Changing with Nix]] setup for pin-pointing a given channel. + +* Fish aliases experimentation + +I had a feeling the built-in =alias= would not work so I ended up trying to define a +/dynamic/ function that would be the name of the command. That's the beauty of the shell, +everything is a command, even function appears as commands. If you define a function +=foo()=, you will be able to run =foo= in your shell, *and* it will take precedence over +the =foo= executable file that would be in your =PATH=. + +I ended up with two main helper function that would create those /alias/ function. + +#+begin_src fish + function _nix_run_package + set -l s $argv[1] + set -l package (string split ":" $s) + switch (count $package) + case 1 + _nix_run $s $s $argv[2] $argv[3] + case 2 + _nix_run $package[1] $package[2] $argv[2] $argv[3] + end + end + + function _nix_run + set -l c $argv[1] + set -l p $argv[2] + set -l channel $argv[3] + set -l channelsfile $argv[4] + function $c --inherit-variable c --inherit-variable p --inherit-variable channel --inherit-variable channelsfile + set -l cmd nix run + if test -n "$channelsfile" + set cmd $cmd -f $channelsfile + end + eval $cmd $channel.$p -c $c $argv + end + end +#+end_src + +In a nutshell, =_nix_run= is the function that create the alias function. There is so +condition in there depending on whether we gave it a channel or not. So, a call like +=_nix_run foo bar unstable channels.nix= would, in the end generate a function =foo= with +the following call : =nix run -f channels.nix unstable.bar -c foo=. + +The other function, =_nix_run_package= is there to make me write less when I define those +aliases β€” aka if the command and the package share the same name, I don't want to write it +twice. So, a call like =_nix_run_package foo nixpkgs= would result in a =_nix_run foo foo +nixpkgs=, whereas a call like =_nix_run_package foo:bar unstable channels.nix= would +result in a =_nix_run foo bar unstable channels.nix=. + +An example is gonna be better than the above paragraphs. This is what I used to have in my +fish configuration. + +#+begin_src fish + function _def_nix_run_aliases + set -l stable mr sshfs ncdu wakeonlan:python36Packages.wakeonlan lspci:pciutils lsusb:usbutils beet:beets gotop virt-manager:virtmanager pandoc nix-prefetch-git:nix-prefetch-scripts nix-prefetch-hg:nix-prefetch-scripts + set -l unstable op:_1password update-desktop-database:desktop-file-utils lgogdownloader + for s in $stable + _nix_run_package $s nixpkgs + end + for s in $unstable + _nix_run_package $s unstable ~/.config/nixpkgs/channels.nix + end + end + # Call the function to create the aliases + _def_nix_run_aliases +#+end_src + +This works like a charm, and for a while, I was happy. But I soon realized something : I'm +not always on my shell β€” like, I tend to spend more and more time in =eshell=. This also +doesn't work with graphic tools like [[https://github.com/DaveDavenport/rofi][=rofi=]]. I needed actual command, so that external +tools would benefit from that. I ended up writing a small tool, [[https://github.com/vdemeester/nr][=nr=]] that integrates +nicely with =nix= and [[https://github.com/rycee/home-manager][=home-manager=]]. + +* A proper tool : =nr= + +The gist for this tool is simple : +- create an executable script that will call =nix run ...= instead of the command +- as for the above fish script, support different channels +- make sure we don't have conflicts β€” if the command already exists, then don't create the + command + +The =nr= tool would have to be able to manage multiple /profile/, which really stands for +multiple file. The main reason is really about how I manage my configuration ; To make it +simple, depending on the computer my configurations are setup, I may not have =go=, thus I +don't want any =go=-related aliases for a computer that doesn't have =go= (using =go= here +but you can replace with anything). + +#+begin_src fish +$ nr default +> nr generate default +> virtmanager already exists +$ nr git +> nr generate git +#+end_src + +=nr= generates a bash script that does the =nr run …= and mark it as executable. =nr= +needs to be able to clean files it has generated (in case we removed it from +aliases). Thus, I went for a really naive comment in the script. When generating a new set +of commands, =nr= will first remove previously generated script for this profile, and for +that, it uses the comment. Let's look at what a generated script looks like, for the +default profile. + +#+begin_src bash +#!/usr/bin/env bash +# Generated by nr default +nix run nixpkgs.nix-prefetch-scripts -c nix-prefetch-git $@ +#+end_src + +The format used in =nr= is =json=. I'm not a /huge fan/ of =json= but it really was the +best format to use for this tool. The reason to use =json= are simple : + +- Go has =encoding/json= built-in, so it's really easy to =Marshall= and =Unmarshall= + structure. + #+begin_src go + type alias struct { + Command string `json:"cmd"` + Package string `json:"pkg"` + Channel string `json:"chan"` + } + #+end_src +- Nix also has built-in support for =json= : =builtins.toJSON= will marshall a /struct/ + into a json file. + +Finally, to avoid conflicts at /build time/ (=home-manager switch=) I couldn't use/define +a nix package, but to execute command(s) at the end of the build. One way to achieve it is +to use =file.?.onChange= script, which is executed after [[https://github.com/rycee/home-manager][=home-manager=]] has updated the +environment, *if* the file has changed. That means it's possible to check for executable +files in =~/.nix-profile/bin/= for defined aliases and create those that are not there, +with =nr=. My configuration then looks like the following. + +#+BEGIN_SRC nix + xdg.configFile."nr/default" = { + text = builtins.toJSON [ + {cmd = "ncdu";} {cmd = "sshfs";} {cmd = "gotop";} {cmd = "pandoc";} + {cmd = "wakeonlan"; pkg = "python36Packages.wakeonlan";} + {cmd = "beet"; pkg = "beets";} + {cmd = "virt-manager"; pkg = "virtmanager";} + {cmd = "nix-prefetch-git"; pkg = "nix-prefetch-scripts";} + {cmd = "nix-prefetch-hg"; pkg = "nix-prefetch-scripts";} + ]; + onChange = "${pkgs.nur.repos.vdemeester.nr}/bin/nr default"; + }; +#+END_SRC + +And there you are, now, each time I update my environment (=home-manager switch=), =nr= +will regenerate my =nix run= aliases. diff --git a/www/vincent.demeester.fr/content/legacy/posts/2019-03-23-gotest-tools-poll.org b/www/vincent.demeester.fr/content/legacy/posts/2019-03-23-gotest-tools-poll.org @@ -0,0 +1,136 @@ +#+TITLE: Golang testing β€” gotest.tools poll +#+date: <2019-03-23 Sat> +#+filetags: go testing poll + +#+TOC: headlines 2 + +* Introduction +Let's continue the [[https://gotest.tools][=gotest.tools=]] serie, this time with the =poll= package. + +#+BEGIN_QUOTE +Package poll provides tools for testing asynchronous code. +#+END_QUOTE + +When you write test, you may test a piece of code that work asynchronously, where the +state you're expecting is gonna take a bit of time to be achieved. This is especially true +when you work on networking or file-system code. And this happens a lot when you write +integration (or end-to-end) test, less for unit-tests. + +The package =poll= is trying to tackle those use cases. We'll first take a look at the +main function, =WaitOn=, then how to write a ~Check~, using the ~Result~ type. + +* ~WaitOn~ + +Let's look into the main ~poll~ function : `WaitOn`. + +#+begin_quote + WaitOn a condition or until a timeout. Poll by calling check and exit when check returns + a done Result. To fail a test and exit polling with an error return a error result. +#+end_quote + +In a gist, ~WaitOn~ will run a /condition/ function until it either times out or +succeed. It wait for a given time/delay between each run. + +#+begin_src go + func WaitOn(t TestingT, check Check, pollOps ...SettingOp) { + // […] + } +#+end_src + +As any /testing helper/ function, the first argument is ~*testing.T~ (or, in this case, +any thing that look like it, thanks to the ~TestingT~ interace). The two other arguments +are way more interesting : + +- The ~Check~ is the condition that will run multiple times until it either timeout, or succeed. +- The ~SettingOp(s)~ which are options to configure the function, things like the timeout, + or the /delay/ between each run. + +The settings are pretty straightforward : + +- ~WithDelay~ : sets the delay to wait between polls. The default delay is 100ms. +- ~WithTimeout~ : sets the timeout. The default timeout is 10s. + +There is existing ~Check~ for common case: + +- ~Connection~ : try to open a connection to the address on the named network. + + #+begin_src go + poll.WaitOn(t, poll.Connection("tcp", "foo.bar:55555"), poll.WithTimeout("5s")) + #+end_src + +- ~FileExists~ : looks on filesystem and check that path exists. + + #+begin_src go + poll.WaitOn(t, poll.FileExists("/should/be/created"), poll.WithDelay("1s")) + #+end_src + + +* ~Check~ and ~Result~ + +~Connection~ and ~FileExists~ are the only two /built-in/ ~Check~ provided by +~gotest.tools~. They are useful, but as usual, where ~gotest.tools~ shines is +extensiblity. It is really easy to define your own ~Check~. + +#+begin_src go + type Check func(t LogT) Result +#+end_src + +A ~Check~ is, thus, only a function that takes ~LogT~ β€” which is anything that can log +something, like ~*testing.T~ β€” and return a ~Result~. Let's look at this intersting +~Result~ type. + +#+begin_src go + type Result interface { + // Error indicates that the check failed and polling should stop, and the + // the has failed + Error() error + // Done indicates that polling should stop, and the test should proceed + Done() bool + // Message provides the most recent state when polling has not completed + Message() string + } +#+end_src + +Although it's an interface, the ~poll~ package defines built-in ~Result~ so that it's easy +to write ~Check~ without having to define you ~Result~ type. + +- ~Continue~ returns a Result that indicates to WaitOn that it should continue + polling. The message text will be used as the failure message if the timeout is reached. +- ~Success~ returns a Result where Done() returns true, which indicates to WaitOn that it + should stop polling and exit without an error. +- ~Error~ returns a Result that indicates to WaitOn that it should fail the test and stop + polling. + +The basic just to write a ~Check~ is then : + +- if the state is not there yet, return ~Continue~, +- if there is an error, unrelated to validating the state, return an ~Error~, +- if the state is there, return ~Success~. + +Let's look at an example taken from the ~moby/moby~ source code. + +#+begin_src go + poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond)) + + func IsInState(ctx context.Context, client client.APIClient, containerID string, state ...string) func(log poll.LogT) poll.Result { + return func(log poll.LogT) poll.Result { + inspect, err := client.ContainerInspect(ctx, containerID) + if err != nil { + return poll.Error(err) + } + for _, v := range state { + if inspect.State.Status == v { + return poll.Success() + } + } + return poll.Continue("waiting for container to be one of (%s), currently %s", strings.Join(state, ", "), inspect.State.Status) + } + } +#+end_src + + +* Conclusion + +… that's a wrap. The =poll= package allows to easily wait for a condition to happen in a +given time-frame β€” with sane defaults. As for most of the ~gotest.tools~ package, we use +this package heavily in =docker/*= projects too… diff --git a/www/vincent.demeester.fr/content/legacy/posts/2020-02-22-digital-minimalism.org b/www/vincent.demeester.fr/content/legacy/posts/2020-02-22-digital-minimalism.org @@ -0,0 +1,130 @@ +#+title: On digital minimalism, Linux, NixOS and Emacs +#+date: <2020-02-22 Sat> +#+filetags: minimalism digital emacs linux nixos +#+setupfile: ../templates/post.org/ + +* Introduction + +I've been reading and listening about Minimalism and Digital Minimalism for a little while +now. I've watch some [[https://www.youtube.com/channel/UCJ24N4O0bP7LGLBDvye7oCA][Matt D'Avella]] youtube video (and documentary), read [[https://www.goodreads.com/book/show/40672036-digital-minimalism][Digital +Minimalism]] from [[https://www.goodreads.com/author/show/147891.Cal_Newport][Cal Newport]], and read a bunch of [[Links][articles and blog posts]]. + +I wouldn't say I am a minimalist, neither am I a digital minimalist *but* it is something +that is a bit appealing and I feel I am slowly taking inspiration from those. I've started +reducing to a minimun what my /smart/-phone does, I've reduced the number of /gadgets/ +I've add over time. But in this posts, I am going to focus on my systems and tools β€” this +means Linux distributions, Emacs and other tools. + +* A little bit of history (or context) + +I've been using GNU/Linux for ages now. It has been around 22 years now that I discovered +Red Hat[fn:1] Linux, 5.2 at the time. I've use it as my primary operating system almost +ever sync. During that time, I've tried so many distribution: from [[https://en.wikipedia.org/wiki/Mandriva_Linux][Mandrake]] (later +Mandriva), to [[https://www.debian.org/][Debian]] (3.0 -> 9.0 on some servers) and [[https://www.ubuntu.com][Ubuntu]] (on the first ever public +release), with a long time on [[https://gentoo.org/][Gentoo]] and later [[https://www.archlinux.org/][Archlinux]]. I've used Gnome, KDE, wmii, +XMonad, awesome-vm. I've used bash, zsh, fish, switching from one to another multiple +times. I've used Emacs, vim, IntelliJ, Eclipse, VSCode… My gentoo/archlinux days made me +try and /use/ a lot of tools (screen, tmux, …) and customization. + +I had the habit to customize everything, down to the theme and icons I use. There was a +time where I would patch and re-package some GTK themes and icons set. *I had to +customize everything*, that was my learning experience. I would often break things and +have to re-install the system β€” and *it was fun* 😎. + +But the more I /grew/, the more I worked for different companies, the less time I had to +do this. I was also learning a lot about tests, reproducibility at that time. My /will/ to +customization and /instability/ slowly faded, I wanted to be able to focus on what +mattered, and not loose time on silly things that I would break soon after making it. + +* Minimalism and Digital Mininalism + +Let's try to define really quick what is minimalism and digital minimalism about.Digital +minimalism is Minimalism in the digital world, and [[https://www.theminimalists.com/minimalism/][minimalism]] can be seen as [[https://jamesstuber.com/minimalism-as-a-framework/][a Framework +for Decision Making]]. + +#+begin_quote +Every decision we make is constrained by limited resources. Money, materials, energy. Even +the richest man in the world is limited by time, and has to make decisions accordingly. + +Here are some factors that make minimalism well suited for decision making: + +- Practicing minimalism helps develop an improved ability to discern what’s important and + what’s not +- It becomes easier to let go of β€˜sort of important’ things +- What you choose to keep is a reflection of your values +#+end_quote + +In a gist, Digital Minimalism is about making conscious choice of what you use and what +you do in the digital world, a world of abundance (information, software, …). + +Let's take two examples: our desktops and our phones. + +On desktop, digital minimalism can be something like the follow blog post : [[http://neugierig.org/software/blog/2014/07/anti-dashboard-manifesto.html][Tech Notes: +The Anti-Dashboard Manifesto]]. + +#+begin_quote +Now take that reasoning further: It's not useful to reserve a portion of my screen for +displaying which applications are running, as the things that are running are visible and +the things that aren't visible can be found when necessary. There's no need for an icon +displaying wifi status; if I'm connected it's uninteresting and if I'm not connected I'll +surely discover it if I attempt to use the internet. + +I took this reasoning to its conclusion. I run my computers with the screen blank except +for the apps I run, and the apps I run I configure to display a minimal amount of +information. I think of this as being anti-dashboard: against the cognitive clutter of +extraneous information. My computer is not a cockpit and I am at my best when I'm only +thinking about the single task at hand. +#+end_quote + +This is taking things a little bit too far for me *but* it does resonate with me, and as +you'll see in the following articles (and on my configuration), I share some of Evan's +belief. By default, my desktop do only show a background (be an image or a color) and +anything related to time, battery and other status is a keybinding away (the =Win= key). + +And on the phone: [[https://robertheaton.com/2020/03/18/yourself-happier-iphone-worse/][How to make yourself happier by making your iPhone worse | Robert Heaton]] +β€” which I followed almost word for word, but this isn't the subject of this post. + +* Emacs, Nix, NixOS, =home-manager= and other tools + +How a GNU/Linux distribution like NixOS comes into this. First, let's see what I want : + +- A system that is tailored to my needs, that makes me efficient. +- A system that is customizable, to answer the previous item. I want to be able to modify + the default behavior if it doesn't suit me. +- A system that is reproductible and easy to replicate. If I change my hardware, or if I + need to re-install my system for /any reason/. +- A way to share configuration on my infra. This is probably the developer speaking, but I + want to write something once and re-use it several times. +- A developer environment that is reproductible, on-demand and /safe/. +- Use defaults of the software I use as much as possible. This one is tricky to achiev for + several reasons: + + I am used to customize everything (/easy to fix/) + + I use a custom keyboard layout ([[https://bepo.fr][bepo]]), so if I go with default keybinding, I will have + the wrong /muscle memory/ (and they could be not optimized either) +- Hackable tools that I can manipulate as I want. +- Use the right tool at the right time, trying not to reinvent the wheel. + +As it turns out Nix and NixOS are giving all the components required for my /almost/ ideal +setup. + +If you complete that with a /not really minimalist/ editor, called [[https://www.gnu.org/software/emacs/][GNU Emacs]], you've got a +pretty composable system without leaving Emacs. I'll dig more into my Emacs configuration +and my NixOS setup in different posts β€” and on the [[https://vincent.demeester.fr/configurations/][=configurations=]] pages β€” but my take +lately is to try to do with what is available (as built-in, in Emacs for example). After +reading the docs, if it's not sufficient, I may look for an external module or tool. + +* Conclusion + +This was a weird post that mixes up tooling, minimalism and thoughts. It's definitely not +what I initially envisioned. But I'm hoping it introduce a bit my take on things. + +* Links + +- [[http://neugierig.org/software/blog/2014/07/anti-dashboard-manifesto.html][Tech Notes: The Anti-Dashboard Manifesto]] +- [[https://blog.zdsmith.com/posts/digital-minimalism-for-the-working-hacker.html][Subset Park: Digital Minimalism for the Working Hacker]] +- [[https://jamesstuber.com/minimalism-as-a-framework/][Minimalism as a Framework for Decision Making | JamesStuber.com]] +- [[https://robertheaton.com/2020/03/18/yourself-happier-iphone-worse/][How to make yourself happier by making your iPhone worse | Robert Heaton]] + +* Footnotes + +[fn:1] And I now work for Red Hat Inc., what a journey 😝 diff --git a/www/vincent.demeester.fr/content/legacy/posts/2020-03-22-org-mode-website.org b/www/vincent.demeester.fr/content/legacy/posts/2020-03-22-org-mode-website.org @@ -0,0 +1,562 @@ +#+title: Migrating to an org-mode website +#+date: <2020-03-22 Sun> +#+filetags: orgmode website emacs +#+setupfile: ../templates/post.org + +* Introduction +:PROPERTIES: +:ID: 24a765bd-a0ed-42cf-b96c-db667f7d37e2 +:END: + +This is a story… a story of me changing the way I code and publish my website. In the +past, I've switch from [[https://vincent.demeester.fr/posts/2012-05-07-reinit-and-jekyll/][Jekyll]] to [[https://vincent.demeester.fr/posts/2015-05-01-orgmode-et-jekyll/]["=orgmode= and Jekyll"]] to [[https://vincent.demeester.fr/posts/2015-05-09-migration-to-hugo/][Hugo]] (sorry those are written +in french). The past year, I've written and documented myself a little bit about +[[https://www.theminimalists.com/minimalism/][minimalism]] and [[https://www.goodreads.com/book/show/40672036-digital-minimalism][digital minimalism]]. Although I don't see myself as a minimalist, it helped +me realize some issues I had. + +I also realized if I want to write more, I need to lower the barrier between writing and +publishing my content ; /if I want it to be published, of course/. This /post/ is about +what I'm putting in place for this, with a premise : I spend my life in [[https://www.gnu.org/software/emacs/][Emacs]] and thus in +[[https://orgmode.org/][=orgmode=]]. And [[https://orgmode.org/][=orgmode=]] is feature-full and has this badass feature : =org-publish=. + +To build and publish this website, we will /try/ to rely on a reproducible setup, meaning +[[https://www.gnu.org/software/emacs/][Emacs]] and [[https://orgmode.org/][=orgmode=]] of course, [[https://www.gnu.org/software/make/][GNU Make]] of course *but* most importantly, [[https://nixos.org/nix/][Nix]] (in the near +future πŸ‘Ό). + +:update: +There is now an article about it, that uses literate programming: [[../articles/meta_publishing_this_website.org][publishing this +website]]. The content of the post might no be up-to-date at some point. +:end: + + +* Requirements +:PROPERTIES: +:ID: 09991f8e-4257-443c-a3a3-40f449df4597 +:END: + +Let's list the requirements I feel I have for my website: + +- Full control over the URL of the published posts. :: + This is a golden rule of the web: should I change the publishing system, I want to be + able to stick to the same URLs or else all external references would be broken. This is + a big no-no and in my opinion it makes most blogging systems unacceptable. +- Top-notch Org support. :: + I believe generators like Jekyll and Hugo only have partial Org support. You end up + requiring some conversion tooling to get the job done. +- Simple publishing pipeline. :: + I want the generation process to be as simple as possible. This is important for + maintenance. Should I someday switch host, I want to be sure that I can set up the same + pipeline. +- Full control over the publishing system. :: + I want maximum control over the generation process. I don’t want to be restricted by a + non-Turing-complete configuration file or a dumb programming language. +- Ease of use. :: + The process as a whole must be as immediate and friction-less as possible, or else I + take the risk of feeling too lazy to publish new posts and update the content. +- Hackability. :: + Last but not least, and this probably supersedes all other requirements: The system must + be hackable. Lisp-based systems are prime contenders in that area. + +* DONE Organizing +CLOSED: [2020-03-21 Sat 17:57] +:PROPERTIES: +:ID: 78251967-c48a-476d-bc39-c748fc412927 +:END: +:LOGBOOK: +- State "DONE" from "TODO" [2020-03-21 Sat 17:57] +:END: + +Let's describe what I do want to publish here: + +- Posts :: this is what we call /blog/ these days : short to medium article that are valid + at a point of time, as may contain /deprecated/ content, or content that do not reflect + my views at a later point in time. +- Articles :: medium to long article about a topic. Those should be up-to-date or + explicitly mark as deprecated or invalid. /In a ideal world this is my *ready for the + public* knowledge database, a bit like [[https://braindump.jethro.dev/][Jethro's Braindump]]/. +- Configurations :: medium to long article about my configurations. Those are base on my + ~home~ /configuration/ mono-repository, and usually follow literate programming + principles. +- About :: an about page about the author of the website (aka [[https://vincent.demeester.fr][me]]), linking external + contributions (GitHub/Gitlab/… profiles, Talks, …). + +* DONE Publishing +CLOSED: [2020-03-25 Wed 15:54] +:PROPERTIES: +:ID: da542763-78b9-46c0-bcf7-d8d2a2b18677 +:END: +:LOGBOOK: +- State "DONE" from "TODO" [2020-03-25 Wed 15:54] +:END: + +As said above, the goal is to publish everything using only [[https://www.gnu.org/software/emacs/][Emacs]] and [[https://orgmode.org/][=orgmode=]] (with the +help of some standard GNU tools). + +The [[file:~/src/sbr/publish.el][~publish.el~]] file is where all the magic happens: + +- I want to generate something that is ~html5~ (almost?). The /preamble/, /content/ and + /postamble/ ~div~ can be customized using ~org-html-div~. Same goes for the /container/ + element that is used for "wrapping top level sections", it is customized using + ~org-html-container-helement~ (we want ~<section>~). There is a few additional variable + that I might document one day πŸ˜›. + + #+begin_src emacs-lisp + (setq org-export-use-babel nil) + (setq org-link-abbrev-alist '(("att" . org-attach-expand-link))) + + ;; setting to nil, avoids "Author: x" at the bottom + (setq org-export-with-section-numbers nil + org-export-with-smart-quotes t + org-export-with-toc nil) + + (defvar sbr-date-format "%b %d, %Y") + + (setq org-html-divs '((preamble "header" "top") + (content "main" "content") + (postamble "footer" "postamble")) + org-html-container-element "section" + org-html-metadata-timestamp-format sbr-date-format + org-html-checkbox-type 'unicode + org-html-html5-fancy t + org-html-doctype "html5" + org-html-htmlize-output-type 'css + org-html-htmlize-font-prefix "org-" + org-src-fontify-natively t + org-html-coding-system 'utf-8-unix) + #+end_src + +- Part of the ~<head>~, preamble and postamble are customized for the website. + + ~head~ :: + #+begin_src emacs-lisp + (defvar sbr-website-html-head + "<link rel='icon' type='image/x-icon' href='/images/favicon.ico'/> + <meta name='viewport' content='width=device-width, initial-scale=1'> + <link rel='stylesheet' href='/css/new.css' type='text/css'/> + <link rel='stylesheet' href='/css/syntax.css' type='text/css'/> + <link href='/index.xml' rel='alternate' type='application/rss+xml' title='Vincent Demeester' />") + #+end_src + + premable :: + #+begin_src emacs-lisp + (defun sbr-website-html-preamble (plist) + "PLIST: An entry." + ;; Skip adding subtitle to the post if :KEYWORDS don't have 'post' has a + ;; keyword + (when (string-match-p "post" (format "%s" (plist-get plist :keywords))) + (plist-put plist + :subtitle (format "Published on %s by %s." + (org-export-get-date plist sbr-date-format) + (car (plist-get plist :author))))) + + ;; Below content will be added anyways + "<nav> + <img src=\"/images/favicon.ico\" id=\"sitelogo\"/> <a href='/'>home</a> / + <a href='/posts/'>posts</a> (<a href='/index.xml'>rss</a>) / + <a href='/articles/'>articles</a> / + <a href='/configurations/'>configurations</a> / + <a href='https://dl.sbr.pm/'>files</a> / + <a href='/about/'>about</a></li> + </nav>") + #+end_src + + postamble :: + #+begin_src emacs-lisp + (defvar sbr-website-html-postamble + "<footer> + <span class='questions'>Questions, comments ? Please use my <a href=\"https://lists.sr.ht/~vdemeester/public-inbox\">public inbox</a> by sending a plain-text email to <a href=\"mailto:~vdemeester/public-inbox@lists.sr.ht\">~vdemeester/public-inbox@lists.sr.ht</a>.</span> + <span class='opinions'>Opinions stated here are my own and do not express the views of my employer, spouse, children, pets, neighbors, secret crushes, favorite authors, or anyone else who is not me. And maybe not even me, depending on how old this is.</span> + <span class='copyright'> + Content and design by Vincent Demeester + (<a rel='licence' href='http://creativecommons.org/licenses/by-nc-sa/3.0/'>Some rights reserved</a>) + </span><br /> + <span class='engine'> + Powered by <a href='https://www.gnu.org/software/emacs/'>Gnu Emacs</a> and <a href='https://orgmode.org'>orgmode</a> + </span> + </footer>") + #+end_src +- =orgmode= is able to generate a site-map. This is what we are going to use to generate + the index files for ~posts/~ and ~articles~ mainly. + #+begin_src emacs-lisp + (defun sbr/org-sitemap-format-entry (entry style project) + "Format posts with author and published data in the index page. + + ENTRY: file-name + STYLE: + PROJECT: `posts in this case." + (cond ((not (directory-name-p entry)) + (format "%s β€” [[file:%s][%s]] + :PROPERTIES: + :PUBDATE: [%s] + :END:" + (format-time-string "%Y-%m-%d" + (org-publish-find-date entry project)) + entry + (org-publish-find-title entry project) + (format-time-string "%Y-%m-%d" + (org-publish-find-date entry project)))) + ((eq style 'tree) (file-name-nondirectory (directory-file-name entry))) + (t entry))) + + (defun sbr/org-publish-sitemap (title list) + "" + (concat "#+TITLE: " title "\n\n" + (org-list-to-subtree list))) + #+end_src +- =orgmode= is able to generate a rss, with =ox-rss=. This is what we are going to use to + generate the rss files for ~posts~ and ~articles~. This is _heavily_ customized. + #+begin_src emacs-lisp + (defun sbr/org-get-first-paragraph (file) + "Get string content of first paragraph of file." + (ignore-errors + (with-temp-buffer + (insert-file-contents file) + (goto-char (point-min)) + (show-all) + (let ((first-begin (progn + (org-forward-heading-same-level 1) + (next-line) + (point))) + (first-end (progn + (org-next-visible-heading 1) + (point)))) + (buffer-substring first-begin first-end))))) + + (defun sbr/org-rss-publish-to-rss (plist filename pub-dir) + "Prepare rss.org file before exporting." + (let* ((postsdir (plist-get plist :base-directory))) + (with-current-buffer (find-file filename) + (erase-buffer) + (insert "#+TITLE: Posts\n") + (insert "#+AUTHOR: Vincent Demeester\n") + (insert "#+OPTIONS: toc:nil\n") + (let* ((files-all + (reverse (directory-files "." nil + "[0-9-]+.*\\.org$"))) + (files (subseq files-all 0 (min (length files-all) 30)))) + (message (format "foo: %s" filename)) + (dolist (post files) + (let* ((post-file post) + (post-title (org-publish-find-title post-file plist)) + (preview-str (sbr/org-get-first-paragraph post-file)) + (date (replace-regexp-in-string + "\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\)-.*" + "\\1" post))) + (insert (concat "* [[file:" postsdir "/" post "][" post-title "]]\n\n")) + (org-set-property "ID" post) + (org-set-property "RSS_TITLE" post-title) + ;; ox-rss prepends html-link-home to permalink + (org-set-property "RSS_PERMALINK" + (concat postsdir "/" + (file-name-sans-extension post) + ".html")) + (org-set-property + "PUBDATE" + (format-time-string + "<%Y-%m-%d %a %H:%M>" + (org-time-string-to-time + (replace-regexp-in-string + "\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\)-.*" + "\\1" post)))) + (insert preview-str) + (newline 1) + (insert (concat "[[file:" postsdir "/" post "][(Read more)]]\n\n")))) + (save-buffer)))) + (let ((user-mail-address "t") + (org-export-with-broken-links t) + (org-rss-use-entry-url-as-guid nil)) + (org-rss-publish-to-rss plist filename pub-dir))) + #+end_src +- Finally let's set the ~org-publish-project-alist~ to publish our projects + #+begin_src emacs-lisp + (setq org-publish-project-alist + `(("posts" + :base-directory "posts" + :base-extension "org" + :recursive t + :publishing-function org-html-publish-to-html + :publishing-directory "./public/posts" + :exclude ,(regexp-opt '("README.org" "draft")) + :auto-sitemap t + :with-footnotes t + :with-toc nil + :with-drawers t + :sitemap-filename "index.org" + :sitemap-title "Posts" + :sitemap-format-entry sbr/org-sitemap-format-entry + :sitemap-style list + :sitemap-sort-files anti-chronologically + :sitemap-function sbr/org-publish-sitemap + :html-head-include-scripts nil + :html-head-include-default-style nil + :html-head ,sbr-website-html-head + :html-preamble sbr-website-html-preamble + :html-postamble ,sbr-website-html-postamble) + ("posts-rss" + :base-directory "posts" + :base-extension "org" + :recursive t + :html-link-home "https://vincent.demeester.fr/" + :rss-link-home "https://vincent.demeester.fr/posts/" + :html-link-use-abs-url t + :rss-extension "xml" + :publishing-directory "./public" + :publishing-function (sbr/org-rss-publish-to-rss) + :section-number nil + :exclude ".*" + :include ("index.org")) + ("articles" + :base-directory "articles" + :base-extension "org" + :recursive t + :publishing-function org-html-publish-to-html + :publishing-directory "./public/articles" + :exclude ,(regexp-opt '("README.org" "draft")) + :auto-sitemap t + :with-footnotes t + :with-toc nil + :with-drawers t + :sitemap-filename "sitemap.org" + :sitemap-title "Articles" + :sitemap-style tree + :sitemap-sort-files anti-chronologically + ;;:sitemap-format-entry sbr/org-sitemap-format-entry + ;;:sitemap-function sbr/org-publish-sitemap + :html-head-include-scripts nil + :html-head-include-default-style nil + :html-head ,sbr-website-html-head + :html-preamble sbr-website-html-preamble + :html-postamble ,sbr-website-html-postamble) + ("articles-assets" + :exclude ,(regexp-opt '("*.org")) + :base-directory "articles" + :base-extension ,site-attachments + :publishing-directory "./public/articles" + :publishing-function org-publish-attachment + :recursive t) + ("about" + :base-directory "about" + :base-extension "org" + :exclude ,(regexp-opt '("README.org" "draft")) + :index-filename "index.org" + :recursive nil + :with-footnotes t + :with-toc nil + :with-drawers t + :publishing-function org-html-publish-to-html + :publishing-directory "./public/about" + :html-head-include-scripts nil + :html-head-include-default-style nil + :html-head ,sbr-website-html-head + :html-preamble sbr-website-html-preamble + :html-postamble ,sbr-website-html-postamble) + ("index" + :base-directory "" + :base-extension "org" + :exclude ,(regexp-opt '("README.org" "draft")) + :index-filename "index.org" + :recursive nil + :with-footnotes t + :with-toc nil + :with-drawers t + :with-title nil + :publishing-function org-html-publish-to-html + :publishing-directory "./public" + :html-head-include-scripts nil + :html-head-include-default-style nil + :html-head ,sbr-website-html-head + :html-preamble sbr-website-html-preamble + :html-postamble ,sbr-website-html-postamble) + ("css" + :base-directory "./css" + :base-extension ,site-attachments + :recursive t + :publishing-directory "./public/css" + :publishing-function org-publish-attachment + :recursive t) + ("images" + :base-directory "./images" + :base-extension ,site-attachments + :publishing-directory "./public/images" + :publishing-function org-publish-attachment + :recursive t) + ("assets" + :base-directory "./assets" + :base-extension ,site-attachments + :publishing-directory "./public/assets" + :publishing-function org-publish-attachment + :recursive t) + ("legacy" + :base-directory "./legacy" + :base-extension ,site-attachments + :publishing-directory "./public/" + :publishing-function org-publish-attachment + :recursive t) + ("all" :components ("posts" "about" "index" "articles" "articles-assets" "css" "images" "assets" "legacy" "posts-rss")))) + #+end_src + +Here are some /inspiration/ I took for this publishing code: + +- [[https://thibaultmarin.github.io/blog/posts/2016-11-13-Personal_website_in_org.html][Personal website in org]] +- [[https://vicarie.in/posts/blogging-with-org.html][Blogging with Org publishing]] +- [[https://ambrevar.xyz/blog-architecture/][A blog in pure Org/Lisp]] +- [[https://zngguvnf.org/2017-07-13--blogging-with-org-static-blog.html][Blogging with org-static-blog]] +- [[https://bastibe.de/2013-11-13-blogging-with-emacs.html][Blogging with Emacs]] +- [[https://gjhenrique.com/meta.html][Blogging with org-mode and Gitlab Pages]] + + +* DONE Styling +CLOSED: [2020-03-23 Mon 19:02] +:PROPERTIES: +:ID: 052e297f-01b2-41d3-9689-cd2946b56cfb +:END: +:LOGBOOK: +- State "DONE" from "STARTED" [2020-03-23 Mon 19:02] +:END: + +The style of the website has be as simple as possible, and also really light. This means: +- use default system font as much as possible +- have a small stylesheet, rely on the default as much as we can + +In addition, I want support for: +- side notes +- code syntax highlight +- table of content + +The inspiration for this website, in term of style are the following: +- [[https://vincent.demeester.fr/posts/2018-08-16-gotest-tools-assertions/][Vincent Demeester]] +- [[https://braindump.jethro.dev/zettels/zettelkasten/][Jethro's Braindump]] +- [[https://hamberg.no/gtd/][GTD in 15 minutes – A Pragmatic Guide to Getting Things Done]] +- [[https://www.inkandswitch.com/local-first.html][Local-first software: You own your data, in spite of the cloud]] +- [[https://archive.casouri.cat/note/2018/blog-in-org-mode-revisited/index.html][Blog in Org Mode, Revisited]] +- [[https://kind.sigs.k8s.io/][kind]] +- [[http://willcrichton.net/notes/idioms-of-dynamic-languages/][Idioms of Dynamic Languages | Will Crichton]] +- [[https://peter.bourgon.org/blog/2019/09/11/programming-with-errors.html][Peter Bourgon Β· Programming with errors]] +- [[https://johv.dk/blog/bare-metal-assembly-tutorial.html][Getting started with bare-metal assembly β€” Jonas Hvid]] + +To be able to define the style a bit, let's try some things below. From this point on, +this is random content just to try my style out. πŸ‘Ό + +There is more in [[../articles/sandbox.org][the sandbox]]. + +------ + +#+begin_abstract +Let's dig into how I setup my development environment when working on ~tektoncd/pipeline~ +#+end_abstract + +#+TOC: headlines 2 + +** sub-heading 1 + +Checking for errors is *very common* in Go, having =Comparison= function for it was a requirement. + +#+BEGIN_aside +This is a side note. If collection is a string, item must also be a string, and is +compared using =strings.Contains()=. If collection is a Map, contains will succeed if item +is a key in the map. +#+END_aside + +- =Error= fails if the error is =nil= *or* the error message is not the expected one. +- =ErrorContains= fails if the error is =nil= *or* the error message does not contain the expected substring. +- =ErrorType= fails if the error is =nil= *or* the error type is not the expected type. + +Let's first look at the most used : =Error= and =ErrorContains=. +When you're working on ~pipeline~, usually you want : + +1. make sure it compiles : ~go build -v ./..~ +2. Running unit tests : ~go test ./...~ (bonus use [[https://github.com/vdemeester/ram][~ram~]] for continuous testing) +3. End-to-end tests : ~go test -tags e2e ./...~ (or simply using `./test/` package) + + *Make sure you re-deploy before running the e2e tests* using ~ko apply -f ./config~, + otherwise you're testing the wrong code. + +#+BEGIN_SRC go + var err error + // will fail with : expected an error, got nil + assert.Check(t, cmp.Error(err, "message in a bottle")) + err = errors.Wrap(errors.New("other"), "wrapped") + // will fail with : expected error "other", got "wrapped: other" + assert.Check(t, cmp.Error(err, "other")) + // will succeed + assert.Check(t, cmp.ErrorContains(err, "other")) +#+END_SRC + +#+CAPTION: This is the caption for the next figure link (or table) +#+NAME: fig:SED-HR4049 +#+ATTR_ORG: :width 400/600 +#+ATTR_HTML: :width 100% +[[../images/emacs/2020-02-29-13-46-08.png]] + +If this is the case, then what makes dynamic languages feel easy? Can we take what we learn in answering this question and improve the ergonomics of our static languages? For example, in 2018, I don’t think there’s as strong an argument for β€œyou don’t have to write out the types,” since modern type inference eliminates most of the keystrokes required (even though many major languages still lack such facilities). Plus, saving a few keystrokes does not seem like a critical bottleneck in the programming process. + +#+NAME: fig:tektoncd-logo +[[../images/emacs/2020-02-29-14-41-59.png]] + + +** sub-heading 2 + +Some content from my other org-mode files. + +#+begin_description +I already wrote 2 previous posts about golang and testing. It's something I care deeply about and I wanted to continue +writing about it. It took me a bit more time than I thought, but getting back to it. Since the [[http://vincent.demeester.fr/posts/2017-04-22-golang-testing-golden-file/][last post]], Daniel +Nephin +and I worked (but mainly Daniel πŸ€—) on bootstrapping a testing helper library. +#+end_description + +#+BEGIN_QUOTE +Package assert provides assertions for comparing expected values to actual values. When +assertion fails a helpful error message is printed. +#+END_QUOTE + +There is already some good =testing= helpers in the Go ecosystem : [[https://github.com/stretchr/testify][=testify=]], [[http://labix.org/gocheck][=gocheck=]], +[[https://github.com/onsi/ginkgo][=ginkgo=]] and a lot more β€” so why create a new one ? There is multiple reason for it, most +of them can be seen in the following [[https://github.com/gotestyourself/gotest.tools/issues/49#issuecomment-362436026][GitHub issue]]. + +[[https://github.com/dnephin/][Daniel]] also wrote a very useful [fn:1]converter if your code base is currently using =testify= : +=gty-migrate-from-testify=. + +#+BEGIN_SRC sh +$ go get -u gotest.tools/assert/cmd/gty-migrate-from-testify +# […] +$ go list \ + -f '{{.ImportPath}} {{if .XTestGoFiles}}{{"\n"}}{{.ImportPath}}_test{{end}}' \ + ./... | xargs gty-migrate-from-testify +#+END_SRC + +We'll Use =Assert= for the rest of the section but any example here would work with +=Check= too. When we said =Comparison= above, it's mainly the [[https://godoc.org/gotest.tools/assert#BoolOrComparison][BoolOrComparison]] interface β€” +it can either be a boolean expression, or a [[https://godoc.org/gotest.tools/assert/cmp#Comparison][cmp.Comparison]] type. =Assert= and =Check= code +will be /smart/ enough to detect which one it is. + +#+BEGIN_SRC go + assert.Assert(t, ok) + assert.Assert(t, err != nil) + assert.Assert(t, foo.IsBar()) +#+END_SRC + + +* What's next ? +:PROPERTIES: +:ID: 678bf6cb-68e7-4fc9-af50-23d283293ab1 +:END: + +One thing is to import old blog posts from [[https://vincent.demeester.fr][vincent.demeester.fr]]. This is easily done with +[[https://pandoc.org/][Pandoc]] and a small bash loop β€” and some manual adjusting later on πŸ˜›. + +#+begin_src bash +for post in ~/src/github.com/vdemeester/blog/content/posts/*.md; do + pandoc -f markdown -t org -o posts/$(basename -s .md ${post}).org ${post} +done +#+end_src + + +What is still /to do/ after this initial take. + +- [ ] List =FILETAGS= for taximony +- [ ] Maybe use [[https://css-tricks.com/snippets/css/complete-guide-grid/][css grid]] for the UI + +* Footnotes +:PROPERTIES: +:ID: 220a0cce-39c3-4b5f-aaa0-66e3a2450f04 +:END: + +[fn:1] foo is bar, bar is baz diff --git a/www/vincent.demeester.fr/content/legacy/posts/2020-04-15-emacs-bankruptcy-is-fun.org b/www/vincent.demeester.fr/content/legacy/posts/2020-04-15-emacs-bankruptcy-is-fun.org @@ -0,0 +1,108 @@ +#+title: Emacs bankruptcy is fun +#+date: <2020-04-15 Wed> +#+filetags: emacs configuration optimization +#+setupfile: ../templates/post.org + +* Introduction + +Since go 1.14 go released, I've had a broken =go-mode= setup on my Emacs. I was using +=lsp-mode= and =gopls= and well, the update broke everything. I initally try to fix it but +I made it worse. At the same time, I started to get fed up with some performance issue of +my configuration and how slow my Emacs starts, about 6s. + +I, thus, declared my third Emacs bankruptcy, =:disabled= everything and slowly started +from scratch, with the following goal: + +- Have it start quick, as less than a second, not too much more than =emacs -Q= would +- Disable anything that I don't use often initially +- Try to use as much built-in as possible (example: using =icomplete= instead of + =ivy=/=counsel=) + +* Do I really need this feature + +Following [[https://protesilaos.com/][Protesilaos Stavrou]]'s emacs videos (and [[https://protesilaos.com/dotemacs/][=dotemacs=]]) for a while now, I have a +tendency to try to use built-in feature as much as possible. The most obvious example is +using =icomplete= instead of =ivy=/=counsel=. + +When I started my /bankruptcy/, I disabled every single customization I had, either using +=:disabled= when using =use-package= *or* the =(when nil)= /hack/. I then started Emacs +and acted on what was missing : + +1. Do I really miss it ? An example would be, at least initially, the completion in a =go= + file. I do miss it, but I miss it *way less* than having Emacs lagging because of + =lsp-mode= and showing me wrong completion. +2. Is there a built-in option to what I previously used ? Here, the =icomplete= example + fits well, or =isearch= instead of =swiper=. +3. Do I need it at startup or /on-demand/ ? + +* Looking into what takes time + +In "Advanced Techniques for Reducing Emacs Startup Time"[fn:1], I discovered the [[https://github.com/jschaf/esup][=esup=]] +emacs library. In a gist, this is a profiler for Emacs. It starts a new Emacs instance and +look at the loading time. + +#+CAPTION: esup "result" view +[[../images/2020-04-15-16-12-54.png]] + +Then, you can do a simple loop: + +- Run =esup= +- Look at the top result +- Fix it (lazy load, removing the feature, …) +- Re-iterate + +* Loading on-demand + +Once you have the setup to know what takes time and what not, it's time to look into how +to load the most thing on demand. + +For this, [[https://github.com/jwiegley/use-package][=use-package=]] is amazing, it tremendously help autoloading modules on-demand. If +you are not using =use-package=, usually you are using =require=, which loads the +underlying source file (all of it). + +With =use-package=, there is multiple ways to load on demand: + +- =:commands= to add callable that will trigger loading the package +- =:bind=, =:bind*=, =:bind-keymap= and =:bind-keymap*= to bind key sequences to the + global keymap or a specific keymap. +- =:mode= and =:interpreter= establish a deferred binding with the =auto-mode-alist= and + =interpreter-mode-alist=. +- =:hook= allows adding functions onto the package hook +- =:defer= is the most generic one, all the previous keyword imply =:defer=. You can + specify a number of second of idle to load the package. + +#+BEGIN_aside +In a gist for =org-babel=, use =use-package ob-python= and never call =org-babel-do-languages=. +#+END_aside +Once this is done, you are left with edge cases, like =org-babel-do-languages=. Those are +to be handle case by case. The good thing about those cases is that you'll learn what +those function do and this will give you an even better understanding of what is +happening. + +Doing this exercise also forces you to make you see if you really use that feature or +not. I ended up removing entire feature from my configuration because they were taking +quite some time to load, and was used almost never. Instead I am forcing myself to learn +more what I can do with the built-in features first. + +* Conclusion + +All in all, this /bankruptcy/ was the most fun I had to do. I consider myself still in the +process but the base is there. + +1. I learned a lot ! +2. My Emacs starts in 0.6s against previously in 5s β€” =emacs -q= starts in about 0.3s so + there is still a little bit of room for improvement. +3. I discovered / re-discovered a lot of built-in feature +4. I started documenting my configuration, see [[../configurations/emacs.org][here]]. + +πŸŽ‰ + +:update: +Well, I've look into the /portable dump/ feature of Emacs, thanks to [[https://archive.casouri.cat/note/2020/painless-transition-to-portable-dumper/index.html][Painless Transition +to Portable Dumper]]. And I am now down to =0.091s= for the startup. There is a few gotchas +with /portable dump/, I'll try to write about it later. +:end: + +* Footnotes + +[fn:1]: [[https://blog.d46.us/advanced-emacs-startup/][Advanced Techniques for Reducing Emacs Startup Time]] diff --git a/www/vincent.demeester.fr/content/legacy/posts/2020-06-21-website-update.org b/www/vincent.demeester.fr/content/legacy/posts/2020-06-21-website-update.org @@ -0,0 +1,16 @@ +#+title: website update +#+date: <2020-06-08 Mon> +#+filetags: website css new simple +#+setupfile: ../templates/post.org + +* Introduction + +A really small article to talk about small updates on the website (/well, maybe not that +small/). In a gist: a new css and articles changes. + +- Updating the style of the website to something really similar to [[https://newcss.net/][new.css]]. It is way + simpler and /pleasing/ (at least for me). +- My [[../articles][=/articles=]] are now exporting from my [[../articles/personal_knowledge_base.org][personal knowledge base]], powered by + [[../articles/org_roam.org][=org-roam=]]. +- A new [[https://dl.sbr.pm/][=/files=]] part is available, where I share some random file. See for example the + [[https://dl.sbr.pm/wallpapers/dynamics/][dynamic wallpaper]] part. diff --git a/www/vincent.demeester.fr/content/legacy/posts/2020-07-08-june-status-update.org b/www/vincent.demeester.fr/content/legacy/posts/2020-07-08-june-status-update.org @@ -0,0 +1,109 @@ +#+title: Status update, June 2020 +#+date: <2020-07-08 Wed> +#+filetags: status update +#+setupfile: ../templates/post.org + +* Introduction + +Time for the first new monthly status update! I do like those updates from [[https://drewdevault.com/2020/06/15/Status-update.html][Drew DeVault]] +and [[https://emersion.fr/blog/2020/status-update-19/][Simon Ser]], so I figured, why not trying myself πŸ™ƒ. I am not sure where to start and +where to end, but I guess I'll figure things out as I go. + +* Tekton & OpenShift Pipelines + +As you know, /or may not/, I am working on the [[https://github.com/tektoncd/][TektonCD]] project and also on our [[https://redhat.com][RedHat]] +product [[https://www.openshift.com/learn/topics/pipelines][OpenShift Pipelines]]. As far as the month of June went : + +- We release [[https://github.com/tektoncd/pipeline/releases/tag/v0.13.0][v0.13.0]] "Bobtail Bishop" and a bunch of fixes ([[https://github.com/tektoncd/pipeline/releases/tag/v0.13.2][v0.13.2]]) + + It's the second release after the =v1beta1= bump, and we start to stabilise things. + + [[https://github.com/tektoncd/pipeline/releases/tag/v0.14.0][v0.14.0]] will (and actually is already) a more feature-packed release because there is + now the =finally= field support and cloud-events opt-in. +- But the *most important* contributions I tried to make during that month is the TEP + process. *TEP* stands for *Tekton Enhancement proposals*. + + #+begin_src markdown + # Tekton Enhancement Proposals (TEPs) + + A Tekton Enhancement Proposal (TEP) is a way to propose, communicate + and coordinate on new efforts for the Tekton project. You can read + the full details of the project in + [TEP-1](https://github.com/tektoncd/community/blob/master/teps/0001-tekton-enhancement-proposal-process.md). + + ## What is a TEP + + A standardized development process for Tekton is proposed in order to + + - provide a common structure and clear checkpoints for proposing + changes to Tekton + - ensure that the motivation for a change is clear + - allow for the enumeration stability milestones and stability + graduation criteria + - persist project information in a Version Control System (VCS) for + future Tekton users and contributors + - support the creation of _high value user facing_ information such + as: + - an overall project development roadmap + - motivation for impactful user facing changes + - ensure community participants are successfully able to drive changes + to completion across one or more releases while stakeholders are + adequately represented throughout the process + + This process is supported by a unit of work called a Tekton + Enhancement Proposal (TEP). A TEP attempts to combine aspects of the + following: + + - feature, and effort tracking document + - a product requirements document + - design document + + into one file which is created incrementally in collaboration with one + or more [Working + Groups](https://github.com/tektoncd/community/blob/master/working-groups.md) + (WGs). + + This process does not block authors from doing early design docs using + any means. It does not block authors from sharing those design docs + with the community (during Working groups, on Slack, GitHub, …. + + ,**This process acts as a requirement when a design docs is ready to be + implemented or integrated in the `tektoncd` projects**. In other words, + a change that impact other `tektoncd` projects or users cannot be + merged if there is no `TEP` associated with it. + + This TEP process is related to + - the generation of an architectural roadmap + - the fact that the what constitutes a feature is still undefined + - issue management + - the difference between an accepted design and a proposal + - the organization of design proposals + + This proposal attempts to place these concerns within a general + framework. + + + See [TEP-1](https://github.com/tektoncd/community/blob/master/teps/0001-tekton-enhancement-proposal-process.md) for more details. + #+end_src + +* =home=, Nixos and the rest of thing + +I did some big changes in my [[https://git.sr.ht/~vdemeester/home][=home=]] repository, it's still much a work-in-progress but it +is in a way better state than before. + +- It is more reproductible. All dependencies are managed by [[https://github.com/nmattia/niv][niv]] and all machines are using + a pinned version of channels from there. That way I can test the configuration (on the + CI) and cache the packages for all channels that my machines uses. I also can decide + when I want to upgrade a particular channel (nixos, unstable, …). +- I am slowly experimenting on simplifying things more and more. This is a bit related to + the [[file:2020-02-22-digital-minimalism.org][previous post]]. I am trying to use Gnome3 everywhere. Well configured, managed by + NixOS, it's enough for my test and reduce the configuration cruft I need to do. + +An ongoing work is my knowledge base and how it is published as part of this website : +[[https://vincent.demeester.fr/articles/][articles]]. I am trying to make all those publishable (for the one that do not hold any +secret). I'll document this a bit more at some point but… +- Configurations are now part of that knowledge base, the =docs= folder of [[https://git.sr.ht/~vdemeester/home][=home=]] is going + away. +- I am trying to use litterate configuration as much as possible. So slowly, the content + from [[https://git.sr.ht/~vdemeester/home][=home=]] will be a tangled version of my knowledge base. At least that is the goal as + of today. + +And I feel that's all /for June/. diff --git a/www/vincent.demeester.fr/content/legacy/posts/2020-12-01-nixify-www.draft b/www/vincent.demeester.fr/content/legacy/posts/2020-12-01-nixify-www.draft @@ -0,0 +1,8 @@ +#+title: Nixifying this website +#+date: <2020-12-01 Fri> +#+filetags: website nix +#+setupfile: ../templates/post.org + +* Introduction + +… diff --git a/www/vincent.demeester.fr/content/legacy/posts/diving-into-nix.org.draft b/www/vincent.demeester.fr/content/legacy/posts/diving-into-nix.org.draft @@ -0,0 +1,8 @@ +#+TITLE: Diving into Nix(OS) β€” introduction +#+SUBTITLE: Series about nixos +#+SETUPFILE: ../templates/post.org + +* TODO Introduction + +This will be the start of a serie of blog post around Nix, Nixpkgs and NixOS. The goal is +to present the pros and cons of Nix and how it benefits to use it in different use cases. diff --git a/www/vincent.demeester.fr/content/legacy/posts/index.org b/www/vincent.demeester.fr/content/legacy/posts/index.org @@ -0,0 +1,90 @@ +#+TITLE: Posts + +* 2020-07-08 β€” [[file:2020-07-08-june-status-update.org][Status update, June 2020]] +:PROPERTIES: +:PUBDATE: [2020-07-08 Wed] +:END: +* 2020-06-08 β€” [[file:2020-06-21-website-update.org][website update]] +:PROPERTIES: +:PUBDATE: [2020-06-08 Mon] +:END: +* 2020-04-15 β€” [[file:2020-04-15-emacs-bankruptcy-is-fun.org][Emacs bankruptcy is fun]] +:PROPERTIES: +:PUBDATE: [2020-04-15 Wed] +:END: +* 2020-03-22 β€” [[file:2020-03-22-org-mode-website.org][Migrating to an org-mode website]] +:PROPERTIES: +:PUBDATE: [2020-03-22 Sun] +:END: +* 2020-02-22 β€” [[file:2020-02-22-digital-minimalism.org][On digital minimalism, Linux, NixOS and Emacs]] +:PROPERTIES: +:PUBDATE: [2020-02-22 Sat] +:END: +* 2019-03-23 β€” [[file:2019-03-23-gotest-tools-poll.org][Golang testing β€” gotest.tools poll]] +:PROPERTIES: +:PUBDATE: [2019-03-23 Sat] +:END: +* 2019-01-26 β€” [[file:2019-01-26-nix-run-alias.org][Nix run aliases]] +:PROPERTIES: +:PUBDATE: [2019-01-26 Sat] +:END: +* 2019-01-20 β€” [[file:2019-01-20-2018-year-review.org][2018 year review]] +:PROPERTIES: +:PUBDATE: [2019-01-20 Sun] +:END: +* 2018-09-18 β€” [[file:2018-09-18-gotest-tools-icmd.org][Golang testing β€” gotest.tools icmd]] +:PROPERTIES: +:PUBDATE: [2018-09-18 Tue] +:END: +* 2018-09-14 β€” [[file:2018-09-14-gotest-tools-fs.org][Golang testing β€” gotest.tools fs]] +:PROPERTIES: +:PUBDATE: [2018-09-14 Fri] +:END: +* 2018-09-06 β€” [[file:2018-09-06-gotest-tools-golden.org][Golang testing β€” gotest.tools golden]] +:PROPERTIES: +:PUBDATE: [2018-09-06 Thu] +:END: +* 2018-09-01 β€” [[file:2018-09-01-gotest-tools-skip.org][Golang testing β€” gotest.tools skip]] +:PROPERTIES: +:PUBDATE: [2018-09-01 Sat] +:END: +* 2018-08-16 β€” [[file:2018-08-16-gotest-tools-assertions.org][Golang testing β€” gotest.tools assertions]] +:PROPERTIES: +:PUBDATE: [2018-08-16 Thu] +:END: +* 2018-07-28 β€” [[file:2018-07-28-gotest-tools-intro.org][Golang testing β€” gotest.tools introduction]] +:PROPERTIES: +:PUBDATE: [2018-07-28 Sat] +:END: +* 2014-03-24 β€” [[file:2014-03-24-redesign-et-rΓ©solutions.org][Redesign et RΓ©solutions]] +:PROPERTIES: +:PUBDATE: [2014-03-24 Mon] +:END: +* 2013-10-12 β€” [[file:2013-10-12-podcasts.org][Podcasts]] +:PROPERTIES: +:PUBDATE: [2013-10-12 Sat] +:END: +* 2013-09-08 β€” [[file:2013-09-08-maven-tmpfs.org][Maven Tmpfs]] +:PROPERTIES: +:PUBDATE: [2013-09-08 Sun] +:END: +* 2012-12-16 β€” [[file:2012-12-16-gollum-comme-wiki-personnel.org][Gollum comme Wiki personnel]] +:PROPERTIES: +:PUBDATE: [2012-12-16 Sun] +:END: +* 2012-07-23 β€” [[file:2012-07-23-maven-release-gitflow.org][Maven Release Gitflow]] +:PROPERTIES: +:PUBDATE: [2012-07-23 Mon] +:END: +* 2012-05-13 β€” [[file:2012-05-13-jekyll-foreman-guard-bundler.org][Jekyll Forman Guard Bundler]] +:PROPERTIES: +:PUBDATE: [2012-05-13 Sun] +:END: +* 2012-05-08 β€” [[file:2012-05-08-gitolite-quick-and-dirty-mirror.org][Gitolite quick and dirty mirror]] +:PROPERTIES: +:PUBDATE: [2012-05-08 Tue] +:END: +* 2012-05-07 β€” [[file:2012-05-07-reinit-and-jekyll.org][Reinit and Jekyll]] +:PROPERTIES: +:PUBDATE: [2012-05-07 Mon] +:END:+ \ No newline at end of file diff --git a/www/vincent.demeester.fr/content/legacy/posts/quick-kubernetes-provisionning.org.draft b/www/vincent.demeester.fr/content/legacy/posts/quick-kubernetes-provisionning.org.draft @@ -0,0 +1,5 @@ +#+TITLE: Quickly provision Kubernetes +#+SUBTITLE: Experiment with different tools to see what is the most efficient way to provision a multi-node kubernetes cluster +#+SETUPFILE: ../templates/post.org + +* TODO Introduction diff --git a/www/vincent.demeester.fr/content/posts/2012-05-07-reinit-and-jekyll.org b/www/vincent.demeester.fr/content/posts/2012-05-07-reinit-and-jekyll.org @@ -1,61 +0,0 @@ -#+title: Reinit and Jekyll -#+date: <2012-05-07 Mon> -#+filetags: website jekyll -#+setupfile: ../templates/post.org - -* Introduction - -Two weeks ago, my /online/ personal server has been attacked and, -somehow, died. I'm in the process of re-installation of it but I'm going -to hardened a bit the security on it. Anyway, this crash meant that -every piece of site I maintain has been down. That's why I moved this -/identity site/ on the github pages, using a CNAME ; That way I can -crash as much as I want my server(s), this page should still be up for a -while. - -And I'm switching on Jekyll for this website as It is supported by -Github page, easy to use and easy to deploy elsewhere (if one day I want -to move from Github). - -The rest of the post is going to be used as a /sandbox/ post to test the -site styles. - -#+BEGIN_QUOTE - This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, - consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. - Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. - - Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse - id sem consectetuer libero luctus adipiscing. -#+END_QUOTE - -** Highlight - :PROPERTIES: - :CUSTOM_ID: highlight - :ID: 6e1687a1-6898-4fe1-ac56-53b6e1e1f310 - :END: - -#+begin_src ruby -def foo puts 'foo' end -#+end_src - -Some bash script... - -#+begin_src bash -#!/bin/bash -update_gems() { - echo "Update gems for all versions ? (y/N)" - read UPDATE_GEMS - test -z "${UPDATE_GEMS}" && UPDATE_GEMS="n" - - if test "${UPDATE_GEMS}" = "y"; then - for version in =ls --color=never $HOME/.rbenv/versions=; do - echo "Updating ${version%/}" - RBENV_VERSION="${version%/}" rbenv exec gem update - RBENV_VERSION="${version%/}" rbenv exec gem install bundler - done - fi -} - -update_gems -#+end_src diff --git a/www/vincent.demeester.fr/content/posts/2012-05-08-gitolite-quick-and-dirty-mirror.org b/www/vincent.demeester.fr/content/posts/2012-05-08-gitolite-quick-and-dirty-mirror.org @@ -1,126 +0,0 @@ -#+title: Gitolite quick and dirty mirror -#+date: <2012-05-08 Tue> -#+filetags: gitolite git linux mirror github -#+setupfile: ../templates/post.org - -* Introduction - -I'm running a gitolite _instance_ on my personal server to manage my repositories -(personnal, private or public) ; and I am quickly going to share with you how I setup a -_quick and dirty_ mirror feature. - -First, I am using **gitolite 3**. The mirroring we are going to setup is not the -_supported_ [[http://sitaramc.github.com/gitolite/mirroring.html][mirroring *built-in*]]. We are going to implement a simplier way to set mirror -thing : - -1. Write a custom gitolite command ; the idea is to be able to write ~git-config~ stuff. -2. Write a hook that take a specific ~git-config~ (let say ~mirror.url~) and do a simple - mirroring. - -* Gitolite commands - -Gitolite 3 has been rewritten to be more flexible : [[http://sitaramc.github.com/gitolite/g3why.html][Why a completely new version]]. The -rewrite made it really easy to extend gitolite. +I've fork [[https://github.com/vdemeester/gitolite][gitolite]] on github+ I've -created a [[http://github.com/vdemeester/vdemeester-gitolite-local-code][repository git]] to easily add commands to my gitolite instance via _local -code_. The gitolite command I wrote is a quick and dirty script in shell to add ~git -config~. The source should speek for itself ; It _should_ include some way to check if the -given config is not already present in the ~gitolite-admin~ configuration file β€” and so -might be rewritten in ~Perl~. - -The command is ~write-git-config~ because a ~git-config~ command already exists -in the built-in commands. - -#+begin_src bash -#!/bin/sh - -# Usage: ssh git@host write-git-config <repo> <key> <value> -# -# Set git-config value for user-created ("wild") repo. - -die() { echo "$@" >&2; exit 1; } -usage() { perl -lne 'print substr($_, 2) if /^# Usage/../^$/' < $0; exit 1; } -[ -z "$1" ] && [ -z "$2" ] && [ -z "$3" ] && usage -[ "$1" = "-h" ] && usage -[ -z "$GL_USER" ] && die GL_USER not set - -# ---------------------------------------------------------------------- -repo=$1; shift -key=$1; shift -value=$1; shift - -# this shell script takes arguments that are completely under the user's -# control, so make sure you quote those suckers! - -if gitolite query-rc -q WRITER_CAN_UPDATE_DESC -then - gitolite access -q "$repo" $GL_USER W any || die You are not authorised -else - gitolite creator "$repo" $GL_USER || die You are not authorised -fi - -# if it passes, $repo is a valid repo name so it is known to contain only sane -# characters. This is because 'gitolite creator' return true only if there -# *is* a repo of that name and it has a gl-creator file that contains the same -# text as $GL_USER. - -configfile=`gitolite query-rc GL_REPO_BASE`/"$repo".git/config - -git config --file "$configfile" "$key" "$value" -#+end_src - -* Gitolite hooks - -The next step is to write a quick ~post-receive~ hook that check if there is a -certain ~git-config~ entry and run ~git push --mirror~. The file is in -~$HOME/.gitolite/hooks/common/post-receive~ ; you could add a better system to -hooks (to be able to add "dynamic" hooks, …). - -#+begin_src bash - -#!/bin/sh - -# Simple gitolite mirroring - -# flush STDIN coming from git, because gitolite's own post-receive.mirrorpush -# script does the same thing -[ -t 0 ] || cat >/dev/null - -[ -z "$GL_REPO" ] && die GL_REPO not set - -target=`git config --get mirror.url` -[ -z "$target" ] && exit 0 - -# Support a REPO variable for wildcard mirrors -gl_repo_escaped=$(echo $GL_REPO | sed 's/\//\\\//g') -target=$(echo $target | sed -e "s/REPO/$gl_repo_escaped/g") - -# Do the mirror push -git push --mirror $target -#+end_src - -The next, and final step is to run `gitolite compile` to update links to hooks -for every repositories. - -* For real - -And finaly, this is the final step you'll do. - -#+begin_src bash -$ ssh git@host write-git-config vincent/vcsh-home mirror.url git@github.com:vdemeester/vcsh-home.git -$ git push -Counting objects: 5, done. -Delta compression using up to 2 threads. -Compressing objects: 100% (3/3), done. -Writing objects: 100% (3/3), 294 bytes, done. -Total 3 (delta 2), reused 0 (delta 0) -remote: To git@github.com:vdemeester/vcsh-home.git -remote: 65681a8..701c990 master -> master -To git@host:vincent/vcsh-home.git - 65681a8..701c990 master -> master -#+end_src - - -And that should be it ! - -_Update 2012/10/04_ : Moved from gitolite fork to _gitolite local code_ -repository. diff --git a/www/vincent.demeester.fr/content/posts/2012-05-13-jekyll-foreman-guard-bundler.org b/www/vincent.demeester.fr/content/posts/2012-05-13-jekyll-foreman-guard-bundler.org @@ -1,90 +0,0 @@ -#+title: Jekyll Forman Guard Bundler -#+date: <2012-05-13 Sun> -#+filetags: jekyll ruby bundler guard foreman -#+setupfile: ../templates/post.org - -* Introduction - -This post is a quick "How did I setup my Jekyll environnement ?". We are -going all the tools that are quite awesome in Ruby. - -* Goal - :PROPERTIES: - :CUSTOM_ID: goal - :END: - -The goal is simple : - -1. I want to be able to install any dependent - [[http://rubygems.org][Gem]] with a /on-liner/ command -2. I want to be able to run a /Jekyll server/ that auto updates. - -We are going to play with : [[http://gembundler.com/][Bundler]], -[[https://github.com/guard/guard][Guard]] and -[[https://github.com/ddollar/foreman][foreman]]. - -* Bundler - :PROPERTIES: - :CUSTOM_ID: bundler - :END: - -Bundler let us run =bundle install= to get all Ruby Gems we will need ; -It use a file name =Gemfile=. The gems we need are simple : =jekyll=, -=guard= and some Guard extensions. - -#+begin_src ruby -source "http://rubygems.org" - -gem 'jekyll' -gem 'guard' -gem 'guard-jekyll2' -gem 'guard-shell' -gem 'guard-bundler' -#+end_src - -* Guard - :PROPERTIES: - :CUSTOM_ID: guard - :END: - -#+BEGIN_QUOTE - Guard is a command line tool to easily handle events on file system - modifications. -#+END_QUOTE - -Guard will be watching file we told him and run action in consequence ; -The file is name =Guardfile=. - -#+begin_src ruby -guard 'jekyll2' do - watch %r{.*} -end - -guard :bundler do - watch('Gemfile') -end -# vim:filetype=ruby -#+end_src - -* Foreman - :PROPERTIES: - :CUSTOM_ID: foreman - :END: - -Finally, foreman will let us declare our processes and will handle the -start, forward the output and handle the shutdown. It can then export -its configuration into more /production-ready/ file (=init=, =upstard=, -...) ; It uses a file named =Procfile=. - -We will tell foreman to run : - -- The jekyll build-in server : =jekyll --server= -- Guard, to handle file changes /in background/. - -#+begin_src bash -web: bundle exec jekyll --server -guard: bundle exec guard -#+end_src - -And that's all folk. Now, you just need to run foreman in the -Jekyll-powered directory and edit your files. diff --git a/www/vincent.demeester.fr/content/posts/2012-07-23-maven-release-gitflow.org b/www/vincent.demeester.fr/content/posts/2012-07-23-maven-release-gitflow.org @@ -1,99 +0,0 @@ -#+title: Maven Release Gitflow -#+date: <2012-07-23 Mon> -#+filetags: maven java git gitflow release -#+setupfile: ../templates/post.org - -* Introduction - -I like a lot the [[http://nvie.com/posts/a-successful-git-branching-model/][gitflow]] way of managing project. When working on maven project, there is -few great plugins that helps to get the work done. One of them is [[http://maven.apache.org/plugins/maven-release-plugin][maven-release-plugin]]. - -Inspired on this [[https://gist.github.com/1043970][gist]], I've come -with a cool way of doing things (let say we want to release a 0.1 -version of an artifact) : - -* Prepare the pom.xml. - :PROPERTIES: - :CUSTOM_ID: prepare-the-pom.xml. - :END: - -It needs =<scm>= entries, =<distributionManagement>= entries (to know -where to deploy the release artifact) and few options for the -maven-release-plugin : - -{{< highlight xml >}} -#+begin_src xml -<project> - - <!-- […] --> - <build> - <plugins> - <!-- […] --> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-release-plugin</artifactId> - <version>2.3.2</version> - <configuration> - <tagNameFormat>v@{project.version}</tagNameFormat> - <pushChanges>false</pushChanges> - <localCheckout>true</localCheckout> - </configuration> - </plugin> - <!-- […] --> - </plugins> - </build> - <!-- […] --> - -</project> -#+end_src - -Few explanation here : - -- =tagNameFormat= is here to change the default tag name (which is - =${project.artifactId}-${project.version}=) to a better one. -- =pushChanges= set to =false= tells maven-release-plugin not to push - changes (this will become useful) -- =localCheckout= set to =true= tells maven-release-plugin to clone from - local repository (not distant). This is especially useful here because - we didn't push anything (so not setting this option would result in a - failure). - -* The real stuff - :PROPERTIES: - :CUSTOM_ID: the-real-stuff - :END: - -First create a release branch from develop. - -#+begin_src bash -$ git checkout -b release/v0.1 develop -#+end_src - -Then run the maven release stuff. - -#+begin_src bash -$ mvn release:prepare # change the pom, commit and tag version, and - # re-change pom (by incrementing SNAPSHOT version) -$ mvn release:perform # get the tagged version, compile and deploy -#+end_src - -And the real fun begins. - -#+begin_src bash -$ git checkout develop # get back to the develop branch -$ git merge --no-ff release/v0.1 # merge the version back into develop -$ git checkout master # go to the master branch -$ git merge --no-ff release/v0.1~1 # merge the version back into master but - # the tagged version instead of the release/v0.1 HEAD -$ git branch -D release/v0.1 # Removing the release branch -$ git push --all && git push --tags # Finally push everything -#+end_src - -The real magic here is the =git merge --no-ff release/v0.1~1= which will -merge into master the commit before the HEAD of the branch -=release/v0.1=. - -The next step would be to create a helper script that automates this and -verify that the =pom.xml= has the right configuration options. - -*Edit 17:58* : You can take a look [[https://github.com/vdemeester/java-config/blob/master/bin/mvn-release-flow][here]] diff --git a/www/vincent.demeester.fr/content/posts/2012-12-16-gollum-comme-wiki-personnel.org b/www/vincent.demeester.fr/content/posts/2012-12-16-gollum-comme-wiki-personnel.org @@ -1,84 +0,0 @@ -#+title: Gollum comme Wiki personnel -#+date: <2012-12-16 Sun> -#+filetags: wiki golum github personnel -#+setupfile: ../templates/post.org - -* Introduction - -Il y a environ 4 mois j'ai eu un accident de vΓ©lo ; un traumatisme -crΓ’nien, des brulures sur la face, quelques points de sutures, un doigt -cassΓ© et une hernie discale m'ont clouΓ© (et me clou encore) plus que -d'habitude sur ma chaise de bureau. Le bon cΓ΄tΓ© des choses, c'est que -cela m'a permit de me poser et de rΓ©flΓ©chir une bonne faΓ§on d'Γͺtre -efficace et organiser, au travail et Γ  la maison :-). - -Une des principales /action/ que j'ai pris est d'utiliser un wiki local -et synchronisΓ© sur /tout/ mes PCs. Le /format/ wiki est assez adaptΓ© Γ  -une prise de note et Γ  la crΓ©ation de contenu plus complet (comme des -[[http://shortbrain.org][articles]] ou de la documentation pour des -projets en cours). Les conditions Γ©taient les suivantes : - -- FacilitΓ© de mise en place. -- Pas de base de donnΓ©es. -- /Merging/ facile ([[http://git-scm.com][git]] /rules my world/). -- [[http://daringfireball.net/projects/markdown/][Markdown]] comme - syntaxe, car utilisΓ© Γ  peu prΓ¨s partout (blogs, articles, READMEs, - documentations). -- Γ‰ditable Γ  partir d'une interface web ou de mon Γ©diteur favoris. - -L'outil qui remplit presque toutes ces conditions s'appelle -[[https://github.com/github/gollum][gollum]]. C'est un moteur wiki, -Γ©cris en ruby, qui se base sur un repository -[[http://git-scm.com][git]]. Il est dΓ©velopper par l'Γ©quipe de -[[http://github.com][Github]] et c'est celui qui est utilisΓ© par les -pages wiki lΓ -bas. Il permet d'utiliser Γ  peu prΓ¨s n'importe quel -syntaxe (dont -[[https://github.com/github/github-flavored-markdown][github-markdown]] -qui est assez proche de celle de -[[http://johnmacfarlane.net/pandoc][pandoc]]). Par ailleurs, comme il se -base sur [[http://git-scm.com][git]], les points /"pas de base de -donnΓ©es"/, /"merging facile"/ et /"Γ©ditable Γ©galement Γ  partir de mon -Γ©diteur favoris"/ sont toutes remplies. - -Avec [[https://github.com/github/gollum][Gollum]] vous avez un wiki -markdown dΓ©centralisΓ©, Γ©ditable via une interface web ou via votre -Γ©diteur favoris. - -* Mise en place - :PROPERTIES: - :CUSTOM_ID: mise-en-place - :END: - -La mise en place est relativement simple ; aprΓ¨s tout dΓ©pend du besoin -que vous avez. L'installation se fait par [[file:rubygems.org][RubyGem]] -ou en clonant le repository. - -{{< highlight bash >}} # Installation de gollum et du format markdown de -github $ gem install gollum gitub-markdown {{< /highlight >}} - -Si vous n'utilisez pas [[https://github.com/sstephenson/rbenv][rbenv]] -ou [[https://rvm.io/][rvm]] il est probable qu'il faille lancer la -commande en root ou utiliser sudo. - -Ensuite, il suffit de lancer -[[https://github.com/github/gollum][Gollum]] dans un dossier qui est un -repository git ; le tour est jouΓ© - -{{< highlight bash >}} # J'ulitise ~/desktop/wiki pour mon wiki $ cd -~/desktop/wiki && gollum {{< /highlight >}} - -L'idΓ©e finale est d'automatiser deux choses : - -1. Le dΓ©marrage de gollum -2. La synchronisation du repository avec les diffΓ©rents autres /remotes/ - -Suivant le systΓ¨me d'exploitation et/ou la distribution utilisΓ©es, il y -a Γ©normΓ©ment de possibilitΓ© d'effectuer cette automatisation. Dans mon -cas, j'ai une [[http://debian.org][Debian]] assez light, avec surtout -plein de scripts. Je dΓ©marre donc -[[https://github.com/github/gollum][Gollum]] au dΓ©marrage de ma session -grΓ’ce Γ  une script qui est lancΓ© dans la foulΓ©e du gestionnaire de -fenΓͺtre. La synchronisation se fait grΓ’ce Γ  une tΓ’che planifiΓ©e /cron/ -qui est "distribuΓ©" sur chacune de mes machines. - -/C'est tout pour le moment/ ;-). diff --git a/www/vincent.demeester.fr/content/posts/2013-09-08-maven-tmpfs.org b/www/vincent.demeester.fr/content/posts/2013-09-08-maven-tmpfs.org @@ -1,158 +0,0 @@ -#+title: Maven Tmpfs -#+date: <2013-09-08 Sun> -#+filetags: maven tmpfs ssd -#+setupfile: ../templates/post.org - -* Introduction - -Je suis un utilisateur convaincu de [[http://maven.apache.org/][maven]], malgrΓ© ces dΓ©fauts, le -moto *"Convention over configuration"* me va vraiment bien. Que ce soit -au boulot ou Γ  la maison, j'ai plus d'ordinateurs Γ©quipΓ©s de ssd (ou de -mΓ©moire flash) que de disque traditionnel (mΓ©canique ?). Pour augmenter -un peu la durΓ©e de vie de ces disques SSD, j'ai cherchΓ© Γ  savoir comment -/dΓ©porter/ le /build/ de maven (qui, pour rappel, se passe dans le -dossier =target/=) hors du SSD ; ici ce sera dans le dossier =/tmp/= qui -est montΓ© en mΓ©moire (merci =tmpfs=), mais on peut imaginer dΓ©porter Γ§a -sur un autre disque, etc.. AprΓ¨s quelques recherches j'ai trouvΓ©s -quelques inspirations. - -#+BEGIN_QUOTE - *Limitations* - - Dans la solution prΓ©sentΓ©e ci-dessous les principales limitations sont - les suivantes (que j'essaierais de diminuer au fil du temp ;P) : - - 1. Il est nΓ©cessaire de modifier le pom.xml du projet ; cela ne - s'appliquera donc pas Γ  tous les projets maven sans modification du - pom.xml. - 2. Cela ne fonctionne que sur une plateforme qui support les liens - symboliques (Linux, Mac OS X, et autre UNIX). - 3. Cela ne fonctionne qu'avec Java 7 ou plus. - 4. Si vous utilisez m2e, il va gentillement gueuler et c'est moche ; pour rΓ©soudre le - problΓ¨me, il faut faire un tour vers [[http://wiki.eclipse.org/M2E_plugin_execution_not_covered][M2E plugin execution not covered]]. -#+END_QUOTE - -Pour [[http://maven.apache.org/][maven]], le dossier =target/= vient de la propriΓ©tΓ© -=project.build.directory=. Dans la thΓ©orie, il suffirait de modifier -(dans =$HOME/.m2/settings.xml=) cette propriΓ©tΓ© et le tour serait jouer. -Malheuresement ce n'est pas possible, =project.build.directory= est une -propriΓ©tΓ© interne et n'est, Γ  priori, pas modifiable. - -Notre souhait est le suivant : - -1. Le build doit se faire dans =/tmp/m2/=, ce qui pour un projet se - traduit par =/tmp/m2/${groupId}:${artifactId}=. -2. Le dossier =target/= dans les sources est un lien symbolique vers le - dossier dans =/tmp/m2/= -3. On passe par un *profile* qui n'est *pas actif* par dΓ©faut (pour ne - pas faire chier le monde) mais *activable via une propriΓ©tΓ©* (maven - nous permet de le faire et c'est cool =^_^=). La propriΓ©tΓ© utilisΓ©e - sera =external.build.root=. - -Le code ci-dessous est repris directement de mon inspiration[fn:1]. Il -s'occupe de crΓ©er le dossier =${groupId}:${artifactId}= dans -=external.build.root= et de faire le lien dans le dossier courant. - -#+begin_src xml -<project> - <!-- […] --> - <profiles> - <profile> - <id>external-build-dir</id> - <activation> - <activeByDefault>false</activeByDefault> - <property> - <name>external.build.root</name> - </property> - </activation> - <build> - <plugins> - <plugin> - <groupId>com.alexecollins.maven.plugin</groupId> - <artifactId>script-maven-plugin</artifactId> - <version>1.0.0</version> - <executions> - <execution> - <id>prep-work-tree</id> - <goals> - <goal>execute</goal> - </goals> - <phase>initialize</phase> - <configuration> - <script> - import java.nio.file.* - def dir = - "${external.build.root}/${project.groupId}:${project.artifactId}" - println "using Maven dir ${dir}" - def dirPath = Paths.get(dir) - if (!Files.exists(dirPath)) { - Files.createDirectories(dirPath) - } - def target = Paths.get("${project.build.directory}") - if (!Files.exists(target)) { - Files.createSymbolicLink(target, dirPath) - }</script> - </configuration> - </execution> - <execution> - <id>drop-symlink</id> - <goals> - <goal>execute</goal> - </goals> - <phase>clean</phase> - <configuration> - <script> - import java.nio.file.* - def target = Paths.get("${project.build.directory}") - if (Files.isSymbolicLink(target)) { - Files.delete(target) - } - </script> - </configuration> - </execution> - </executions> - <dependencies> - <dependency> - <groupId>org.codehaus.groovy</groupId> - <artifactId>groovy</artifactId> - <version>1.8.6</version> - </dependency> - </dependencies> - <configuration> - <language>groovy</language> - </configuration> - </plugin> - </plugins> - </build> - </profile> - </profiles> - <!-- […] --> -</project> -#+end_src - -Ainsi, il suffit ensuite d'avoir quelques choses du genre dans son -=$HOME/.m2/settings.xml= pour que les builds qui ont ce profil se -/build/ dans =/tmp/m2/=. On peut aussi ne rien avoir dans -=$HOME/.m2/settings.xml= et utilise =-Dexternal.build.root=/tmp/m2/= -avec la commande =mvn=. - -#+begin_src xml -<settings> - <!-- […] --> - <profiles> - <profile> - <id>build-in-ramfs</id> - <properties> - <external.build.root>/tmp/m2/</external.build.root> - </properties> - </profile> - </profiles> - <activeProfiles> - <activeProfile>build-in-ramfs</activeProfile> - </activeProfiles> - <!-- […] --> -</settings> -#+end_src - - -[fn:1] [[http://elehack.net/writings/programming/maven-target-in-tmpfs][PuttingMaven build directories out-of-tree]] par [[http://elehack.net/][Michal Ekstrand]] diff --git a/www/vincent.demeester.fr/content/posts/2013-10-12-podcasts.org b/www/vincent.demeester.fr/content/posts/2013-10-12-podcasts.org @@ -1,91 +0,0 @@ -#+TITLE: Podcasts -#+date: <2013-10-12 Sat> -#+filetags: music podcast -#+setupfile: ../templates/post.org - -* Introduction - -#+BEGIN_QUOTE - Voici un petit billet prΓ©sentant les diffΓ©rents podcast que j'Γ©coute - plus ou moins rΓ©guliΓ¨rement. -#+END_QUOTE - -J'Γ©coute Γ©normΓ©ment de musique et de podcast ; je passe beaucoup de -temps avec des Γ©couteurs sur la tΓͺte ou la chaine hifi en route. Les -podcasts ont une grande place. Voici une liste plus ou moins bien triΓ©s -de ceux auxquels je suis souscrit et/ou que j'Γ©coute en ce moment. Je -tiendrais peut-Γͺtre ce post Γ  jour ou en crΓ©erait un nouveau sinon :-). - -* Geek & co - :PROPERTIES: - :CUSTOM_ID: geek-co - :END: - -Je suis un developpeur, un geek et convaincu des logiciels libres, les -podcasts qui suivent reflΓ¨te assez cette partie lΓ  de mon identitΓ©. - -- [[http://www.captainweb.net/][L'apΓ©ro de Captain (fr)]] : dans le - genre geeky, dΓ©jantΓ© et sauvage, en franΓ§ais, on ne fait pas mieux. - Pas vraiment safe for work, et pas tout Γ  fait "libriste" comme - j'aime, les tranches de rires sont garanties ; mΓͺme si parfois on est - un peu verreux d'avoir Γ©couter jusqu'Γ  la fin et le bien nommΓ© - "wazzuf". -- [[http://www.agencetousgeeks.com/][Agence Tous Geeks (fr)]] : fils - cachΓ© de l'apΓ©ro du captain, on y retrouve des amis (et membres de ce - dernier), mais c'est un peu plus calme. -- [[http://bazingcast.com/about/][Bazingcast (fr)]] : podcast geek, plus - posΓ© que les deux prΓ©cΓ©dents mais avec des dΓ©bats, des trolls et tout - ce que l'on peut attendre de geeks. -- [[http://www.captainposix.net/][Parole de Tux (fr-be)]] : podcast - venant de nos voisin belge, pas trop long et plutΓ΄t sympa ; et si - comme moi vous adorez l'accent belge, c'est le top. -- [[http://faif.us/][Free as in Freedom (en)]] : podcast parlant de - logiciel libre principalement cΓ΄tΓ© license, truc lΓ©gal, etc.. en - anglais, faut parfois s'accrocher. -- [[http://episodes.gitminutes.com/][Git Minutes (en)]] : podcast Γ  - propos de Git et des outils de son Γ©cosystΓ¨me (vcsh, etc..). - -* Radio - :PROPERTIES: - :CUSTOM_ID: radio - :END: - -J'aime bien la radio, bien plus que la tΓ©lΓ©vision (que j'allume si peu -qu'Γ  chaque fois la box se met Γ  jour =;-p=). - -- [[http://www.franceinter.fr/emission-laura-leishman-project][France - Inter - LLP (Laura leishman Project)]] -- [[http://radiofrance-podcast.net/podcast09/rss_12265.xml][Le Mouv' - - Laura Leishman Project]] -- [[http://www.franceinter.fr/emission-interception][France Inter - - Interception]] -- [[http://www.la-bas.org/][France Inter - lΓ -bas si j'y suis]] -- [[http://www.franceculture.fr/podcast/4689840][France Culture - - Pixel]] -- [[http://www.franceculture.fr/podcast/4685228][France Culture - Place - de la toile]] -- [[http://www.franceculture.fr/podcast/4689418][France Culture - - Philippe Meyer]] -- [[http://radiofrance-podcast.net/podcast09/rss_12582.xml][Le Mouv' - - Glitch (sur le Mouv')]] -- [[http://radiofrance-podcast.net/podcast09/rss_12691.xml][Le Mouv' - - Code Source]] -- [[http://radiofrance-podcast.net/podcast09/rss_12190.xml][Le Mouv' - - Suivez le geek]] -- [[http://www.divergence-fm.org/-http-www-divergence-fm-org-ecrire-exec-rubrique-id_rubrique-61-.html][Divergence - Numerique]] - -* Musique - :PROPERTIES: - :CUSTOM_ID: musique - :END: - -Les podcast suivant sont purement musique, tous musique techno ou trance -(que j'aime bien). - -- [[http://podcasts.flaix.fr/corstencountdown][Ferry Corsten - Corsten - Countdown]] -- [[http://www.galexmusic.com/podcast/gareth.xml][Gareth Emery Podcast]] -- [[http://feedproxy.feedburner.com/Tiestos_club_life][Tiesto Club - Life]] -- [[http://oakenfold.libsyn.com/rss][Oakenfold Perfecto Podcast]] diff --git a/www/vincent.demeester.fr/content/posts/2014-03-24-redesign-et-rΓ©solutions.org b/www/vincent.demeester.fr/content/posts/2014-03-24-redesign-et-rΓ©solutions.org @@ -1,76 +0,0 @@ -#+title: Redesign et RΓ©solutions -#+date: <2014-03-24 Mon> -#+filetags: jekyll design images redesign -#+setupfile: ../templates/post.org - -* Introduction - -Un /tout petit/ post pour parler rapidement, entre autre, du redesign de -[[http://vincent.demeester.fr][vincent.demeester.fr]] et de mes -rΓ©solutions. - -* Redesign - :PROPERTIES: - :CUSTOM_ID: redesign - :END: - -Les raisons de ce redesign sont assez simple : je change d'employeur et -de ville (retour sur Paris =\o/=). Cela fait pas mal de changement, et -en voulant mettre Γ  jour la page d'accueil (ce que je n'ai toujours pas -fait =;-P=), j'avais envie de /dΓ©mΓ©nager/ le site en quelques sortes. - -Pour l'inspiration c'est assez facile Γ  trouver, regarder -[[http://medium.com][medium]], le site de -[[http://www.viksit.com/][Viksit Gaur]] ou encore celui de -[[http://silent-strength.com/][Michael]] (coucou =:-P=). J'adore ce -genre de site, assez Γ©purΓ© mais avec une partie fixe (Γ  gauche ou Γ  -droite) et avec des images changeantes. - -Les images en fond de l'espace de gauche peuvent changer d'une page Γ  -l'autre. Je suis en train de m'amuser un peu avec -[[http://jekyllrb.com][Jekyll]]. Il faut que je trouve un moyen -d'optimiser un peu les images que j'utilise parce que lΓ  je joue un peu -le bourrin. - -* RΓ©solutions - :PROPERTIES: - :CUSTOM_ID: rΓ©solutions - :END: - -Nouvelle annΓ©e, nouveau boulot, implique nouvelles rΓ©solutions. Il y a -deux aspects Γ  ces /rΓ©solutions/ : sur un plan -informatique/geek/travail/organisation, et sur un plan purement -/physique/. - -Mon retour sur Paris devrait me permettre de participer Γ  un peu plus -d'Γ©vΓ¨nements, notamment du cΓ΄tΓ© des Java User Group, mais aussi FSFE, et -j'en passe. Je compte Γ©galement continuΓ© Γ  jouer le /factotum/ en me -gardant un peu de temps au niveau personnel pour travailler sur des -aspects /geek/ que je n'aurais peut-Γͺtre pas l'occasion de pratiquer au -travail. Au niveau de mes /points d'entrΓ©es/ sur le web, je compte -remettre un peu en route la partie blog de ce site et tourner -shortbrain.org (ou autre) en un site plus "documentation" en me basant -sur mes notes (powered by [[http://org-mode.org][org-mode]]). Je me suis -Γ©galement remis Γ  [[https://www.gnu.org/software/emacs/][Gnu Emacs]] et -/oh god/ qu'est-ce que c'est bon =:-D=. - -Au niveau phyisque, dans la continuitΓ© de la fin 2012, l'annΓ©e 2013 a -probablement Γ©tΓ© la pire de ma vie. Les problΓ¨mes de dos c'est pas -facile tous les jours.. En fΓ©vrier, j'avais peur de ne jamais remonter -sur un vΓ©lo ; en Mai je remontais pour la premiΓ¨re fois sur le vΓ©lo, en -Septembre je faisais 40km par semaine et en dΓ©cembre j'Γ©tais presque -capable de faire 30km en une journΓ©e. MΓ¨me si l'annΓ©e 2014 a mal -commencΓ© (je me suis felΓ© un cΓ΄te en Janvier), mes objectifs pour cette -annΓ©e et les futurs sont nettement plus positif. Le -[[http://fitbit.com][fitbit]] que j'ai acquis en FΓ©vrier me permet -d'avoir un objectif de marche (certes assez modeste) de 10000 pas et -8,05 km par jour. Je compte bien me fixer d'autre objectifs : monter la -barre plus haut (15000 voir plus), faire 50km de vΓ©lo sur une journΓ©e. - -Les objectifs physiques Γ  trΓ¨s long termes sont Γ©galement assez simples -: retrouver ma forme physique et ne plus Γͺtre gΓ©nΓ© Γ  cause du dos. En -gros c'est : Γͺtre capable de faire 120km de vΓ©lo et monter des cols (Col -du Sapenay, Mont Revard) et Γ  long terme, faire des courses de footing -(i.e.Β 10km, Paris-Versaille, Semi-marathon, marathon :D). - -Sur ce, je vais retourner Γ  mes cartons :-P. diff --git a/www/vincent.demeester.fr/content/posts/2018-07-28-gotest-tools-intro.org b/www/vincent.demeester.fr/content/posts/2018-07-28-gotest-tools-intro.org @@ -1,45 +0,0 @@ -#+TITLE: Golang testing β€” gotest.tools introduction -#+date: <2018-07-28 Sat> -#+filetags: feature go testing -#+setupfile: ../templates/post.org - -* Introduction - -I already wrote 2 previous posts about golang and testing. It's something I care deeply -about and I wanted to continue writing about it. It took me a bit more time than I -thought, but getting back to it. Since the [[http://vincent.demeester.fr/posts/2017-04-22-golang-testing-golden-file/][last post]], Daniel Nephin and I worked (but -mainly Daniel πŸ€—) on bootstrapping a testing helper library. - -Let me introduce it to you this library : [[https://gotest.tools][=gotest.tools=]]. As described in the [[https://godoc.org/gotest.tools][godoc]] package comment, =gotest.tools= is a -collection of packages to augment =testing= and support common patterns. It's an enhanced and growing version of the -initial helpers we (the docker/moby maintainers) wrote initially in [[https://github.com/docker/docker][=docker/docker=]] repository. We are using in quite some -project here at [[https://github.com][Docker]]. - -There is a bunch of packages that will all have their own post (linked here when available) : - -- [[file:2018-08-16-gotest-tools-assertions.org][=assert=]] (with =assert/cmp= and =assert/opt=) that provides assertions for comparing expected values to actual values. -- =env= that provides functions to test code that read environment variable or the current working directory. -- [[file:2018-09-14-gotest-tools-fs.org][=fs=]] that provides tools for creating temporary files, and testing the contents and structure of a directory. -- [[file:2018-09-06-gotest-tools-golden.org][=golden=]] that provides tools for comparing large multi-line strings. -- [[file:2018-09-18-gotest-tools-icmd.org][=icmd=]] that executes binaries and provides convenient assertions for testing the results. -- [[file:2019-03-23-gotest-tools-poll.org][=poll=]] that provides tools for testing asynchronous code. -- [[file:2018-09-01-gotest-tools-skip.org][=skip=]] that provides functions for skipping a test and printing the source code of the condition used to skip the test. - -There is also experimental package, using the =x= notation (as the golang team uses, for example with =golang.org/x/sync=) : - -- =x/subtest= that provides a =TestContext= to subtests which handles cleanup and provides a =testing.TB= and =context.Context=. - -There is already some good =testing= helpers in the Go ecosystem : [[https://github.com/stretchr/testify][=testify=]], [[http://labix.org/gocheck][=gocheck=]], [[https://github.com/onsi/ginkgo][=ginkgo=]] and a lot more β€” so -why create a new one ? There is multiple reason for it, most of them can be seen in the following [[https://github.com/gotestyourself/gotest.tools/issues/49#issuecomment-362436026][GitHub issue]]. - -[[https://github.com/dnephin/][Daniel]] also wrote a very useful converter if your code base is currently using =testify= : =gty-migrate-from-testify=. - -#+BEGIN_SRC sh -$ go get -u gotest.tools/assert/cmd/gty-migrate-from-testify -# […] -$ go list \ - -f '{{.ImportPath}} {{if .XTestGoFiles}}{{"\n"}}{{.ImportPath}}_test{{end}}' \ - ./... | xargs gty-migrate-from-testify -#+END_SRC - -In the next post, let's dig into the assertion part of the library, package =assert= πŸ‘Ό. diff --git a/www/vincent.demeester.fr/content/posts/2018-08-16-gotest-tools-assertions.org b/www/vincent.demeester.fr/content/posts/2018-08-16-gotest-tools-assertions.org @@ -1,340 +0,0 @@ -#+title: Golang testing β€” gotest.tools assertions -#+date: <2018-08-16 Thu> -#+filetags: go testing assert -#+setupfile: ../templates/post.org - -#+TOC: headlines 2 - -* Introduction - -Let's take a closer look at [[https://gotest.tools][=gotest.tools=]] assertions packages. This is mainly about =assert=, =assert/cmp= and -=assert/opt=. - -#+BEGIN_QUOTE -Package assert provides assertions for comparing expected values to actual values. When assertion fails a helpful error -message is printed. -#+END_QUOTE - -There is two main functions (=Assert= and =Check=) and some helpers (like =NilError=, …). They all take a =*testing.T= as -a first argument, pretty common across testing Go libraries. Let's dive into those ! - -* =Assert= and =Check= - -Both those functions accept a =Comparison= (we'll check what it is later on) and fail the test when that comparison -fails. The one difference is that =Assert= will end the test execution at immediately whereas =Check= will fail the test -and proceed with the rest of the test case. This is similar to =FailNow= and =Fail= from the standard library -=testing=. Both have their use cases. - -We'll Use =Assert= for the rest of the section but any example here would work with =Check= too. When we said -=Comparison= above, it's mainly the [[https://godoc.org/gotest.tools/assert#BoolOrComparison][BoolOrComparison]] interface β€” it can either be a boolean expression, or a -[[https://godoc.org/gotest.tools/assert/cmp#Comparison][cmp.Comparison]] type. =Assert= and =Check= code will be /smart/ enough to detect which one it is. - -#+BEGIN_SRC go - assert.Assert(t, ok) - assert.Assert(t, err != nil) - assert.Assert(t, foo.IsBar()) -#+END_SRC - -So far not anything extra-ordinary. Let's first look at some more /helper/ functions in the =assert= package and quickly -dive a bit deeper with =Comparison=. - -* More =assert= helpers - -The additional helper functions are the following - -- =Equal= uses the ==== operator to assert two values are equal. -- =DeepEqual= uses =google/go-cmp= to assert two values are equal (it's /close/ to =reflect.DeepEqual= but not - quite). We'll detail a bit more the /options/ part of this function with =cmp.DeepEqual=. -- =Error= fails if the error is =nil= *or* the error message is not the expected one. -- =ErrorContains= fails if the error is =nil= *or* the error message does not contain the expected substring. -- =ErrorType= fails if the error is =nil= *or* the error type is not the expected type. -- =NilError= fails if the error is not =nil=. - -All those helper functions have a equivalent function in the =cmp= package that returns a =Comparison=. I, personally, -prefer to use =assert.Check= or =assert.Assert= in combination with =cmp.Comparison= as it allows me to write all my -assertions the same way, with built-ins comparison or with my own β€” i.e. =assert.Assert(t, is.Equal(…), "message"= or -=assert.Assert(t, stackIsUp(c, time…), "another message")=. - -* =cmp.Comparison= - -This is where it get really interesting, =gotest.tools= tries to make it as easy as possible for you to create -appropriate comparison β€” making you test readable as much as possible. - -Let's look a bit at the =cmp.Comparison= type. - -#+BEGIN_SRC go -type Comparison func() Result -#+END_SRC - -It's just a function that returns a =cmp.Result=, so let's look at =cmp.Result= definition. - -#+BEGIN_SRC go -type Result interface { - Success() bool -} -#+END_SRC - -Result is an =interface=, thus any /struct/ that provide a function =Success= that returns a =bool= can be used as a -comparison result, making it really easy to use in your code. There is also existing type of result to make it even -quicker to write your own comparison. - -- =ResultSuccess= is a constant which is returned to indicate success. -- =ResultFailure= and =ResultFailureTemplate= return a failed Result with a failure message. -- =ResultFromError= returns =ResultSuccess= if =err= is nil. Otherwise =ResultFailure= is returned with the error - message as the failure message. It works a bit like the =errors.Wrap= function of the [[https://github.com/pkg/errors][=github.com/pkgs/errors=]] - package. - -The =cmp= package comes with a few defined comparison that, we think, should cover a high number of use-cases. Let's -look at them. - -** Equality with =Equal= and =DeepEqual= - -#+BEGIN_QUOTE -Equal uses the == operator to assert two values are equal and fails the test if they are not equal. - -If the comparison fails Equal will use the variable names for x and y as part of the failure message to identify the -actual and expected values. - -If either x or y are a multi-line string the failure message will include a unified diff of the two values. If the -values only differ by whitespace the unified diff will be augmented by replacing whitespace characters with visible -characters to identify the whitespace difference. -#+END_QUOTE - -On the other hand… - -#+BEGIN_QUOTE -DeepEqual uses google/go-cmp (http://bit.do/go-cmp) to assert two values are equal and fails the test if they are not -equal. - -Package https://godoc.org/gotest.tools/assert/opt provides some additional commonly used Options. -#+END_QUOTE - -Using one or the other is as simple as : if you wrote your =if= with ==== then use =Equal=, otherwise use =DeepEqual=. -=DeepEqual= (and usually =reflect.DeepEqual=) is used when you want to compare anything more complex than primitive -types. One advantage of using =cmp.DeepEqual= over =reflect.DeepEqual= (in an if), is that you get a well crafted -message that shows the diff between the expected and the actual structs compared – and you can pass options to it. - -#+BEGIN_SRC go -assert.Assert(t, cmp.DeepEqual([]string{"a", "b"}, []string{"b", "a"})) -// Will print something like -// --- result -// +++ exp -// {[]string}[0]: -// -: "a" -// +: "b" -// {[]string}[1]: -// -: "b" -// +: "a" -foo := &someType(a: "with", b: "value") -bar := &someType(a: "with", b: "value") -// the following will succeed as foo and bar are _DeepEqual_ -assert.Assert(t, cmp.DeepEqual(foo, bar)) -#+END_SRC - -When using =DeepEqual=, you may end up with really weird behavior(s). You may want to ignore some fields, or consider -=nil= slice or map the same as empty ones ; or more common, your /struct/ contains some unexported fields that you -cannot use when comparing (as they are not exported πŸ˜“). In those case, you can use =go-cmp= options. - -Some existing one are : -- [[https://godoc.org/github.com/google/go-cmp/cmp/cmpopts#EquateEmpty][=EquateEmpty=]] returns a Comparer option that determines all maps and slices with a length of zero to be equal, - regardless of whether they are nil. -- [[https://godoc.org/github.com/google/go-cmp/cmp/cmpopts#IgnoreFields][=IgnoreFields=]] returns an Option that ignores exported fields of the given names on a single struct type. The struct - type is specified by passing in a value of that type. -- [[https://godoc.org/github.com/google/go-cmp/cmp/cmpopts#IgnoreUnexported][=IgnoreUnexported=]] returns an Option that only ignores the immediate unexported fields of a struct, including anonymous - fields of unexported types. -- [[https://godoc.org/github.com/google/go-cmp/cmp/cmpopts#SortSlices][=SortSlices=]] returns a Transformer option that sorts all =[]V= -- … and [[https://godoc.org/github.com/google/go-cmp/cmp/cmpopts][more]] πŸ‘Ό - -=gotest.tools= also defines some *and* you can define yours ! As an example, =gotest.tools= defines =TimeWithThreshold= -and =DurationWithThreshold= that allows to not fails if the time (or duration) is not exactly the same but in the -specified threshold we specified. Here is the code for =DurationWithThreshold= for inspiration. - -#+BEGIN_SRC go -// DurationWithThreshold returns a gocmp.Comparer for comparing time.Duration. The -// Comparer returns true if the difference between the two Duration values is -// within the threshold and neither value is zero. -func DurationWithThreshold(threshold time.Duration) gocmp.Option { - return gocmp.Comparer(cmpDuration(threshold)) -} - -func cmpDuration(threshold time.Duration) func(x, y time.Duration) bool { - return func(x, y time.Duration) bool { - if x == 0 || y == 0 { - return false - } - delta := x - y - return delta <= threshold && delta >= -threshold - } -} -#+END_SRC - -Another good example for those options is when you want to skip some field. In [[https://github.com/docker/docker][=docker/docker=]] we want to be able to -easily check for equality between two service specs, but those might have different =CreatedAt= and =UpdatedAt= values -that we usually don't care about – what we want is to make sure it happens in the past 20 seconds. You can easily define -an option for that. - -#+BEGIN_SRC go - func cmpServiceOpts() cmp.Option { - const threshold = 20 * time.Second - - // Apply withinThreshold only for the following fields - metaTimeFields := func(path cmp.Path)bool { - switch path.String() { - case "Meta.CreatedAt", "Meta.UpdatedAt": - return true - } - return false - } - // have a 20s threshold for the time value that will be passed - withinThreshold := cmp.Comparer(func(x, y time.Time) bool { - delta := x.Sub(y) - return delta < threshold && delta > -threshold - }) - - return cmp.FilterPath(metaTimeFields, withinThreshold) - } -#+END_SRC - -I recommend you look at the [[https://godoc.org/gotest.tools/assert/opt][gotest.tools/assert/opt]] documentation to see which one are defined and how to use them. - -** Errors with =Error=, =ErrorContains= and =ErrorType= - -Checking for errors is *very common* in Go, having =Comparison= function for it was a requirement. - -- =Error= fails if the error is =nil= *or* the error message is not the expected one. -- =ErrorContains= fails if the error is =nil= *or* the error message does not contain the expected substring. -- =ErrorType= fails if the error is =nil= *or* the error type is not the expected type. - -Let's first look at the most used : =Error= and =ErrorContains=. - -#+BEGIN_SRC go - var err error - // will fail with : expected an error, got nil - assert.Check(t, cmp.Error(err, "message in a bottle")) - err = errors.Wrap(errors.New("other"), "wrapped") - // will fail with : expected error "other", got "wrapped: other" - assert.Check(t, cmp.Error(err, "other")) - // will succeed - assert.Check(t, cmp.ErrorContains(err, "other")) -#+END_SRC - -As you can see =ErrorContains= is especially useful when working with /wrapped/ errors. -Now let's look at =ErrorType=. - -#+BEGIN_SRC go - var err error - // will fail with : error is nil, not StubError - assert.Check(t, cmp.ErrorType(err, StubError{})) - - err := StubError{"foo"} - // will succeed - assert.Check(t, cmp.ErrorType(err, StubError{})) - - // Note that it also work with a function returning an error - func foo() error {} - assert.Check(t, cmp.ErrorType(foo, StubError{})) -#+END_SRC - -** Bonus with =Panics= - -Sometimes, a code is supposed to /panic/, see [[https://golang.org/doc/effective_go.html#panic][Effective Go (#Panic)]] for more information. And thus, you may want to make -sure you're code panics in such cases. It's always a bit tricky to test a code that panic as you have to use a deferred -function to recover the panic β€” but then if the panic doesn't happen how do you fail the test ? - -This is where =Panics= comes handy. - -#+BEGIN_SRC go - func foo(shouldPanic bool) { - if shouldPanic { - panic("booooooooooh") - } - // don't worry, be happy - } - // will fail with : did not panic - assert.Check(t, cmp.Panics(foo(false))) - // will succeed - assert.Check(t, cmp.Panics(foo(true))) -#+END_SRC - -** Miscellaneous with =Contains=, =Len= and =Nil= - -Those last three /built-in/ =Comparison= are pretty straightforward. - -- =Contains= succeeds if item is in collection. Collection may be a string, map, slice, or array. - - If collection is a string, item must also be a string, and is compared using =strings.Contains()=. If collection is a - Map, contains will succeed if item is a key in the map. If collection is a slice or array, item is compared to each - item in the sequence using ==reflect.DeepEqual()==. -- =Len= succeeds if the sequence has the expected length. -- =Nil= succeeds if obj is a nil interface, pointer, or function. - -#+BEGIN_SRC go - // Contains works on string, map, slice or arrays - assert.Check(t, cmp.Contains("foobar", "foo")) - assert.Check(t, cmp.Contains([]string{"a", "b", "c"}, "b")) - assert.Check(t, cmp.Contains(map[string]int{"a": 1, "b": 2, "c": 4}, "b")) - - // Len also works on string, map, slice or arrays - assert.Check(t, cmp.Len("foobar", 6)) - assert.Check(t, cmp.Len([]string{"a", "b", "c"}, 3)) - assert.Check(t, cmp.Len(map[string]int{"a": 1, "b": 2, "c": 4}, 3)) - - // Nil - var foo *MyStruc - assert.Check(t, cmp.Nil(foo)) - assert.Check(t, cmp.Nil(bar())) -#+END_SRC - -But let's not waste more time and let's see how to write our own =Comparison= ! - -** Write your own =Comparison= - -One of the main aspect of =gotest.tools/assert= is to make it easy for developer to write as less boilerplate code as -possible while writing tests. Writing your own =Comparison= allows you to write a well named function that will be easy -to read and that can be re-used across your tests. - -Let's look back at the =cmp.Comparison= and =cmp.Result= types. - -#+BEGIN_SRC go -type Comparison func() Result - -type Result interface { - Success() bool -} -#+END_SRC - -A =Comparison= for =assert.Check= or =assert.Check= is a function that return a =Result=, it's pretty straightforward to -implement, especially with =cmp.ResultSuccess= and =cmp.ResultFailure(…)= (as seen previously). - -#+BEGIN_SRC go - func regexPattern(value string, pattern string) cmp.Comparison { - return func() cmp.Result { - re := regexp.MustCompile(pattern) - if re.MatchString(value) { - return cmp.ResultSuccess - } - return cmp.ResultFailure( - fmt.Sprintf("%q did not match pattern %q", value, pattern)) - } - } - - // To use it - assert.Check(t, regexPattern("12345.34", `\d+.\d\d`)) -#+END_SRC - -As you can see, it's pretty easy to implement, and you can do quite a lot in there easily. If a function call returns an -error inside of your =Comparison= function, you can use =cmp.ResultFromError= for example. Having something like -=assert.Check(t, isMyServerUp(":8080"))= is way more readable than a 30-line of code to check it. - -* Conclusion… - -… and that's a wrap. We only looked at the =assert= package of [[https://gotest.tools][=gotest.tools=]] so far, but it's already quite a bit to process. - -We've seen : -- the main functions provided by this package : =assert.Assert= and =assert.Check= -- some helper functions like =assert.NilError=, … -- the =assert/cmp=, and =assert/opt= sub-package that allows you to write more custom =Comparison= - -Next time, we'll look at the =skip= package, that is a really simple wrapper on top of =testing.Skip= function. - -** diff --git a/www/vincent.demeester.fr/content/posts/2018-09-01-gotest-tools-skip.org b/www/vincent.demeester.fr/content/posts/2018-09-01-gotest-tools-skip.org @@ -1,59 +0,0 @@ -#+title: Golang testing β€” gotest.tools skip -#+date: <2018-09-01 Sat> -#+filetags: go testing skip -#+setupfile: ../templates/post.org - -* Introduction - -Let's continue the [[https://gotest.tools][=gotest.tools=]] serie, this time with the =skip= package. This is a -really simple one so this should be quick. - -#+BEGIN_QUOTE -=skip= provides functions for skipping a test and printing the source code of the -condition used to skip the test. -#+END_QUOTE - -The package consists of only one function : =If=. The idea comes mainly from -[[https://github.com/docker/docker][=docker/docker=]] integration test suite, where we wanted to skip some test (or test suites) -given different context. By context I mean things like the system we are running on -(=Windows=, =Linux=, …) or the capabilities of the running kernel or node (is =apparmor= -available or not on the current machine). - -This =If= method takes a =testing.T= pointer and either a boolean, a function that -returns a boolean, *or* an expression. - -#+BEGIN_SRC go - // boolean - // --- SKIP: TestName (0.00s) - // skip.go:19: MissingFeature - var MissingFeature bool - skip.If(t, MissingFeature) - - // function - // --- SKIP: TestName (0.00s) - // skip.go:19: !IsExperimentalDaemon(dockerClient): daemon is not experimental - skip.If(t, IsExperimentalDaemon(dockerClient), "daemon is not experimental") - - // expression - // --- SKIP: TestName (0.00s) - // skip.go:19: apiVersion < version("v1.24") - skip.If(t, apiVersion < version("v1.24")) -#+END_SRC - -There is few elements to note though : - -- This package (as other parts of the =gotest.tools= packages), will try to look at source - files to display the expression used (same goes for =assert=). This is usually not a - problem because you run tests where the source code is. *However*, in the cases you - generate a test binary to be executed later (Γ -la =kubernetes= or other projects), this - can display a weird error message if the sources are not available… You shouldn't be - worried too much about it, but it's better if you know :) -- The main reason to use =skip.If= is mainly for new contributors to get in quickly, - *reducing potential friction of them running the tests on their environment*. The more - the tests are written in a way they explicitely declare their requirements (and skipped - if the environment does not meet those), the easier it makes contributors run your - tests. *But*, this also means, you should try to measure the skipped tests on your - continuous integration system to make sure you run all of them eventually… otherwise - it's dead code. /But more on that in later posts πŸ˜‰/. - -That's all for today folks, told you that was going to be quick. diff --git a/www/vincent.demeester.fr/content/posts/2018-09-06-gotest-tools-golden.org b/www/vincent.demeester.fr/content/posts/2018-09-06-gotest-tools-golden.org @@ -1,75 +0,0 @@ -#+TITLE: Golang testing β€” gotest.tools golden -#+date: <2018-09-06 Thu> -#+filetags: go testing golden -#+setupfile: ../templates/post.org - -#+TOC: headlines 2 - -* Introduction -Let's continue the [[https://gotest.tools][=gotest.tools=]] serie, this time with the =golden= package. This is a -[[/posts/2017-04-22-golang-testing-golden-file/][/quick follow-up/ on a previous =golden= post]], but focused on the =gotest.tools= -implementation. I'm gonna be quicker, please read that one if =golden= files is a new -concept for you. - -#+BEGIN_QUOTE -Package =golden= provides tools for comparing large mutli-line strings. - -Golden files are files in the =./testdata/= sub-directory of the package under test. -#+END_QUOTE - -In the previous article, we described the problem, and how to fix it by writing a small -helper. Well, that small helper is in =gotest.tools/golden= now, and it has a tiny bit -more features. - -One of the difference between the =gotest.tools= implementation and the previous post is -the flag name. In =gotest.tools/golden=, the flag is =-test.update-golden= (was just -=-test.update= before). Just as before, if the =-test.update-golden= flag is set then the -actual content is written to the golden file, before reading it and comparing. - -There is two ways to use the =golden= package: -- on it's own, using =golden.Assert= or =golden.AssertBytes= -- as a =cmp.Comparison=, with =golden.String= or =golden.Bytes= - -* =Assert= and =AssertBytes= - -Using =Assert= functions should be straightforward. Both =Assert= function compares the -actual content to the expected content in the golden file and returns whether the -assertion was successful (true) or not (false). - -- =Assert= uses string. Note that this one *removes carriage return* before comparing to - depend as less as possible of the system (=\n= vs =\r\n= πŸ˜…) -- =AssertBytes= uses raw data (in the form of =[]byte=) - -#+BEGIN_SRC go - golden.Assert(t, "foo", "foo-content.golden") - // Could also be used to check some binary format - golden.AssertBytes(t, []byte("foo"), "foo-content.golden") -#+END_SRC - -* =Bytes= and =String= - -As written in a [[/posts/2018-08-16-gotest-tools-assertions/][previous post (about the =assert= package)]], I prefer to use =cmp.Comparison=. - -#+BEGIN_QUOTE -All those helper functions have a equivalent function in the =cmp= package that returns a -=Comparison=. I, personally, prefer to use =assert.Check= or =assert.Assert= in -combination with =cmp.Comparison= as it allows me to write all my assertions the same way, -with built-ins comparison or with my own β€” i.e. =assert.Assert(t, is.Equal(…), "message"= -or =assert.Assert(t, stackIsUp(c, time…), "another message")=. -#+END_QUOTE - -The =golden= package gives us that too, in the form of =Bytes= and =String=. Using the -=assert.Check= or =assert.Assert= functions with those is equivalent to their /helper/ -counter-part =golden.Assert= and =golden.AssertBytes=. - -#+BEGIN_SRC go - assert.Assert(t, golden.String("foo", "foo-content.golden")) - // Could also be used to check some binary format - assert.Assert(t, golden.Bytes([]byte("foo"), "foo-content.golden")) -#+END_SRC - -* Conclusion… - -… that's a wrap. As for [[/posts/2018-09-01-gotest-tools-skip/][=skip=]], this is a small package, so the post was going to be -quick. =golden= package just solve a specific problem (read [[/posts/2017--04-22-golang-testing-golden-file/][Golang testing β€” golden file]]) -in a simple way. diff --git a/www/vincent.demeester.fr/content/posts/2018-09-14-gotest-tools-fs.org b/www/vincent.demeester.fr/content/posts/2018-09-14-gotest-tools-fs.org @@ -1,187 +0,0 @@ -#+TITLE: Golang testing β€” gotest.tools fs -#+date: <2018-09-14 Fri> -#+filetags: go testing fs -#+setupfile: ../templates/post.org - -#+TOC: headlines 2 - -* Introduction - -Let's continue the [[https://gotest.tools][=gotest.tools=]] serie, this time with the =fs= package. - -#+BEGIN_QUOTE -Package fs provides tools for creating temporary files, and testing the contents and structure of a directory. -#+END_QUOTE - -This package is heavily using functional arguments (as we saw in [[/posts/2017-01-01-go-testing-functionnal-builders/][functional arguments for -wonderful builders]]). Functional arguments is, in a nutshell, a combinaison of two Go -features : /variadic/ functions (=...= operation in a function signature) and the fact -that =func= are /first class citizen/. This looks more or less like that. - -#+BEGIN_SRC go - type Config struct {} - - func MyFn(ops ...func(*Config)) *Config { - c := &Config{} // with default values - for _, op := range ops { - op(c) - } - return c - } - - // Calling it - conf := MyFn(withFoo, withBar("baz")) -#+END_SRC - -The =fs= package has too *main* purpose : - -1. create folders and files required for testing in a simple manner -2. compare two folders structure (and content) - -* Create folder structures - -Sometimes, you need to create folder structures (and files) in tests. Doing =i/o= work -takes time so try to limit the number of tests that needs to do that, especially in unit -tests. Doing it in tests adds a bit of boilerplate that could be avoid. As stated [[/posts/2017-01-01-go-testing-functionnal-builders/][before]] : - -#+BEGIN_QUOTE -One of the most important characteristic of a unit test (and any type of test really) is -*readability*. This means it should be easy to read but most importantly it should *clearly -show the intent* of the test. The setup (and cleanup) of the tests should be as small as -possible to avoid the noise. -#+END_QUOTE - -In a test you usually end up using =ioutil= function to create what you need. This looks -somewhat like the following. - -#+BEGIN_SRC go - path, err := ioutil.TempDir("", "bar") - if err != nil { // or using `assert.Assert` - t.Fatal(err) - } - if err := os.Mkdir(filepath.Join(path, "foo"), os.FileMode(0755)); err != nil { - t.Fatal(err) - } - if err := ioutil.WriteFile(filepath.Join(path, "foo", "bar"), []byte("content"), os.FileMode(0777)); err != nil { - t.Fatal(err) - } - defer os.RemoveAll(path) // to clean up at the end of the test -#+END_SRC - -The =fs= package intends to help reduce the noise and comes with a bunch function to create -folder structure : - -- two main function =NewFile= and =NewDir= -- a bunch of /operators/ : =WithFile=, =WithDir=, … - -#+BEGIN_SRC go - func NewDir(t assert.TestingT, prefix string, ops ...PathOp) *Dir { - // … - } - - func NewFile(t assert.TestingT, prefix string, ops ...PathOp) *File { - // … - } -#+END_SRC - -The =With*= function are all satisfying the =PathOp= interface, making =NewFile= and -=NewDir= extremely composable. Let's first see how our above example would look like using -the =fs= package, and then, we'll look a bit more at the main =PathOp= function… - -#+BEGIN_SRC go - dir := fs.NewDir(t, "bar", fs.WithDir("foo", - fs.WithFile("bar", fs.WithContent("content"), fs.WithMode(os.FileMode(0777))), - )) - defer dir.Remove() -#+END_SRC - -It's clean and simple to read. The intent is well described and there is not that much of -noise. =fs= functions tends to have /sane/ and /safe/ defaults value (for =os.FileMode= -for example). Let's list the main, useful, =PathOp= provided by =gotest.tools/fs=. - -- =WithDir= creates a sub-directory in the directory at path. -- =WithFile= creates a file in the directory at path with content. -- =WithSymlink= creates a symlink in the directory which links to target. Target must be a - path relative to the directory. -- =WithHardlink= creates a link in the directory which links to target. Target must be a - path relative to the directory. -- =WithContent= and =WWithBytes= write content to a file at Path (from a =string= or a - =[]byte= slice). -- =WithMode= sets the file mode on the directory or file at path. -- =WithTimestamps= sets the access and modification times of the file system object at - path. -- =FromDir= copies the directory tree from the source path into the new Dir. This is - pretty useful when you have a huge folder structure already present in you =testdata= - folder or elsewhere. -- =AsUser= changes ownership of the file system object at Path. - -Also, note that =PathOp= being an function type, you can provide your own implementation -for specific use-cases. Your function just has to satisfy =PathOp= signature. - -#+BEGIN_SRC go - type PathOp func(path Path) error -#+END_SRC - -* Compare folder structures - -Sometimes, the code you're testing is creating a folder structure, and you would like to -be able to tests that, with the given arguments, it creates the specified structure. =fs= -allows you to do that too. - -The package provides a =Equal= function, which returns a =Comparison=, that the [[/posts/2018-08-16-gotest-tools-assertions/][=assert=]] -package understand. It works by comparing a =Manifest= type provided by the test and a -=Manifest= representation of the specified folder. - -#+BEGIN_QUOTE - Equal compares a directory to the expected structured described by a manifest and returns - success if they match. If they do not match the failure message will contain all the - differences between the directory structure and the expected structure defined by the - Manifest. -#+END_QUOTE - -A =Manifest= stores the expected structure and properties of files and directories in a -file-system. You can create a =Manifest= using either the functions =Expected= or -=ManifestFromDir=. - -We're going to focus on the =Expected= function, as =ManifestFromDir= does pretty much -what you would expected : it takes the specified path, and returns a =Manifest= that -represent this folder. - -#+BEGIN_SRC go - func Expected(t assert.TestingT, ops ...PathOp) Manifest -#+END_SRC - -=Expected= is close to =NewDir= function : it takes the same =PathOp= functional -arguments. This makes creating a =Manifest= straightforward, as it's working the same. Any -function that satisfy =PathOp= can be used for =Manifest= the exact same way you're using -them on =fs.NewDir=. - -There is a few additional functions that are only useful with =Manifest= : - -- =MatchAnyFileContent= updates a Manifest so that the file at path may contain any content. -- =MatchAnyFileMode= updates a Manifest so that the resource at path will match any file mode. -- =MatchContentIgnoreCarriageReturn= ignores cariage return discrepancies. -- =MatchExtraFiles= updates a Manifest to allow a directory to contain unspecified files. - -#+BEGIN_SRC go - path := operationWhichCreatesFiles() - expected := fs.Expected(t, - fs.WithFile("one", "", - fs.WithBytes(golden.Get(t, "one.golden")), - fs.WithMode(0600)), - fs.WithDir("data", - fs.WithFile("config", "", fs.MatchAnyFileContent)), - ) - - assert.Assert(t, fs.Equal(path, expected)) -#+END_SRC - -The following example compares the result of =operationWhichCreatesFiles= to the expected -=Manifest=. As you can see it also integrates well with other part of the =gotest.tools= -library, with the [[/posts/2018-09-06-gotest-tools-golden/][=golden= package]] in this example. - -* Conclusion… - -… that's a wrap. In my opinion, this is one the most useful package provided by -=gotest.tools= after =assert=. It allows to create simple or complex folder structure -without the noise that usually comes with it. diff --git a/www/vincent.demeester.fr/content/posts/2018-09-18-gotest-tools-icmd.org b/www/vincent.demeester.fr/content/posts/2018-09-18-gotest-tools-icmd.org @@ -1,209 +0,0 @@ -#+TITLE: Golang testing β€” gotest.tools icmd -#+date: <2018-09-18 Tue> -#+filetags: go testing cmd -#+setupfile: ../templates/post.org - -#+TOC: headlines 2 - -* Introduction -Let's continue the [[https://gotest.tools][=gotest.tools=]] serie, this time with the =icmd= package. - -#+BEGIN_QUOTE -Package icmd executes binaries and provides convenient assertions for testing the results. -#+END_QUOTE - -After file-system operations (seen in [[/posts/2018-09-14-gotest-tools-fs/][=fs=]]), another common use-case in tests is to -*execute a command*. The reasons can be you're testing the =cli= you're currently writing -or you need to setup something using a command line. A classic execution in a test might -lookup like the following. - -#+BEGIN_SRC go - cmd := exec.Command("echo", "foo") - cmd.Stout = &stdout - cmd.Env = env - if err := cmd.Run(); err != nil { - t.Fatal(err) - } - if string(stdout) != "foo" { - t.Fatalf("expected: foo, got %s", string(stdout)) - } -#+END_SRC - -The package =icmd= is there to ease your pain (as usual πŸ˜‰) β€” we used /the name =icmd=/ -instead of =cmd= because it's a pretty common identifier in Go source code, thus would be -really easy to /shadow/ β€” and have some really weird problems going on. - -The usual =icmd= workflow is the following: - -1. Describe the command you want to execute using : type =Cmd=, function =Command= and - =CmdOp= operators. -2. Run it using : function =RunCmd= or =RunCommand= (that does 1. for you). You can also - use =StartCmd= and =WaitOnCmd= if you want more control on the execution workflow. -3. Check the result using the =Assert=, =Equal= or =Compare= methods attached to the - =Result= struct that the command execution return. - -* Create and run a command - -Let's first dig how to create commands. In this part, the assumption here is that the -command is successful, so we'll have =.Assert(t, icmd.Success)= for now β€” we'll learn more -about =Assert= in the next section πŸ‘Ό. - -The simplest way to create and run a command is using =RunCommand=, it has the same -signature as =os/exec.Command=. A simple command execution goes as below. - -#+BEGIN_SRC go - icmd.RunCommand("echo", "foo").Assert(t, icmd.Sucess) -#+END_SRC - -Sometimes, you need to customize the command a bit more, like adding some environment -variable. In those case, you are going to use =RunCmd=, it takes a =Cmd= and operators. -Let's look at those functions. - -#+BEGIN_SRC go - func RunCmd(cmd Cmd, cmdOperators ...CmdOp) *Result - - func Command(command string, args ...string) Cmd - - type Cmd struct { - Command []string - Timeout time.Duration - Stdin io.Reader - Stdout io.Writer - Dir string - Env []string - } -#+END_SRC - -As we've seen [[/posts/2017-01-01-go-testing-functionnal-builders/][multiple]] [[/posts/2018-08-16-gotest-tools-assertions/][times]] [[/posts/2018-09-14-gotest-tools-fs/][before]], it uses the /powerful/ functional arguments. At the -time I wrote this post, the =icmd= package doesn't contains too much =CmdOp= [fn:1], so I'll -propose two version for each example : one with =CmdOpt= present in [[https://github.com/gotestyourself/gotest.tools/pull/122][this PR]] and one -without them. - -#+BEGIN_SRC go - // With - icmd.RunCmd(icmd.Command("sh", "-c", "echo $FOO"), - icmd.WithEnv("FOO=bar", "BAR=baz"), icmd.Dir("/tmp"), - icmd.WithTimeout(10*time.Second), - ).Assert(t, icmd.Success) - - // Without - icmd.RunCmd(icmd.Cmd{ - Command: []string{"sh", "-c", "echo $FOO"}, - Env: []string{"FOO=bar", "BAR=baz"}, - Dir: "/tmp", - Timeout: 10*time.Second, - }).Assert(t, icmd.Success) -#+END_SRC - -As usual, the intent is clear, it's simple to read and composable (with =CmdOp='s). - -[fn:1] The =icmd= package is one of the oldest =gotest.tools= package, that comes from the -[[https://github.com/docker/docker][=docker/docker=]] initially. We introduced these =CmdOp= but implementations were in -=docker/docker= at first and we never really updated them. - -* Assertions - -Let's dig into the assertion part of =icmd=. Running a command returns a struct -=Result=. It has the following methods : - -- =Assert= compares the Result against the Expected struct, and fails the test if any of - the expectations are not met. -- =Compare= compares the result to Expected and return an error if they do not match. -- =Equal= compares the result to Expected. If the result doesn't match expected - returns a formatted failure message with the command, stdout, stderr, exit code, and any - failed expectations. It returns an =assert.Comparison= struct, that can be used by other - =gotest.tools=. -- =Combined= returns the stdout and stderr combined into a single string. -- =Stderr= returns the stderr of the process as a string. -- =Stdout= returns the stdout of the process as a string. - -When you have a result, you, most likely want to do two things : - -- /assert/ that the command succeed or failed with some specific values (exit code, - stderr, stdout) -- use the output β€” most likely =stdout= but maybe =stderr= β€” in the rest of the test. - -As seen above, /asserting/ the command result is using the =Expected= struct. - -#+BEGIN_SRC go - type Expected struct { - ExitCode int // the exit code the command returned - Timeout bool // did it timeout ? - Error string // error returned by the execution (os/exe) - Out string // content of stdout - Err string // content of stderr - } -#+END_SRC - -=Success= is a constant that defines a success β€” it's an exit code of =0=, didn't timeout, -no error. There is also the =None= constant, that should be used for =Out= or =Err=, to -specify that we don't want any content for those standard outputs. - -#+BEGIN_SRC go - icmd.RunCmd(icmd.Command("cat", "/does/not/exist")).Assert(t, icmd.Expected{ - ExitCode: 1, - Err: "cat: /does/not/exist: No such file or directory", - }) - - // In case of success, we may want to do something with the result - result := icmd.RunCommand("cat", "/does/exist") - result.Assert(t, icmd.Success) - // Read the output line by line - scanner := bufio.NewScanner(strings.NewReader(result.Stdout())) - for scanner.Scan() { - // Do something with it - } -#+END_SRC - -If the =Result= doesn't map the =Expected=, a test failure will happen with a useful -message that will contains the executed command and what differs between the result and -the expectation. - -#+BEGIN_SRC go - result := icmd.RunCommand(…) - result.Assert(t, icmd.Expected{ - ExitCode: 101, - Out: "Something else", - Err: None, - }) - // Command: binary arg1 - // ExitCode: 99 (timeout) - // Error: exit code 99 - // Stdout: the output - // Stderr: the stderr - // - // Failures: - // ExitCode was 99 expected 101 - // Expected command to finish, but it hit the timeout - // Expected stdout to contain "Something else" - // Expected stderr to contain "[NOTHING]" - … -#+END_SRC - -Finally, we listed =Equal= above, that returns a =Comparison= struct. This means we can -use it easily with the =assert= package. As written in a [[/posts/2018-08-16-gotest-tools-assertions/][previous post (about the =assert= -package)]], I tend prefer to use =cmp.Comparison=. Let's convert the above examples using -=assert=. - -#+BEGIN_SRC go - result := icmd.RunCmd(icmd.Command("cat", "/does/not/exist")) - assert.Check(t, result.Equal(icmd.Expected{ - ExitCode: 1, - Err: "cat: /does/not/exist: No such file or directory", - })) - - // In case of success, we may want to do something with the result - result := icmd.RunCommand("cat", "/does/exist") - assert.Assert(t, result.Equal(icmd.Success)) - // Read the output line by line - scanner := bufio.NewScanner(strings.NewReader(result.Stdout())) - for scanner.Scan() { - // Do something with it - } -#+END_SRC - -* Conclusion… - -… that's a wrap. The =icmd= package allows to easily run command and describe what result -are expected of the execution, with the least noise possible. We *use this package heavily* -on several =docker/*= projects (the engine, the cli)… diff --git a/www/vincent.demeester.fr/content/posts/2019-01-20-2018-year-review.org b/www/vincent.demeester.fr/content/posts/2019-01-20-2018-year-review.org @@ -1,151 +0,0 @@ -#+title: 2018 year review -#+date: <2019-01-20 Sun> -#+filetags: review - -* Introduction - -Here is my review of 2018, the first of its kind, hopefully not the last πŸ‘Ό. I saw -some[fn:1] /2018[fn:2] reviews/[fn:3] articles[fn:4] in my Feedly feed and I thought it -would be a good idea to write my own too. - -I'll try in the next year β€” maybe month if I ever want to do monthly reviews β€” to automate -some of it ; using the beloved =org-mode=. - -[fn:1] [[https://punchagan.muse-amuse.in/blog/2018-in-review/][2018 in Review - Noetic Nought]] -[fn:2] [[https://medium.com/@buster/42-dig-deeper-e2278d1fe015][42 β€” Dig deeper – Buster Benson – Medium]] -[fn:3] [[https://jvns.ca/blog/2018/12/23/2018--year-in-review/][2018: Year in review - Julia Evans]] -[fn:4] [[https://writing.natwelch.com/post/685][Nat? Nat. Nat! | #685 2018 Year in Review]] - -* Work - -The big change this year is : I changed job πŸ‘Ό. I went from Docker Inc. to Red Hat. I -needed a change and 5 month in, I think it was the *best choice I made in my life* so far -πŸ’ƒ. I'm doing open-source for a living and best part, I am working remotely (more on that -later). - -Before that, at Docker Inc., I continued the work I started years before, -a.k.a. maintaining the Moby project and the docker engine, among other Docker project -(both open-source and closed-source). I also helped the work on the compose side, from the -root of =docker/compose-on-kubernetes= (before it got open-sourced), to the =docker/app= -experiments. - -At Red Hat, I started to work upstream in the Kubernetes community, mainly on the Knative -projects. I also work on the Openshift Cloud Function project (and thus team), and those -fellows are awesome ! Digging more into Openshift, and other part of the Red Hat portfolio -is a really good learning experience, and it's just the start ! - -As stated above, I am now working home, full-time. I could work from home from time to -time when I was at Docker inc, but working home full-time is another kind of beast. So far -it is really good, some adjustments were needed but it's for the best. Here is a small -take on "working from home": - -- It's easy to have *no distraction*, thus having *really productive* piece of time -- It's also *really easy to work long day or really long period of time*. It's especially - true if, like me, you work on a distributed team (across multiple timezones). - - I ended up using the Pomodoro technique to make sure I move at least few times a day - - I try to make sure I don't make an habits of checking out work code, email and other - material after a certain hour in the evening. It's ok to do it sometimes, but for your - sanity, you need some rest time. -- It's easy to adapt your day to circumstance. If you got to run errands in the middle of - the day, it's no big deal. You can take the time back later on. -- It's so good to have *no* commmute time. That said I end up /walking or taking the bike/ - early morning to clear my head before work 😝. - -* Personal - -Health wise, it's a mix of good and bad year. The first half was really good, the second -way less. End of august, I felt something weird in the right knee, and well, turns out my -internal meniscus is in a real bad shape. Just as before joining Docker, I'm gonna need a -surgery, on the right knee that time. It's gonna affect 2019 (the first half, I'm not -gonna be able to move around much but.. meh, it's life). - -Now that I work from home, I'm really glad I got a standing desk at the end of 2017. I -tend to work standing most of the time -- except when my knee hurts (and most likely for -few months after the surgery πŸ˜…). I invested on a ultrawide screen, to get the same -experience I had at Docker. And oh boy those screens are good ! - -I also try to clean my desk and it's "neighboorhood". As I get older, I want less messy -stuuf (desk, flat, ...). I'm leaning towards having less stuff, being commputer related or -not. It's not minimalism, but it feel good to have less stuff, but stuff that you actually -use. I still have trouble throwing old computer away, mainly because I fell they can be -useful in some way. - -[[/images/2019/01/desk1.jpg]] - -This year I migrate all of my "infrastructure" computer to NixOS. I learned a lot of Nix, -reworked my configuration multiple time to end up with a [[https://github.com/vdemeester/nixos-configuration.git][system configuration repository]] -that uses modules, and a [[https://github.com/vdemeester/home.git][/home configuration repository/]] (for user configuration). The -[[https://github.com/vdemeester/home.git][home]] repository uses [[https://github.com/rycee/home-manager.git][=home-manager=]] and thus doesn't make any assumption of running on top -of NixOS. This allows me to have an /easy to get/ setup on any system that =nixpkgs= -supports (any Linux distribution, Mac OSX, Windows Subsystem Linux). The current -configuration is not yet optimal but I'm pretty happy about what I got : - -- Custom DNS server @home to make it easier to target local hosts. -- Local proxies and mirrors for docker images, nixpkgs binary package and go modules to - eat less bandwidth. -- Easy to setup VPN using [[https://www.wireguard.com/][WireGuard]]. -- File replication using =syncthing= and automatic backup on my local NAS. -- Automatic system upgrade, thanks to NixOS. I'll probably write an article about that - later on this year. - -I started to use =todoist= in 2017, and boy, oh boy, it helped me quite a lot ! I'm using -it daily to organize my work and quickly get idea, and /todos/ out of my head. The main -problem with it is it's not integrated with another tool I'm using daily : Emacs and -=org-mode=. =org-mode= is a fantastic piece of software and is, on its own, the main -reason for me to invest time in Emacs. I'm taking note in =org-mode=, I write my daily -standup notes in there too. I end up going back and forth between =org-mode= and =todoist= -for those daily standup. I am lazy, I want to automate that. And the best way to do it, is -to also use =org-mode= for task management. I'm in a /transition/ mode right now, but my -goal for 2019 is to use todoist to take quick note/todo(s) on the move (aka on the phone) -and use =org-mode= for the rest. - - -* Reading & Writing - -I used to like reading, but the past years, I didn't really read that much, except some -technical books. 2018 in, that respect, is not an exception, I didn't read too much. Worse -than that, I started some book and stopped at some point, for no apparent reason ; and -now, I need to start back from the beginning, which, well, is not helping me want to read -them again. - -I'm trying two thing to counter that and consume more books for the years to come. - -1. I now have a reading list on my =org-mode= files, where I track which one I read and - when I read them ; and maybe notes too. I have a lot of book on my kindle, that only - wait for one thing, being read.. -2. I subscribed to [[https://www.audible.fr][Audible]] πŸ‘Ό. Working from home, I tend to take a long break after lunch, - where I'm going for a walk, for around an hour. I can't read while walking but I - definitely can listen - that make audio books perfect for these moments. I also - alternate between audio books and non-musical podcasts. - -On the writing side, 2017 was a slow year in terms of writing (only 2 posts), 2018 was a -bit better, 6 posts -- it's a bit cheating, as it was mainly between changing jobs, and -on a series I still need to finish. I'm hoping to write more this year, hence the goals -I've set to myself below. - -* 2019 Goals - -- *Get back on my feet after knee surgery (exercices, …)* πŸƒ -- *Read at least one book per month (be audible, ebook or paper)* πŸ“– -- *Giving at least a talk (on Knative, containers, nixos, ..)* πŸ™Š - - I didn't give too much talk in 2018 (at least less than 2017). I'm gonna try to get back - at it this year. The surgery won't help but it's just few months. - -- *At least 1 video per month* πŸ“Ή - - I want to start recording some video, as I feel it's an easier medium than writing and, - well, I wanna try ! - -- *At least 1 post per month* ✍️ -- *Enhance my emacs skills (aka don't be afraid of the lisp)* ⌨️ - - I'm using Emacs for almost anything that doesn't happen in a web browser. But I still - feel like a newbie. I want to learn more, to write more lisp that help me being even - more lazier (aka achieve more doing less 😝) - -- *Enhance my Nix(OS) skills* 🐧 -- *Learn / master a new language* 🎽 - - I'm working with Go 90% of my time. I want to master and learn more language. On my list - are Emacs Lisp, Rust, Typescript and Haskell. diff --git a/www/vincent.demeester.fr/content/posts/2019-01-26-nix-run-alias.org b/www/vincent.demeester.fr/content/posts/2019-01-26-nix-run-alias.org @@ -1,200 +0,0 @@ -#+title: Nix run aliases -#+date: <2019-01-26 Sat> -#+filetags: nixos fish alias nix shell home manager - -* Introduction - -I use [[https://nixos.org/][=NixOS=]] each and every day, everywhere. One really cool feature of =nix= is -=nix-shell= and more recently (with =nix= >= =2.0.0=), =nix run=. - -#+begin_src man -Usage: nix run <FLAGS>... <INSTALLABLES>... - -Summary: run a shell in which the specified packages are available. - -Flags: - --arg <NAME> <EXPR> argument to be passed to Nix functions - --argstr <NAME> <STRING> string-valued argument to be passed to Nix functions - -c, --command <COMMAND> <ARGS> command and arguments to be executed; defaults to 'bash' - -f, --file <FILE> evaluate FILE rather than the default - -i, --ignore-environment clear the entire environment (except those specified with --keep) - -I, --include <PATH> add a path to the list of locations used to look up <...> file names - -k, --keep <NAME> keep specified environment variable - -u, --unset <NAME> unset specified environment variable - -Examples: - - To start a shell providing GNU Hello from NixOS 17.03: - $ nix run -f channel:nixos-17.03 hello - - To start a shell providing youtube-dl from your 'nixpkgs' channel: - $ nix run nixpkgs.youtube-dl - - To run GNU Hello: - $ nix run nixpkgs.hello -c hello --greeting 'Hi everybody!' - - To run GNU Hello in a chroot store: - $ nix run --store ~/my-nix nixpkgs.hello -c hello - -Note: this program is EXPERIMENTAL and subject to change. -#+end_src - -As you can see from the =-h= summary, it makes it really easy to run a shell or a command -with some packages that are not in your main configuration. It will download the -package(s) if there are not available in the Nix store (=/nix/store/=). - -A few month ago I decided it would be a perfect use-case for command I do not run -often. My idea was, let's define =aliases= (in the shell) that would make a simple command -call, like =ncdu=, become =nix run nixpkgs.ncdu -c ndcu=. My /shell of choice/ is [[https://fishshell.com/][fish]], so -I decided to dig into the /language/ in order to implement that. - -The use case is the following : -- When I type =foo=, I want the command =foo= in package =bar= to be executed. -- I want to be able to pin a channel for the package β€” I'm using [[https://matthewbauer.us/][Matthew Bauer]] [[https://matthewbauer.us/blog/channel-changing.html][Channel - Changing with Nix]] setup for pin-pointing a given channel. - -* Fish aliases experimentation - -I had a feeling the built-in =alias= would not work so I ended up trying to define a -/dynamic/ function that would be the name of the command. That's the beauty of the shell, -everything is a command, even function appears as commands. If you define a function -=foo()=, you will be able to run =foo= in your shell, *and* it will take precedence over -the =foo= executable file that would be in your =PATH=. - -I ended up with two main helper function that would create those /alias/ function. - -#+begin_src fish - function _nix_run_package - set -l s $argv[1] - set -l package (string split ":" $s) - switch (count $package) - case 1 - _nix_run $s $s $argv[2] $argv[3] - case 2 - _nix_run $package[1] $package[2] $argv[2] $argv[3] - end - end - - function _nix_run - set -l c $argv[1] - set -l p $argv[2] - set -l channel $argv[3] - set -l channelsfile $argv[4] - function $c --inherit-variable c --inherit-variable p --inherit-variable channel --inherit-variable channelsfile - set -l cmd nix run - if test -n "$channelsfile" - set cmd $cmd -f $channelsfile - end - eval $cmd $channel.$p -c $c $argv - end - end -#+end_src - -In a nutshell, =_nix_run= is the function that create the alias function. There is so -condition in there depending on whether we gave it a channel or not. So, a call like -=_nix_run foo bar unstable channels.nix= would, in the end generate a function =foo= with -the following call : =nix run -f channels.nix unstable.bar -c foo=. - -The other function, =_nix_run_package= is there to make me write less when I define those -aliases β€” aka if the command and the package share the same name, I don't want to write it -twice. So, a call like =_nix_run_package foo nixpkgs= would result in a =_nix_run foo foo -nixpkgs=, whereas a call like =_nix_run_package foo:bar unstable channels.nix= would -result in a =_nix_run foo bar unstable channels.nix=. - -An example is gonna be better than the above paragraphs. This is what I used to have in my -fish configuration. - -#+begin_src fish - function _def_nix_run_aliases - set -l stable mr sshfs ncdu wakeonlan:python36Packages.wakeonlan lspci:pciutils lsusb:usbutils beet:beets gotop virt-manager:virtmanager pandoc nix-prefetch-git:nix-prefetch-scripts nix-prefetch-hg:nix-prefetch-scripts - set -l unstable op:_1password update-desktop-database:desktop-file-utils lgogdownloader - for s in $stable - _nix_run_package $s nixpkgs - end - for s in $unstable - _nix_run_package $s unstable ~/.config/nixpkgs/channels.nix - end - end - # Call the function to create the aliases - _def_nix_run_aliases -#+end_src - -This works like a charm, and for a while, I was happy. But I soon realized something : I'm -not always on my shell β€” like, I tend to spend more and more time in =eshell=. This also -doesn't work with graphic tools like [[https://github.com/DaveDavenport/rofi][=rofi=]]. I needed actual command, so that external -tools would benefit from that. I ended up writing a small tool, [[https://github.com/vdemeester/nr][=nr=]] that integrates -nicely with =nix= and [[https://github.com/rycee/home-manager][=home-manager=]]. - -* A proper tool : =nr= - -The gist for this tool is simple : -- create an executable script that will call =nix run ...= instead of the command -- as for the above fish script, support different channels -- make sure we don't have conflicts β€” if the command already exists, then don't create the - command - -The =nr= tool would have to be able to manage multiple /profile/, which really stands for -multiple file. The main reason is really about how I manage my configuration ; To make it -simple, depending on the computer my configurations are setup, I may not have =go=, thus I -don't want any =go=-related aliases for a computer that doesn't have =go= (using =go= here -but you can replace with anything). - -#+begin_src fish -$ nr default -> nr generate default -> virtmanager already exists -$ nr git -> nr generate git -#+end_src - -=nr= generates a bash script that does the =nr run …= and mark it as executable. =nr= -needs to be able to clean files it has generated (in case we removed it from -aliases). Thus, I went for a really naive comment in the script. When generating a new set -of commands, =nr= will first remove previously generated script for this profile, and for -that, it uses the comment. Let's look at what a generated script looks like, for the -default profile. - -#+begin_src bash -#!/usr/bin/env bash -# Generated by nr default -nix run nixpkgs.nix-prefetch-scripts -c nix-prefetch-git $@ -#+end_src - -The format used in =nr= is =json=. I'm not a /huge fan/ of =json= but it really was the -best format to use for this tool. The reason to use =json= are simple : - -- Go has =encoding/json= built-in, so it's really easy to =Marshall= and =Unmarshall= - structure. - #+begin_src go - type alias struct { - Command string `json:"cmd"` - Package string `json:"pkg"` - Channel string `json:"chan"` - } - #+end_src -- Nix also has built-in support for =json= : =builtins.toJSON= will marshall a /struct/ - into a json file. - -Finally, to avoid conflicts at /build time/ (=home-manager switch=) I couldn't use/define -a nix package, but to execute command(s) at the end of the build. One way to achieve it is -to use =file.?.onChange= script, which is executed after [[https://github.com/rycee/home-manager][=home-manager=]] has updated the -environment, *if* the file has changed. That means it's possible to check for executable -files in =~/.nix-profile/bin/= for defined aliases and create those that are not there, -with =nr=. My configuration then looks like the following. - -#+BEGIN_SRC nix - xdg.configFile."nr/default" = { - text = builtins.toJSON [ - {cmd = "ncdu";} {cmd = "sshfs";} {cmd = "gotop";} {cmd = "pandoc";} - {cmd = "wakeonlan"; pkg = "python36Packages.wakeonlan";} - {cmd = "beet"; pkg = "beets";} - {cmd = "virt-manager"; pkg = "virtmanager";} - {cmd = "nix-prefetch-git"; pkg = "nix-prefetch-scripts";} - {cmd = "nix-prefetch-hg"; pkg = "nix-prefetch-scripts";} - ]; - onChange = "${pkgs.nur.repos.vdemeester.nr}/bin/nr default"; - }; -#+END_SRC - -And there you are, now, each time I update my environment (=home-manager switch=), =nr= -will regenerate my =nix run= aliases. diff --git a/www/vincent.demeester.fr/content/posts/2019-03-23-gotest-tools-poll.org b/www/vincent.demeester.fr/content/posts/2019-03-23-gotest-tools-poll.org @@ -1,136 +0,0 @@ -#+TITLE: Golang testing β€” gotest.tools poll -#+date: <2019-03-23 Sat> -#+filetags: go testing poll - -#+TOC: headlines 2 - -* Introduction -Let's continue the [[https://gotest.tools][=gotest.tools=]] serie, this time with the =poll= package. - -#+BEGIN_QUOTE -Package poll provides tools for testing asynchronous code. -#+END_QUOTE - -When you write test, you may test a piece of code that work asynchronously, where the -state you're expecting is gonna take a bit of time to be achieved. This is especially true -when you work on networking or file-system code. And this happens a lot when you write -integration (or end-to-end) test, less for unit-tests. - -The package =poll= is trying to tackle those use cases. We'll first take a look at the -main function, =WaitOn=, then how to write a ~Check~, using the ~Result~ type. - -* ~WaitOn~ - -Let's look into the main ~poll~ function : `WaitOn`. - -#+begin_quote - WaitOn a condition or until a timeout. Poll by calling check and exit when check returns - a done Result. To fail a test and exit polling with an error return a error result. -#+end_quote - -In a gist, ~WaitOn~ will run a /condition/ function until it either times out or -succeed. It wait for a given time/delay between each run. - -#+begin_src go - func WaitOn(t TestingT, check Check, pollOps ...SettingOp) { - // […] - } -#+end_src - -As any /testing helper/ function, the first argument is ~*testing.T~ (or, in this case, -any thing that look like it, thanks to the ~TestingT~ interace). The two other arguments -are way more interesting : - -- The ~Check~ is the condition that will run multiple times until it either timeout, or succeed. -- The ~SettingOp(s)~ which are options to configure the function, things like the timeout, - or the /delay/ between each run. - -The settings are pretty straightforward : - -- ~WithDelay~ : sets the delay to wait between polls. The default delay is 100ms. -- ~WithTimeout~ : sets the timeout. The default timeout is 10s. - -There is existing ~Check~ for common case: - -- ~Connection~ : try to open a connection to the address on the named network. - - #+begin_src go - poll.WaitOn(t, poll.Connection("tcp", "foo.bar:55555"), poll.WithTimeout("5s")) - #+end_src - -- ~FileExists~ : looks on filesystem and check that path exists. - - #+begin_src go - poll.WaitOn(t, poll.FileExists("/should/be/created"), poll.WithDelay("1s")) - #+end_src - - -* ~Check~ and ~Result~ - -~Connection~ and ~FileExists~ are the only two /built-in/ ~Check~ provided by -~gotest.tools~. They are useful, but as usual, where ~gotest.tools~ shines is -extensiblity. It is really easy to define your own ~Check~. - -#+begin_src go - type Check func(t LogT) Result -#+end_src - -A ~Check~ is, thus, only a function that takes ~LogT~ β€” which is anything that can log -something, like ~*testing.T~ β€” and return a ~Result~. Let's look at this intersting -~Result~ type. - -#+begin_src go - type Result interface { - // Error indicates that the check failed and polling should stop, and the - // the has failed - Error() error - // Done indicates that polling should stop, and the test should proceed - Done() bool - // Message provides the most recent state when polling has not completed - Message() string - } -#+end_src - -Although it's an interface, the ~poll~ package defines built-in ~Result~ so that it's easy -to write ~Check~ without having to define you ~Result~ type. - -- ~Continue~ returns a Result that indicates to WaitOn that it should continue - polling. The message text will be used as the failure message if the timeout is reached. -- ~Success~ returns a Result where Done() returns true, which indicates to WaitOn that it - should stop polling and exit without an error. -- ~Error~ returns a Result that indicates to WaitOn that it should fail the test and stop - polling. - -The basic just to write a ~Check~ is then : - -- if the state is not there yet, return ~Continue~, -- if there is an error, unrelated to validating the state, return an ~Error~, -- if the state is there, return ~Success~. - -Let's look at an example taken from the ~moby/moby~ source code. - -#+begin_src go - poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond)) - - func IsInState(ctx context.Context, client client.APIClient, containerID string, state ...string) func(log poll.LogT) poll.Result { - return func(log poll.LogT) poll.Result { - inspect, err := client.ContainerInspect(ctx, containerID) - if err != nil { - return poll.Error(err) - } - for _, v := range state { - if inspect.State.Status == v { - return poll.Success() - } - } - return poll.Continue("waiting for container to be one of (%s), currently %s", strings.Join(state, ", "), inspect.State.Status) - } - } -#+end_src - - -* Conclusion - -… that's a wrap. The =poll= package allows to easily wait for a condition to happen in a -given time-frame β€” with sane defaults. As for most of the ~gotest.tools~ package, we use -this package heavily in =docker/*= projects too… diff --git a/www/vincent.demeester.fr/content/posts/2020-02-22-digital-minimalism.org b/www/vincent.demeester.fr/content/posts/2020-02-22-digital-minimalism.org @@ -1,130 +0,0 @@ -#+title: On digital minimalism, Linux, NixOS and Emacs -#+date: <2020-02-22 Sat> -#+filetags: minimalism digital emacs linux nixos -#+setupfile: ../templates/post.org/ - -* Introduction - -I've been reading and listening about Minimalism and Digital Minimalism for a little while -now. I've watch some [[https://www.youtube.com/channel/UCJ24N4O0bP7LGLBDvye7oCA][Matt D'Avella]] youtube video (and documentary), read [[https://www.goodreads.com/book/show/40672036-digital-minimalism][Digital -Minimalism]] from [[https://www.goodreads.com/author/show/147891.Cal_Newport][Cal Newport]], and read a bunch of [[Links][articles and blog posts]]. - -I wouldn't say I am a minimalist, neither am I a digital minimalist *but* it is something -that is a bit appealing and I feel I am slowly taking inspiration from those. I've started -reducing to a minimun what my /smart/-phone does, I've reduced the number of /gadgets/ -I've add over time. But in this posts, I am going to focus on my systems and tools β€” this -means Linux distributions, Emacs and other tools. - -* A little bit of history (or context) - -I've been using GNU/Linux for ages now. It has been around 22 years now that I discovered -Red Hat[fn:1] Linux, 5.2 at the time. I've use it as my primary operating system almost -ever sync. During that time, I've tried so many distribution: from [[https://en.wikipedia.org/wiki/Mandriva_Linux][Mandrake]] (later -Mandriva), to [[https://www.debian.org/][Debian]] (3.0 -> 9.0 on some servers) and [[https://www.ubuntu.com][Ubuntu]] (on the first ever public -release), with a long time on [[https://gentoo.org/][Gentoo]] and later [[https://www.archlinux.org/][Archlinux]]. I've used Gnome, KDE, wmii, -XMonad, awesome-vm. I've used bash, zsh, fish, switching from one to another multiple -times. I've used Emacs, vim, IntelliJ, Eclipse, VSCode… My gentoo/archlinux days made me -try and /use/ a lot of tools (screen, tmux, …) and customization. - -I had the habit to customize everything, down to the theme and icons I use. There was a -time where I would patch and re-package some GTK themes and icons set. *I had to -customize everything*, that was my learning experience. I would often break things and -have to re-install the system β€” and *it was fun* 😎. - -But the more I /grew/, the more I worked for different companies, the less time I had to -do this. I was also learning a lot about tests, reproducibility at that time. My /will/ to -customization and /instability/ slowly faded, I wanted to be able to focus on what -mattered, and not loose time on silly things that I would break soon after making it. - -* Minimalism and Digital Mininalism - -Let's try to define really quick what is minimalism and digital minimalism about.Digital -minimalism is Minimalism in the digital world, and [[https://www.theminimalists.com/minimalism/][minimalism]] can be seen as [[https://jamesstuber.com/minimalism-as-a-framework/][a Framework -for Decision Making]]. - -#+begin_quote -Every decision we make is constrained by limited resources. Money, materials, energy. Even -the richest man in the world is limited by time, and has to make decisions accordingly. - -Here are some factors that make minimalism well suited for decision making: - -- Practicing minimalism helps develop an improved ability to discern what’s important and - what’s not -- It becomes easier to let go of β€˜sort of important’ things -- What you choose to keep is a reflection of your values -#+end_quote - -In a gist, Digital Minimalism is about making conscious choice of what you use and what -you do in the digital world, a world of abundance (information, software, …). - -Let's take two examples: our desktops and our phones. - -On desktop, digital minimalism can be something like the follow blog post : [[http://neugierig.org/software/blog/2014/07/anti-dashboard-manifesto.html][Tech Notes: -The Anti-Dashboard Manifesto]]. - -#+begin_quote -Now take that reasoning further: It's not useful to reserve a portion of my screen for -displaying which applications are running, as the things that are running are visible and -the things that aren't visible can be found when necessary. There's no need for an icon -displaying wifi status; if I'm connected it's uninteresting and if I'm not connected I'll -surely discover it if I attempt to use the internet. - -I took this reasoning to its conclusion. I run my computers with the screen blank except -for the apps I run, and the apps I run I configure to display a minimal amount of -information. I think of this as being anti-dashboard: against the cognitive clutter of -extraneous information. My computer is not a cockpit and I am at my best when I'm only -thinking about the single task at hand. -#+end_quote - -This is taking things a little bit too far for me *but* it does resonate with me, and as -you'll see in the following articles (and on my configuration), I share some of Evan's -belief. By default, my desktop do only show a background (be an image or a color) and -anything related to time, battery and other status is a keybinding away (the =Win= key). - -And on the phone: [[https://robertheaton.com/2020/03/18/yourself-happier-iphone-worse/][How to make yourself happier by making your iPhone worse | Robert Heaton]] -β€” which I followed almost word for word, but this isn't the subject of this post. - -* Emacs, Nix, NixOS, =home-manager= and other tools - -How a GNU/Linux distribution like NixOS comes into this. First, let's see what I want : - -- A system that is tailored to my needs, that makes me efficient. -- A system that is customizable, to answer the previous item. I want to be able to modify - the default behavior if it doesn't suit me. -- A system that is reproductible and easy to replicate. If I change my hardware, or if I - need to re-install my system for /any reason/. -- A way to share configuration on my infra. This is probably the developer speaking, but I - want to write something once and re-use it several times. -- A developer environment that is reproductible, on-demand and /safe/. -- Use defaults of the software I use as much as possible. This one is tricky to achiev for - several reasons: - + I am used to customize everything (/easy to fix/) - + I use a custom keyboard layout ([[https://bepo.fr][bepo]]), so if I go with default keybinding, I will have - the wrong /muscle memory/ (and they could be not optimized either) -- Hackable tools that I can manipulate as I want. -- Use the right tool at the right time, trying not to reinvent the wheel. - -As it turns out Nix and NixOS are giving all the components required for my /almost/ ideal -setup. - -If you complete that with a /not really minimalist/ editor, called [[https://www.gnu.org/software/emacs/][GNU Emacs]], you've got a -pretty composable system without leaving Emacs. I'll dig more into my Emacs configuration -and my NixOS setup in different posts β€” and on the [[https://vincent.demeester.fr/configurations/][=configurations=]] pages β€” but my take -lately is to try to do with what is available (as built-in, in Emacs for example). After -reading the docs, if it's not sufficient, I may look for an external module or tool. - -* Conclusion - -This was a weird post that mixes up tooling, minimalism and thoughts. It's definitely not -what I initially envisioned. But I'm hoping it introduce a bit my take on things. - -* Links - -- [[http://neugierig.org/software/blog/2014/07/anti-dashboard-manifesto.html][Tech Notes: The Anti-Dashboard Manifesto]] -- [[https://blog.zdsmith.com/posts/digital-minimalism-for-the-working-hacker.html][Subset Park: Digital Minimalism for the Working Hacker]] -- [[https://jamesstuber.com/minimalism-as-a-framework/][Minimalism as a Framework for Decision Making | JamesStuber.com]] -- [[https://robertheaton.com/2020/03/18/yourself-happier-iphone-worse/][How to make yourself happier by making your iPhone worse | Robert Heaton]] - -* Footnotes - -[fn:1] And I now work for Red Hat Inc., what a journey 😝 diff --git a/www/vincent.demeester.fr/content/posts/2020-03-22-org-mode-website.org b/www/vincent.demeester.fr/content/posts/2020-03-22-org-mode-website.org @@ -1,562 +0,0 @@ -#+title: Migrating to an org-mode website -#+date: <2020-03-22 Sun> -#+filetags: orgmode website emacs -#+setupfile: ../templates/post.org - -* Introduction -:PROPERTIES: -:ID: 24a765bd-a0ed-42cf-b96c-db667f7d37e2 -:END: - -This is a story… a story of me changing the way I code and publish my website. In the -past, I've switch from [[https://vincent.demeester.fr/posts/2012-05-07-reinit-and-jekyll/][Jekyll]] to [[https://vincent.demeester.fr/posts/2015-05-01-orgmode-et-jekyll/]["=orgmode= and Jekyll"]] to [[https://vincent.demeester.fr/posts/2015-05-09-migration-to-hugo/][Hugo]] (sorry those are written -in french). The past year, I've written and documented myself a little bit about -[[https://www.theminimalists.com/minimalism/][minimalism]] and [[https://www.goodreads.com/book/show/40672036-digital-minimalism][digital minimalism]]. Although I don't see myself as a minimalist, it helped -me realize some issues I had. - -I also realized if I want to write more, I need to lower the barrier between writing and -publishing my content ; /if I want it to be published, of course/. This /post/ is about -what I'm putting in place for this, with a premise : I spend my life in [[https://www.gnu.org/software/emacs/][Emacs]] and thus in -[[https://orgmode.org/][=orgmode=]]. And [[https://orgmode.org/][=orgmode=]] is feature-full and has this badass feature : =org-publish=. - -To build and publish this website, we will /try/ to rely on a reproducible setup, meaning -[[https://www.gnu.org/software/emacs/][Emacs]] and [[https://orgmode.org/][=orgmode=]] of course, [[https://www.gnu.org/software/make/][GNU Make]] of course *but* most importantly, [[https://nixos.org/nix/][Nix]] (in the near -future πŸ‘Ό). - -:update: -There is now an article about it, that uses literate programming: [[../articles/meta_publishing_this_website.org][publishing this -website]]. The content of the post might no be up-to-date at some point. -:end: - - -* Requirements -:PROPERTIES: -:ID: 09991f8e-4257-443c-a3a3-40f449df4597 -:END: - -Let's list the requirements I feel I have for my website: - -- Full control over the URL of the published posts. :: - This is a golden rule of the web: should I change the publishing system, I want to be - able to stick to the same URLs or else all external references would be broken. This is - a big no-no and in my opinion it makes most blogging systems unacceptable. -- Top-notch Org support. :: - I believe generators like Jekyll and Hugo only have partial Org support. You end up - requiring some conversion tooling to get the job done. -- Simple publishing pipeline. :: - I want the generation process to be as simple as possible. This is important for - maintenance. Should I someday switch host, I want to be sure that I can set up the same - pipeline. -- Full control over the publishing system. :: - I want maximum control over the generation process. I don’t want to be restricted by a - non-Turing-complete configuration file or a dumb programming language. -- Ease of use. :: - The process as a whole must be as immediate and friction-less as possible, or else I - take the risk of feeling too lazy to publish new posts and update the content. -- Hackability. :: - Last but not least, and this probably supersedes all other requirements: The system must - be hackable. Lisp-based systems are prime contenders in that area. - -* DONE Organizing -CLOSED: [2020-03-21 Sat 17:57] -:PROPERTIES: -:ID: 78251967-c48a-476d-bc39-c748fc412927 -:END: -:LOGBOOK: -- State "DONE" from "TODO" [2020-03-21 Sat 17:57] -:END: - -Let's describe what I do want to publish here: - -- Posts :: this is what we call /blog/ these days : short to medium article that are valid - at a point of time, as may contain /deprecated/ content, or content that do not reflect - my views at a later point in time. -- Articles :: medium to long article about a topic. Those should be up-to-date or - explicitly mark as deprecated or invalid. /In a ideal world this is my *ready for the - public* knowledge database, a bit like [[https://braindump.jethro.dev/][Jethro's Braindump]]/. -- Configurations :: medium to long article about my configurations. Those are base on my - ~home~ /configuration/ mono-repository, and usually follow literate programming - principles. -- About :: an about page about the author of the website (aka [[https://vincent.demeester.fr][me]]), linking external - contributions (GitHub/Gitlab/… profiles, Talks, …). - -* DONE Publishing -CLOSED: [2020-03-25 Wed 15:54] -:PROPERTIES: -:ID: da542763-78b9-46c0-bcf7-d8d2a2b18677 -:END: -:LOGBOOK: -- State "DONE" from "TODO" [2020-03-25 Wed 15:54] -:END: - -As said above, the goal is to publish everything using only [[https://www.gnu.org/software/emacs/][Emacs]] and [[https://orgmode.org/][=orgmode=]] (with the -help of some standard GNU tools). - -The [[file:~/src/sbr/publish.el][~publish.el~]] file is where all the magic happens: - -- I want to generate something that is ~html5~ (almost?). The /preamble/, /content/ and - /postamble/ ~div~ can be customized using ~org-html-div~. Same goes for the /container/ - element that is used for "wrapping top level sections", it is customized using - ~org-html-container-helement~ (we want ~<section>~). There is a few additional variable - that I might document one day πŸ˜›. - - #+begin_src emacs-lisp - (setq org-export-use-babel nil) - (setq org-link-abbrev-alist '(("att" . org-attach-expand-link))) - - ;; setting to nil, avoids "Author: x" at the bottom - (setq org-export-with-section-numbers nil - org-export-with-smart-quotes t - org-export-with-toc nil) - - (defvar sbr-date-format "%b %d, %Y") - - (setq org-html-divs '((preamble "header" "top") - (content "main" "content") - (postamble "footer" "postamble")) - org-html-container-element "section" - org-html-metadata-timestamp-format sbr-date-format - org-html-checkbox-type 'unicode - org-html-html5-fancy t - org-html-doctype "html5" - org-html-htmlize-output-type 'css - org-html-htmlize-font-prefix "org-" - org-src-fontify-natively t - org-html-coding-system 'utf-8-unix) - #+end_src - -- Part of the ~<head>~, preamble and postamble are customized for the website. - + ~head~ :: - #+begin_src emacs-lisp - (defvar sbr-website-html-head - "<link rel='icon' type='image/x-icon' href='/images/favicon.ico'/> - <meta name='viewport' content='width=device-width, initial-scale=1'> - <link rel='stylesheet' href='/css/new.css' type='text/css'/> - <link rel='stylesheet' href='/css/syntax.css' type='text/css'/> - <link href='/index.xml' rel='alternate' type='application/rss+xml' title='Vincent Demeester' />") - #+end_src - + premable :: - #+begin_src emacs-lisp - (defun sbr-website-html-preamble (plist) - "PLIST: An entry." - ;; Skip adding subtitle to the post if :KEYWORDS don't have 'post' has a - ;; keyword - (when (string-match-p "post" (format "%s" (plist-get plist :keywords))) - (plist-put plist - :subtitle (format "Published on %s by %s." - (org-export-get-date plist sbr-date-format) - (car (plist-get plist :author))))) - - ;; Below content will be added anyways - "<nav> - <img src=\"/images/favicon.ico\" id=\"sitelogo\"/> <a href='/'>home</a> / - <a href='/posts/'>posts</a> (<a href='/index.xml'>rss</a>) / - <a href='/articles/'>articles</a> / - <a href='/configurations/'>configurations</a> / - <a href='https://dl.sbr.pm/'>files</a> / - <a href='/about/'>about</a></li> - </nav>") - #+end_src - + postamble :: - #+begin_src emacs-lisp - (defvar sbr-website-html-postamble - "<footer> - <span class='questions'>Questions, comments ? Please use my <a href=\"https://lists.sr.ht/~vdemeester/public-inbox\">public inbox</a> by sending a plain-text email to <a href=\"mailto:~vdemeester/public-inbox@lists.sr.ht\">~vdemeester/public-inbox@lists.sr.ht</a>.</span> - <span class='opinions'>Opinions stated here are my own and do not express the views of my employer, spouse, children, pets, neighbors, secret crushes, favorite authors, or anyone else who is not me. And maybe not even me, depending on how old this is.</span> - <span class='copyright'> - Content and design by Vincent Demeester - (<a rel='licence' href='http://creativecommons.org/licenses/by-nc-sa/3.0/'>Some rights reserved</a>) - </span><br /> - <span class='engine'> - Powered by <a href='https://www.gnu.org/software/emacs/'>Gnu Emacs</a> and <a href='https://orgmode.org'>orgmode</a> - </span> - </footer>") - #+end_src -- =orgmode= is able to generate a site-map. This is what we are going to use to generate - the index files for ~posts/~ and ~articles~ mainly. - #+begin_src emacs-lisp - (defun sbr/org-sitemap-format-entry (entry style project) - "Format posts with author and published data in the index page. - - ENTRY: file-name - STYLE: - PROJECT: `posts in this case." - (cond ((not (directory-name-p entry)) - (format "%s β€” [[file:%s][%s]] - :PROPERTIES: - :PUBDATE: [%s] - :END:" - (format-time-string "%Y-%m-%d" - (org-publish-find-date entry project)) - entry - (org-publish-find-title entry project) - (format-time-string "%Y-%m-%d" - (org-publish-find-date entry project)))) - ((eq style 'tree) (file-name-nondirectory (directory-file-name entry))) - (t entry))) - - (defun sbr/org-publish-sitemap (title list) - "" - (concat "#+TITLE: " title "\n\n" - (org-list-to-subtree list))) - #+end_src -- =orgmode= is able to generate a rss, with =ox-rss=. This is what we are going to use to - generate the rss files for ~posts~ and ~articles~. This is _heavily_ customized. - #+begin_src emacs-lisp - (defun sbr/org-get-first-paragraph (file) - "Get string content of first paragraph of file." - (ignore-errors - (with-temp-buffer - (insert-file-contents file) - (goto-char (point-min)) - (show-all) - (let ((first-begin (progn - (org-forward-heading-same-level 1) - (next-line) - (point))) - (first-end (progn - (org-next-visible-heading 1) - (point)))) - (buffer-substring first-begin first-end))))) - - (defun sbr/org-rss-publish-to-rss (plist filename pub-dir) - "Prepare rss.org file before exporting." - (let* ((postsdir (plist-get plist :base-directory))) - (with-current-buffer (find-file filename) - (erase-buffer) - (insert "#+TITLE: Posts\n") - (insert "#+AUTHOR: Vincent Demeester\n") - (insert "#+OPTIONS: toc:nil\n") - (let* ((files-all - (reverse (directory-files "." nil - "[0-9-]+.*\\.org$"))) - (files (subseq files-all 0 (min (length files-all) 30)))) - (message (format "foo: %s" filename)) - (dolist (post files) - (let* ((post-file post) - (post-title (org-publish-find-title post-file plist)) - (preview-str (sbr/org-get-first-paragraph post-file)) - (date (replace-regexp-in-string - "\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\)-.*" - "\\1" post))) - (insert (concat "* [[file:" postsdir "/" post "][" post-title "]]\n\n")) - (org-set-property "ID" post) - (org-set-property "RSS_TITLE" post-title) - ;; ox-rss prepends html-link-home to permalink - (org-set-property "RSS_PERMALINK" - (concat postsdir "/" - (file-name-sans-extension post) - ".html")) - (org-set-property - "PUBDATE" - (format-time-string - "<%Y-%m-%d %a %H:%M>" - (org-time-string-to-time - (replace-regexp-in-string - "\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\)-.*" - "\\1" post)))) - (insert preview-str) - (newline 1) - (insert (concat "[[file:" postsdir "/" post "][(Read more)]]\n\n")))) - (save-buffer)))) - (let ((user-mail-address "t") - (org-export-with-broken-links t) - (org-rss-use-entry-url-as-guid nil)) - (org-rss-publish-to-rss plist filename pub-dir))) - #+end_src -- Finally let's set the ~org-publish-project-alist~ to publish our projects - #+begin_src emacs-lisp - (setq org-publish-project-alist - `(("posts" - :base-directory "posts" - :base-extension "org" - :recursive t - :publishing-function org-html-publish-to-html - :publishing-directory "./public/posts" - :exclude ,(regexp-opt '("README.org" "draft")) - :auto-sitemap t - :with-footnotes t - :with-toc nil - :with-drawers t - :sitemap-filename "index.org" - :sitemap-title "Posts" - :sitemap-format-entry sbr/org-sitemap-format-entry - :sitemap-style list - :sitemap-sort-files anti-chronologically - :sitemap-function sbr/org-publish-sitemap - :html-head-include-scripts nil - :html-head-include-default-style nil - :html-head ,sbr-website-html-head - :html-preamble sbr-website-html-preamble - :html-postamble ,sbr-website-html-postamble) - ("posts-rss" - :base-directory "posts" - :base-extension "org" - :recursive t - :html-link-home "https://vincent.demeester.fr/" - :rss-link-home "https://vincent.demeester.fr/posts/" - :html-link-use-abs-url t - :rss-extension "xml" - :publishing-directory "./public" - :publishing-function (sbr/org-rss-publish-to-rss) - :section-number nil - :exclude ".*" - :include ("index.org")) - ("articles" - :base-directory "articles" - :base-extension "org" - :recursive t - :publishing-function org-html-publish-to-html - :publishing-directory "./public/articles" - :exclude ,(regexp-opt '("README.org" "draft")) - :auto-sitemap t - :with-footnotes t - :with-toc nil - :with-drawers t - :sitemap-filename "sitemap.org" - :sitemap-title "Articles" - :sitemap-style tree - :sitemap-sort-files anti-chronologically - ;;:sitemap-format-entry sbr/org-sitemap-format-entry - ;;:sitemap-function sbr/org-publish-sitemap - :html-head-include-scripts nil - :html-head-include-default-style nil - :html-head ,sbr-website-html-head - :html-preamble sbr-website-html-preamble - :html-postamble ,sbr-website-html-postamble) - ("articles-assets" - :exclude ,(regexp-opt '("*.org")) - :base-directory "articles" - :base-extension ,site-attachments - :publishing-directory "./public/articles" - :publishing-function org-publish-attachment - :recursive t) - ("about" - :base-directory "about" - :base-extension "org" - :exclude ,(regexp-opt '("README.org" "draft")) - :index-filename "index.org" - :recursive nil - :with-footnotes t - :with-toc nil - :with-drawers t - :publishing-function org-html-publish-to-html - :publishing-directory "./public/about" - :html-head-include-scripts nil - :html-head-include-default-style nil - :html-head ,sbr-website-html-head - :html-preamble sbr-website-html-preamble - :html-postamble ,sbr-website-html-postamble) - ("index" - :base-directory "" - :base-extension "org" - :exclude ,(regexp-opt '("README.org" "draft")) - :index-filename "index.org" - :recursive nil - :with-footnotes t - :with-toc nil - :with-drawers t - :with-title nil - :publishing-function org-html-publish-to-html - :publishing-directory "./public" - :html-head-include-scripts nil - :html-head-include-default-style nil - :html-head ,sbr-website-html-head - :html-preamble sbr-website-html-preamble - :html-postamble ,sbr-website-html-postamble) - ("css" - :base-directory "./css" - :base-extension ,site-attachments - :recursive t - :publishing-directory "./public/css" - :publishing-function org-publish-attachment - :recursive t) - ("images" - :base-directory "./images" - :base-extension ,site-attachments - :publishing-directory "./public/images" - :publishing-function org-publish-attachment - :recursive t) - ("assets" - :base-directory "./assets" - :base-extension ,site-attachments - :publishing-directory "./public/assets" - :publishing-function org-publish-attachment - :recursive t) - ("legacy" - :base-directory "./legacy" - :base-extension ,site-attachments - :publishing-directory "./public/" - :publishing-function org-publish-attachment - :recursive t) - ("all" :components ("posts" "about" "index" "articles" "articles-assets" "css" "images" "assets" "legacy" "posts-rss")))) - #+end_src - -Here are some /inspiration/ I took for this publishing code: - -- [[https://thibaultmarin.github.io/blog/posts/2016-11-13-Personal_website_in_org.html][Personal website in org]] -- [[https://vicarie.in/posts/blogging-with-org.html][Blogging with Org publishing]] -- [[https://ambrevar.xyz/blog-architecture/][A blog in pure Org/Lisp]] -- [[https://zngguvnf.org/2017-07-13--blogging-with-org-static-blog.html][Blogging with org-static-blog]] -- [[https://bastibe.de/2013-11-13-blogging-with-emacs.html][Blogging with Emacs]] -- [[https://gjhenrique.com/meta.html][Blogging with org-mode and Gitlab Pages]] - - -* DONE Styling -CLOSED: [2020-03-23 Mon 19:02] -:PROPERTIES: -:ID: 052e297f-01b2-41d3-9689-cd2946b56cfb -:END: -:LOGBOOK: -- State "DONE" from "STARTED" [2020-03-23 Mon 19:02] -:END: - -The style of the website has be as simple as possible, and also really light. This means: -- use default system font as much as possible -- have a small stylesheet, rely on the default as much as we can - -In addition, I want support for: -- side notes -- code syntax highlight -- table of content - -The inspiration for this website, in term of style are the following: -- [[https://vincent.demeester.fr/posts/2018-08-16-gotest-tools-assertions/][Vincent Demeester]] -- [[https://braindump.jethro.dev/zettels/zettelkasten/][Jethro's Braindump]] -- [[https://hamberg.no/gtd/][GTD in 15 minutes – A Pragmatic Guide to Getting Things Done]] -- [[https://www.inkandswitch.com/local-first.html][Local-first software: You own your data, in spite of the cloud]] -- [[https://archive.casouri.cat/note/2018/blog-in-org-mode-revisited/index.html][Blog in Org Mode, Revisited]] -- [[https://kind.sigs.k8s.io/][kind]] -- [[http://willcrichton.net/notes/idioms-of-dynamic-languages/][Idioms of Dynamic Languages | Will Crichton]] -- [[https://peter.bourgon.org/blog/2019/09/11/programming-with-errors.html][Peter Bourgon Β· Programming with errors]] -- [[https://johv.dk/blog/bare-metal-assembly-tutorial.html][Getting started with bare-metal assembly β€” Jonas Hvid]] - -To be able to define the style a bit, let's try some things below. From this point on, -this is random content just to try my style out. πŸ‘Ό - -There is more in [[../articles/sandbox.org][the sandbox]]. - ------- - -#+begin_abstract -Let's dig into how I setup my development environment when working on ~tektoncd/pipeline~ -#+end_abstract - -#+TOC: headlines 2 - -** sub-heading 1 - -Checking for errors is *very common* in Go, having =Comparison= function for it was a requirement. - -#+BEGIN_aside -This is a side note. If collection is a string, item must also be a string, and is -compared using =strings.Contains()=. If collection is a Map, contains will succeed if item -is a key in the map. -#+END_aside - -- =Error= fails if the error is =nil= *or* the error message is not the expected one. -- =ErrorContains= fails if the error is =nil= *or* the error message does not contain the expected substring. -- =ErrorType= fails if the error is =nil= *or* the error type is not the expected type. - -Let's first look at the most used : =Error= and =ErrorContains=. -When you're working on ~pipeline~, usually you want : - -1. make sure it compiles : ~go build -v ./..~ -2. Running unit tests : ~go test ./...~ (bonus use [[https://github.com/vdemeester/ram][~ram~]] for continuous testing) -3. End-to-end tests : ~go test -tags e2e ./...~ (or simply using `./test/` package) - - *Make sure you re-deploy before running the e2e tests* using ~ko apply -f ./config~, - otherwise you're testing the wrong code. - -#+BEGIN_SRC go - var err error - // will fail with : expected an error, got nil - assert.Check(t, cmp.Error(err, "message in a bottle")) - err = errors.Wrap(errors.New("other"), "wrapped") - // will fail with : expected error "other", got "wrapped: other" - assert.Check(t, cmp.Error(err, "other")) - // will succeed - assert.Check(t, cmp.ErrorContains(err, "other")) -#+END_SRC - -#+CAPTION: This is the caption for the next figure link (or table) -#+NAME: fig:SED-HR4049 -#+ATTR_ORG: :width 400/600 -#+ATTR_HTML: :width 100% -[[../images/emacs/2020-02-29-13-46-08.png]] - -If this is the case, then what makes dynamic languages feel easy? Can we take what we learn in answering this question and improve the ergonomics of our static languages? For example, in 2018, I don’t think there’s as strong an argument for β€œyou don’t have to write out the types,” since modern type inference eliminates most of the keystrokes required (even though many major languages still lack such facilities). Plus, saving a few keystrokes does not seem like a critical bottleneck in the programming process. - -#+NAME: fig:tektoncd-logo -[[../images/emacs/2020-02-29-14-41-59.png]] - - -** sub-heading 2 - -Some content from my other org-mode files. - -#+begin_description -I already wrote 2 previous posts about golang and testing. It's something I care deeply about and I wanted to continue -writing about it. It took me a bit more time than I thought, but getting back to it. Since the [[http://vincent.demeester.fr/posts/2017-04-22-golang-testing-golden-file/][last post]], Daniel -Nephin -and I worked (but mainly Daniel πŸ€—) on bootstrapping a testing helper library. -#+end_description - -#+BEGIN_QUOTE -Package assert provides assertions for comparing expected values to actual values. When -assertion fails a helpful error message is printed. -#+END_QUOTE - -There is already some good =testing= helpers in the Go ecosystem : [[https://github.com/stretchr/testify][=testify=]], [[http://labix.org/gocheck][=gocheck=]], -[[https://github.com/onsi/ginkgo][=ginkgo=]] and a lot more β€” so why create a new one ? There is multiple reason for it, most -of them can be seen in the following [[https://github.com/gotestyourself/gotest.tools/issues/49#issuecomment-362436026][GitHub issue]]. - -[[https://github.com/dnephin/][Daniel]] also wrote a very useful [fn:1]converter if your code base is currently using =testify= : -=gty-migrate-from-testify=. - -#+BEGIN_SRC sh -$ go get -u gotest.tools/assert/cmd/gty-migrate-from-testify -# […] -$ go list \ - -f '{{.ImportPath}} {{if .XTestGoFiles}}{{"\n"}}{{.ImportPath}}_test{{end}}' \ - ./... | xargs gty-migrate-from-testify -#+END_SRC - -We'll Use =Assert= for the rest of the section but any example here would work with -=Check= too. When we said =Comparison= above, it's mainly the [[https://godoc.org/gotest.tools/assert#BoolOrComparison][BoolOrComparison]] interface β€” -it can either be a boolean expression, or a [[https://godoc.org/gotest.tools/assert/cmp#Comparison][cmp.Comparison]] type. =Assert= and =Check= code -will be /smart/ enough to detect which one it is. - -#+BEGIN_SRC go - assert.Assert(t, ok) - assert.Assert(t, err != nil) - assert.Assert(t, foo.IsBar()) -#+END_SRC - - -* What's next ? -:PROPERTIES: -:ID: 678bf6cb-68e7-4fc9-af50-23d283293ab1 -:END: - -One thing is to import old blog posts from [[https://vincent.demeester.fr][vincent.demeester.fr]]. This is easily done with -[[https://pandoc.org/][Pandoc]] and a small bash loop β€” and some manual adjusting later on πŸ˜›. - -#+begin_src bash -for post in ~/src/github.com/vdemeester/blog/content/posts/*.md; do - pandoc -f markdown -t org -o posts/$(basename -s .md ${post}).org ${post} -done -#+end_src - - -What is still /to do/ after this initial take. - -- [ ] List =FILETAGS= for taximony -- [ ] Maybe use [[https://css-tricks.com/snippets/css/complete-guide-grid/][css grid]] for the UI - -* Footnotes -:PROPERTIES: -:ID: 220a0cce-39c3-4b5f-aaa0-66e3a2450f04 -:END: - -[fn:1] foo is bar, bar is baz diff --git a/www/vincent.demeester.fr/content/posts/2020-04-15-emacs-bankruptcy-is-fun.org b/www/vincent.demeester.fr/content/posts/2020-04-15-emacs-bankruptcy-is-fun.org @@ -1,108 +0,0 @@ -#+title: Emacs bankruptcy is fun -#+date: <2020-04-15 Wed> -#+filetags: emacs configuration optimization -#+setupfile: ../templates/post.org - -* Introduction - -Since go 1.14 go released, I've had a broken =go-mode= setup on my Emacs. I was using -=lsp-mode= and =gopls= and well, the update broke everything. I initally try to fix it but -I made it worse. At the same time, I started to get fed up with some performance issue of -my configuration and how slow my Emacs starts, about 6s. - -I, thus, declared my third Emacs bankruptcy, =:disabled= everything and slowly started -from scratch, with the following goal: - -- Have it start quick, as less than a second, not too much more than =emacs -Q= would -- Disable anything that I don't use often initially -- Try to use as much built-in as possible (example: using =icomplete= instead of - =ivy=/=counsel=) - -* Do I really need this feature - -Following [[https://protesilaos.com/][Protesilaos Stavrou]]'s emacs videos (and [[https://protesilaos.com/dotemacs/][=dotemacs=]]) for a while now, I have a -tendency to try to use built-in feature as much as possible. The most obvious example is -using =icomplete= instead of =ivy=/=counsel=. - -When I started my /bankruptcy/, I disabled every single customization I had, either using -=:disabled= when using =use-package= *or* the =(when nil)= /hack/. I then started Emacs -and acted on what was missing : - -1. Do I really miss it ? An example would be, at least initially, the completion in a =go= - file. I do miss it, but I miss it *way less* than having Emacs lagging because of - =lsp-mode= and showing me wrong completion. -2. Is there a built-in option to what I previously used ? Here, the =icomplete= example - fits well, or =isearch= instead of =swiper=. -3. Do I need it at startup or /on-demand/ ? - -* Looking into what takes time - -In "Advanced Techniques for Reducing Emacs Startup Time"[fn:1], I discovered the [[https://github.com/jschaf/esup][=esup=]] -emacs library. In a gist, this is a profiler for Emacs. It starts a new Emacs instance and -look at the loading time. - -#+CAPTION: esup "result" view -[[../images/2020-04-15-16-12-54.png]] - -Then, you can do a simple loop: - -- Run =esup= -- Look at the top result -- Fix it (lazy load, removing the feature, …) -- Re-iterate - -* Loading on-demand - -Once you have the setup to know what takes time and what not, it's time to look into how -to load the most thing on demand. - -For this, [[https://github.com/jwiegley/use-package][=use-package=]] is amazing, it tremendously help autoloading modules on-demand. If -you are not using =use-package=, usually you are using =require=, which loads the -underlying source file (all of it). - -With =use-package=, there is multiple ways to load on demand: - -- =:commands= to add callable that will trigger loading the package -- =:bind=, =:bind*=, =:bind-keymap= and =:bind-keymap*= to bind key sequences to the - global keymap or a specific keymap. -- =:mode= and =:interpreter= establish a deferred binding with the =auto-mode-alist= and - =interpreter-mode-alist=. -- =:hook= allows adding functions onto the package hook -- =:defer= is the most generic one, all the previous keyword imply =:defer=. You can - specify a number of second of idle to load the package. - -#+BEGIN_aside -In a gist for =org-babel=, use =use-package ob-python= and never call =org-babel-do-languages=. -#+END_aside -Once this is done, you are left with edge cases, like =org-babel-do-languages=. Those are -to be handle case by case. The good thing about those cases is that you'll learn what -those function do and this will give you an even better understanding of what is -happening. - -Doing this exercise also forces you to make you see if you really use that feature or -not. I ended up removing entire feature from my configuration because they were taking -quite some time to load, and was used almost never. Instead I am forcing myself to learn -more what I can do with the built-in features first. - -* Conclusion - -All in all, this /bankruptcy/ was the most fun I had to do. I consider myself still in the -process but the base is there. - -1. I learned a lot ! -2. My Emacs starts in 0.6s against previously in 5s β€” =emacs -q= starts in about 0.3s so - there is still a little bit of room for improvement. -3. I discovered / re-discovered a lot of built-in feature -4. I started documenting my configuration, see [[../configurations/emacs.org][here]]. - -πŸŽ‰ - -:update: -Well, I've look into the /portable dump/ feature of Emacs, thanks to [[https://archive.casouri.cat/note/2020/painless-transition-to-portable-dumper/index.html][Painless Transition -to Portable Dumper]]. And I am now down to =0.091s= for the startup. There is a few gotchas -with /portable dump/, I'll try to write about it later. -:end: - -* Footnotes - -[fn:1]: [[https://blog.d46.us/advanced-emacs-startup/][Advanced Techniques for Reducing Emacs Startup Time]] diff --git a/www/vincent.demeester.fr/content/posts/2020-06-21-website-update.org b/www/vincent.demeester.fr/content/posts/2020-06-21-website-update.org @@ -1,16 +0,0 @@ -#+title: website update -#+date: <2020-06-08 Mon> -#+filetags: website css new simple -#+setupfile: ../templates/post.org - -* Introduction - -A really small article to talk about small updates on the website (/well, maybe not that -small/). In a gist: a new css and articles changes. - -- Updating the style of the website to something really similar to [[https://newcss.net/][new.css]]. It is way - simpler and /pleasing/ (at least for me). -- My [[../articles][=/articles=]] are now exporting from my [[../articles/personal_knowledge_base.org][personal knowledge base]], powered by - [[../articles/org_roam.org][=org-roam=]]. -- A new [[https://dl.sbr.pm/][=/files=]] part is available, where I share some random file. See for example the - [[https://dl.sbr.pm/wallpapers/dynamics/][dynamic wallpaper]] part. diff --git a/www/vincent.demeester.fr/content/posts/2020-07-08-june-status-update.org b/www/vincent.demeester.fr/content/posts/2020-07-08-june-status-update.org @@ -1,109 +0,0 @@ -#+title: Status update, June 2020 -#+date: <2020-07-08 Wed> -#+filetags: status update -#+setupfile: ../templates/post.org - -* Introduction - -Time for the first new monthly status update! I do like those updates from [[https://drewdevault.com/2020/06/15/Status-update.html][Drew DeVault]] -and [[https://emersion.fr/blog/2020/status-update-19/][Simon Ser]], so I figured, why not trying myself πŸ™ƒ. I am not sure where to start and -where to end, but I guess I'll figure things out as I go. - -* Tekton & OpenShift Pipelines - -As you know, /or may not/, I am working on the [[https://github.com/tektoncd/][TektonCD]] project and also on our [[https://redhat.com][RedHat]] -product [[https://www.openshift.com/learn/topics/pipelines][OpenShift Pipelines]]. As far as the month of June went : - -- We release [[https://github.com/tektoncd/pipeline/releases/tag/v0.13.0][v0.13.0]] "Bobtail Bishop" and a bunch of fixes ([[https://github.com/tektoncd/pipeline/releases/tag/v0.13.2][v0.13.2]]) - + It's the second release after the =v1beta1= bump, and we start to stabilise things. - + [[https://github.com/tektoncd/pipeline/releases/tag/v0.14.0][v0.14.0]] will (and actually is already) a more feature-packed release because there is - now the =finally= field support and cloud-events opt-in. -- But the *most important* contributions I tried to make during that month is the TEP - process. *TEP* stands for *Tekton Enhancement proposals*. - - #+begin_src markdown - # Tekton Enhancement Proposals (TEPs) - - A Tekton Enhancement Proposal (TEP) is a way to propose, communicate - and coordinate on new efforts for the Tekton project. You can read - the full details of the project in - [TEP-1](https://github.com/tektoncd/community/blob/master/teps/0001-tekton-enhancement-proposal-process.md). - - ## What is a TEP - - A standardized development process for Tekton is proposed in order to - - - provide a common structure and clear checkpoints for proposing - changes to Tekton - - ensure that the motivation for a change is clear - - allow for the enumeration stability milestones and stability - graduation criteria - - persist project information in a Version Control System (VCS) for - future Tekton users and contributors - - support the creation of _high value user facing_ information such - as: - - an overall project development roadmap - - motivation for impactful user facing changes - - ensure community participants are successfully able to drive changes - to completion across one or more releases while stakeholders are - adequately represented throughout the process - - This process is supported by a unit of work called a Tekton - Enhancement Proposal (TEP). A TEP attempts to combine aspects of the - following: - - - feature, and effort tracking document - - a product requirements document - - design document - - into one file which is created incrementally in collaboration with one - or more [Working - Groups](https://github.com/tektoncd/community/blob/master/working-groups.md) - (WGs). - - This process does not block authors from doing early design docs using - any means. It does not block authors from sharing those design docs - with the community (during Working groups, on Slack, GitHub, …. - - ,**This process acts as a requirement when a design docs is ready to be - implemented or integrated in the `tektoncd` projects**. In other words, - a change that impact other `tektoncd` projects or users cannot be - merged if there is no `TEP` associated with it. - - This TEP process is related to - - the generation of an architectural roadmap - - the fact that the what constitutes a feature is still undefined - - issue management - - the difference between an accepted design and a proposal - - the organization of design proposals - - This proposal attempts to place these concerns within a general - framework. - - - See [TEP-1](https://github.com/tektoncd/community/blob/master/teps/0001-tekton-enhancement-proposal-process.md) for more details. - #+end_src - -* =home=, Nixos and the rest of thing - -I did some big changes in my [[https://git.sr.ht/~vdemeester/home][=home=]] repository, it's still much a work-in-progress but it -is in a way better state than before. - -- It is more reproductible. All dependencies are managed by [[https://github.com/nmattia/niv][niv]] and all machines are using - a pinned version of channels from there. That way I can test the configuration (on the - CI) and cache the packages for all channels that my machines uses. I also can decide - when I want to upgrade a particular channel (nixos, unstable, …). -- I am slowly experimenting on simplifying things more and more. This is a bit related to - the [[file:2020-02-22-digital-minimalism.org][previous post]]. I am trying to use Gnome3 everywhere. Well configured, managed by - NixOS, it's enough for my test and reduce the configuration cruft I need to do. - -An ongoing work is my knowledge base and how it is published as part of this website : -[[https://vincent.demeester.fr/articles/][articles]]. I am trying to make all those publishable (for the one that do not hold any -secret). I'll document this a bit more at some point but… -- Configurations are now part of that knowledge base, the =docs= folder of [[https://git.sr.ht/~vdemeester/home][=home=]] is going - away. -- I am trying to use litterate configuration as much as possible. So slowly, the content - from [[https://git.sr.ht/~vdemeester/home][=home=]] will be a tangled version of my knowledge base. At least that is the goal as - of today. - -And I feel that's all /for June/. diff --git a/www/vincent.demeester.fr/content/posts/2020-12-01-nixify-www.draft b/www/vincent.demeester.fr/content/posts/2020-12-01-nixify-www.draft @@ -1,8 +0,0 @@ -#+title: Nixifying this website -#+date: <2020-12-01 Fri> -#+filetags: website nix -#+setupfile: ../templates/post.org - -* Introduction - -… diff --git a/www/vincent.demeester.fr/content/posts/diving-into-nix.org.draft b/www/vincent.demeester.fr/content/posts/diving-into-nix.org.draft @@ -1,8 +0,0 @@ -#+TITLE: Diving into Nix(OS) β€” introduction -#+SUBTITLE: Series about nixos -#+SETUPFILE: ../templates/post.org - -* TODO Introduction - -This will be the start of a serie of blog post around Nix, Nixpkgs and NixOS. The goal is -to present the pros and cons of Nix and how it benefits to use it in different use cases. diff --git a/www/vincent.demeester.fr/content/posts/quick-kubernetes-provisionning.org.draft b/www/vincent.demeester.fr/content/posts/quick-kubernetes-provisionning.org.draft @@ -1,5 +0,0 @@ -#+TITLE: Quickly provision Kubernetes -#+SUBTITLE: Experiment with different tools to see what is the most efficient way to provision a multi-node kubernetes cluster -#+SETUPFILE: ../templates/post.org - -* TODO Introduction diff --git a/www/vincent.demeester.fr/content/sitemap.org b/www/vincent.demeester.fr/content/sitemap.org @@ -0,0 +1,14 @@ +#+TITLE: content + +- 2022-05-07 β€” [[file:index.org][Vincent Demeester]] + :PROPERTIES: + :PUBDATE: [2022-05-07 Sat] + :END: +- 2022-05-05 β€” [[file:posts/2022-05-05-random.org][Random thoughts after 2 years]] + :PROPERTIES: + :PUBDATE: [2022-05-05 Thu] + :END: +- 2022-04-25 β€” [[file:about/index.org][About me]] + :PROPERTIES: + :PUBDATE: [2022-04-25 Mon] + :END:+ \ No newline at end of file diff --git a/www/vincent.demeester.fr/lib/publish.el b/www/vincent.demeester.fr/lib/publish.el @@ -15,13 +15,13 @@ :base-extension "org" :recursive t :publishing-function org-html-publish-to-html - :publishing-directory "./public/posts" - :exclude ,(regexp-opt '("README.org" "draft" "legacy")) + :publishing-directory "./public" + :exclude ,(regexp-opt '("README.org" "draft" "legacy" "articles")) :auto-sitemap t :with-footnotes t :with-toc nil :with-drawers t - :sitemap-filename "index.org" + :sitemap-filename "sitemap.org" :sitemap-title "content" :sitemap-format-entry sbr/org-sitemap-format-entry :sitemap-style list @@ -157,7 +157,7 @@ :publishing-directory "./public/" :publishing-function org-publish-attachment :recursive t) - ("all" :components ("posts" "about" "index" "articles" "articles-assets" "css" "images" "assets" "legacy" "posts-rss")))) + ("all" :components ("content" "posts" "about" "index" "articles" "articles-assets" "css" "images" "assets" "legacy" "posts-rss")))) (defun publish () "Build vincent.demeester.fr website"