main
1<!DOCTYPE html>
2<html lang="en">
3<head>
4<!-- Oct 07, 2022 -->
5<meta charset="utf-8" />
6<meta name="viewport" content="width=device-width, initial-scale=1" />
7<title>emacs: Managing projects</title>
8<meta name="author" content="Vincent Demeester" />
9<meta name="generator" content="Org Mode" />
10<link rel='icon' type='image/x-icon' href='/images/favicon.ico'/>
11<meta name='viewport' content='width=device-width, initial-scale=1'>
12<link rel='stylesheet' href='/css/new.css' type='text/css'/>
13<link rel='stylesheet' href='/css/syntax.css' type='text/css'/>
14<link href='/index.xml' rel='alternate' type='application/rss+xml' title='Vincent Demeester' />
15</head>
16<body>
17<main id="content" class="content">
18<header>
19<h1 class="title">emacs: Managing projects</h1>
20</header><p>
21Working on <b>project</b> a key part of my workflow when using <a href="emacs.html">GNU/Emacs</a>. Almost everything I
22work on can be part of a project. It might be simpler to give examples:
23<a href="https://github.com/tektoncd/pipeline"><code>tektoncd/pipeline</code></a> checked out is a project, my <code>~/desktop/org</code> is another project. There
24is only a handful of buffer in Emacs that I do not consider of any project, one example is
25the <a href="org_mode.html">org-mode</a> agenda.
26</p>
27
28<p>
29In a <b>project</b>, I want to be able to:
30</p>
31
32<ul class="org-ul">
33<li>List <b>all</b> files in that project
34<ul class="org-ul">
35<li><i>optionally</i> spiking the files from <code>.gitignore</code></li>
36</ul></li>
37<li>Search in all <b>project</b> files
38<ul class="org-ul">
39<li>using <code>ripgrep</code> or something else</li>
40<li>possibly to a search and replace</li>
41</ul></li>
42<li>Run commands on the project <span class="underline">root</span> folder
43<ul class="org-ul">
44<li>could be a compilation, some tests, some random commands</li>
45</ul></li>
46<li>Manage the version control (using <code>magit</code>)
47<ul class="org-ul">
48<li>adding files, switch branches, …</li>
49</ul></li>
50<li>Switch between project buffers</li>
51</ul>
52
53<p>
54Emacs 27.1 ships with a <code>project</code> library that has some useful function. But prior to this,
55the <a href="https://github.com/bbatsov/projectile"><code>projectile</code></a> project has been the way to go for managing projects in
56<a href="emacs.html">GNU/Emacs</a>. <a href="https://github.com/bbatsov/projectile"><code>projectile</code></a> is also quite extensible and integrates relatively well with a
57bunch of other libraries.
58</p>
59
60<p>
61The <i>mnemonics</i> key for the <b>project</b> is <code>C-c p</code>, and thus, any project command will start with
62that prefix.
63</p>
64
65
66<nav id="table-of-contents" role="doc-toc">
67<h2>Table of Contents</h2>
68<div id="text-table-of-contents" role="doc-toc">
69<ul>
70<li><a href="#Projectile">Projectile</a>
71<ul>
72<li><a href="#Custom%20project%20types">Custom project types</a>
73<ul>
74<li><a href="#%3Dko%3D"><span class="todo TODO">TODO</span> <code>ko</code></a></li>
75<li><a href="#Others"><span class="todo TODO">TODO</span> Others</a></li>
76</ul>
77</li>
78</ul>
79</li>
80<li><a href="#Configuration%20layout">Configuration layout</a></li>
81</ul>
82</div>
83</nav>
84
85<section id="outline-container-Projectile" class="outline-2">
86<h2 id="Projectile">Projectile</h2>
87<div class="outline-text-2" id="text-Projectile">
88<p>
89Let’s configure <code>projectile</code> using <code>use-package</code>.
90</p>
91
92<div class="org-src-container">
93<pre class="src src-emacs-lisp" id="org10b55c6">(use-package projectile
94 <span class="org-builtin">:commands</span>
95 (projectile-ack
96 projectile-ag
97 projectile-compile-project
98 projectile-configure-project
99 projectile-package-project
100 projectile-install-project
101 projectile-test-project
102 projectile-run-project
103 projectile-dired
104 projectile-find-dir
105 projectile-find-file
106 projectile-find-file-dwim
107 projectile-find-file-in-directory
108 projectile-find-tag
109 projectile-test-project
110 projectile-grep
111 projectile-invalidate-cache
112 projectile-kill-buffers
113 projectile-multi-occur
114 projectile-project-p
115 projectile-project-root
116 projectile-recentf
117 projectile-regenerate-tags
118 projectile-replace
119 projectile-replace-regexp
120 projectile-run-async-shell-command-in-root
121 projectile-run-shell-command-in-root
122 projectile-switch-project
123 projectile-switch-to-buffer
124 projectile-vc
125 projectile-commander)
126 <span class="org-builtin">:bind-keymap</span> (<span class="org-string">"C-c p"</span> . projectile-command-map)
127 <span class="org-builtin">:config</span>
128 <<projectile-completion>>
129 <<projectile-variables>>
130 <<projectile-compilation>>
131 <<projectile-known-projects>>
132 <<projectile-commander-methods>>
133 <<projectile-custom-types>>
134 (projectile-mode))
135</pre>
136</div>
137
138<p>
139First thing first, let’s tell <code>projectile</code> to use the default completion system instead of
140<code>ivy</code>, or <code>helm</code>, or …
141</p>
142
143<div class="org-src-container">
144<pre class="src src-emacs-lisp" id="orgde12c8e">(<span class="org-keyword">setq-default</span> projectile-completion-system 'default)
145</pre>
146</div>
147
148<p>
149Let’s also configure some <code>projectile</code> behavior.
150</p>
151
152<ul class="org-ul">
153<li>The default action when switch to a project should be the <code>commander</code>, as it allows to do
154different actions. This is done by setting <code>projectile-command</code> to
155<code>project-switch-project-action</code>.</li>
156<li>When switching to tests (<code>C-c p t</code>), if there is no test files, create one. This is done
157by setting <code>projectile-create-missing-test-files</code>.</li>
158</ul>
159
160<div class="org-src-container">
161<pre class="src src-emacs-lisp" id="orgfa1a302">(<span class="org-keyword">setq-default</span> projectile-switch-project-action #'projectile-commander
162 projectile-create-missing-test-files t)
163</pre>
164</div>
165
166<p>
167In order to make sure we can have a <i>living</i> compilation per project, we need to modify the
168buffer name to include the project name. This is easily do-able by writing a function for
169<code>compilation-buffer-name-function</code>.
170</p>
171
172<div class="org-src-container">
173<pre class="src src-emacs-lisp" id="org7dccd1f">(<span class="org-keyword">setq-default</span> compilation-buffer-name-function (<span class="org-keyword">lambda</span> (mode) (concat <span class="org-string">"*"</span> (downcase mode) <span class="org-string">": "</span> (projectile-project-name) <span class="org-string">"*"</span>)))
174</pre>
175</div>
176
177<p>
178Do not track known projects automatically, instead call projectile-add-known-project
179Remove dead projects when Emacs is idle
180</p>
181
182<div class="org-src-container">
183<pre class="src src-emacs-lisp" id="orgc9691a9">(<span class="org-keyword">setq-default</span> projectile-track-known-projects-automatically nil)
184(run-with-idle-timer 10 nil #'projectile-cleanup-known-projects)
185</pre>
186</div>
187
188<div class="org-src-container">
189<pre class="src src-emacs-lisp" id="org9967408">(def-projectile-commander-method ?s
190 <span class="org-string">"Open a *shell* buffer for the project"</span>
191 (projectile-run-eshell nil))
192(def-projectile-commander-method ?c
193 <span class="org-string">"Run `</span><span class="org-string"><span class="org-constant">compile</span></span><span class="org-string">' in the project"</span>
194 (projectile-compile-project nil))
195</pre>
196</div>
197</div>
198
199<div id="outline-container-Custom%20project%20types" class="outline-3">
200<h3 id="Custom%20project%20types">Custom project types</h3>
201<div class="outline-text-3" id="text-Custom%20project%20types">
202<p>
203 <a href="https://github.com/bbatsov/projectile"><code>projectile</code></a> allows to add custom project type, in addition to the built-in project
204types. See <a href="https://projectile.readthedocs.io/en/latest/projects/">Projects - Projectile: The Project Interaction Library for Emacs</a> for a little
205bit more detail. It should be possible to configure the <code>configure</code>, <code>compile</code>, <code>package</code>,
206<code>install</code> and <code>test</code> commands. One a the hope of this section is to be able to define highly
207customized project types so that doing <code>C-p u</code> on, let’s say, <code>tektoncd/pipeline</code> does the
208right thing by default.
209</p>
210
211<p>
212An example of custom project type is the following.
213</p>
214
215<div class="org-src-container">
216<pre class="src src-emacs-lisp"><span class="org-comment-delimiter">;; </span><span class="org-comment">Ruby + RSpec</span>
217(projectile-register-project-type 'ruby-rspec '(<span class="org-string">"Gemfile"</span> <span class="org-string">"lib"</span> <span class="org-string">"spec"</span>)
218 <span class="org-builtin">:project-file</span> <span class="org-string">"Gemfile"</span>
219 <span class="org-builtin">:compile</span> <span class="org-string">"bundle exec rake"</span>
220 <span class="org-builtin">:src-dir</span> <span class="org-string">"lib/"</span>
221 <span class="org-builtin">:test</span> <span class="org-string">"bundle exec rspec"</span>
222 <span class="org-builtin">:test-dir</span> <span class="org-string">"spec/"</span>
223 <span class="org-builtin">:test-suffix</span> <span class="org-string">"_spec"</span>)
224</pre>
225</div>
226
227<p>
228One nice aspect of <code>:compile</code> (and some others) is that it can take a symbolic reference to
229a function, which means, you can define dynamic behavior. Based on the doc this works for
230<code>:compile</code>, <code>:configure</code>, <code>:compilation-dir</code> and <code>:run</code> (but <i>my hope is it would work for <code>:test</code>
231and that a <code>:package</code> and an <code>:install</code> would exist</i>).
232</p>
233</div>
234
235<div id="outline-container-%3Dko%3D" class="outline-4">
236<h4 id="%3Dko%3D"><span class="todo TODO">TODO</span> <code>ko</code></h4>
237<div class="outline-text-4" id="text-%3Dko%3D">
238<p>
239First thing first, what makes a <code>ko</code> project. In most cases, a <code>.ko.yaml</code> will be present (at
240the root folder of the project). Let’s also define a function do detect if a it’s a <code>ko</code>
241project that uses the <i>standard</i> <code>config</code> folder for yamls.
242</p>
243
244<div class="org-src-container">
245<pre class="src src-emacs-lisp">(<span class="org-keyword">defun</span> <span class="org-function-name">projectile-ko-project-p</span> ()
246 <span class="org-doc">"Check if a project contains a .ko.yaml file."</span>
247 (projectile-verify-file <span class="org-string">".ko.yaml"</span>))
248(<span class="org-keyword">defun</span> <span class="org-function-name">projectile-ko-with-config-project-p</span> ()
249 <span class="org-doc">"Check if a project is a ko project and has a config/ folder full of yaml"</span>
250 (<span class="org-keyword">and</span> (projectile-ko-project-p)
251 (projectile-verify-file-wildcard <span class="org-string">"config/*.yaml"</span>)))
252</pre>
253</div>
254
255<p>
256Let’s register the <code>ko</code> project (with config). Long-term, the idea is to make different
257function for <code>ko</code> and <code>ko-with-config</code> projects.
258</p>
259
260<div class="org-src-container">
261<pre class="src src-emacs-lisp">(projectile-register-project-type 'ko-with-config #'projectile-ko-with-config-project-p
262 <span class="org-builtin">:project-file</span> <span class="org-string">".ko.yaml"</span> <span class="org-comment-delimiter">; </span><span class="org-comment">might not be required</span>
263 <span class="org-builtin">:configure</span> 'projectile-ko-configure-command
264 <span class="org-builtin">:compile</span> 'projectile-ko-compile-command
265 <span class="org-builtin">:test</span> 'projectile-ko-test-command
266 <span class="org-builtin">:run</span> 'projectile-ko-run-command
267 <span class="org-builtin">:package</span> 'projectile-ko-package-command
268 <span class="org-builtin">:install</span> 'projectile-ko-install-command)
269</pre>
270</div>
271
272
273<p>
274Let’s now dig a little bit more into the configure, compile, test, run, package and
275install commands. As we can pass it a function, we can define behaviour depending on the
276current opened buffer, etc. One assumption that we can make is that a <code>ko</code> project is also a
277<code>go</code> project.
278</p>
279
280<dl class="org-dl">
281<dt>configure</dt><dd><p>
282configure stands for <code>./configure</code> scripts usually. Let’s see what it could
283be for <code>ko</code> project. Most likely related to file generations.
284</p>
285<ul class="org-ul">
286<li>default to <code>./hack/update-codegen.sh</code> if it is present.</li>
287</ul>
288<div class="org-src-container">
289<pre class="src src-emacs-lisp">(<span class="org-keyword">defun</span> <span class="org-function-name">projectile-ko-configure-command</span> ()
290 <span class="org-doc">"define a configure command for a ko project, depending on the opened file"</span>
291 (<span class="org-keyword">cond</span>
292 ((projectile-file-exists-p <span class="org-string">"hack/update-codegen.sh"</span>) <span class="org-string">"./hack/update-codegen.sh"</span>)))
293</pre>
294</div></dd>
295<dt>compile</dt><dd><p>
296compile might be slightly different depending on the current major mode we
297are in, and maybe also depending on the folder.
298</p>
299
300<ul class="org-ul">
301<li>default to <code>go build -v ./...</code></li>
302<li><code>go</code> file (<code>go-mode</code>)
303<ul class="org-ul">
304<li>default to build the current package</li>
305<li>if it is a test file, tests the current package</li>
306</ul></li>
307</ul>
308
309<div class="org-src-container">
310<pre class="src src-emacs-lisp">(<span class="org-keyword">defun</span> <span class="org-function-name">projectile-ko-compile-command</span> ()
311 <span class="org-doc">"define a compile command for a ko project, depending on the openend file "</span>
312 (<span class="org-keyword">cond</span>
313 ((eq major-mode 'go-mode) (projectile-ko-compile-command-go))
314 ((eq major-mode 'yaml-mode) <span class="org-string">"yamllint ."</span>)
315 (t <span class="org-string">"go build -v ./..."</span>)
316 ))
317
318(<span class="org-keyword">defun</span> <span class="org-function-name">projectile-ko-compile-command-go</span> ()
319 <span class="org-doc">"compile command for a ko project if in a go file"</span>
320 (<span class="org-keyword">let*</span> ((current-file (buffer-file-name (current-buffer)))
321 (relative-current-file (file-relative-name current-file (projectile-project-root)))
322 (relative-current-folder (file-name-directory relative-current-file)))
323 (message relative-current-file)
324 (<span class="org-keyword">cond</span>
325 ((string-suffix-p <span class="org-string">"_test.go"</span> relative-current-file) (format <span class="org-string">"go test -c -v ./%s"</span> relative-current-folder))
326 (t (format <span class="org-string">"go build -v ./%s"</span> relative-current-folder)))))
327</pre>
328</div></dd>
329<dt>test</dt><dd><p>
330test might be slightly different depending on the current major mode we are in,
331and might depend on the folder.
332</p>
333
334<ul class="org-ul">
335<li>default to <code>go test -v ./...</code></li>
336<li><code>go</code> file (<code>go-mode</code>)
337<ul class="org-ul">
338<li>default to run tests on the current package</li>
339<li>if it is a test file, tests the current file (like <code>go-test-current-file</code> or
340<code>gotest-ui-current-file</code>)</li>
341</ul></li>
342</ul>
343
344<div class="org-src-container">
345<pre class="src src-emacs-lisp">(<span class="org-keyword">defun</span> <span class="org-function-name">projectile-ko-test-command</span> ()
346 <span class="org-doc">"define a test command for a ko project, depending on the openend file"</span>
347 (<span class="org-keyword">cond</span>
348 ((eq major-mode 'go-mode) (projectile-ko-test-command-go))
349 (t <span class="org-string">"go test -v ./..."</span>)))
350
351(<span class="org-keyword">defun</span> <span class="org-function-name">projectile-ko-test-command-go</span> ()
352 <span class="org-doc">"test command for a ko project if in a go file"</span>
353 (<span class="org-keyword">let*</span> ((current-file (buffer-file-name (current-buffer)))
354 (relative-current-file (file-relative-name current-file (projectile-project-root)))
355 (relative-current-folder (file-name-directory relative-current-file)))
356 (<span class="org-keyword">cond</span>
357 ((string-suffix-p <span class="org-string">"_test.go"</span> relative-current-file) (projectile-ko-command-go-test relative-current-file))
358 (t (format <span class="org-string">"go test -v ./%s"</span> relative-current-folder)))))
359
360(<span class="org-keyword">defun</span> <span class="org-function-name">projectile-ko-command-go-test</span> (current-file)
361 <span class="org-doc">"get the command for a go test"</span>
362 (<span class="org-keyword">cond</span>
363 ((gotest-module-available-p) (projectile-ko-command-go-test-gotest current-file))
364 (t (format <span class="org-string">"go test -v ./%s"</span> current-file))))
365
366(<span class="org-keyword">defun</span> <span class="org-function-name">projectile-ko-command-go-test-gotest</span> (current-file)
367 <span class="org-doc">"get the command for a go test with gotest module enabled"</span>
368 (message default-directory)
369 (<span class="org-keyword">let</span> ((data (go-test--get-current-file-testing-data)))
370 (format <span class="org-string">"go test -run='</span><span class="org-string"><span class="org-constant">%s</span></span><span class="org-string">' -v ./%s"</span> data (file-name-directory current-file))))
371
372(<span class="org-keyword">defun</span> <span class="org-function-name">gotest-module-available-p</span> ()
373 <span class="org-doc">"is go-test module available"</span>
374 (fboundp 'go-test--get-current-file-data))
375</pre>
376</div></dd>
377<dt>run</dt><dd><p>
378run is usually about running the project binary or something.
379</p>
380<div class="org-src-container">
381<pre class="src src-emacs-lisp">(<span class="org-keyword">defun</span> <span class="org-function-name">projectile-ko-run-command</span> ()
382 <span class="org-doc">"define a run command for a ko project, depending on the openend file "</span>
383 (<span class="org-keyword">cond</span>
384 ((eq major-mode 'go-mode) (projectile-ko-run-command-go))
385 <span class="org-comment-delimiter">;; </span><span class="org-comment">nothing by default ?</span>
386 ))
387
388(<span class="org-keyword">defun</span> <span class="org-function-name">projectile-ko-run-command-go</span> ()
389 <span class="org-doc">"test command for a ko project if in a go file"</span>
390 (<span class="org-keyword">let*</span> ((current-file (buffer-file-name (current-buffer)))
391 (relative-current-file (file-relative-name current-file (projectile-project-root)))
392 (relative-current-folder (file-name-directory relative-current-file)))
393 (<span class="org-keyword">cond</span>
394 ((string-prefix-p <span class="org-string">"cmd/"</span> relative-current-file) (format <span class="org-string">"go run ./%s"</span> relative-current-folder)))))
395</pre>
396</div></dd>
397<dt>package</dt><dd><p>
398package is usually about generating a package, for a maven project this would
399be <code>mvn package</code>, for a project with a <code>Dockerfile</code>, this could be build the image(s). For a
400<code>ko</code> project this is about building and pushing the images that are going to be
401deployed. This is achieved by doing a <code>ko resolve</code>.
402</p>
403<div class="org-src-container">
404<pre class="src src-emacs-lisp">(<span class="org-keyword">defun</span> <span class="org-function-name">projectile-ko-package-command</span> ()
405 <span class="org-doc">"define a package command for a ko project, depending on the openend file "</span>
406 (<span class="org-keyword">cond</span>
407 ((eq major-mode 'go-mode) (projectile-ko-package-command-go))
408 (t <span class="org-string">"ko resolve --push=false --oci-layout-path=/tmp/oci -f config"</span>)
409 ))
410
411(<span class="org-keyword">defun</span> <span class="org-function-name">projectile-ko-package-command-go</span> ()
412 <span class="org-doc">"package command for a ko project if in a go file"</span>
413 (<span class="org-keyword">let*</span> ((current-file (buffer-file-name (current-buffer)))
414 (relative-current-file (file-relative-name current-file (projectile-project-root)))
415 (relative-current-folder (file-name-directory relative-current-file)))
416 (<span class="org-keyword">cond</span>
417 ((string-prefix-p <span class="org-string">"cmd/"</span> relative-current-file) (format <span class="org-string">"ko publish --push=false ./%s"</span> relative-current-folder)))))
418</pre>
419</div></dd>
420<dt>install</dt><dd><p>
421install is about installing the project artifact somewhere (usually <code>make install</code>)
422</p>
423<div class="org-src-container">
424<pre class="src src-emacs-lisp">(<span class="org-keyword">defun</span> <span class="org-function-name">projectile-ko-install-command</span> ()
425 <span class="org-doc">"define a install command for a ko project, depending on the openend file "</span>
426 <span class="org-string">"ko apply -f config/"</span>)
427</pre>
428</div></dd>
429</dl>
430</div>
431</div>
432
433<div id="outline-container-Others" class="outline-4">
434<h4 id="Others"><span class="todo TODO">TODO</span> Others</h4>
435<div class="outline-text-4" id="text-Others">
436<ul class="org-ul">
437<li>Detect project type
438<ul class="org-ul">
439<li><code>.ko.yaml</code> => run is <code>ko apply -f …</code></li>
440<li>is there a <code>Makefile</code> ?</li>
441<li><code>tkn</code> and <code>tekton</code> file</li>
442<li><code>home</code> detection: <code>systems</code>, <code>users</code>, <code>ci.nix</code>, <code>shell.nix</code>,
443<code>hosts.nix</code>, <code>systems.nix</code>
444<ul class="org-ul">
445<li>if in <code>pkgs</code>, run <code>nix-build pkgs -A …</code>, and try to detect the file derivations</li>
446<li>if in <code>tools/emacs</code> (elisp), tangle files from <code>~/desktop/org/notes</code></li>
447<li>detect <code>hostname</code> and act based on it:
448<ul class="org-ul">
449<li><code>naruhodo</code>: <code>make home-switch</code>, …</li>
450<li><code>wakasu</code>: <code>make switch</code>, …</li>
451<li>Could also detect using <code>nixos-version</code></li>
452</ul></li>
453</ul></li>
454</ul></li>
455<li>Hook projectile run/compile/test to multi-compile
456Group things together, so that I can either choose from a list of different compile
457options <b>or</b> run my command</li>
458</ul>
459
460<p>
461<a href="https://github.com/asok/projectile-rails">asok/projectile-rails: Emacs Rails mode based on projectile</a> is also quite interesting.
462</p>
463</div>
464</div>
465</div>
466</section>
467
468<section id="outline-container-Configuration%20layout" class="outline-2">
469<h2 id="Configuration%20layout">Configuration layout</h2>
470<div class="outline-text-2" id="text-Configuration%20layout">
471<p>
472Here we define the <code>config-projects.el</code> file that gets generated by the source blocks in our Org
473document. This is the file that actually gets loaded on startup. The placeholders in
474angled brackets correspond to the <code>NAME</code> directives above the <code>SRC</code> blocks throughout this
475document.
476</p>
477
478<div class="org-src-container">
479<pre class="src src-emacs-lisp"><span class="org-comment-delimiter">;;; </span><span class="org-comment">config-projects.el --- -*- lexical-binding: t; -*-</span>
480<span class="org-comment-delimiter">;;; </span><span class="org-comment">Commentary:</span>
481<span class="org-comment-delimiter">;;; </span><span class="org-comment">Project related configuration.</span>
482<span class="org-comment-delimiter">;;; </span><span class="org-comment">This is mainly using projectile now, but built-in projects module seems promising for long-term.</span>
483<span class="org-comment-delimiter">;;; </span><span class="org-comment">Note: this file is autogenerated from an org-mode file.</span>
484<span class="org-comment-delimiter">;;; </span><span class="org-comment">Code:</span>
485
486<<projectile>>
487
488(<span class="org-keyword">provide</span> '<span class="org-constant">config-projects</span>)
489<span class="org-comment-delimiter">;;; </span><span class="org-comment">config-projects.el ends here</span>
490</pre>
491</div>
492</div>
493</section>
494</main>
495<footer id="postamble" class="status">
496<footer>
497 <small><a href="/" rel="history">Index</a> • <a href="/sitemap.html">Sitemap</a> • <a href="https://dl.sbr.pm/">Files</a></small><br/>
498 <small 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>.</small><br/>
499 <small class='copyright'>
500 Content and design by Vincent Demeester
501 (<a rel='licence' href='http://creativecommons.org/licenses/by-nc-sa/3.0/'>Some rights reserved</a>)
502 </small><br />
503</footer>
504</footer>
505</body>
506</html>