Commits: 3

Improve the publish

Fix the changelog containing too many items (a bug).

Add two chances to abort: before committing and before pushing.

Print a nice table with a summary.

Modularize the logic into many neat functions. Leverage that a function will not pollute environment, including $PWD. So now each function changes directory to where it needs to be.

index 61bc9e8..2d0e5be 100755
--- a/publish.nu
+++ b/publish.nu
@@ -1,97 +1,155 @@
-use std/dirs
-
-def main [] {
-  # Get the current source branch
-  # Switch pages to the same branch. Create it if needed.
-  # Find the time of last commit to pages.
-  # Make a list of commits in source since then.
-  # Format it as markdown list (only titles and hashes)
-  # Prepare a commit, showing a diff for users review
-  # Push to publish
-
-  if (not (git is-clean)) {
-    print "The source repository is not clean. First commit, then publish."
-    git status
-    exit 2
+# Build and publish the website
+def main [
+  --source: path = "."            # path to the source repository
+  --pages: path = "./pages.git"   # path to the Codeberg Pages repository
+] {
+  # A record of information about source and pages repositories gathered as we go.
+  mut spec = {
+    source_path: ($source | path expand)
+    pages_path: ($pages | path expand)
=  }
=
-  let source_branch = (git symbolic-ref --short HEAD)
-  print $"Source branch is ($source_branch)"
+  if (not (git is-clean $spec.source_path)) {
+    error make --unspanned {
+      msg: $"The source repository is not clean.",
+      help: $"First commit, then publish. Here are the changes:\n\n (git status --porcelain)"
+    }
+  }
+
+  $spec.branch = git current-branch $spec.source_path
=
-  dirs add pages.git
+  git clean-repo $spec.pages_path
+  git switch-or-create $spec.pages_path $spec.branch
=
-  print "Cleaning pages worktree..."
+  $spec.last_deployment_time = git last_commit_time $spec.pages_path
+
+  $spec.changelog = git changelog $spec.source_path $spec.last_deployment_time
+
+  let now = date now | date to-timezone utc | format date "%A, %F %T (utc)"
+
+  # TODO: Find a way to work with indentation.
+  # See https://github.com/nushell/nushell/issues/11477
+  let commit_template = $"
+Deployment ($now)
+
+Changes since ($spec.last_deployment_time | date to-timezone utc | format date '%A, %F %T (utc)'):
+
+($spec.changelog | format changelog)
+  "
+
+  zola build
+
+  print "About to commit and publish. Here is the summary: "
+  $spec | table --expand | print
+  input --numchar 1 "Press any key to continue, or ctrl-c to abort."
+
+  $commit_template | git publish $spec.pages_path $spec.branch
+}
+
+# Takes commit message as input
+def "git publish" [repo: path, branch: string]: string -> nothing {
+  cd $repo
+  let commit_template_file = mktemp --tmpdir
+  $in | save --append $commit_template_file
+  git add .
+  # TODO: Provide a mechanism to abort (use --template ?)
+  git commit --verbose --file $commit_template_file --edit
+
+  input --numchar 1 "Commit ready. Press any key to publish, or ctrl-c to abort."
+  git push --set-upstream origin $branch
+}
+
+def "git is-clean" [repo: path]: nothing -> bool {
+  cd $repo
+  git status --porcelain | is-empty
+}
+
+def "git current-branch" [repo: path]: nothing -> string {
+    cd $repo
+    git symbolic-ref --short HEAD
+}
+
+def "git clean-repo" [repo: path] {
+  cd $repo
+  print $"Cleaning ($repo) worktree..."
=  git restore .
=  git clean -fd
+}
=
-  if (git has branch $source_branch) {
-    print "The branch exists in pages repository. Switching."
-    git switch --quiet $source_branch
+def "git switch-or-create" [repo: path, branch: string] {
+  cd $repo
+  if (git has branch $repo $branch) {
+    print $"The branch ($branch) already exists in ($repo). Switching..."
+    git switch --quiet $branch
=
=    if (git is-tracking) {
-      print "The branch was already deployed. Pulling."
+      print $"The branch ($branch) was already deployed. Pulling..."
=      git pull --quiet
=    }
=  } else {
-     print "The branch does not exist in pages repository."
+     print $"The branch ($branch) does not yet exist in ($repo)."
=     loop {
-        match (input --numchar 1 $"Create and deploy a new branch '($source_branch)'? [y|n]: ") {
+        match (input --numchar 1 $"Create a new branch '($branch)'? [y|n]: ") {
=          "y" => break,
=          "n" => { exit 1 },
=          _ => continue,
=        }
=     }
-     git switch main
-     git switch --quiet --create $source_branch
+     git switch --quiet main
+     git switch --quiet --create $branch
=  }
=
-  let last_commit_time = git show --no-patch --format=%cI
-  print $"Pages last published on ($last_commit_time)"
-
-  let commit_template = mktemp --tmpdir
-  let now = date now | date to-timezone utc | format date "%A, %F %T (utc)"
-
-  echo $"Update ($now)"
-  | save --append $commit_template
-  echo "\n\n"
-  | save --append $commit_template
-  echo $"Changes since ($last_commit_time | date to-timezone utc | format date '%A, %F %T (utc)')\n\n"
-  | save --append $commit_template
-  dirs prev
-
-  git log --pretty=reference  --since=$last_commit_time
-  | save --append $commit_template
-
-  zola build
+}
=
-  dirs next
-  git add .
-  git commit --verbose --file $commit_template --edit
-  git push --set-upstream origin $source_branch
+def "git last_commit_time" [repo: path]: nothing -> datetime {
+  cd $repo
+  git show --no-patch --format=%cI | into datetime
=}
=
=# List all branches that can be switched to
-def "git list branches" [] {
+def "git list branches" [repo: path] {
+  cd $repo
=  git branch --list --all --format='%(refname:lstrip=-1)'
=  | lines
=  | filter { |line| $line != "HEAD" }
=  | uniq
=}
=
-def "git has branch" [name: string] {
-  $name in (git list branches)
+def "git has branch" [repo: path, branch: string] {
+  cd $repo
+  $branch in (git list branches $repo)
=}
=
-
+# Check if the current branch is tracking a remote.
=def "git is-tracking" [] {
=  try {
-    git rev-parse --abbrev-ref --symbolic-full-name @{u} err> /dev/null
+    git rev-parse --abbrev-ref --symbolic-full-name @{u} err>| ignore
=    true
=  } catch {
=    false
=  }
=}
=
-def "git is-clean" [] {
-  git status --porcelain | is-empty
+
+
+def "git changelog" [repo: path, since: datetime]: nothing -> table {
+  cd $repo
+  git log --pretty=reference --date=iso-strict
+  | parse "{hash} ({title}, {date})"
+  | update date { |row| $row.date | into datetime }
+  | where date > $since
+}
+
+def "format changelog" []: table -> string {
+  $in
+  | group-by { |row| $row.date | format date "%A, %F" }
+  | transpose date commits
+  | each { |day| $"($day.date):\n\n($day.commits | format changelog day)\n\n" }
+  | str join "\n\n"
+}
+
+def "format changelog day" []: table -> string {
+  $in
+  | format pattern "  * {hash} {title}"
+  | str join "\n"
=}

Move people pages to a new People section

The home page now focuses on our services and commitments.

index acdeccb..b8c0bff 100644
--- a/content/_index.md
+++ b/content/_index.md
@@ -3,3 +3,8 @@
=
=We are an international network of IT professionals and entrepreneurs dedicated to promote and support free software and use of services respecting human rights, dignity and privacy.
=
+At the heart of our services lies a commitment to Free and Open Source Software (FOSS) and EU cloud solutions, ensuring privacy and adherence to open standards.
+
+Based entirely in Europe, [our team](@/people/_index.md) is on-shore, allowing us to provide localized, high-quality services. We offer assistance in multiple languages, including English, Dutch, French, Polish, Galician, Spanish, Portuguese, German, and Hungarian.
+
+Our primary focus is on helping European Small and Medium-sized Enterprises (SMEs), Non-Governmental Organizations (NGOs), local governments, and educational institutions with implementing free, open-source and European software solutions.
new file mode 100644
index 0000000..00b8130
--- /dev/null
+++ b/content/people/_index.md
@@ -0,0 +1,7 @@
+---
+title: People
+template: people.html
+---
+
+We are an international network of IT professionals and entrepreneurs dedicated to promote and support free software and use of services respecting human rights, dignity and privacy.
+
similarity index 100%
rename from content/joachim-nuyttens.md
rename to content/people/joachim-nuyttens.md
similarity index 100%
rename from "content/jo\303\243o-campos.md"
rename to "content/people/jo\303\243o-campos.md"
similarity index 100%
rename from content/richard-cronan.md
rename to content/people/richard-cronan.md
similarity index 100%
rename from content/tad-lazurski.md
rename to content/people/tad-lazurski.md
similarity index 100%
rename from content/tibor-eszes.md
rename to content/people/tibor-eszes.md
similarity index 100%
rename from "content/v\303\255tor-andrade.md"
rename to "content/people/v\303\255tor-andrade.md"
index 8888a53..12a7973 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -77,12 +77,17 @@
=
=      body > header {
=        font-size: var(--prompt-font-size);
-        align-items: center;
-        display: flex;
-        gap: 1ch;
=        color: var(--dark-color);
=        padding: 3rem 1rem;
=        overflow: hidden;
+
+        &> a {
+          align-items: center;
+          display: flex;
+          gap: 1ch;
+          text-decoration: none;
+          color: inherit;
+        }
=      }
=
=      body > main {
@@ -124,6 +129,7 @@
=
=      #logo {
=        height: 3em;
+        width: 3em;
=      }
=
=      p {
@@ -284,17 +290,20 @@
=    <![endif]-->
=
=    <header>
-      <img
-        id="logo"
-        src="esc-logo.svg"
-        alt="The ESC logo with a sky-blue ring and a bright yellow ray passing through it."
-      />
-
-      <hgroup class="title">
-        <h1>esc collective</h1>
-        <p>supporting european digital independence</p>
-      </hgroup>
+      <a href="{\{ get_url(path='@/_index.md') \}}">
+        <img
+          id="logo"
+          src="{\{ get_url(path='/esc-logo.svg') \}}"
+          alt="The ESC logo with a sky-blue ring and a bright yellow ray passing through it."
+        />
+
+        <hgroup class="title">
+          <h1>esc collective</h1>
+          <p>supporting european digital independence</p>
+        </hgroup>
+      </a>
=    </header>
+
=    <main>
=      {\% filter indent(prefix="      ") -%}
=      {\%- block main_content -%}
@@ -303,7 +312,12 @@
=    </main>
=
=    <footer class="full-bleed-background">
-      <img src="favicon.svg" alt="ESC Icon" class="icon" />
+      <img
+        src="{\{ get_url(path='/favicon.svg') \}}"
+        alt="ESC Icon"
+        class="icon"
+      />
+
=
=      <ul class="prompt">
=        <!-- Remember to adjust the --prompts-count variable -->
index 4645233..4a9795b 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -2,17 +2,4 @@
=
={\%- block main_content -%}
={\{- section.content | safe -\}}
-
-<section class="card-grid">
-    {# Each page in this category represents a person.
-
-       It's a bit clunky. We will improve it later.
-    #}
-    {\%- for page in section.pages | sort(attribute="date") -%}
-    <article>
-        <h2>{\{ page.title \}}</h2>
-        {\{ page.content | safe \}}
-    </article>
-    {\%- endfor -%}
-</section>
={\%- endblock main_content -%}
new file mode 100644
index 0000000..223d7ed
--- /dev/null
+++ b/templates/page.html
@@ -0,0 +1,5 @@
+{\% extends "base.html" %}
+
+{\%- block main_content -%}
+{\{- page.content | safe -\}}
+{\%- endblock main_content -%}
new file mode 100644
index 0000000..4645233
--- /dev/null
+++ b/templates/people.html
@@ -0,0 +1,18 @@
+{\% extends "base.html" %}
+
+{\%- block main_content -%}
+{\{- section.content | safe -\}}
+
+<section class="card-grid">
+    {# Each page in this category represents a person.
+
+       It's a bit clunky. We will improve it later.
+    #}
+    {\%- for page in section.pages | sort(attribute="date") -%}
+    <article>
+        <h2>{\{ page.title \}}</h2>
+        {\{ page.content | safe \}}
+    </article>
+    {\%- endfor -%}
+</section>
+{\%- endblock main_content -%}

Copy services description from Joachim to home page

Joachim wrote a draft of our services' description here:

https://club.tad-lispy.com/t/esc-services-definition-website/189/3?u=tad-lispy

I besically copied it verbatim. As he said himself, we need to work on this content more, but IMO it's a great start.

For now everything is on the home page, which now looks like a wall of text. We will need to separate some of it to sub-pages. Maybe also create some taxonomies for languages and skills, so people can be found this way.

index b8c0bff..435d9d3 100644
--- a/content/_index.md
+++ b/content/_index.md
@@ -7,4 +7,63 @@ At the heart of our services lies a commitment to Free and Open Source Software
=
=Based entirely in Europe, [our team](@/people/_index.md) is on-shore, allowing us to provide localized, high-quality services. We offer assistance in multiple languages, including English, Dutch, French, Polish, Galician, Spanish, Portuguese, German, and Hungarian.
=
+
+# Our Services
+
=Our primary focus is on helping European Small and Medium-sized Enterprises (SMEs), Non-Governmental Organizations (NGOs), local governments, and educational institutions with implementing free, open-source and European software solutions.
+
+
+## Cloud assistance
+
+Your organisation probably depends on multiple cloud services. Setting up, managing and migrating cloud services can be complicated. Or maybe your company is or wants to self-host some applications? Our team is happy to assist you on projects or provide operational support for your cloud and self-hosted services.
+
+Our technical capabilities include:
+
+  * Cloud infrastructure: Kubernetes, Docker, OpenShift, and pretty much any Cloud provider
+  * Infrastructure automation: Terraform, Ansible, GitOps with ArgoCD, GitHub Actions, Jenkins
+  * Service architecture: service mesh, service discovery, containerization, server-less
+  * System reliability:  monitoring (Prometheus, Grafana, Nagios), observability, high availability
+  * Linux, Unix wizardry
+  * Scripting
+
+Reach out to us for any project or operational support in
+
+  * Setting up cloud or self-hosted services, applications
+  * Migrating your cloud or self-hosted services
+  * Administering your cloud or self-hosted services
+  * Trusted Admin Failover as a Service (TAFaaS), if you would like to have a backup plan when your trusted admin is absent unexpectedly
+
+
+## Software Development
+
+Need any customization or extension of FOSS functionalities? Need hard coding skills to set up a (web)application? Want to automate some tasks? Our team has a broad technical skillset, including (but not limited to):
+
+  * HTML, CSS, JavaScript
+  * Python
+  * Rust
+  * Elm
+  * Haskell
+  * Groovy
+  * SQL
+  * Bash
+  * Go
+
+
+## Consulting
+
+We can assist with small and big projects, provide momentary advice or define long term strategies for your organisation. Some examples:
+
+  * Project management
+  * Define migration strategies from concept to technical detail, for example moving to a new collaborative software platform (email, calendar, file sharing, etc.)
+  * Define technical solutions to meet your data security and data privacy requirements
+
+
+## Training and coaching
+
+We can provide training and coaching on different topics. The following list is not exhaustive, don't hesitate to reach out for other needs in the FOSS domain:
+
+  * Web Development
+  * Agile development, Test driven development, Pair prohramming
+  * Relational Databases (RDBMS)
+  * Version management using Git
+  * Operating Systems (Linux, Nix / NixOS)