Basics off

Basics off is a metaproject about the 58 "Basics of…" projects (49 started). It consists of three programs that use the contents of the repositories in order to count the conversions (in fish), extract the activity data (in Julia), and build the webpages (in D); also several gnuplot files that create the graphics, and a Makefile to build everything. The results are shown below: the conversion reports, the activity graphics and a hand-made report on the size of one of the executables.

Number of conversions by programming language

Programming language Finished conversions Unfinished conversions

D

20

0

C#

19

0

Chapel

19

0

Crystal

19

0

Hare

19

0

Kotlin

19

0

Nim

19

0

Odin

19

0

Pike

19

0

V

19

0

Raku

17

0

Go

15

0

Julia

15

0

Janet

14

0

Arturo

13

0

Python

12

0

Ring

11

1

Swift

10

0

Scala

9

0

Scheme

8

1

FreeBASIC

7

0

C3

6

0

Icon

5

1

8th

5

0

Ada

5

0

Vala

4

1

Haxe

3

0

Racket

3

0

Rust

3

0

F#

2

1

Factor

2

1

Io

2

0

Lobster

2

0

Oberon-07

2

0

Nit

1

2

Elixir

1

1

Gleam

1

1

Nature

1

1

Neat

1

1

Neko

1

1

OCaml

1

1

Pony

1

1

Rexx

1

1

Zig

1

1

Clojure

1

0

Lua

1

0

Nelua

1

0

Retro

1

0

Styx

1

0

Haskell

0

1

Lisp

0

1

Prolog

0

1

Red

0

1

SBCL

0

1

Dylan

0

0

Euphoria

0

0

Miranda

0

0

Vale

0

0

TOTALS

381

22

Programs converted to each programming language

Programming language 3D Plot Bagels Bug Bunny Chase Diamond Hammurabi High Noon Math Mugwump Name Poetry Russian Roulette Seance Sine Wave Slots Stars Strings Xchange Z-End

8th

Ada

Arturo

C#

C3

Chapel

Clojure

Crystal

D

Dylan

Elixir

Euphoria

F#

Factor

FreeBASIC

Gleam

Go

Hare

Haskell

Haxe

Icon

Io

Janet

Julia

Kotlin

Lisp

Lobster

Lua

Miranda

Nature

Neat

Neko

Nelua

Nim

Nit

Oberon-07

OCaml

Odin

Pike

Pony

Prolog

Python

Racket

Raku

Red

Retro

Rexx

Ring

Rust

SBCL

Scala

Scheme

Styx

Swift

V

Vala

Vale

Zig

Symbols

Finished program

Unfinished program

Monthly started conversions

Monthly commits

Size of the Sine Wave executable

Sine Wave is used to compare the sizes of the executables, native or handled, created by the programming languages of the project. The following table contains the results in increasing order.

Language Version KiB Type Compiler Optimisation Compressor Note

Arturo

0.9.83

0.4

handled

the interpreter is apart: 6.4 MiB

Kotlin

2.3.10

2.2

handled

kotlinc-jvm

minimal .jar, executable only by kotlin

Kotlin

2.1.0

2.2

handled

kotlinc-jvm

minimal .jar, executable only by kotlin

Kotlin

1.9.10

2.2

handled

kotlinc-jvm

minimal .jar, executable only by kotlin

Kotlin

1.3.31

2.4

handled

kotlinc-jvm

minimal .jar, executable only by kotlin

C#

5

4.0

handled

mcs 6.8.0.105

.exe for Mono/.NET

C#

6

4.0

handled

mcs 6.12.0.200

.exe for Mono/.NET

Icon

9.5.23a

4.4

handled

the interpreter is apart: 415 KiB

C#

13

9.0

handled

csc 3.9.0-6.21124.20

.exe for Mono/.NET

Scala

3.3.0

6.8

handled

minimal .jar, executable only by scala, made by sbt

Scala

3.3.0

7.0

handled

scalac

minimal .jar, executable only by scala

Swift

6.1

9.2

native

strip, upx --best

Swift

6.1

11.1

native

upx --best

Scheme

11.3

native

bigloo 4.6a

upx --best

Ada

Ada 2012

12.5

native

gnat 12.2.0

strip, upx --best

Vala

0.56.3

17.0

native

Ada

Ada 2012

17.1

native

gnat 12.2.0

upx --best

Ring

1.20

19.7

native

Swift

6.1

19.9

native

strip

Swift

5.9.1

22.0

native

-Osize

Scheme

23.8

native

bigloo 4.6a

C3

0.6.8

25.1

native

-Oz

strip, upx --best

Haxe

4.3.6

25.4

handled

.n for Neko, made with the option -neko

Swift

6.1

25.5

native

the options -O and -Osize don’t reduce the size

Swift

5.9.1

26.0

native

Ada

Ada 2012

26.8

native

gnat 12.2.0

strip

Nim

2.2.0

27.4

native

--opt=size

strip, upx --best

Nim

2.2.2

27.4

native

--opt=size

strip, upx --best

Nim

2.2.2

34.2

native

--opt=size

upx --best

Nim

2.2.2

34.4

native

strip, upx --best

Oberon-07

39.0

native

OBNC 0.16.1

Nim

2.2.2

42.0

native

upx --best

Haxe

4.3.6

43.3

handled

.hl for HashLink, made with the option -hl

Ada

Ada 2012

43.8

native

gnat 12.2.0

Haxe

4.3.6

44.9

handled

.jar made with the option -java

Hare

0.24.2

46.2

native

-R

strip, upx --best

FreeBASIC

1.09.0

53.0

native

C3

0.6.8

56.4

native

-Oz

strip

Haxe

4.3.6

57.5

handled

.exe for Mono/.NET, made with the option -cs (C#)

D

59.5

native

LDC2 1.42.0-beta2

-O -link-defaultlib-shared -release

strip, upx --best

D

59.6

native

LDC2 1.42.0-beta2

-O -link-defaultlib-shared

strip, upx --best

Nim

2.2.0

66.7

native

--opt=size

strip

Nim

2.2.2

66.7

native

--opt=size

strip

Odin

dev-2024-12

67.7

native

-o:size

upx --best

Odin

dev-2024-12

67.8

native

-o:size

upx

Odin

dev-2023-11

69.0

native

-o:size

upx

C3

0.6.8

69.1

native

-Oz

D

69.2

native

gdc 12.2.0

strip, upx --best

Haxe

4.3.6

69.7

handled

.jar made with the option -jvm

Odin

dev-2024-04

71.0

native

-o:size

upx

Odin

dev-2024-11

71.2

native

-o:size

upx

Odin

dev-2024-07

71.3

native

-o:size

upx

Nim

2.2.0

87.5

native

--opt=size

Nim

2.2.2

87.7

native

--opt=size

Nim

2.0.0

95.0

native

--opt=size

Nim

2.2.2

106.7

native

strip

Nelua

0.2.0-dev

110.0

native

Hare

0.24.2

111.5

native

-R

upx --best

Nim

1.x

122.0

native

Nim

2.2.0

130.3

native

Nim

2.2.2

130.7

native

Hare

0.24.2

136.7

native

-R

strip

Hare

0.24.2

152.7

native

upx --best

V

0.4.10

152.9

native

strip, upx --best

V

0.4.9

155.0

native

strip, upx --best

Nim

2.0.0

158.0

native

Kotlin

2.3.10

168.2

native

kotlinc-native

-opt

upx

Kotlin

1.9.10

176.0

native

kotlinc-native

-opt

upx

Crystal

1.14.0

177.0

native

--release --no-debug

strip, upx --best

Crystal

1.14.0

177.5

native

--release --no-debug

strip, upx

Odin

dev-2023-11

186.0

native

-o:size

Odin

dev-2024-12

189.7

native

-o:size

Odin

dev-2024-11

191.3

native

-o:size

Odin

dev-2024-03

195.0

native

-o:size

D

195.3

native

LDC2 1.42.0-beta2

-O -link-defaultlib-shared -release

strip

Crystal

1.14.0

195.6

native

--release --no-debug

upx --best

Crystal

1.14.0

196.6

native

--release --no-debug

upx

D

203.3

native

LDC2 1.42.0-beta2

-O -link-defaultlib-shared

strip

Kotlin

2.3.10

210.1

native

kotlinc-native

upx

Odin

dev-2024-07

210.9

native

-o:size

Odin

dev-2024-04

216.0

native

-o:size

C3

0.6.8

230.4

native

-Os

D

253.4

native

LDC2 1.42.0-beta2

-O -link-defaultlib-shared -release

Ring

1.19

255.7

native

C3

0.6.8

261.7

native

-O5

D

262.2

native

LDC2 1.42.0-beta2

-O -link-defaultlib-shared

C3

0.6.8

262.4

native

-O4

Odin

dev-2023-11

280.0

native

D

284.9

native

DMD64 OpenD 2.107.0-beta.1

strip, upx --best

D

285.0

native

LDC2 1.30.0

-O

D

287.1

native

DMD 2.110.0

strip, upx --best

D

287.1

native

gdc 12.2.0

strip

D

306.1

native

LDC2 1.42.0-beta2

-link-defaultlib-shared

Haxe

4.3.6

320.2

native

strip, upx --best

made with the option -cpp (C++)

Janet

1.40.1

326.1

native

strip, upx --best

Hare

0.24.2

334.9

native

-R

Odin

dev-2024-04

345.0

native

D

364.6

native

DMD 2.109.0

-O

upx --best

D

369.1

native

DMD 2.110.0

upx --best

D

370.9

native

DMD 2.109.0

-O

upx

C3

0.6.8

388.9

native

-O2

D

387.7

native

gdc 12.2.0

C3

0.6.8

390.3

native

-O3

Haxe

4.3.6

397.9

native

upx --best

made with the option -cpp (C++)

V

0.4.10

406.9

native

strip

V

0.4.9

414.4

native

strip

Ring

1.20

415.0

native

make with the option -static

Chapel

2.7.0

438.1

native

strip, upx --best

Crystal

1.14.0

456.3

native

--release --no-debug

strip

Chapel

1.29.0

456.4

native

strip, upx --best

Hare

0.24.2

477.9

native

Kotlin

2.3.10

488.1

native

kotlinc-native

-opt

V

0.4.10

491.0

native

V

0.4.9

500.1

native

D

521.2

native

DMD 2.110.0

strip

D

521.9

native

DMD64 OpenD 2.107.0-beta.1

strip

Crystal

1.14.0

524.9

native

--release --no-debug

Kotlin

1.9.10

530.0

native

kotlinc-native

-opt

Chapel

2.4.0

534.7

native

strip, upx --best

C3

0.6.8

572.1

native

C3

0.6.8

471.6

native

-O1

Go

1.19.8

575.6

native

strip, upx --best

Go

1.19.8

640.5

native

strip, upx --best

.exe for MS Windows

Ring

1.19

642.0

native

make with the option -static

Go

1.23.4

654.2

native

strip, upx --best

Kotlin

2.3.10

675.2

native

kotlinc-native

Go

1.23.4

704.5

native

strip, upx --best

.exe for MS Windows

Chapel

2.7.0

812.1

native

upx --best

Chapel

1.29.0

817.8

native

upx --best

Janet

1.40.1

822.9

native

strip

Kotlin

1.9.10

828.0

native

kotlinc-native

Janet

1.40.1

844.1

native

upx --best

Crystal

1.10.1

864.0

native

--release

Chapel

2.4.0

905.5

native

upx --best

Rust

1.82.0

950.7

native

upx --best

Crystal

1.14.0

1003.8

native

--release

Go

1.19.8

1207.4

native

upx --best

Scheme

1020.1

native

bigloo 4.6a

-static-bigloo -O3

strip, upx --best

Haxe

4.3.6

1223.1

native

strip

made with the option -cpp (C++)

D

1250.3

native

LDC2 1.42.0-beta2

-O

D

1261.0

native

DMD 2.109.0

-O

D

1272.0

native

DMD 2.110.0

D

1279.0

native

DMD 2.105.0

-O

D

1292.8

native

DMD64 OpenD 2.107.0-beta.1

Go

1.19.8

1305.8

native

strip

Chapel

1.29.0

1368.9

native

strip

V

0.3.4

1393.0

native

Go

1.19.8

1249.5

native

upx --best

.exe for MS Windows

Go

1.23.4

1475.3

native

strip

Chapel

2.7.0

1512.8

native

strip

Go

1.19.8

1519.5

native

strip

.exe for MS Windows

Haxe

4.3.6

1544.4

native

made with the option -cpp (C++)

Chapel

2.4.0

1668.8

native

strip

Kotlin

2.1.0

1657.2

handled

kotlinc-jvm

full .jar, executable by kotlin o java -jar

Go

1.23.4

1677.5

native

strip

.exe for MS Windows

Kotlin

2.3.10

1761.7

handled

kotlinc-jvm

full .jar, executable by kotlin o java -jar

Crystal

1.10.1

1812.0

native

Go

1.19.8

1954.1

native

Janet

1.40.1

2010.2

native

Go

1.19.8

2085.0

native

.exe for MS Windows

Crystal

1.14.0

2096.5

native

Scheme

2367.7

native

gambit 0.4.9

strip, upx --best

Chapel

2.7.0

2449.8

native

Chapel

2.4.0

2583.2

native

Chapel

1.29.0

2604.0

native

Kotlin

1.3.31

3058.0

handled

kotlinc-jvm

full .jar, executable by kotlin o java -jar

Scheme

3450.3

native

bigloo 4.6a

-static-bigloo

Rust

1.82.0

3788.9

native

Kotlin

2.1.0

4416.0

native

upx --best

built by GraalVM out of a full .jar

Kotlin

1.9.10

4428.6

native

upx

built by GraalVM out of a full .jar

Kotlin

1.9.10

4833.8

handled

full .jar, executable by kotlin or java -jar

Scala

3.3.0

7037.0

handled

full .jar, executable by scala or java -jar, made by sbt and sbt-assembly

8th

23.06

8646.0

native

plus a data file of 2.9 KiB

Scheme

11131.6

native

gambit 0.4.9

Kotlin

2.1.0

13231.3

native

built by GraalVM out of a full .jar

Kotlin

1.3.31

13640.0

native

built by GraalVM out of a full .jar

Kotlin

1.9.10

13664.3

native

built by GraalVM out of a full .jar

Scala

3.3.0

15008.0

native

built by GraalVM out of a full .jar

Racket

8.15

58192.0

native

Tools

Conversion counter

This program, written in fish, creates two AsciiDoc documents containing tables which show the number of conversions per programming language and which program has been converted to which.

#!/usr/bin/env fish

# count_conversions.fish

# Copyright (c) 2023, 2024, 2025, 2026, Marcos Cruz (programandala.net)
# Fair License (Fair)
# SPDX-License-Identifier: Fair

# This program is part of "Basics off".

# Last modified 20260207T1133+0100.
# See change log at the end of the file.

# Config {{{1
# ==============================================================

set column_separator "|"
set first_column_title "{language}"
set table_mark "|==="

# Get data {{{1
# ==============================================================

# get list of project directories
set project_dirs \
    (find -L ./repos -maxdepth 1 -type d \
    | grep "/basics_of_.*" )

# get list of project identifiers
set project_ids \
    (string split " " $project_dirs \
    | sed -e 's/.\+basics_of_\([a-z0-9-]\+\)/\1/')

# get list of unique programs
set programs \
    (find -L $project_dirs -maxdepth 2 -type f -name "*.bas" \
    | grep --invert-match "\.free\.bas" \
    | grep --invert-match --ignore-case d3_plot \
    | grep --invert-match Diamond \
    | grep --invert-match Sine_wave \
    | grep --invert-match sinewave \
    | xargs -I _ basename --suffix .bas _ \
    | sort \
    | uniq)

# calculate the number of projects
set projects (count $project_ids)

# width of the first
set column_width 0
for i in $project_ids "$first_column_title"
    set column_width (math "max($column_width[1], $(string length $i) + 1)")
end

# return the given string with an uppercase initial
function capitalized # string
    echo (string upper (string sub --length 1 $argv[1]))(string sub --start 2 $argv[1])
end

# convert the given project identifier (which is a language name in
# lowercase) to the proper language name
function language # project_id
    switch $argv[1]
        case c-sharp   ; echo "C#"
        case f-sharp   ; echo "F#"
        case freebasic ; echo FreeBASIC
        case ocaml     ; echo OCaml
        case sbcl      ; echo SBCL
        case "*"       ; echo (capitalized "$argv[1]")
    end
end

# return the filename extension correspondent to the given project identifier
function extension # project_id
    switch $argv[1]
        case ada       ; echo "adb"
        case arturo    ; echo "art"
        case c-sharp   ; echo "cs"
        case chapel    ; echo "chpl"
        case clojure   ; echo "clj"
        case crystal   ; echo "cr"
        case elixir    ; echo "ex"
        case euphoria  ; echo "eu"
        case f-sharp   ; echo "fs"
        case freebasic ; echo "free.bas"
        case hare      ; echo "ha"
        case haskell   ; echo "hs"
        case haxe      ; echo "hx"
        case icon      ; echo "icn"
        case julia     ; echo "jl"
        case kotlin    ; echo "kt"
        case miranda   ; echo "m"
        case nature    ; echo "n"
        case neat      ; echo "nt"
        case oberon-07 ; echo "obn"
        case ocaml     ; echo "ml"
        case prolog    ; echo "pl"
        case python    ; echo "py"
        case racket    ; echo "rkt"
        case rust      ; echo "rs"
        case sbcl      ; echo "lisp"
        case scheme    ; echo "scm"
        case styx      ; echo "sx"
        case '*'       ; echo $argv[1]
    end
end

# return the program name correspondent to its given base filename
function program_name # base_filename
    set result ''
    set name $argv[1]
    set name (string replace '_' ' ' $name)
    set name (string replace '3d' '3D' $name)
    set characters (string split '' $name)
    set previous_character ' '
    for character in $characters
        if test $previous_character = ' ' -o $previous_character = '-'
            set character (string upper $character)
        end
        set result "$result$character"
        set previous_character $character
    end
    echo $result
end

# Make table with the number of conversions per language {{{1
# ==============================================================

function print_column_titles

    # calculate the width of data columns
    set column_title $first_column_title "{finished}" "{unfinished}"
    for i in 2 3
        set column_width[$i] (string length $column_title[$i])
    end

    for i in 1 2 3
        echo -n \
            $column_separator \
            (string pad --right --width (math "$column_width[$i]+1") "$column_title[$i]")
    end
    echo -e "\n"

end

set total_number_of_finished_conversions 0
set total_number_of_unfinished_conversions 0

function print_conversions_rows

    for i in (seq $projects)

        # get the proper files from the project's <src> directory
        set source_files (\
            find -L $project_dirs[$i]/src -maxdepth 1 -name "*.$(extension $project_ids[$i])")

        # get the finished and unfinished conversions
        if test (count $source_files) -eq 0
            set list_of_finished_conversions
            set list_of_unfinished_conversions
        else
            set list_of_finished_conversions (\
                grep \
                    --files-without-match \
                    "XXX\sUNDER\sDEVELOPMENT" $source_files)
            set list_of_unfinished_conversions (\
                grep \
                    "XXX\sUNDER\sDEVELOPMENT" $source_files)
        end

        set number_of_finished_conversions (count $list_of_finished_conversions)
        set number_of_unfinished_conversions (count $list_of_unfinished_conversions)

        set total_number_of_finished_conversions (math "$total_number_of_finished_conversions + $number_of_finished_conversions")
        set total_number_of_unfinished_conversions (math "$total_number_of_unfinished_conversions + $number_of_unfinished_conversions")

        # print the row
        echo \
            $column_separator \
            (string pad --right --width $column_width[1] "$(language $project_ids[$i])") \
            $column_separator \
            (string pad --width $column_width[2] $number_of_finished_conversions) \
            $column_separator \
            (string pad --width $column_width[3] $number_of_unfinished_conversions)

    end

end

# print the ranking, i.e. a table containing the number of finished and unfinished
# conversions per language, sorted in descending order of conversions.
function print_ranking

    echo "ifdef::en[]"
    echo "== Number of conversions by programming language"
    echo "endif::en[]"
    echo "ifdef::eo[]"
    echo "== Nombro de konvertoj po program-lingvo"
    echo "endif::eo[]"
    echo "ifdef::es[]"
    echo "== Número de conversiones por lenguaje de programación"
    echo "endif::es[]"
    echo "ifdef::ie[]"
    echo "== Númere de conversiones por lingue de programation"
    echo "endif::ie[]"

    echo

    echo "ifdef::en[]"
    echo ":language: Programming language"
    echo ":finished: Finished conversions"
    echo ":unfinished: Unfinished conversions"
    echo ":totals: TOTALS"
    echo "endif::en[]"
    echo "ifdef::eo[]"
    echo ":language: Program-lingvo"
    echo ":finished: Finitaj konvertoj"
    echo ":unfinished: Nefinitaj konvertoj"
    echo ":totals: TOTALOJ"
    echo "endif::eo[]"
    echo "ifdef::es[]"
    echo ":language: Lenguaje de programación"
    echo ":finished: Conversiones acabadas"
    echo ":unfinished: Conversiones inacabadas"
    echo ":totals: TOTALES"
    echo "endif::es[]"
    echo "ifdef::ie[]"
    echo ":language: Lingue de programation"
    echo ":finished: Conversiones terminat"
    echo ":unfinished: Conversiones ínterminat"
    echo ":totals: TOTALES"
    echo "endif::ie[]"

    echo

    echo '[width=50%, cols="<10h,>6,>6"]'
    echo $table_mark
    print_column_titles
    print_conversions_rows \
        | sort \
            --ignore-case \
            --field-separator=$column_separator \
            --key=3nr --key=4nr --key=2d

    # print the totals row
    echo
    echo \
        $column_separator \
        (string pad --right --width $column_width[1] "{totals}") \
        $column_separator \
        (string pad --width $column_width[2] $total_number_of_finished_conversions) \
        $column_separator \
        (string pad --width $column_width[3] $total_number_of_unfinished_conversions)
    echo $table_mark

end

# Make table with the names of the programs converted per language {{{1
# ==============================================================

set symbol_of_finished_program "✓"
set symbol_of_unfinished_program "…"

function print_programs_rows

    for project_number in (seq $projects)

        echo -n \
            $column_separator \
            (string pad --right --width $column_width[1] (language $project_ids[$project_number]))

        for program_number in (seq (count $programs))

            set column_number (math "$program_number + 1")

            set program_filename $programs[$program_number].(extension $project_ids[$project_number])

            switch $program_filename
                case "3d_plot.hx"
                    set conversion (find -L $project_dirs[$project_number] -maxdepth 2 -type f -path "*/src/D3_plot.hx")
                case "3d_plot.nim"
                    set conversion (find -L $project_dirs[$project_number] -maxdepth 2 -type f -path "*/src/d3_plot.nim")
                case "diamond.hx"
                    set conversion (find -L $project_dirs[$project_number] -maxdepth 2 -type f -path "*/src/Diamond.hx")
                case "sine_wave.hx"
                    set conversion (find -L $project_dirs[$project_number] -maxdepth 2 -type f -path "*/src/Sine_wave.hx")
                case "sine_wave.obn"
                    set conversion (find -L $project_dirs[$project_number] -maxdepth 2 -type f -path "*/src/sinewave.obn")
                case '*'
                    set conversion (find -L $project_dirs[$project_number] -maxdepth 2 -type f -path "*/src/$program_filename")
            end

            if test -z "$conversion"
                # the program has not been converted to the current language
                echo -n $column_separator (string repeat --count $column_width[$column_number] " ")
            else if grep --quiet "XXX\sUNDER\sDEVELOPMENT" $conversion
                # the program conversion is under development
                echo -n $column_separator (string pad --right --width $column_width[$column_number] $symbol_of_unfinished_program)
            else
                # the program conversion is finished
                echo -n $column_separator (string pad --right --width $column_width[$column_number] $symbol_of_finished_program)
            end

        end

        echo

    end

end

# print a table showing the programs (columns) converted to each
# language (rows), including unfinished conversions (marked with a
# different symbol).
function print_programs

    echo "ifdef::en[]"
    echo "== Programs converted to each programming language"
    echo "endif::en[]"
    echo "ifdef::eo[]"
    echo "== Programoj konvertitaj al ĉiu program-lingvo"
    echo "endif::eo[]"
    echo "ifdef::es[]"
    echo "== Programas convertidos a cada lenguaje de programación"
    echo "endif::es[]"
    echo "ifdef::ie[]"
    echo "== Programas convertet a chascun lingue de programation"
    echo "endif::ie[]"

    echo

    echo "ifdef::en[]"
    echo ":language: Programming language"
    echo ":symbols: Symbols"
    echo ":finished_program: Finished program"
    echo ":unfinished_program: Unfinished program"
    echo "endif::en[]"
    echo "ifdef::eo[]"
    echo ":language: Program-lingvo"
    echo ":symbols: Simboloj"
    echo ":finished_program: Finita programo"
    echo ":unfinished_program: Nefinita programo"
    echo "endif::eo[]"
    echo "ifdef::es[]"
    echo ":language: Lenguaje de programación"
    echo ":symbols: Símbolos"
    echo ":finished_program: Programa completado"
    echo ":unfinished_program: Programa inacabado"
    echo "endif::es[]"
    echo "ifdef::ie[]"
    echo ":language: Lingue de programation"
    echo ":symbols: Simboles"
    echo ":finished_program: Programa terminat"
    echo ":unfinished_program: Programa ínterminat"
    echo "endif::ie[]"

    echo

    echo "[cols=\"<3h,"(count $programs)"*^1\"]"
    echo $table_mark

    # calculate the width of the data columns
    for column_number in (seq 2 (math "$(count $programs) + 1"))
        set column_width[$column_number] (math "$(string length $programs[(math "$column_number - 1")]) + 1")
    end

    set column_title $first_column_title $programs
    for column_number in (seq (count $column_title))
        # echo $column_width[$column_number] $column_title[$column_number]
        echo -n \
            $column_separator \
            (string pad --right --width $column_width[$column_number] (program_name $column_title[$column_number]))
    end
    echo -e "\n"
    print_programs_rows \
        | sort \
            --ignore-case \
            --field-separator=$column_separator \
            --key=2d

    echo $table_mark

    echo -e "\n.{symbols}\n"
    echo "$symbol_of_finished_program:: {finished_program}"
    echo "$symbol_of_unfinished_program:: {unfinished_program}"

end

# Main {{{1
# ==============================================================

# print the document corresponding to the given report type,
# "ranking" or "programs".
function print_document # report

    set report $argv[1]

    echo "= Basics off"
    echo ":author: Marcos Cruz (programandala.net)"
    echo ":revdate:" (date +%Y-%m-%d)

    switch $report
        case ranking
            echo ":pdf-page-layout: portrait"
            echo
            print_ranking
        case programs
            echo ":pdf-page-layout: landscape"
            echo
            print_programs
    end

end

print_document $argv[1]

# Change log {{{1
# ==============================================================

# 2023-09-16: Start.
#
# 2023-09-17: Reject the conversions containing a note "XXX UNDER
# DEVELOPMENT". Fix and improve the filename extensions regex. Select
# the conversions by filename extension. Print the language names with
# proper case. Add a column with the unfinished conversions. Add
# column titles. Sort the table.
#
# 2023-10-17: Add the Arturo language.
#
# 2023-10-19: Add the OCaml language.
#
# 2023-10-29: Update to the new project tree. Build an AsciiDoc table.
#
# 2023-11-02: Add the Swift language.
#
# 2023-11-08: Prepare to build a second table.
#
# 2023-11-10: Build a second table with the programs converted to each
# language.
#
# 2023-11-14: Add the Haxe language.
#
# 2023-11-26: Support the Oberon-07's exceptional base filename "sinewave".
#
# 2023-12-15: Set variables with the symbols of finished and
# unfinished programs; add a legend. Convert program base filenames to
# names.
#
# 2024-07-19: Improve the layout of the report.
#
# 2024-07-20: Update after the renaming the project and its subprojecs.
#
# 2024-10-29: Add the Python language.
#
# 2024-11-20: Update extension of FreeBASIC source files.
#
# 2024-11-26: Add the Nit language.
#
# 2024-11-30: Add the Factor language.
#
# 2024-12-02: Add languages Io and Raku.
#
# 2024-12-10: Add the Clojure language.
#
# 2024-12-14: Add the Hare language.
#
# 2024-12-16: Simplify the filename extensions function.
#
# 2024-12-19: Support Haxe's <D3_plot.hx>. Improve layout of tables.
#
# 2024-12-20: Add the C# language.
#
# 2024-12-24: Update the C# language project identifier.
#
# 2024-12-26: Add the Rust language.
#
# 2024-12-27: Simplify expression and conversion of unusual filenames.
# Support Haxe's <Sine_wave.hx>.
#
# 2024-12-28: Fix bug: the source files of the conversions were
# searched for also in project directories other than <src>.
#
# 2024-12-30: Add the F# language.
#
# 2025-01-02: Add totals.
#
# 2025-01-20: Update path to repos.  Split the document in two,
# depending on an argument: the language ranking and the programs table.
#
# 2025-03-19: Add the Ada language.
#
# 2025-03-31: Rename main lists.
#
# 2025-04-07: Fix typo.
#
# 2025-06-10: Build multilingual Asciidoctor documents.
#
# 2025-06-11: Reword translation. Translate also the legend.
#
# 2025-06-27: Simplify layout of legend.  Reword some texts.
#
# 2025-08-14: Reword some texts.  Translate "totals".
#
# 2025-12-17: Add the Styx and Neat languages.
#
# 2025-12-19: Update name conversion to support "Z-End".
#
# 2026-01-23: Add the Haskell and Elixir languages.
#
# 2026-02-07: Fix typo; improve table headings and titles.

Activity calculator

This program, written in Julia, creates several CSV-format data files containing the activity of the project repositories.

# calculate_activity.jl

# Copyright (c) 2024, 2025, Marcos Cruz (programandala.net)
# Fair License (Fair)
# SPDX-License-Identifier: Fair

# This program is part of "Basics off".

# Last modified 20250604T0056+0200.
# See change log at the end of the file.

# Requirements {{{1
# ==============================================================

import CSV
import DataFrames

# Globals {{{1
# ==============================================================

const SRC_DIR    = "repos"
const TARGET_DIR = "target"
const TMP_DIR    = "tmp"

@enum Command no_command log_command summary_command tabular_command
command::Command = no_command

@enum Plot no_plot commits_plot started_conversions_plot
plot_type::Plot = no_plot

# Input file path set by the `--input` command-line option.
input_file = ""

# Output file path set by the `--output` command-line option.
output_file = ""

# Errors and warnings {{{1
# ============================================================================

# Print an error message and quit.

function fail(message::String, result::Int = 1)

    println("Error: $message.")
    exit(result)

end

function fail_because_of_missing_value(option::String)

    fail("falta el valor de la opción `$option`")

end

function fail_because_of_repeated_option(option::String)

    fail("se permite solamente una opción `$option`")

end

function warn(message::String)

    println("Aviso: $message.")

end

function warn_about_useless_option(command::String, option::String)

    warn("el comando `$command` no necesita la opción `$option`")

end

# Languages {{{1
# ==============================================================

# Convert the given language label into its proper name.

function language_name(label::SubString)::String

    if label == "c-sharp"
        return "C#"
    elseif label == "f-sharp"
        return "F#"
    elseif label == "freebasic"
        return "FreeBASIC"
    elseif label == "ocaml"
        return "OCaml"
    elseif label == "sbcl"
        return "SBCL"
    else
        return uppercasefirst(label)
    end

end

# Make log data file {{{1
# ==============================================================

# Make a log data file out of the Mercurial logs of the projects.
#
# The log data file is a headerless CSV file containing 2 fields: commit month
# (in YYYY-MM format) and programming language; i.e. one record for every
# commit done in the given month.

function make_log_data_file()

    global log_data_file
    global output_file
    global plot_type

    output_file = abspath(output_file == "" ? log_data_file : output_file)
    # (the output file must be absolute in order to pass it to mercurial, which
    # is called in a subdirectory)

    rm(output_file, force = true)

    # get list of project directories
    project_dir::Array{String} = []
    for entry in readdir(SRC_DIR)
        if isdir(joinpath(SRC_DIR, entry))
            push!(project_dir, entry)
        end
    end

    # add the data, extracted from the Mercurial repositories
    working_dir = pwd()
    for dir in project_dir
        project_id = match(r"basics_of_([a-z0-9-]+)", dir).captures[1]
        cd(joinpath(SRC_DIR, dir))
        if plot_type == commits_plot
            mercurial_command = `hg log --template "{date|shortdate},$(language_name(project_id))\n"`
        else # conversions_plot
            mercurial_command = `hg log --template "{date|shortdate},{file_adds},$(language_name(project_id))\n"`
        end
        run(pipeline(mercurial_command, stdout = output_file, append = true))
        cd(working_dir)
    end

    # select and format the data lines
    lines = readlines(output_file)
    selected_and_formatted_lines::Array{String} = []
    for line in lines
        if plot_type == commits_plot
            field = match(r"(\d\d\d\d-\d\d)-\d\d,(.+)", line).captures
            month = field[1]
            language = field[2]
            push!(selected_and_formatted_lines, "$month,$language")
        else # conversions_plot
            field = match(r"(\d\d\d\d-\d\d)-\d\d,(.*),(.+)", line).captures
            month = field[1]
            files = field[2]
            language = field[3]
            if contains(files, ".bas") && !contains(files, ".free.bas")
                push!(selected_and_formatted_lines, "$month,$language")
            end
        end
    end

    # sort the lines into the data file
    output_io = open(output_file, "w")
    for line in sort(selected_and_formatted_lines, by=uppercase)
        println(output_io, line)
    end
    close(output_io)

end

# Make summary data file {{{1
# ==============================================================

# Make a summary data file out of the log data file.
#
# The summary data file is a headerless CSV file containing 3 fields: commit
# month (in YYYY-MM format), programming language and number of commits; i.e.
# one record for every language used in any month.

function make_summary_data_file()

    global log_data_file
    global summary_data_file
    global input_file
    global output_file
    global plot_type

    input_file = input_file == "" ? log_data_file : input_file
    output_file = output_file == "" ? summary_data_file : output_file

    output_io = open(output_file, "w")

    previous_month = ""
    previous_language = ""
    language_activity_per_month = 0

    if plot_type == commits_plot
        println(output_io, "month,language,commits")
    else
        println(output_io, "month,language,started_conversions")
    end

    for record in push!(readlines(input_file), "end_of_data,end_of_data")

        field = split(record, ",")
        month = field[1]
        language = field[2]

        if (month == previous_month && language == previous_language) ||
            (previous_month == "" && previous_language == "")

            language_activity_per_month += 1

        else

            println(output_io, "$previous_month,$previous_language,$language_activity_per_month")
            language_activity_per_month = 1

        end

        previous_month = month
        previous_language = language

    end

    close(output_io)

end

# Make tabular data file {{{1
# ==============================================================

# Make a tabular data file out of the summary data file.
#
# The tabular data file is a CSV file containing one field for the month (in
# YYYY-MM format) and one field for the commit count of every language; i.e.
# one record to hold the number of commits done in a month in every language.

function make_tabular_data_file()

    global summary_data_file
    global table_data_file
    global input_file
    global output_file

    input_file = input_file == "" ? summary_data_file : input_file
    output_file = output_file == "" ? table_data_file : output_file

    lines = readlines(input_file)

    first_record = lines[2] # skip headers
    last_record = last(lines)

    first_date = split(first_record, ",")[1]
    last_date = split(last_record, ",")[1]

    first_year = parse(Int, split(first_date, "-")[1])
    last_year = parse(Int, split(last_date, "-")[1])

    first_month = parse(Int, split(first_date, "-")[2])
    last_month = parse(Int, split(last_date, "-")[2])

    # extract the language names from the log 

    langs::Array{String} = []
    for line in lines[2 : end] # skip headers
        lang = match(r"\d\d\d\d-\d\d,(.+),\d+", line).captures[1]
        push!(langs, lang)
    end
    sort!(langs, by=uppercase)
    unique!(langs)

    output_io = open(output_file, "w")

    # create the column headers

    print(output_io, "date")
    for lang in langs
        print(output_io, ",$lang")
    end
    println(output_io, "")

    # add the data

    for year in first_year : last_year

        for month in 1 : 12

            if (year == first_year && month < first_month) ||
                (year == last_year && month > last_month)
                continue
            end

            padded_month = lpad(month, 2, '0')
            print(output_io, "$year-$padded_month")

            for lang in langs
                count = 0
                for record in readlines(input_file)
                    if contains(record, "$year-$padded_month,$lang,")
                        count = split(record, ",")[3]
                        break
                    end
                end
                print(output_io, ",$count")
            end

            println(output_io, "")

        end

    end

end

# Command-line arguments {{{1
# ============================================================================

# Read and check the given command-line arguments.

function read_arguments(args::Array{String})

    global command
    global input_file
    global output_file
    global plot_type

    local user_command

    i = 1

    while i <= length(args)

        if i == 1

            # Check the user command, which must be the first argument

            user_command = args[1]

            if user_command in ["log", "l"]
                command = log_command
            elseif user_command in ["summary", "s"]
                command = summary_command
            elseif user_command in ["tabular", "t"]
                command = tabular_command
            else
                fail("comando `$user_command` no reconocido")
            end

        else

            # Check the options

            if args[i] in ["--input", "-input", "-i"]
                if ! (command in [summary_command, tabular_command])
                    warn_about_useless_option(user_command, args[i])
                end
                if input_file != ""
                    fail_because_of_repeated_option(args[i])
                else
                    if i < length(args)
                        input_file = args[i+1]
                        i += 1
                    else
                        fail_because_of_missing_value(args[i])
                    end
                end
            elseif args[i] in ["--output", "-output", "-o"]
                if ! (command in [log_command, summary_command, tabular_command])
                    warn_about_useless_option(user_command, args[i])
                end
                if output_file != ""
                    fail_because_of_repeated_option(args[i])
                else
                    if i < length(args)
                        output_file = args[i+1]
                        i += 1
                    else
                        fail_because_of_missing_value(args[i])
                    end
                end
            elseif args[i] in ["--plot-type", "-plot-type", "-p"]
                if ! (command in [log_command, summary_command])
                    warn_about_useless_option(user_command, args[i])
                end
                if plot_type != no_plot
                    fail_because_of_repeated_option(args[i])
                else
                    if i < length(args)

                        plot_type_argument = args[i+1]
                        i += 1

                        if plot_type_argument in ["commits", "c"]
                            plot_type = commits_plot
                        elseif plot_type_argument in ["started_conversions", "sc", "s", "conversions", "c"]
                            plot_type = started_conversions_plot
                        else
                            fail("opción `$plot_type_argument` no reconocida")
                        end

                    else
                        fail_because_of_missing_value(args[i])
                    end
                end
            else
                fail("opción no reconocida: $(args[i])")
            end

        end

        i += 1

    end

end

function check_plot_type()

    if plot_type == no_plot
        fail("falta la opción `--plot-type`")
    end

end

# Main {{{1
# ==============================================================

read_arguments(ARGS)

if plot_type == commits_plot
    log_data_file = joinpath(TMP_DIR, "activity__commits.log")
    summary_data_file = joinpath(TARGET_DIR, "activity__commits_summary.csv")
    table_data_file = joinpath(TARGET_DIR, "activity__commits_table.csv")
elseif plot_type == started_conversions_plot
    log_data_file = joinpath(TMP_DIR, "activity__conversions.log")
    summary_data_file = joinpath(TARGET_DIR, "activity__conversions_summary.csv")
    table_data_file = joinpath(TARGET_DIR, "activity__conversions_table.csv")
end

if command == log_command
    check_plot_type()
    make_log_data_file()
elseif command == summary_command
    check_plot_type()
    make_summary_data_file()
elseif command == tabular_command
    make_tabular_data_file()
else
    fail("es necesario un comando")
end


# Change log {{{1
# ==============================================================

# 2024-07-07: Start in fish shell. Make log CSV data file out of the logs of
# the repos.
#
# 2024-07-09: Make summary CSV data file: activity per month and language. Make
# tabular CSV data file. Make gnuplot data file. Make a graphic chart. Start
# conversion of the fish code to the Julia language.
#
# 2024-07-10: Convert to Julia the making of the log and summary files.
#
# 2024-07-11: Convert to Julia the making of the tabular file.
#
# 2024-07-18: Organize the code into functions. Simplify the sorting of the log
# data file lines.
#
# 2024-07-19: Remove 2 unnecessary `rm()`.
#
# 2024-07-20: Update after the renaming the project and its subprojects.
#
# 2024-07-21: Rename this file. Support `--input` and `--output` options.
#
# 2024-07-23: Add a `plot` no-op command.
#
# 2024-12-20: Update regular expression to catch "c#".
#
# 2024-12-24: Update after the renaming of the C# repository link.
#
# 2024-12-30: Add the F# language.
#
# 2025-01-07: Remove the plot code, which was unfinished: the plotting is done
# by gnuplot. Copy to <calculate_commits_activity.jl> and
# <calculate_conversions_activity.jl>; keep only the code common to both.
#
# 2025-01-08: Undo the file factoring: integrate the different code of
# <calculate_commits_activity.jl> and <calculate_conversions_activity.jl>; add
# a `--plot-type` option accordingly.
#
# 2025-01-20: Update path to repos.
#
# 2025-02-14: Update naming convention of output files.

Page builder

This program, written in D, creates the Fendo-format sources of the pages of programandala.net about the Basics off metaproject, the 49 started "Basics of…" projects and their 20 programs. Everything in 4 languages; therefore, a total of 280 pages.

// build_pages.d

// Copyright (c) 2025, 2026, Marcos Cruz (programandala.net)
// Fair License (Fair)
// SPDX-License-Identifier: Fair

// This program is part of "Basics off"

// Last modified 20260215T2003+0100.
// See change log at the end of the file.

// Requirements {{{1
// =============================================================================

import std.algorithm.searching :
    can_find = canFind,
    find;
import std.algorithm.sorting :
    sort;
import std.array :
    array,
    replicate,
    split;
import std.ascii :
    is_digit = isDigit;
import std.conv :
    octal,
    to;
import std.datetime.systime :
    Clock,
    System_Time = SysTime;
import std.file :
    dir_entries = dirEntries,
    exists,
    is_dir = isDir,
    mkdir,
    read_text = readText,
    remove,
    set_attributes = setAttributes,
    Span_Mode = SpanMode,
    fwrite = write;
import std.format :
    format;
import std.path :
    base_name = baseName,
    build_normalized_path = buildNormalizedPath,
    build_path = buildPath,
    dir_name = dirName;
import std.range :
    retro;
import std.stdio :
    File,
    writef,
    writefln,
    writeln;
import std.string :
    capitalize,
    starts_with = startsWith,
    to_lower = toLower;

// Syntactic sugar {{{1
// =============================================================================

/// Fake keyword used as a user-defined attribute for marking the declarations
/// of functions in order to facilitate searching the sources, especially with
/// external tools like `grep`, and their legibility.

enum fn;

/// Fake keyword used as a user-defined attribute for marking the declarations
/// of variables in order to facilitate searching the sources, especially with
/// external tools like `grep`, and their legibility.

enum var;

// Constants {{{1
// =============================================================================

/// Absolute path to the directory of this project.

@var immutable string WORKING_DIR = build_path(dir_name(__FILE_FULL_PATH__), "..");

/// Absolute path to the directory containing the links to the projects' repos.

@var immutable string REPOS_DIR = build_normalized_path(WORKING_DIR, "repos");

/// Absolute path to a project directory containing all outputs of the original
/// programs.

@var immutable string OUTPUTS_DIR =
    build_path(REPOS_DIR, "basics_of_d", "_meta", "outputs");

/// Absolute path to the target directory where the pages will be built.

@var immutable string TARGET_DIR =
    build_normalized_path(WORKING_DIR, "target", "pages");

/// ISO codes of the languages used in the target website, plus languages used
/// in some names, plus two fake ones used as default and for abstraction.

enum Human_Lang
{
    en, // English
    eo, // Esperanto
    es, // Spanish
    ie, // Interlingue/Occidental
    it, // Italian
    pt, // Portuguese
    none,
    any
};

/// ISO codes of the languages used in the target website.

@var immutable Human_Lang[] human_langs =
    [Human_Lang.en, Human_Lang.eo, Human_Lang.es, Human_Lang.ie];

/// Comment used in the unfinished source files.  Files containing this comment
/// will not be included in the pages.

@var immutable string UNFINISHED = "XXX UNDER DEVELOPMENT";

// Data conversions {{{1
// =============================================================================

/// Convert the given programming language identifier to the human language of
/// its proper name.

@fn Human_Lang programming_lang_name_lang(string programming_lang_id)
{
    switch(programming_lang_id)
    {
        case "ada":
        case "c3":
        case "d":
        case "io":
        case "miranda":
        case "neko":
        case "nim":
        case "nit":
        case "raku":
        case "retro":
        case "sbcl":
        case "v":
        case "vala":
        case "vale":
            return Human_Lang.any;
        case "arturo":
            return Human_Lang.es;
        case "scala":
            return Human_Lang.it;
        case "lua":
            return Human_Lang.pt;
        default:
            return Human_Lang.en;
    }
}

/// Convert the given programming language identifier to its proper name.

@fn string programming_lang_name(string programming_lang_id)
{
    switch (programming_lang_id)
    {
        case "c-sharp":
            return "C#";
        case "f-sharp":
            return "F#";
        case "freebasic":
            return "FreeBASIC";
        case "ocaml":
            return "OCaml";
        case "sbcl":
            return "SBCL";
        default:
            return capitalize(programming_lang_id);
    }
}

/// Convert the given programming language identifier to the corresponding
/// filename extension, with initial dot.

@fn string filename_extension(string programming_lang_id)
{
    switch (programming_lang_id)
    {
        case "ada":
            return ".adb";
        case "arturo":
            return ".art";
        case "c-sharp":
            return ".cs";
        case "chapel":
            return ".chpl";
        case "clojure":
            return ".clj";
        case "crystal":
            return ".cr";
        case "elixir":
            return ".ex";
        case "euphoria":
            return ".eu";
        case "f-sharp":
            return ".fs";
        case "freebasic":
            return ".free.bas";
        case "hare":
            return ".ha";
        case "haskell":
            return ".hs";
        case "haxe":
            return ".hx";
        case "icon":
            return ".icn";
        case "julia":
            return ".jl";
        case "kotlin":
            return ".kt";
        case "miranda":
            return ".m";
        case "nature":
            return ".n";
        case "neat":
            return ".nt";
        case "oberon-07":
            return ".obn";
        case "ocaml":
            return ".ml";
        case "prolog":
            return ".pl";
        case "python":
            return ".py";
        case "racket":
            return ".rkt";
        case "rust":
            return ".rs";
        case "sbcl":
            return ".lisp";
        case "scheme":
            return ".scm";
        case "styx":
            return ".sx";
        default:
            return "." ~ programming_lang_id;
    }
}

/// Convert the given programming language identifier to a flag indicating the
/// corresponding language is actually used in its "basics_of_" project, ie. at
/// least one program has been converted to it.

// Ideally, all the relevant information about the projects should be collected
// at the start of the program into a one single array of structures, in order
// to make it accessible to every function.  This function is a simpler,
// hard-coded workaround.

@fn bool is_programming_lang_used(string programming_lang_id)
{
    switch (programming_lang_id)
    {
        case "dylan":
        case "euphoria":
        case "haskell":
        case "lisp":
        case "miranda":
        case "prolog":
        case "red":
        case "sbcl":
        case "vale":
            return false;
        default:
            return true;
    }
}

/// Convert a program identifier (which may have a special form, imposed by its
/// programming language) to its usual form, i.e. "lower_camel_case".

@fn string usual_program_id(string program_id)
{
    switch (program_id)
    {
        case "Diamond":
            return "diamond";
        case "D3_plot", "d3_plot":
            return "3d_plot";
        case "Sine_wave", "sinewave":
            return "sine_wave";
        default:
            return program_id;
    }
}

/// Convert a usual program identifier to the actual one used by the given
/// programming language.

@fn string actual_program_id(string program_id, string programming_lang_id)
{
    final switch (program_id)
    {
        case "3d_plot":
            final switch (programming_lang_id)
            {
                case "haxe":
                    return "D3_plot";
                case "nim":
                    return "d3_plot";
            }
        case "diamond":
            final switch (programming_lang_id)
            {
                case "haxe":
                    return "Diamond";
            }
        case "sine_wave":
            final switch (programming_lang_id)
            {
                case "haxe":
                    return "Sine_wave";
                case "oberon-07":
                    return "sinewave";
            }
    }
    return program_id;
}

/// Return the [Fendo](http://programandala.net/en.program.fendo.html) language
/// markup to open an inlined text in the given language.

@fn string open_lang_markup(Human_Lang human_lang)
{
    return iso_code(human_lang) ~ "(( ";
}

/// Return a true flag if the given text starts with the
/// [Fendo](http://programandala.net/en.program.fendo.html) language markup
/// that opens an inlined text in the given language.

@fn bool already_in_lang_markup(string text, Human_Lang human_lang)
{
    return starts_with(text, open_lang_markup(human_lang));
}

/// Return the given string with the
/// [Fendo](http://programandala.net/en.program.fendo.html) markup
/// corresponding to the given language, unless it already has it.

@fn string in_lang_markup(string text, Human_Lang human_lang)
{
    if (already_in_lang_markup(text, human_lang))
    {
        return text;
    }
    else
    {
        return open_lang_markup(human_lang) ~ text ~ " ))";
    }
}

/// Return the given string with the
/// [Fendo](http://programandala.net/en.program.fendo.html) markup
/// corresponding to its given language, unless the language of the string is
/// the same as the given context language.

@fn string in_lang_markup_if_needed
(
    string text,
    Human_Lang text_human_lang,
    Human_Lang context_human_lang,
)
{
    if (text_human_lang == context_human_lang)
    {
        return text;
    }
    else
    {
        return in_lang_markup(text, text_human_lang);
    }
}

/// Return the given string with the
/// [Fendo](http://programandala.net/en.program.fendo.html) markup
/// corresponding to the English language, unless the given language is
/// English.

@fn string in_english_markup
(
    string text,
    Human_Lang context_human_lang = Human_Lang.none,
)
{
    return in_lang_markup_if_needed(text, Human_Lang.en, context_human_lang);
}

/// Return the title of the program whose identifier is given, in the given
/// language.

@fn string program_title(string program_id, Human_Lang human_lang = Human_Lang.none)
{
    @var string result;
    switch (program_id.to_lower)
    {
        case "3d_plot", "d3_plot":
            result = "3D Plot";
            break;
        case "high_noon":
            result = "High Noon";
            break;
        case "sine_wave":
            result = "Sine Wave";
            break;
        case "russian_roulette":
            result = "Russian Roulette";
            break;
        case "z-end":
            result = "Z-End";
            break;
        default:
            result = capitalize(program_id);
    }
    return in_english_markup(result, human_lang);
}

/// Return the programming language identifier corresponding to the given
/// programming language project identifier.

@fn string programming_lang_id_of_project(string project)
{
    return split(project, "_")[$ - 1];
}

unittest
{
    assert(programming_lang_id_of_project("foo_bar") == "bar");
    assert(programming_lang_id_of_project("foo_bar_zx") == "zx");
}

/// Return the page title corresponding to the given project, in the given
/// language.

@fn string page_title(string project, Human_Lang human_lang)
{
    @var string result;
    if (starts_with(project, "basics_of_"))
    {
        @var string programming_lang_id = programming_lang_id_of_project(project);
        result = "Basics of " ~ programming_lang_name(programming_lang_id);
    }
    else if (project == "basics_off")
    {
        result = "Basics off";
    }
    else
    {
        result = program_title(project, human_lang);
    }
    return in_english_markup(result, human_lang);
}

/// Identifiers of translatable texts.

enum Text
{
    activity,
    activity_tool,
    all_versions_of,
    commits_plot,
    conversions, // XXX OLD not used
    conversions_plot,
    conversions_tool,
    en,
    first,
    gnuplot_files,
    new_page,
    original, // (substantive)
    output, // (substantive)
    pages_tool,
    program,
    reports,
    second,
    see_also,
    size_report,
    third,
    tools,
}

/// Return a text, whose [identifier][Text] is given, translated to the given
/// language.

@fn string translated(Text text, Human_Lang human_lang)
{
    final switch (text)
    {
        case Text.activity:
            switch (human_lang)
            {
                case Human_Lang.en:
                    return "Activity";
                case Human_Lang.eo:
                    return "Aktivecon";
                case Human_Lang.es:
                    return "Actividad";
                case Human_Lang.ie:
                    return "Activitá";
                default:
                    assert(false);
            }
        case Text.activity_tool:
            switch (human_lang)
            {
                case Human_Lang.en:
                    return "Activity calculator";
                case Human_Lang.eo:
                    return "Aktiveco-kalkulilo";
                case Human_Lang.es:
                    return "Calculador de actividad";
                case Human_Lang.ie:
                    return "Calculator de activitá";
                default:
                    assert(false);
            }
        case Text.all_versions_of:
            switch (human_lang)
            {
                case Human_Lang.en:
                    return "All versions of";
                case Human_Lang.eo:
                    return "Ĉiuj versioj de";
                case Human_Lang.es:
                    return "Todas las versiones de";
                case Human_Lang.ie:
                    return "Omni versiones de";
                default:
                    assert(false);
            }
        case Text.commits_plot:
            switch (human_lang)
            {
                case Human_Lang.en:
                    return "Monthly commits";
                case Human_Lang.eo:
                    return "Monataj enmetoj";
                case Human_Lang.es:
                    return "Entradas mensuales";
                case Human_Lang.ie:
                    return "Depositiones mensual";
                default:
                    assert(false);
            }
        case Text.conversions: // XXX OLD not used
            switch (human_lang)
            {
                case Human_Lang.en:
                    return "Conversions";
                case Human_Lang.eo:
                    return "Konvertoj";
                case Human_Lang.es:
                    return "Conversiones";
                case Human_Lang.ie:
                    return "Conversiones";
                default:
                    assert(false);
            }
        case Text.conversions_plot:
            switch (human_lang)
            {
                case Human_Lang.en:
                    return "Monthly started conversions";
                case Human_Lang.eo:
                    return "Monate komencitaj konvertoj";
                case Human_Lang.es:
                    return "Conversiones empezadas mensualmente";
                case Human_Lang.ie:
                    return "Conversiones comensat mensualmen";
                default:
                    assert(false);
            }
        case Text.conversions_tool:
            switch (human_lang)
            {
                case Human_Lang.en:
                    return "Conversion counter";
                case Human_Lang.eo:
                    return "Konverto-kalkulilo";
                case Human_Lang.es:
                    return "Calculador de conversiones";
                case Human_Lang.ie:
                    return "Calculator de conversiones";
                default:
                    assert(false);
            }
        case Text.en:
            switch (human_lang)
            {
                case Human_Lang.en:
                    return "in";
                case Human_Lang.eo:
                    return "en";
                case Human_Lang.es:
                    return "en";
                case Human_Lang.ie:
                    return "in";
                default:
                    assert(false);
            }
        case Text.first:
            switch (human_lang)
            {
                case Human_Lang.en:
                    return "first";
                case Human_Lang.eo:
                    return "unua";
                case Human_Lang.es:
                    return "primer";
                case Human_Lang.ie:
                    return "unesim";
                default:
                    assert(false);
            }
        case Text.gnuplot_files:
            switch (human_lang)
            {
                case Human_Lang.en:
                    return "en(( gnuplot )) files for making the graphics";
                case Human_Lang.eo:
                    return "Dosieroj de en(( gnuplot )) por krei la grafikaĵojn";
                case Human_Lang.es:
                    return "Ficheros de en(( gnuplot )) para crear los gráficos";
                case Human_Lang.ie:
                    return "Files de en(( gnuplot )) por crear li grafics";
                default:
                    assert(false);
            }
        case Text.new_page:
            switch (human_lang)
            {
                case Human_Lang.en:
                    return "New page";
                case Human_Lang.eo:
                    return "Nova paĝo";
                case Human_Lang.es:
                    return "Nueva página";
                case Human_Lang.ie:
                    return "Nov págine";
                default:
                    assert(false);
            }
        case Text.original: // (substantive)
            switch (human_lang)
            {
                case Human_Lang.en:
                    return "Original";
                case Human_Lang.eo:
                    return "Originalo";
                case Human_Lang.es:
                    return "Original";
                case Human_Lang.ie:
                    return "Originale";
                default:
                    assert(false);
            }
        case Text.output: // (substantive)
            switch (human_lang)
            {
                case Human_Lang.en:
                    return "Output";
                case Human_Lang.eo:
                    return "Eliraĵo";
                case Human_Lang.es:
                    return "Salida";
                case Human_Lang.ie:
                    return "Printate";
                default:
                    assert(false);
            }
        case Text.pages_tool:
            switch (human_lang)
            {
                case Human_Lang.en:
                    return "Page builder";
                case Human_Lang.eo:
                    return "Paĝo-konstruilo";
                case Human_Lang.es:
                    return "Constructor de páginas";
                case Human_Lang.ie:
                    return "Constructor de págines";
                default:
                    assert(false);
            }
        case Text.program:
            switch (human_lang)
            {
                case Human_Lang.en:
                    return "program";
                case Human_Lang.eo:
                    return "programo";
                case Human_Lang.es:
                    return "programa";
                case Human_Lang.ie:
                    return "programa";
                default:
                    assert(false);
            }
        case Text.reports:
            switch (human_lang)
            {
                case Human_Lang.en:
                    return "Reports";
                case Human_Lang.eo:
                    return "Raportoj";
                case Human_Lang.es:
                    return "Informes";
                case Human_Lang.ie:
                    return "Raportes";
                default:
                    assert(false);
            }
        case Text.second:
            switch (human_lang)
            {
                case Human_Lang.en:
                    return "second";
                case Human_Lang.eo:
                    return "dua";
                case Human_Lang.es:
                    return "segundo";
                case Human_Lang.ie:
                    return "duesim";
                default:
                    assert(false);
            }
        case Text.see_also:
            switch (human_lang)
            {
                case Human_Lang.en:
                    return "See also";
                case Human_Lang.eo:
                    return "Vidu ankaŭ";
                case Human_Lang.es:
                    return "Véase también";
                case Human_Lang.ie:
                    return "Vide anc";
                default:
                    assert(false);
            }
        case Text.size_report:
            switch (human_lang)
            {
                case Human_Lang.en:
                    return "Size of the en(( Sine Wave )) executable";
                case Human_Lang.eo:
                    return "Grando de la ekzekutebla dosiero de en(( Sine Wave ))";
                case Human_Lang.es:
                    return "Tamaño del ejecutable de en(( Sine Wave ))";
                case Human_Lang.ie:
                    return "Grandore del executibile de en(( Sine Wave ))";
                default:
                    assert(false);
            }
        case Text.third:
            switch (human_lang)
            {
                case Human_Lang.en:
                    return "third";
                case Human_Lang.eo:
                    return "tria";
                case Human_Lang.es:
                    return "tercer";
                case Human_Lang.ie:
                    return "triesim";
                default:
                    assert(false);
            }
        case Text.tools:
            switch (human_lang)
            {
                case Human_Lang.en:
                    return "Tools";
                case Human_Lang.eo:
                    return "Iloj";
                case Human_Lang.es:
                    return "Herramientas";
                case Human_Lang.ie:
                    return "Instrumentes";
                default:
                    assert(false);
            }
    }
}

/// Return the textual form of the given [ISO code language
/// identifier][Human_Lang].

@fn string iso_code(Human_Lang human_lang)
{
    switch (human_lang)
    {
        case
            Human_Lang.en,
            Human_Lang.eo,
            Human_Lang.es,
            Human_Lang.ie,
            Human_Lang.it,
            Human_Lang.pt:
            return to!string(human_lang);
        default:
            assert(false);
    }
}

/// Return the filename prefix for the given human lang.

@fn string filename_lang_prefix(Human_Lang human_lang)
{
    return iso_code(human_lang) ~ ".";
}

/// Return the filename suffix for the given human lang.

@fn string filename_lang_suffix(Human_Lang human_lang)
{
    return "__" ~ iso_code(human_lang);
}

/// Return the page identifier corresponding to the given project and language.
/// The page identifier used by
/// [Fendo](http://programandala.net/en.program.fendo.html) is the page
/// filename without the [Gforth](http://gnu.org/software/gforth)'s ".fs"
/// filename extension, with an optional anchor at the end (including its "#"
/// prefix).

@fn string page_id(string project, Human_Lang human_lang)
{
    return
        iso_code(human_lang)
        ~ "."
        ~ translated(Text.program, human_lang)
        ~ "."
        ~ usual_program_id(project);
}

/// Return the page filename corresponding to the given project and language.

@fn string page_filename(string project, Human_Lang human_lang)
{
    return page_id(project, human_lang) ~ ".fs";
}

/// Return the item which is before the given one in the given list; if the
/// given item is the first one, return an empty string instead.

@fn string previous(string item, ref string[] list)
{
    @var auto previous_items = find!("a < b")(retro(list), item);
    return previous_items.length ? previous_items[0] : "";
}

unittest
{
    @var string[] list = ["a", "b", "c"];
    assert(previous("a", list) == "");
    assert(previous("b", list) == "a");
    assert(previous("c", list) == "b");
}

/// Return the item which is after the given one in the given list; if the
/// given item is the last one, return an empty string instead.

@fn string next(string item, ref string[] list)
{
    @var auto next_items = find!("a > b")(list, item);
    return next_items.length ? next_items[0] : "";
}

unittest
{
    @var string[] list = ["a", "b", "c"];
    assert(next("a", list) == "b");
    assert(next("b", list) == "c");
    assert(next("c", list) == "");
}

// Markup {{{2
// -----------------------------------------------------------------------------

/// [Fendo](http://programandala.net/en.program.fendo.html) markup to start an
/// unordered list item.

@var immutable string LIST_ITEM = "- ";

/// Return the given text as a heading
/// [Fendo](http://programandala.net/en.program.fendo.html) markup of the given
/// level.

@fn string heading(int level, string text)
{
    @var string ruler = replicate("=", level);
    return ruler ~ " " ~ text ~ " " ~ ruler ~ "\n";
}

/// Return the given text as
/// [Fendo](http://programandala.net/en.program.fendo.html) code.

@fn string in_code_markup(string text)
{
    return "<[ " ~ text ~ " ]>\n";
}

/// Return the given string with
/// [Fendo](http://programandala.net/en.program.fendo.html) image markup.

@fn string image(string s)
{
    return "{{ " ~ s ~ " }}\n";
}

/// Return [Fendo](http://programandala.net/en.program.fendo.html) code to
/// import the corresponding file path into the page.

@fn string as_source_code_import_markup(string file_path)
{
    return in_code_markup("s\" prog/" ~ file_path ~ "\" source_code");
}

/// Return [Fendo](http://programandala.net/en.program.fendo.html) code to
/// import the corresponding file path into the page.

@fn string as_source_code_block_markup(string code)
{
    enum string code_block_markup = "\n####\n";
    return code_block_markup ~ code ~ code_block_markup;
}

/// Return the given text with the Fendo
/// [Fendo](http://programandala.net/en.program.fendo.html) paragraph markup.

@fn string paragraph(string text)
{
    enum string PARAGRAPH_START = "_ "; // Fendo markup
    return PARAGRAPH_START ~ text ~ "\n";
}

/// Return [Fendo](http://programandala.net/en.program.fendo.html) markup
/// required to include the contents of the given filename into the page.

@fn string include_file_markup
(
    string filename,
    string filename_extension, // with initial dot
    string project_dir // dir containing the <src> subdir
)
{
    @var string result;

    // make fendo use a neovim's specific syntax instead the default one
    // associated to some filename extensions

    switch(filename_extension)
    {
        case ".fs":
            // fsharp instead of gforth
            result ~= in_code_markup("s\" fsharp\" programming_language!");
            break;
        case ".free.bas":
            // freebasic instead of generic basic
            result ~= in_code_markup("s\" freebasic\" programming_language!");
            break;
        default:
            break;
    }

    return
        result
        ~ as_source_code_import_markup
        (
            build_path
            (
                project_dir,
                "src",
                base_name(filename)
            )
        );
}

/// Return the [Fendo](http://programandala.net/en.program.fendo.html) markup
/// to create a link to the given target page using the given link text.

@fn string link(string target, string text = "")
{
    enum FENDO_NEW_LINK_FORMAT = false;
    if (text.length)
    {
        return
            FENDO_NEW_LINK_FORMAT
            ? format("[[ \"%s\" \"%s\" ]]", target, text)
            : format("[[ %s | %s ]]", target, text);
    }
    else
    {
        return
            FENDO_NEW_LINK_FORMAT
            ? format("[[ \"%s\" ]]", target)
            : format("[[ %s ]]", target);
    }
}

/// Return the [Fendo](http://programandala.net/en.program.fendo.html) markup
/// to create a link to the "Sine Wave" executables report with the link text
/// in the given language.

@fn string size_report_link(Human_Lang human_lang)
{
    return
        link
        (
            page_id("basics_off#sine_wave", human_lang),
            translated(Text.size_report, human_lang)
        );
}

/// Return the [Fendo](http://programandala.net/en.program.fendo.html) markup
/// to create a link to the "Sine Wave" program page with the link text in the
/// given language.

@fn string sine_wave_link(Human_Lang human_lang)
{
    @var string program_id = "sine_wave";
    return
        link
        (
            page_id(program_id, human_lang),
            program_title(program_id, human_lang)
        );
}

/// Return the given string as a proper anchor ID.

@fn string proper_id(string id)
in
{
    assert(id.length);
}
do
{
    if (is_digit(id[0]))
    {
        id = '_' ~ id;
    }
    if (id[$ - 1] == '#')
    {
        id = id[0 .. $ - 1] ~ "sharp";
    }
    return to_lower(id);
}

/// Return [Fendo](http://programandala.net/en.program.fendo.html) markup
/// required to create an anchor with the given ID.

@fn string anchor(string id)
{
    return in_code_markup("s\" " ~ proper_id(id) ~ "\" id=!");
}

// Pages {{{1
// =============================================================================

// Data {{{2
// -----------------------------------------------------------------------------

/// Identifiers of page types.

enum Page_Type { metaproject, lang_project, program_project }

/// Program version data.

struct Program_Version
{
    @var string programming_lang_id;
    @var string source_file;
    @var string filename_extension;
    @var string project_dir;
}

/// Return an array of [versions][Program_Version] of the given program
/// project.

Program_Version[] program_project_versions(string project)
{
    @var Program_Version[] list;
    foreach (string programming_lang_id; programming_lang_ids)
    {
        @var string project_dir = "basics_of_" ~ programming_lang_id;
        @var string source_dir = build_path(REPOS_DIR, project_dir, "src");
        @var string filename_extension = filename_extension(programming_lang_id);
        @var string source_file_wildcard = project ~ filename_extension;
        foreach
        (
            string source_file;
            dir_entries(source_dir, source_file_wildcard, Span_Mode.shallow)
        )
        {
            if (is_finished(source_file))
            {
                list ~= Program_Version
                (
                    programming_lang_id,
                    source_file,
                    filename_extension,
                    project_dir
                );
            }
        }
    }
    return list;
}

// Pages metadata {{{2
// -----------------------------------------------------------------------------

/// Struct to hold the dates of a page.

struct Page_Date
{
    // Fendo metadata fields:

    @var string start; /// When the page was started.
    @var string creation; /// When the page was created, i.e. finished.
    @var string modification; /// When the page was modified.

    /// When the page file was modified.  Added as a comment.  This is useful
    /// in order to force the rebuilding of a page in case its textual contents
    /// have not changed but its embedded contents, e.g. a source code file,
    /// have been updated.

    @var string last_modified;
}

/// Return the dates of page.

@fn Page_Date page_date(string filename)
{
    enum string FIRST_START = "2025-06-01";
    enum string FIRST_CREATION = "2025-08-19";
    enum string LAST_MODIFIED = "20260208T1302+0100";

    enum string LAST_PROJECT_ADDITION = "2026-02-07";

    switch (filename)
    {

        // "basics off" metaproject

        case "en.program.basics_off.fs":
        case "eo.programo.basics_off.fs":
        case "es.programa.basics_off.fs":
        case "ie.programa.basics_off.fs":
            return Page_Date(FIRST_START, FIRST_CREATION, "2026-02-15", "20260215T2003+0100");

        // "basics of…" projects

        case "en.program.basics_of_8th.fs":
        case "eo.programo.basics_of_8th.fs":
        case "es.programa.basics_of_8th.fs":
        case "ie.programa.basics_of_8th.fs":
        case "en.program.basics_of_ada.fs":
        case "eo.programo.basics_of_ada.fs":
        case "es.programa.basics_of_ada.fs":
        case "ie.programa.basics_of_ada.fs":
        case "en.program.basics_of_arturo.fs":
        case "eo.programo.basics_of_arturo.fs":
        case "es.programa.basics_of_arturo.fs":
        case "ie.programa.basics_of_arturo.fs":
        case "en.program.basics_of_c-sharp.fs":
        case "eo.programo.basics_of_c-sharp.fs":
        case "es.programa.basics_of_c-sharp.fs":
        case "ie.programa.basics_of_c-sharp.fs":
        case "en.program.basics_of_c3.fs":
        case "eo.programo.basics_of_c3.fs":
        case "es.programa.basics_of_c3.fs":
        case "ie.programa.basics_of_c3.fs":
            return Page_Date(FIRST_START, FIRST_CREATION, LAST_PROJECT_ADDITION, LAST_MODIFIED);
        case "en.program.basics_of_chapel.fs":
        case "eo.programo.basics_of_chapel.fs":
        case "es.programa.basics_of_chapel.fs":
        case "ie.programa.basics_of_chapel.fs":
            return Page_Date(FIRST_START, FIRST_CREATION, "2026-02-09", "20260209T1324+0100");
        case "en.program.basics_of_clojure.fs":
        case "eo.programo.basics_of_clojure.fs":
        case "es.programa.basics_of_clojure.fs":
        case "ie.programa.basics_of_clojure.fs":
        case "en.program.basics_of_crystal.fs":
        case "eo.programo.basics_of_crystal.fs":
        case "es.programa.basics_of_crystal.fs":
        case "ie.programa.basics_of_crystal.fs":
        case "en.program.basics_of_d.fs":
        case "eo.programo.basics_of_d.fs":
        case "es.programa.basics_of_d.fs":
        case "ie.programa.basics_of_d.fs":
            return Page_Date(FIRST_START, FIRST_CREATION, LAST_PROJECT_ADDITION, LAST_MODIFIED);
        case "en.program.basics_of_elixir.fs":
        case "eo.programo.basics_of_elixir.fs":
        case "es.programa.basics_of_elixir.fs":
        case "ie.programa.basics_of_elixir.fs":
            return Page_Date("2026-01-23", "2026-01-23", LAST_PROJECT_ADDITION, LAST_MODIFIED);
        case "en.program.basics_of_f-sharp.fs":
        case "eo.programo.basics_of_f-sharp.fs":
        case "es.programa.basics_of_f-sharp.fs":
        case "ie.programa.basics_of_f-sharp.fs":
        case "en.program.basics_of_factor.fs":
        case "eo.programo.basics_of_factor.fs":
        case "es.programa.basics_of_factor.fs":
        case "ie.programa.basics_of_factor.fs":
        case "en.program.basics_of_freebasic.fs":
        case "eo.programo.basics_of_freebasic.fs":
        case "es.programa.basics_of_freebasic.fs":
        case "ie.programa.basics_of_freebasic.fs":
            return Page_Date(FIRST_START, FIRST_CREATION, LAST_PROJECT_ADDITION, LAST_MODIFIED);
        case "en.program.basics_of_gleam.fs":
        case "eo.programo.basics_of_gleam.fs":
        case "es.programa.basics_of_gleam.fs":
        case "ie.programa.basics_of_gleam.fs":
            return Page_Date("2026-01-23", "2026-01-23", LAST_PROJECT_ADDITION, LAST_MODIFIED);
        case "en.program.basics_of_go.fs":
        case "eo.programo.basics_of_go.fs":
        case "es.programa.basics_of_go.fs":
        case "ie.programa.basics_of_go.fs":
            return Page_Date(FIRST_START, FIRST_CREATION, LAST_PROJECT_ADDITION, LAST_MODIFIED);
        case "en.program.basics_of_hare.fs":
        case "eo.programo.basics_of_hare.fs":
        case "es.programa.basics_of_hare.fs":
        case "ie.programa.basics_of_hare.fs":
            return Page_Date(FIRST_START, FIRST_CREATION, "2026-02-13", "20260213T1646+0100");
        case "en.program.basics_of_haxe.fs":
        case "eo.programo.basics_of_haxe.fs":
        case "es.programa.basics_of_haxe.fs":
        case "ie.programa.basics_of_haxe.fs":
        case "en.program.basics_of_icon.fs":
        case "eo.programo.basics_of_icon.fs":
        case "es.programa.basics_of_icon.fs":
        case "ie.programa.basics_of_icon.fs":
        case "en.program.basics_of_io.fs":
        case "eo.programo.basics_of_io.fs":
        case "es.programa.basics_of_io.fs":
        case "ie.programa.basics_of_io.fs":
            return Page_Date(FIRST_START, FIRST_CREATION, LAST_PROJECT_ADDITION, LAST_MODIFIED);
        case "en.program.basics_of_janet.fs":
        case "eo.programo.basics_of_janet.fs":
        case "es.programa.basics_of_janet.fs":
        case "ie.programa.basics_of_janet.fs":
            return Page_Date("2025-12-25", "2025-12-25", "2026-02-15", "20260215T2003+0100");
        case "en.program.basics_of_julia.fs":
        case "eo.programo.basics_of_julia.fs":
        case "es.programa.basics_of_julia.fs":
        case "ie.programa.basics_of_julia.fs":
        case "en.program.basics_of_kotlin.fs":
        case "eo.programo.basics_of_kotlin.fs":
        case "es.programa.basics_of_kotlin.fs":
        case "ie.programa.basics_of_kotlin.fs":
        case "en.program.basics_of_lobster.fs":
        case "eo.programo.basics_of_lobster.fs":
        case "es.programa.basics_of_lobster.fs":
        case "ie.programa.basics_of_lobster.fs":
        case "en.program.basics_of_lua.fs":
        case "eo.programo.basics_of_lua.fs":
        case "es.programa.basics_of_lua.fs":
        case "ie.programa.basics_of_lua.fs":
            return Page_Date(FIRST_START, FIRST_CREATION, LAST_PROJECT_ADDITION, LAST_MODIFIED);
        case "en.program.basics_of_nature.fs":
        case "eo.programo.basics_of_nature.fs":
        case "es.programa.basics_of_nature.fs":
        case "ie.programa.basics_of_nature.fs":
            return Page_Date("2025-12-25", "2025-12-25", LAST_PROJECT_ADDITION, LAST_MODIFIED);
        case "en.program.basics_of_neat.fs":
        case "eo.programo.basics_of_neat.fs":
        case "es.programa.basics_of_neat.fs":
        case "ie.programa.basics_of_neat.fs":
            return Page_Date("2025-12-17", "2025-12-17", LAST_PROJECT_ADDITION, LAST_MODIFIED);
        case "en.program.basics_of_neko.fs":
        case "eo.programo.basics_of_neko.fs":
        case "es.programa.basics_of_neko.fs":
        case "ie.programa.basics_of_neko.fs":
        case "en.program.basics_of_nelua.fs":
        case "eo.programo.basics_of_nelua.fs":
        case "es.programa.basics_of_nelua.fs":
        case "ie.programa.basics_of_nelua.fs":
        case "en.program.basics_of_nim.fs":
        case "eo.programo.basics_of_nim.fs":
        case "es.programa.basics_of_nim.fs":
        case "ie.programa.basics_of_nim.fs":
        case "en.program.basics_of_nit.fs":
        case "eo.programo.basics_of_nit.fs":
        case "es.programa.basics_of_nit.fs":
        case "ie.programa.basics_of_nit.fs":
        case "en.program.basics_of_oberon-07.fs":
        case "eo.programo.basics_of_oberon-07.fs":
        case "es.programa.basics_of_oberon-07.fs":
        case "ie.programa.basics_of_oberon-07.fs":
        case "en.program.basics_of_ocaml.fs":
        case "eo.programo.basics_of_ocaml.fs":
        case "es.programa.basics_of_ocaml.fs":
        case "ie.programa.basics_of_ocaml.fs":
        case "en.program.basics_of_odin.fs":
        case "eo.programo.basics_of_odin.fs":
        case "es.programa.basics_of_odin.fs":
        case "ie.programa.basics_of_odin.fs":
        case "en.program.basics_of_pike.fs":
        case "eo.programo.basics_of_pike.fs":
        case "es.programa.basics_of_pike.fs":
        case "ie.programa.basics_of_pike.fs":
            return Page_Date(FIRST_START, FIRST_CREATION, LAST_PROJECT_ADDITION, LAST_MODIFIED);
        case "en.program.basics_of_pony.fs":
        case "eo.programo.basics_of_pony.fs":
        case "es.programa.basics_of_pony.fs":
        case "ie.programa.basics_of_pony.fs":
            return Page_Date("2026-02-07", "2026-02-07", LAST_PROJECT_ADDITION, LAST_MODIFIED);
        case "en.program.basics_of_python.fs":
        case "eo.programo.basics_of_python.fs":
        case "es.programa.basics_of_python.fs":
        case "ie.programa.basics_of_python.fs":
        case "en.program.basics_of_racket.fs":
        case "eo.programo.basics_of_racket.fs":
        case "es.programa.basics_of_racket.fs":
        case "ie.programa.basics_of_racket.fs":
        case "en.program.basics_of_raku.fs":
        case "eo.programo.basics_of_raku.fs":
        case "es.programa.basics_of_raku.fs":
        case "ie.programa.basics_of_raku.fs":
        case "en.program.basics_of_retro.fs":
        case "eo.programo.basics_of_retro.fs":
        case "es.programa.basics_of_retro.fs":
        case "ie.programa.basics_of_retro.fs":
        case "en.program.basics_of_rexx.fs":
        case "eo.programo.basics_of_rexx.fs":
        case "es.programa.basics_of_rexx.fs":
        case "ie.programa.basics_of_rexx.fs":
        case "en.program.basics_of_ring.fs":
        case "eo.programo.basics_of_ring.fs":
        case "es.programa.basics_of_ring.fs":
        case "ie.programa.basics_of_ring.fs":
        case "en.program.basics_of_rust.fs":
        case "eo.programo.basics_of_rust.fs":
        case "es.programa.basics_of_rust.fs":
        case "ie.programa.basics_of_rust.fs":
        case "en.program.basics_of_scala.fs":
        case "eo.programo.basics_of_scala.fs":
        case "es.programa.basics_of_scala.fs":
        case "ie.programa.basics_of_scala.fs":
            return Page_Date(FIRST_START, FIRST_CREATION, LAST_PROJECT_ADDITION, LAST_MODIFIED);
        case "en.program.basics_of_scheme.fs":
        case "eo.programo.basics_of_scheme.fs":
        case "es.programa.basics_of_scheme.fs":
        case "ie.programa.basics_of_scheme.fs":
            return Page_Date("2025-12-01", "2025-12-01", LAST_PROJECT_ADDITION, LAST_MODIFIED);
        case "en.program.basics_of_styx.fs":
        case "eo.programo.basics_of_styx.fs":
        case "es.programa.basics_of_styx.fs":
        case "ie.programa.basics_of_styx.fs":
            return Page_Date("2025-12-17", "2025-12-17", LAST_PROJECT_ADDITION, LAST_MODIFIED);
        case "en.program.basics_of_swift.fs":
        case "eo.programo.basics_of_swift.fs":
        case "es.programa.basics_of_swift.fs":
        case "ie.programa.basics_of_swift.fs":
        case "en.program.basics_of_v.fs":
        case "eo.programo.basics_of_v.fs":
        case "es.programa.basics_of_v.fs":
        case "ie.programa.basics_of_v.fs":
        case "en.program.basics_of_vala.fs":
        case "eo.programo.basics_of_vala.fs":
        case "es.programa.basics_of_vala.fs":
        case "ie.programa.basics_of_vala.fs":
        case "en.program.basics_of_zig.fs":
        case "eo.programo.basics_of_zig.fs":
        case "es.programa.basics_of_zig.fs":
        case "ie.programa.basics_of_zig.fs":
            return Page_Date(FIRST_START, FIRST_CREATION, LAST_PROJECT_ADDITION, LAST_MODIFIED);

        // programs

        case "en.program.3d_plot.fs":
        case "eo.programo.3d_plot.fs":
        case "es.programa.3d_plot.fs":
        case "ie.programa.3d_plot.fs":
        case "en.program.bagels.fs":
        case "eo.programo.bagels.fs":
        case "es.programa.bagels.fs":
        case "ie.programa.bagels.fs":
        case "en.program.bug.fs":
        case "eo.programo.bug.fs":
        case "es.programa.bug.fs":
        case "ie.programa.bug.fs":
        case "en.program.bunny.fs":
        case "eo.programo.bunny.fs":
        case "es.programa.bunny.fs":
        case "ie.programa.bunny.fs":
        case "en.program.chase.fs":
        case "eo.programo.chase.fs":
        case "es.programa.chase.fs":
        case "ie.programa.chase.fs":
        case "en.program.diamond.fs":
        case "eo.programo.diamond.fs":
        case "es.programa.diamond.fs":
        case "ie.programa.diamond.fs":
        case "en.program.hammurabi.fs":
        case "eo.programo.hammurabi.fs":
        case "es.programa.hammurabi.fs":
        case "ie.programa.hammurabi.fs":
        case "en.program.high_noon.fs":
        case "eo.programo.high_noon.fs":
        case "es.programa.high_noon.fs":
        case "ie.programa.high_noon.fs":
        case "en.program.math.fs":
        case "eo.programo.math.fs":
        case "es.programa.math.fs":
        case "ie.programa.math.fs":
        case "en.program.mugwump.fs":
        case "eo.programo.mugwump.fs":
        case "es.programa.mugwump.fs":
        case "ie.programa.mugwump.fs":
        case "en.program.name.fs":
        case "eo.programo.name.fs":
        case "es.programa.name.fs":
        case "ie.programa.name.fs":
        case "en.program.poetry.fs":
        case "eo.programo.poetry.fs":
        case "es.programa.poetry.fs":
        case "ie.programa.poetry.fs":
        case "en.program.russian_roulette.fs":
        case "eo.programo.russian_roulette.fs":
        case "es.programa.russian_roulette.fs":
        case "ie.programa.russian_roulette.fs":
        case "en.program.seance.fs":
        case "eo.programo.seance.fs":
        case "es.programa.seance.fs":
        case "ie.programa.seance.fs":
            return Page_Date(FIRST_START, FIRST_CREATION, "2026-02-13", "20260213T1646+0100");
        case "en.program.sine_wave.fs":
        case "eo.programo.sine_wave.fs":
        case "es.programa.sine_wave.fs":
        case "ie.programa.sine_wave.fs":
            return Page_Date(FIRST_START, FIRST_CREATION, "2026-02-15", "20260215T2004+0100");
        case "en.program.slots.fs":
        case "eo.programo.slots.fs":
        case "es.programa.slots.fs":
        case "ie.programa.slots.fs":
        case "en.program.stars.fs":
        case "eo.programo.stars.fs":
        case "es.programa.stars.fs":
        case "ie.programa.stars.fs":
        case "en.program.strings.fs":
        case "eo.programo.strings.fs":
        case "es.programa.strings.fs":
        case "ie.programa.strings.fs":
        case "en.program.xchange.fs":
        case "eo.programo.xchange.fs":
        case "es.programa.xchange.fs":
        case "ie.programa.xchange.fs":
            return Page_Date(FIRST_START, FIRST_CREATION, "2026-02-13", "20260213T1646+0100");
        case "en.program.z-end.fs":
        case "eo.programo.z-end.fs":
        case "es.programa.z-end.fs":
        case "ie.programa.z-end.fs":
            return Page_Date("2025-12-19", "2025-12-19", "2026-02-09", "20260209T1324+0100");
        default:
            assert(false);
    }
}

/// Write the [Fendo](http://programandala.net/en.program.fendo.html) metadata
/// fields of the given project, of the give type, in the given language and to
/// the given file.

@fn void write_metadata
(
    File file,
    string project,
    Human_Lang human_lang,
    Page_Type type,
    Page_Date page_date,
)
{
    @var string programming_lang_id = programming_lang_id_of_project(project);
    @var string programming_lang_name = programming_lang_name(programming_lang_id);

    @var string page_title = page_title(project, human_lang);

    @var string root =
        filename_lang_prefix(human_lang)
        ~ translated(Text.program, human_lang);

    file.writeln("\ndata{\n");

    file.writeln("edit_summary ", translated(Text.new_page, human_lang), ".");

    file.writeln("title ", page_title);

    // `description` field
    file.write("description ");
    final switch(type)
    {
        case Page_Type.metaproject:
            switch(human_lang)
            {
                case Human_Lang.en:
                    file.writeln("Metaproject about the \"Basics of…\" projects.");
                    break;
                case Human_Lang.eo:
                    file.writeln("Metaprojekto pri la projektoj en(( «Basics of…» )) .");
                    break;
                case Human_Lang.es:
                    file.writeln("Metaproyecto sobre los proyectos en(( «Basics of…» )) .");
                    break;
                case Human_Lang.ie:
                    file.writeln("Metaprojecte pri li projectes en(( «Basics of…» )) .");
                    break;
                default:
                    assert(false);
            }
            break;
        case Page_Type.lang_project:
            switch(human_lang)
            {
                case Human_Lang.en:
                    file.write("Conversion of old BASIC programs to ");
                    break;
                case Human_Lang.eo:
                    file.write("Konverto de malnovaj BASIC-programoj al ");
                    break;
                case Human_Lang.es:
                    file.write("Conversión de antiguos programas de BASIC a ");
                    break;
                case Human_Lang.ie:
                    file.write("Conversion de old BASIC-programas a ");
                    break;
                default:
                    assert(false);
            }
            file.write(programming_lang_name, " ");
            switch(human_lang)
            {
                case Human_Lang.en:
                    file.writeln("in order to learn the basics of this language.");
                    break;
                case Human_Lang.eo:
                    file.writeln("por lerni la fundamentojn de ĉi-tiu lingvo.");
                    break;
                case Human_Lang.es:
                    file.writeln("para aprender los rudimentos de este lenguaje.");
                    break;
                case Human_Lang.ie:
                    file.writeln("por aprender lu elementari de ti-ci lingue.");
                    break;
                default:
                    assert(false);
            }
            break;
        case Page_Type.program_project:
            switch(human_lang)
            {
                case Human_Lang.en:
                    file.write("Conversion of ");
                    break;
                case Human_Lang.eo:
                    file.write("Konvertado de ");
                    break;
                case Human_Lang.es:
                    file.write("Conversión de ");
                    break;
                case Human_Lang.ie:
                    file.write("Conversion de ");
                    break;
                default:
                    assert(false);
            }
            file.write(page_title);
            switch(human_lang)
            {
                case Human_Lang.en:
                    file.writeln(" to several programming languages.");
                    break;
                case Human_Lang.eo:
                    file.writeln(" al pluri program-lingvoj.");
                    break;
                case Human_Lang.es:
                    file.writeln(" a varios lenguajes de programación.");
                    break;
                case Human_Lang.ie:
                    file.writeln(" a pluri lingues de programation.");
                    break;
                default:
                    assert(false);
            }
            break;
    }

    file.writeln("started ", page_date.start);
    file.writeln("created ", page_date.creation);
    file.writeln("modified ", page_date.modification);

    file.writeln("upper_page ", root);

    final switch(type)
    {
        case Page_Type.metaproject:

            file.writeln("previous_page [calculated]");
            file.writeln("next_page [calculated]");
            break;

        case Page_Type.lang_project:

            // NB the contents of these fields must be manually adapted.

            @var string previous = previous(programming_lang_id, programming_lang_ids);
            file.writeln
            (
                "previous_page",
                previous.length ? format(" %s.basics_of_%s", root, previous) : ""
            );

            @var string next = next(programming_lang_id, programming_lang_ids);
            file.writeln
            (
                "next_page",
                next.length ? format(" %s.basics_of_%s", root, next) : ""
            );

            break;

        case Page_Type.program_project:

            // NB the contents of these fields must be manually adapted.

            @var string previous = previous(project, program_ids);
            file.writeln
            (
                "previous_page",
                previous.length ? format(" %s.%s", root, previous) : ""
            );

            @var string next = next(project, program_ids);
            file.writeln("next_page", (next.length ? format(" %s.%s", root, next) : ""));

            break;
    }

    file.writeln("first_page ", root, ".**first**");
    file.writeln("last_page ", root, ".**last**");

    file.writeln("language ", human_lang);

    // `alternate` fields
    foreach (Human_Lang lang; human_langs)
    {
        if (lang != human_lang)
        {
            file.writefln
            (
                "alternate.%1$s %1$s.%s.%s",
                iso_code(lang),
                translated(Text.program, lang),
                project
            );
        }
    }

    // `related` field
    file.write("related");
    final switch(type)
    {
        case Page_Type.metaproject:
            foreach (programming_lang_id_; programming_lang_ids)
            {
                file.write(" ", page_id("basics_of_" ~ programming_lang_id_, human_lang));
            }
            file.writeln();
            break;
        case Page_Type.lang_project:
            file.write(" ");
            file.write(page_id("basics_off", human_lang));
            foreach (programming_lang_id_; programming_lang_ids)
            {
                if (programming_lang_id_ != programming_lang_id)
                {
                    file.write(" ", page_id("basics_of_" ~ programming_lang_id_, human_lang));
                }
            }
            file.writeln();
            break;
        case Page_Type.program_project:
            file.write(" ");
            file.write(page_id("basics_off", human_lang));
            if (project == "sine_wave")
            {
                file.write(" ", page_id("basics_off#sine_wave", human_lang));
            }
            file.writeln();
            break;
    }

    // `external_related` field
    final switch(type)
    {
        case Page_Type.metaproject:
            file.write("external_related");
            foreach (programming_lang_id_; programming_lang_ids)
            {
                file.write(" " ~ programming_lang_id_ ~ "_ext" );
            }
            file.writeln();
            break;
        case Page_Type.lang_project:
            file.writeln("external_related " ~ programming_lang_id ~ "_ext" );
            break;
        case Page_Type.program_project:
            file.write("external_related");
            foreach (program_version; program_project_versions(project))
            {
                file.write(" " ~ program_version.programming_lang_id ~ "_ext");
            }
            file.writeln();
            break;
    }

    // `tags` field
    final switch(type)
    {
        case Page_Type.metaproject:
            file.write("tags");
            foreach (programming_lang_id_; programming_lang_ids)
            {
                file.write(" ", programming_lang_id_);
            }
            file.writeln();
            break;
        case Page_Type.lang_project:
            file.writeln("tags ", programming_lang_id_of_project(project));
            break;
        case Page_Type.program_project:
            file.write("tags");
            foreach (program_version; program_project_versions(project))
            {
                file.write(" ", program_version.programming_lang_id);
            }
            break;
    }

    file.writeln("\n}data");
}

// "Basics off" metaproject pages {{{2
// -----------------------------------------------------------------------------

/// Return the description of the conversions tool in the given language.

@fn string description_of_conversions_tool(Human_Lang human_lang)
{
    switch (human_lang)
    {
        case Human_Lang.en:
            return
                "This program, written in [[ fish ]] ,\n"
                ~ "creates two [[ asciidoc ]] documents\n"
                ~ "containing tables which show the number of\n"
                ~ "conversions per programming language and which program\n"
                ~ "has been converted to which.";
        case Human_Lang.eo:
            return
                "Ĉi-tiu programo, verkita en [[ fish ]] ,\n"
                ~ "kreas du [[ asciidoc | AsciiDoc-dokumentojn ]]\n"
                ~ "enhavantajn tabelojn kiuj montras\n"
                ~ "la nombron de programoj konvertitaj al ĉiu\n"
                ~ "program-lingvo kaj kiu programo estas konvertitaj en kiu.";
        case Human_Lang.es:
            return
                "Este programa, escrito en [[ fish ]] ,\n"
                ~ "crea dos documentos en [[ asciidoc ]]\n"
                ~ "con tablas que muestran el número de\n"
                ~ "programas convertidos a cada lenguaje de programación y\n"
                ~ "qué programa ha sido convertido a cuál.";
        case Human_Lang.ie:
            return
                "Ti-ci programa, scrit in [[ fish ]] ,\n"
                ~ "crea du documentes en [[ asciidoc ]]\n"
                ~ "con tabelles queles monstra li númere de\n"
                ~ "programas convertet a chascun lingue de programation e\n"
                ~ "quel programa ha esset convertet a quel.";
        default:
            assert(false);
    }
}

/// Return the description of the activity tool in the given language.

@fn string description_of_activity_tool(Human_Lang human_lang)
{
    switch (human_lang)
    {
        case Human_Lang.en:
            return
                "This program, written in [[ julia ]] , creates several\n"
                ~ "CSV-format data files containing the activity\n"
                ~ "of the project repositories.";
        case Human_Lang.eo:
            return
                "Ĉi-tiu programo, verkita en [[ julia ]] , kreas plurajn\n"
                ~ "CSV-formatajn daten-dosierojn enhavantajn la aktivecon\n"
                ~ "de la projektaj deponejoj.";
        case Human_Lang.es:
            return
                "Este programa, escrito en [[ julia ]] , crea varios\n"
                ~ "ficheros de datos en formato CSV que contienen la\n"
                ~ "actividad de los repositorios de los proyectos.";
        case Human_Lang.ie:
            return
                "Ti-ci programa, scrit in [[ julia ]] , crea pluri\n"
                ~ "data-files in CSV-formate queles contene li activitá\n"
                ~ "del depositorias del projectes.";
        default:
            assert(false);
    }
}

/// Return the description of the pages tool in the given language.

@fn string description_of_pages_tool(Human_Lang human_lang)
{
    @var immutable ulong programming_languages = programming_lang_ids.length;
    @var immutable ulong human_languages = human_langs.length;
    @var immutable ulong programs = program_ids.length;
    @var immutable ulong pages =
        (1 + programming_languages + programs) * human_languages;

    @var immutable string programming_languages_ = to!string(programming_languages);
    @var immutable string human_languages_ = to!string(human_languages);
    @var immutable string programs_ = to!string(programs);
    @var immutable string pages_ = to!string(pages);

    switch (human_lang)
    {
        case Human_Lang.en:
            return
                "This program, written in [[ d ]] ,\n"
                ~ "creates the [[ fendo | Fendo-format ]] sources\n"
                ~ "of the pages of [[ en | programandala.net ]] about the\n"
                ~ " // Basics off // metaproject, the\n"
                ~ programming_languages_
                ~ " started \"Basics of…\" projects and their\n"
                ~ programs_ ~ " programs.\n"
                ~ "Everything in " ~ human_languages_ ~ " languages;\n"
                ~ "therefore, a total of " ~ pages_ ~ " pages.";
        case Human_Lang.eo:
            return
                "Ĉi-tiu programo, verkita en [[ d ]] ,\n"
                ~ "kreas la [[ fendo | Fendo-formatajn ]] fontojn\n"
                ~ "de la paĝoj de [[ eo | programandala.net ]] pri la\n"
                ~ "metaprojekto // en(( Basics off )) // , la\n"
                ~ programming_languages_
                ~ " komencitaj projektoj « en(( Basic of… )) » kaj\n"
                ~ "iliaj " ~ programs_ ~ " programoj.\n"
                ~ "Ĉio en " ~ human_languages_ ~ " lingvoj;\n"
                ~ "do, totale " ~ pages_ ~ " paĝojn.";
        case Human_Lang.es:
            return
                "Este programa, escrito en [[ d ]] ,\n"
                ~ "crea las fuentes, en formato de [[ fendo ]] ,\n"
                ~ "de las páginas de [[ es | programandala.net ]] sobre el\n"
                ~ "metaproyecto « en(( Basics off )) » , los\n"
                ~ programming_languages_
                ~ " proyectos « en(( Basic of… )) » iniciados y\n"
                ~ "sus " ~ programs_ ~ " programas.\n"
                ~ "Todo ello en " ~ human_languages_ ~ " lenguas;\n"
                ~ "por tanto, un total de " ~ pages_ ~ " páginas.";
        case Human_Lang.ie:
            return
                "Ti-ci programa, scrit in [[ d ]] ,\n"
                ~ "crea li fontes, in formate de [[ fendo ]] ,\n"
                ~ "del págines de [[ ie | programandala.net ]] pri li\n"
                ~ "metaprojecte // en(( Basics off )) // , li\n"
                ~ programming_languages_
                ~ " projectes « en(( Basic of… )) » iniciat e\n"
                ~ "lor " ~ programs_ ~ " programas.\n"
                ~ "Omno in " ~ human_languages_ ~ " lingues;\n"
                ~ "dunc, un totale de " ~ pages_ ~ " págines.";
        default:
            assert(false);
    }
}

/// Return the description of the gnuplot files in the given language.

@fn string description_of_gnuplot_files(Human_Lang human_lang)
{
    switch (human_lang)
    {
        case Human_Lang.en:
            return
                "These [[ gnuplot ]] configuration files make it possible\n"
                ~ "to convert the CSV data files to graphics.";
        case Human_Lang.eo:
            return
                "Ĉi-tiuj dosieroj de konfiguro de [[ gnuplot ]] ebligas\n"
                ~ "konverti la CSV-formatajn daten-dosierojn en grafikaĵojn.";
        case Human_Lang.es:
            return
                "Estos ficheros de configuración de [[ gnuplot ]]\n"
                ~ "posibilitan convertir los ficheros de datos\n"
                ~ "de formato CSV en gráficos.";
        case Human_Lang.ie:
            return
                "Ti-ci files de configuration de [[ gnuplot ]] possibilisa\n"
                ~ "converter li data-files de CSV-formate in grafics.";
        default:
            assert(false);
    }
}

/// Return the description of the "basics off" metaproject.

@fn string metaproject_description(Human_Lang human_lang)
{
    @var immutable ulong projects = project_dirs.length;
    @var immutable ulong programming_languages = programming_lang_ids.length;
    @var immutable string projects_ = to!string(projects);
    @var immutable string programming_languages_ = to!string(programming_languages);
    switch (human_lang)
    {
        case Human_Lang.en:
            return
                "// Basics off // is a metaproject about the "
                ~ projects_
                ~ " \"Basics of…\" projects"
                ~ " (" ~ programming_languages_ ~ " started)."
                ~ " It consists of three programs that"
                ~ " use the contents of the repositories in order to "
                ~ link("#conversions_tool", "count the conversions (in fish)")
                ~ " , "
                ~ link("#activity_tool", "extract the activity data (in Julia)")
                ~ " , and "
                ~ link("#pages_tool", "build the webpages (in D)")
                ~ " ; also several "
                ~ link("#gnuplot_files", "gnuplot files")
                ~ " that create the graphics, and a "
                ~ link("#makefile", "Makefile")
                ~ " to build everything. The results are shown below: the "
                ~ link("#conversions", "conversion reports")
                ~ " , the "
                ~ link("#graphics", "activity graphics")
                ~ " and a hand-made "
                ~ link("#sine_wave", "report on the size of one of the executables")
                ~ " ."
                ;
        case Human_Lang.eo:
            return
                "en(( // Basics off // )) estas metaprojekto pri la "
                ~ projects_
                ~ " projektoj « en(( // Basics of… // )) »"
                ~ " (" ~ programming_languages_ ~ " komencitaj)."
                ~ " Ĝi konsistas el tri programoj kiuj"
                ~ " uzas la enhavon de la deponejoj por "
                ~ link("#conversions_tool", "kalkuli la konvertojn (en fish)")
                ~ " , "
                ~ link("#activity_tool", "elpreni la datenojn pri aktiveco (en Julia)")
                ~ " , kaj "
                ~ link("#pages_tool", "konstrui la ret-paĝojn (en D)")
                ~ " ; ankaŭ el pluraj "
                ~ link("#gnuplot_files", "dosieroj de gnuplot")
                ~ " kiuj kreas la grafikaĵojn, kaj "
                ~ link("#makefile", "Makefile")
                ~ " por konstrui ĉion. La rezultoj estas sube montritaj: la "
                ~ link("#conversions", "raportoj pri la konvertoj")
                ~ " , la "
                ~ link("#graphics", "grafikaĵoj pri aktiveco")
                ~ " kaj man-farita "
                ~ link("#sine_wave", "raporto pri la grando de ekzekutebla dosiero")
                ~ " ."
                ;
        case Human_Lang.es:
            return
                "en(( // Basics off // )) es un metaproyecto sobre los "
                ~ projects_
                ~ " proyectos « en(( // Basics of… // )) »"
                ~ " (" ~ programming_languages_ ~ " comenzados)."
                ~ " Consta de tres programas que"
                ~ " usan el contenido de los repositorios para "
                ~ link("#conversions_tool", "contar las conversiones (en fish)")
                ~ " , "
                ~ link("#activity_tool", "extraer los datos de actividad (en Julia)")
                ~ " , y "
                ~ link("#pages_tool", "construir las páginas web (en D)")
                ~ " ; así como de varios "
                ~ link("#gnuplot_files", "ficheros de gnuplot")
                ~ " que crean los gráficos, y un "
                ~ link("#makefile", "Makefile")
                ~ " para construir todo. Los resultados se muestran a continuación: los "
                ~ link("#conversions", "informes sobre las conversiones")
                ~ " , los "
                ~ link("#graphics", "gráficos de actividad")
                ~ " y un "
                ~ link("#sine_wave", "informe sobre el tamaño de un ejecutable")
                ~ " , elaborado a mano"
                ~ " ."
                ;
        case Human_Lang.ie:
            return
                "en(( // Basics off // )) es un metaprojecte pri li "
                ~ projects_
                ~ " projectes « en(( // Basics of… // )) »"
                ~ " (" ~ programming_languages_ ~ " comensat)."
                ~ " It consiste in tri programas queles"
                ~ " usa li contenete del depositorias por "
                ~ link("#conversions_tool", "contar li conversiones (in fish)")
                ~ " , "
                ~ link("#activity_tool", "extracter li data de activitá (in Julia)")
                ~ " , e "
                ~ link("#pages_tool", "constructer li págines retal (in D)")
                ~ " ; anc pluri "
                ~ link("#gnuplot_files", "gnuplot-files")
                ~ " queles crea li grafics e un "
                ~ link("#makefile", "Makefile")
                ~ " por constructer omno. Li resultates es monstrat in infra: li "
                ~ link("#conversions", "raportes pri li conversiones")
                ~ " , li "
                ~ link("#graphics", "grafics de activitá")
                ~ " e un "
                ~ link("#sine_wave", "raporte pri li grandore de un executibile")
                ~ " , manualmen fat"
                ~ " ."
                ;
        default:
            assert(false);
    }
}

/// Write to the given file the contents of the metaproject [page
/// type][Page_Type] in the given language.

@fn void write_contents_of_metaproject(File file, Human_Lang human_lang)
{
    enum string PROG_TARGET = "prog/basics_off/target/";
    enum string SOURCE = "basics_off/src/";
    enum string PROG_SOURCE = "prog/" ~ SOURCE;
    file.writeln(paragraph(metaproject_description(human_lang)));
    file.writeln(anchor("conversions"));

    // NB The following fake `<a>` HTML tag is added in order to force Fendo to
    // execute it and apply the "conversions" anchor to it; otherwise the
    // anchor will have no effect in the final code because the first HTML tag
    // after it is not created directly by a Fendo markup but by the HTML code
    // Fendo inserts out of a converted AsciiDoc document:

    file.writeln("<a>");
    file.writeln("</a>");

    file.writeln
    (
        in_code_markup
        (
            "s\"  --attribute "
            ~ to!string(human_lang)
            ~ "\" asciidoctor_options!"
            ~ " s\" "
            ~ PROG_TARGET
            ~ "conversions__language_ranking.adoc\" include_asciidoctor"
            ~ " s\" \" asciidoctor_options!"
        )
    );
    file.writeln
    (
        in_code_markup
        (
            "s\"  --attribute "
            ~ to!string(human_lang)
            ~ "\" asciidoctor_options!"
            ~ " s\" "
            ~ PROG_TARGET
            ~ "conversions__programs_and_languages.adoc\" include_asciidoctor"
            ~ " s\" \" asciidoctor_options!"
        )
    );

    file.writeln(anchor("graphics"));
    file.writeln(heading(2, translated(Text.conversions_plot, human_lang)));
    file.writeln
    (
        image
        (
            PROG_TARGET
            ~ "activity__conversions_plot_rowstacked"
            ~ filename_lang_suffix(human_lang)
            ~ ".png"
        )
    );

    file.writeln(heading(2, translated(Text.commits_plot, human_lang)));
    file.writeln
    (
        image
        (
            PROG_TARGET
            ~ "activity__commits_plot_rowstacked"
            ~ filename_lang_suffix(human_lang)
            ~ ".png"
        )
    );

    file.writeln(anchor("sine_wave"));
    file.writeln(heading(2, translated(Text.size_report, human_lang)));
    switch(human_lang)
    {
        case Human_Lang.en:
            file.writeln
            (
                paragraph
                (
                    sine_wave_link(human_lang)
                    ~ " is used to compare the sizes of the executables,"
                    ~ " native or handled,"
                    ~ " created by the programming languages of the project."
                    ~ " The following table contains the results"
                    ~ " in increasing order."
                )
            );
            break;
        case Human_Lang.eo:
            file.writeln
            (
                paragraph
                (
                    sine_wave_link(human_lang)
                    ~ " estas uzita por kompari la grandojn de la ekzekuteblaj dosieroj,"
                    ~ " dosieroj, nativaj aŭ traktataj,"
                    ~ " kreitaj de la program-lingvoj de la projekto."
                    ~ " La suba tabelo enhavas la rezultojn"
                    ~ " en kreskanta ordo."
                )
            );
            break;
        case Human_Lang.es:
            file.writeln
            (
                paragraph
                (
                    sine_wave_link(human_lang)
                    ~ " se usa para comparar los tamaños de los ejecutables,"
                    ~ " nativos o gestionados,"
                    ~ " creados por los lenguajes de programación del proyecto."
                    ~ " La siguiente tabla contiene los resultados"
                    ~ " en orden creciente."
                )
            );
            break;
        case Human_Lang.ie:
            file.writeln
            (
                paragraph
                (
                    sine_wave_link(human_lang)
                    ~ " es usat por comparar li grandores del executibiles,"
                    ~ " natal o tractat,"
                    ~ " creat per li lingues de programation del projecte."
                    ~ " Li sequent tabelle contene li resultates"
                    ~ " in órdine crescent."
                )
            );
            break;
        default:
            assert(false);
    }
    file.writeln
    (
        in_code_markup
        (
            "s\"  --attribute "
            ~ to!string(human_lang)
            ~ "\" asciidoctor_options!"
            ~ " s\" "
            ~ PROG_SOURCE
            ~ "sine_wave_executable_sizes.adoc\" include_asciidoctor"
            ~ " s\" \" asciidoctor_options!"
        )
    );

    file.writeln(heading(2, translated(Text.tools, human_lang)));

    file.writeln(anchor("conversions_tool"));
    file.writeln(heading(3, translated(Text.conversions_tool, human_lang)));
    file.writeln(paragraph(description_of_conversions_tool(human_lang)));
    file.writeln(as_source_code_import_markup(SOURCE ~ "count_conversions.fish"));

    file.writeln(anchor("activity_tool"));
    file.writeln(heading(3, translated(Text.activity_tool, human_lang)));
    file.writeln(paragraph(description_of_activity_tool(human_lang)));
    file.writeln(as_source_code_import_markup(SOURCE ~ "calculate_activity.jl"));

    file.writeln(anchor("pages_tool"));
    file.writeln(heading(3, translated(Text.pages_tool, human_lang)));
    file.writeln(paragraph(description_of_pages_tool(human_lang)));
    file.writeln(as_source_code_import_markup(SOURCE ~ "build_pages.d"));

    file.writeln(anchor("gnuplot_files"));
    file.writeln(heading(3, translated(Text.gnuplot_files, human_lang)));
    file.writeln(paragraph(description_of_gnuplot_files(human_lang)));
    file.writeln
    (
        as_source_code_import_markup(SOURCE ~ "make_conversions_plot_rowstacked.gnuplot")
    );
    file.writeln
    (
        as_source_code_import_markup(SOURCE ~ "make_commits_plot_rowstacked.gnuplot")
    );
    file.writeln
    (
        as_source_code_import_markup(SOURCE ~ "make_conversions_plot_with_linespoints.gnuplot")
    );
    file.writeln
    (
        as_source_code_import_markup(SOURCE ~ "make_commits_plot_with_linespoints.gnuplot")
    );
    file.writeln(as_source_code_import_markup(SOURCE ~ "common.gnuplot"));
    file.writeln(as_source_code_import_markup(SOURCE ~ "colors.gnuplot"));

    file.writeln(anchor("makefile"));
    file.writeln(heading(3, "Makefile"));
    file.writeln(as_source_code_import_markup("basics_off/Makefile"));
}

// "Basics of…" project pages {{{2
// -----------------------------------------------------------------------------

/// Does the given source file contains a finished program? Unfinished programs
/// contain a comment with a conventional [note][UNFINISHED].

@fn bool is_finished(string source_file)
{
    return (!can_find(read_text(source_file), UNFINISHED));
}

/// Write to the given file the contents of a language project [page
/// type][Page_Type] in the given human and programming languages.

@fn void write_contents_of_lang_project
(
    File file,
    string project,
    Human_Lang human_lang,
    string programming_lang_id,
)
{
    @var string programming_lang_name = programming_lang_name(programming_lang_id);
    @var string filename_extension = filename_extension(programming_lang_id);
    @var string source_dir = build_path(REPOS_DIR, project, "src");

    foreach
    (
        string source_file;
        dir_entries(source_dir, "*" ~ filename_extension, Span_Mode.shallow)
            .array()
            .sort()
    )
    {
        if (is_finished(source_file))
        {
            @var string source_file_base_name =
                base_name(source_file, filename_extension);
            file.writeln(anchor(source_file_base_name));
            file.writeln
            (
                heading(2, program_title(source_file_base_name, human_lang))
            );
            file.writeln
            (
                include_file_markup
                (
                    source_file,
                    filename_extension,
                    project
                )
            );
            file.write(LIST_ITEM);
            file.writeln
            (
                link
                (
                    page_id(source_file_base_name, human_lang),
                    translated(Text.all_versions_of, human_lang)
                        ~ " "
                        ~ program_title(source_file_base_name, human_lang)
                )
            );
            if (source_file_base_name == "sine_wave")
            {
                file.write(LIST_ITEM);
                file.writeln(size_report_link(human_lang));
            }
            file.writeln();
        }
    }
}

// Program pages {{{2
// -----------------------------------------------------------------------------

/// Write to the given file the contents of a program project [page
/// type][Page_Type] in the given language.

@fn void write_contents_of_program_project
(
    File file,
    string project,
    Human_Lang human_lang,
)
{
    // Return the [Fendo](http://programandala.net/en.program.fendo.html)
    // markup to create a link to the "Basics off" metaproject page.

    @fn string basics_off_link()
    {
        return
            link
            (
                page_id("basics_off", human_lang),
                "// Basics off //"
            );
    };

    @fn bool has_output(string)
    {
        switch (project)
        {
            case "diamond":
            case "3d_plot":
            case "bunny":
                return true;
            default:
                return false;
        }
    }

    // Return a note about the given conversion order of a project.

    @fn string conversion_order(string order)
    {
        switch (human_lang)
        {
            case Human_Lang.en:
                return
                    "Usually "
                    ~ program_title(project, human_lang)
                    ~ " is the "
                    ~ order
                    ~ " program converted in the "
                    ~ basics_off_link()
                    ~ " projects.";
            case Human_Lang.eo:
                return
                    program_title(project, human_lang)
                    ~ " ofte estas la "
                    ~ order
                    ~ " programo konvertita en la projektoj "
                    ~ basics_off_link()
                    ~ " .";
            case Human_Lang.es:
                return
                    "Normalmente "
                    ~ program_title(project, human_lang)
                    ~ " es el "
                    ~ order
                    ~ " programa convertido en los proyectos "
                    ~ basics_off_link()
                    ~ " .";
            case Human_Lang.ie:
                return
                    program_title(project, human_lang)
                    ~ " normalmen es li "
                    ~ order
                    ~ " programa convertet in li projectes "
                    ~ basics_off_link()
                    ~ " .";
            default:
                assert(false);
        }
    }

    // Write a note about the given conversion order of a project.

    @fn void write_conversion_order(Text order)
    {
        file.writeln
        (
            paragraph(conversion_order(translated(order, human_lang)))
        );
    }

    // If the project is one of the first three, write a note about its
    // conversion order.

    switch (project)
    {
        case "diamond":
            write_conversion_order(Text.first);
            break;
        case "3d_plot":
            write_conversion_order(Text.second);
            break;
        case "sine_wave":
            write_conversion_order(Text.third);
            break;
        default:
            break;
    }

    @fn string plural(ulong n, string ending = "s")
    {
        return n > 1 ? ending : "";
    }

    // Write a note about the number of conversions.

    @var Program_Version[] program_versions = program_project_versions(project);
    @var string number_of_versions = to!string(program_versions.length);

    switch (human_lang)
    {
        case Human_Lang.en:
            file.writeln
            (
                paragraph
                (
                    "This program has been converted to "
                    ~ number_of_versions
                    ~ " programming language" ~ plural(program_versions.length) ~ "."
                )
            );
            break;
        case Human_Lang.eo:
            file.writeln
            (
                paragraph
                (
                    "Ĉi-tiu programo estas konvertita en "
                    ~ number_of_versions
                    ~ " program-lingvo" ~ plural(program_versions.length, "j") ~ "n."
                )
            );
            break;
        case Human_Lang.es:
            file.writeln
            (
                paragraph
                (
                    "Este programa ha sido convertido a "
                    ~ number_of_versions
                    ~ " lenguaje" ~ plural(program_versions.length) ~ " de programación."
                )
            );
            break;
        case Human_Lang.ie:
            file.writeln
            (
                paragraph
                (
                    "Ti-ci programa esset convertet a "
                    ~ number_of_versions
                    ~ " lingue" ~ plural(program_versions.length) ~ " de programation."
                )
            );
            break;
        default:
            assert(false);
    }

    // If the project is "Sine Wave", create a link to its executables report
    // in the "Basics off" metaproject page.

    if (project == "sine_wave")
    {
        file.writeln
        (
            paragraph
            (
                translated(Text.see_also, human_lang)
                ~ ": "
                ~ size_report_link(human_lang)
                ~ " ."
            )
        );
    }

    file.writeln
    (
        heading(2, capitalize(translated(Text.original, human_lang)))
    );
    file.writeln(human_lang == Human_Lang.en ? "en((((" : "");
    file.writeln
    (
        include_file_markup
        (
            base_name
            (
                program_versions[0].source_file,
                program_versions[0].filename_extension,
            ) ~ ".txt",
            "",
            program_versions[0].project_dir
        )
    );
    file.writeln(human_lang == Human_Lang.en ? "))))" : "");
    @var string source_base_filename = base_name
    (
        program_versions[0].source_file,
        program_versions[0].filename_extension,
    );
    file.writeln
    (
        include_file_markup
        (
            source_base_filename ~ ".bas",
            "",
            program_versions[0].project_dir
        )
    );

    if (has_output(source_base_filename))
    {
        file.writeln
        (
            heading(3, capitalize(translated(Text.output, human_lang)))
        );

        import std.file : read;
        file.writeln
        (
            as_source_code_block_markup
            (
                to!string
                (
                    read
                    (
                        build_path(OUTPUTS_DIR, source_base_filename ~ "_basic.txt")
                    )
                )
            )
        );
    }

    // Return the human lang of the name of the given programming lang.

    @fn Human_Lang programming_lang_lang(string programming_lang_id)
    {
        Human_Lang lang = programming_lang_name_lang(programming_lang_id);
        return lang == Human_Lang.any ? human_lang : lang;
    }

    // Add a section for each version of the program.

    foreach (program_version; program_versions)
    {
        file.writeln
        (
            anchor(programming_lang_name(program_version.programming_lang_id))
        );
        file.writeln
        (
            heading
            (
                2,
                capitalize(translated(Text.en, human_lang))
                ~ " "
                ~ in_lang_markup_if_needed
                (
                    programming_lang_name
                    (
                        program_version.programming_lang_id
                    ),
                    programming_lang_lang(program_version.programming_lang_id),
                    human_lang
                )
            )
        );
        file.writeln
        (
            include_file_markup
            (
                program_version.source_file,
                program_version.filename_extension,
                program_version.project_dir
            )
        );
        file.write(LIST_ITEM);
        file.writeln
        (
            link
            (
                page_id
                (
                    "basics_of_" ~ program_version.programming_lang_id,
                    human_lang
                )
            ),
            "\n"
        );
    }
}

// Page building {{{2
// -----------------------------------------------------------------------------

/// Write to the given file the header of the currently built page, including
/// the shebang required to execute the
/// [Fendo](http://programandala.net/en.program.fendo.html) page with
/// [Gforth](http://gnu.org/software/gforth).

@fn void write_header(File file, string filename)
{
    file.writeln("#! /usr/bin/env gforth");
    file.writeln("\n\\ ", filename);
    file.writeln("\n\\ N.B. this file was created by <build_pages.d>,");
    file.writeln("\\ which is part of the \"basics off\" metaproject");
    file.writeln("\\ (http://programandala.net/en.program.basics_off.html);");
    file.writeln("\\ please do not edit.");
    file.writeln("\n\\ last modified ", page_date(filename).last_modified);
    file.writeln("\nrequire ../fendo-programandala.fs");
}

/// Write to the given file the footer of the currently built page, contating
/// just a Vim modeline to set the filetype to
/// [Fendo](http://programandala.net/en.program.fendo.html).

@fn void write_footer(File file)
{
    file.writeln("\n\\ vim: filetype=fendo");
}

@fn void write_redirection(File file, string project, Human_Lang human_lang)
{
    switch (project)
    {
        case "basics_off":
            file.writefln
            (
                "\nredirect %s.%s.%s",
                human_lang,
                translated(Text.program, human_lang),
                "basics_of_haskell"
            );
            break;
        default:
            break;
    }
}

/// Build the page corresponding to the given project, [type][Page_Type], human
/// and programming languages.

@fn void build_page
(
    string project,
    Human_Lang human_lang,
    Page_Type type,
    string programming_lang_id = "",
)
{
    @var string filename = page_filename(project, human_lang);
    @var string filepath = build_path(TARGET_DIR, filename);

    debug (build)
    {
        writefln("building <%s>...", filename);
    }

    // XXX FIXME DMD 2.111.0 crashes when a user attribute is applied to a
    // `File` declaration, unless the initialization is separated

    @var File file;
    file = File(filepath, "w");

    write_header(file, filename);
    write_metadata(file, project, human_lang, type, page_date(filename));
    write_redirection(file, project, human_lang);

    file.writeln("\ncontent{\n");

    final switch (type)
    {
        case Page_Type.metaproject:
            write_contents_of_metaproject(file, human_lang);
            break;
        case Page_Type.lang_project:
            write_contents_of_lang_project
            (
                file,
                project,
                human_lang,
                programming_lang_id
            );
            break;
        case Page_Type.program_project:
            write_contents_of_program_project(file, project, human_lang);
            break;
    }

    file.writeln("\n}content");
    write_footer(file);
    file.close();
    set_attributes(filepath, octal!755);
}

// Init {{{1
// =============================================================================

/// Array containing the directories of the "Basics of…" projects.

@var string[] project_dirs;

/// Array containing the identifiers of the programming languages.

@var string[] programming_lang_ids;

/// Array containing the identifiers of the programs.

@var string[] program_ids;

/// Init the data used by the program, i.e. arrays `programming_lang_ids`
/// and `program_ids`.

@fn void init_data()
{
    foreach
    (
        string entry;
        dir_entries(REPOS_DIR, "basics_of_*", Span_Mode.shallow)
    )
    {
        if (is_dir(entry))
        {
            project_dirs ~= entry;
        }
    }

    debug (data)
    {
        writefln("project_dirs = %s", project_dirs);
    }

    foreach (string dir; project_dirs)
    {
        if (is_dir(build_path(REPOS_DIR, dir)))
        {
            @var string programming_lang_id = programming_lang_id_of_project(dir);
            if (is_programming_lang_used(programming_lang_id))
            {
                programming_lang_ids ~= programming_lang_id;
            }
        }
    }
    sort(programming_lang_ids);
    debug (data)
    {
        writefln("programming_lang_ids = %s", programming_lang_ids);
    }

    foreach (string project_dir; project_dirs)
    {
        if (is_dir(build_path(REPOS_DIR, project_dir)))
        {
            @var string project_source_path =
                build_path(REPOS_DIR, project_dir, "src");
            debug (init)
            {
                writefln("project_source_path = %s", project_source_path);
            }
            foreach
            (
                string file;
                dir_entries(project_source_path, "*", Span_Mode.shallow)
            )
            {
                @var string[] filename = split(split(file, "/")[$ - 1], ".");
                @var string base_filename = filename[0];
                @var string extension = "";
                if (filename.length > 1)
                {
                    extension = filename[1];
                }
                debug (init)
                {
                    writefln("extension = %s", extension);
                }
                if (extension == "txt")
                {
                    debug (init)
                    {
                        writefln
                        (
                            "%s -> %s + %s",
                            file,
                            base_filename,
                            extension
                        );
                    }
                    @var string program_id = usual_program_id(base_filename);
                    debug (init)
                    {
                        writeln("program_id = ", program_id);
                    }
                    if (!can_find(program_ids, program_id))
                    {
                        debug (init)
                        {
                            writefln("adding &s", program_id);
                        }
                        program_ids ~= program_id;
                    }
                }
            }
        }
    }
    sort(program_ids);
}

/// Init the program.

@fn void init()
{
    // Make sure the target directory exists.
    if (exists(TARGET_DIR))
    {
        assert
        (
            is_dir(TARGET_DIR),
            "Fatal error: <" ~ TARGET_DIR ~ "> exists but it's not a directory"
        );
    }
    else
    {
        mkdir(TARGET_DIR);
    }

    init_data();
}

// Main {{{1
// =============================================================================

/// Init the data and build all pages in the four languages of the website.

@fn void main()
{
    init();

    foreach (Human_Lang human_lang; human_langs)
    {
        build_page
        (
            "basics_off",
            human_lang,
            Page_Type.metaproject,
        );
        foreach (string programming_lang_id; programming_lang_ids)
        {
            // note the `programming_lang_ids` array contains only the
            // identifiers of the programming languages actually used to make
            // at least one conversion.  the filter is hardcoded in
            // `is_programming_lang_used` and was applied in `init_data`.
            build_page
            (
                "basics_of_" ~ programming_lang_id,
                human_lang,
                Page_Type.lang_project,
                programming_lang_id,
            );
        }
        foreach (string program_id; program_ids)
        {
            build_page
            (
                program_id,
                human_lang,
                Page_Type.program_project,
            );
        }
    }
}

// Change log {{{1
// =============================================================================

// 2025-02-16: First draft in Nim. Convert or get programming language names,
// filename extensions, program names, texts, page names; create lists of
// project dirs and programs.
//
// 2025-02-23: Fix `program_page_name`.  Improve the names of many identifiers.
//
// 2025-03-12: Remove unnecessary `of` in `filename_extension`.
//
// 2025-03-16: Start conversion from Nim to Pike.
//
// 2025-03-22: Finish conversion from Nim to Pike. Convert from Pike to D.
//
// 2025-05-31: Update conversion from Pike.
//
// 2025-06-02: fix, improve. build basic contents of "basics_off" and
// "basics_of_*" pages.
//
// 2025-06-03: improve building flow.  build basic contents of program pages.
// improve page metadata. start text translations.
//
// 2025-06-04: improve pages metadata, handling of target dir. factor.
//
// 2025-06-06: improve identifiers. add `tags` metadata field.  add content of
// `related` metadata fields to metaproject pages.  factor the calculation of
// program versions.
//
// 2025-06-08: add `description` metadata field.  add `require`.  make page
// files executable.  fix `alternate` fields.  fix path to source code files.
// fix markup of `description` metadata field.  fix default value of hierarchy
// metadata fields.  fix path of executable sizes report.  improve `related`
// metadata field.
//
// 2025-06-09: add `external_related` metadata field.  add translated version
// of the executable sizes report.
//
// 2025-06-10: remove files of empty "basics_of_" projects.  omit unused
// languages. add graphics and <Makefile> to "basics off" projects.
//
// 2025-06-11: add conversion reports to "basics off" project pages.
//
// 2025-06-27: move graphics and add them descriptions.  convert code to allman
// style.  reword some texts. rearrange headings of "basics off" project pages.
//
// 2025-08-08: add link to all version of a program after its source code; add
// links to "sine wave" size report after its source code and to the `related`
// field of its page.  fix `page_id`.  add descriptions to the "basics off"
// programs.
//
// 2025-08-13: add description texts to the program pages.  fix f# vim syntax
// identifier.  complete page metadata.
//
// 2025-08-14: fix/improve markup creation.  add text and link to "sine wave"
// sizes report.
//
// 2025-08-15: add description to the "basics off" pages.  factor calculation
// and printing of conversion order text.  add note to pages' header about
// their origin.
//
// 2025-08-16: improve note about "sine wave" report.
//
// 2025-08-29: fix typo.
//
// 2025-11-18: add `@fn` and `@var`.
//
// 2025-12-17: make page dates configurable.  add "basics of styx" and "basics
// of neat".
//
// 2025-12-18: fix typo.
//
// 2025-12-19: add "z-end".
//
// 2025-12-20: update page dates.
//
// 2025-12-25: add "basics of nature" and "basics of janet".
//
// 2025-12-27: update page dates.
//
// 2025-12-30: update page dates; add anchors to the program headers of "basics
// of" pages.
//
// 2026-01-06: update page dates.
//
// 2026-01-23: add "basics of elixir", "basics of gleam", "basics of haskell".
//
// 2026-02-06: update.
//
// 2026-02-07: update "basics of gleam"; fix typos; improve description of the
// "basics off" page; add "basics of pony".
//
// 2026-02-08: fix: remove "basics of haskell", add it to the projects having
// no conversion and create a redirection for it. update "basics of chapel".
// fix the final rendering of the "conversions" anchor.
//
// 2026-02-13: update "basics of hare".
//
// 2026-02-15: update "sine wave" in janet.

gnuplot files for making the graphics

These gnuplot configuration files make it possible to convert the CSV data files to graphics.

# make_conversions_plot_rowstacked.gnuplot
#
# By Marcos Cruz (programandala.net), 2024, 2025, 2026.
#
# This file is part of "Basics off"

# Last modified 20260207T1550+0100.

set grid y

set style data histograms
set style histogram rowstacked
set boxwidth 0.5
set style fill solid 1.0 border -1

plot for [col=2:56] input_file using col:xticlabels(1) linetype col

# make_commits_plot_rowstacked.gnuplot
#
# By Marcos Cruz (programandala.net), 2024, 2025, 2026.
#
# This file is part of "Basics off"

# Last modified 20260207T1550+0100.

set grid y

set style data histograms
set style histogram rowstacked
set boxwidth 0.5
set style fill solid 1.0 border -1

plot for [col=2:59] input_file using col:xticlabels(1) linetype col

# make_conversions_plot_with_linespoints.gnuplot
#
# By Marcos Cruz (programandala.net), 2024, 2025, 2026.
#
# This file is part of "Basics off"

# Last modified 20260206T1309+0100.

set grid

plot for [col=2:48] input_file using col:xticlabels(1) linetype col with linespoints

# make_commits_plot_with_linespoints.gnuplot
#
# By Marcos Cruz (programandala.net), 2024, 2025, 2026.
#
# This file is part of "Basics off"

# Last modified 20260206T1309+0100.

set grid

plot for [col=2:48] input_file using col:xticlabels(1) linetype col with linespoints

# comman.gnuplot
#
# By Marcos Cruz (programandala.net), 2024, 2025, 2026.
#
# This file is part of "Basics off"

# Last modified 20260206T1257+0100.

set datafile separator comma

set terminal png size 2560, 2048

set title title_
set key autotitle columnhead
set xlabel xlabel_
set ylabel ylabel_

set key rmargin
set xtics rotate by 45 right

# colors.gnuplot
#
# By Marcos Cruz (programandala.net), 2025, 2026.
#
# This file is part of "Basics off".

# Last modified 20260123T2151+0100.
# See change log at the end of the file.

# Acknowledgment:
# The palette of distinct colors was made by https://mokole.com/palette.html

set linetype  2 lc rgb '#a9a9a9' lw 1 # darkgray
set linetype  3 lc rgb '#2f4f4f' lw 1 # darkslategray
set linetype  4 lc rgb '#556b2f' lw 1 # darkolivegreen
set linetype  5 lc rgb '#8b4513' lw 1 # saddlebrown
set linetype  6 lc rgb '#2e8b57' lw 1 # seagreen
set linetype  7 lc rgb '#800000' lw 1 # maroon
set linetype  8 lc rgb '#191970' lw 1 # midnightblue
set linetype  9 lc rgb '#708090' lw 1 # slategray
set linetype 10 lc rgb '#808000' lw 1 # olive
set linetype 11 lc rgb '#008000' lw 1 # green
set linetype 12 lc rgb '#bc8f8f' lw 1 # rosybrown
set linetype 13 lc rgb '#663399' lw 1 # rebeccapurple
set linetype 14 lc rgb '#008080' lw 1 # teal
set linetype 15 lc rgb '#b8860b' lw 1 # darkgoldenrod
set linetype 16 lc rgb '#cd853f' lw 1 # peru
set linetype 17 lc rgb '#4682b4' lw 1 # steelblue
set linetype 18 lc rgb '#000080' lw 1 # navy
set linetype 19 lc rgb '#d2691e' lw 1 # chocolate
set linetype 20 lc rgb '#9acd32' lw 1 # yellowgreen
set linetype 21 lc rgb '#20b2aa' lw 1 # lightseagreen
set linetype 22 lc rgb '#32cd32' lw 1 # limegreen
set linetype 23 lc rgb '#7f007f' lw 1 # purple2
set linetype 24 lc rgb '#8fbc8f' lw 1 # darkseagreen
set linetype 25 lc rgb '#b03060' lw 1 # maroon3
set linetype 26 lc rgb '#9932cc' lw 1 # darkorchid
set linetype 27 lc rgb '#ff0000' lw 1 # red
set linetype 28 lc rgb '#ffa500' lw 1 # orange
set linetype 29 lc rgb '#ffd700' lw 1 # gold
set linetype 30 lc rgb '#ffff00' lw 1 # yellow
set linetype 31 lc rgb '#c71585' lw 1 # mediumvioletred
set linetype 32 lc rgb '#0000cd' lw 1 # mediumblue
set linetype 33 lc rgb '#deb887' lw 1 # burlywood
set linetype 34 lc rgb '#00ff00' lw 1 # lime
set linetype 35 lc rgb '#00ff7f' lw 1 # springgreen
set linetype 36 lc rgb '#4169e1' lw 1 # royalblue
set linetype 37 lc rgb '#dc143c' lw 1 # crimson
set linetype 38 lc rgb '#00ffff' lw 1 # aqua
set linetype 39 lc rgb '#00bfff' lw 1 # deepskyblue
set linetype 40 lc rgb '#9370db' lw 1 # mediumpurple
set linetype 41 lc rgb '#0000ff' lw 1 # blue
set linetype 42 lc rgb '#a020f0' lw 1 # purple3
set linetype 43 lc rgb '#f08080' lw 1 # lightcoral
set linetype 44 lc rgb '#adff2f' lw 1 # greenyellow
set linetype 45 lc rgb '#ff6347' lw 1 # tomato
set linetype 46 lc rgb '#da70d6' lw 1 # orchid
set linetype 47 lc rgb '#d8bfd8' lw 1 # thistle
set linetype 48 lc rgb '#ff00ff' lw 1 # fuchsia
set linetype 49 lc rgb '#1e90ff' lw 1 # dodgerblue
set linetype 50 lc rgb '#db7093' lw 1 # palevioletred
set linetype 51 lc rgb '#f0e68c' lw 1 # khaki
set linetype 52 lc rgb '#dda0dd' lw 1 # plum
set linetype 53 lc rgb '#90ee90' lw 1 # lightgreen
set linetype 54 lc rgb '#87ceeb' lw 1 # skyblue
set linetype 55 lc rgb '#ff1493' lw 1 # deeppink
set linetype 56 lc rgb '#ffa07a' lw 1 # lightsalmon
set linetype 57 lc rgb '#afeeee' lw 1 # paleturquoise
set linetype 58 lc rgb '#7fffd4' lw 1 # aquamarine
set linetype 59 lc rgb '#ff69b4' lw 1 # hotpink
set linetype 60 lc rgb '#ffe4c4' lw 1 # bisque
set linetype 61 lc rgb '#ffb6c1' lw 1 # lightpink

# Change log

# 2025-01-07: Start with 45 colors.
# 2025-03-19: Update to 49 colors.
# 2025-08-14: Update to 50 colors.
# 2026-02-07: Update to 60 colors.

Makefile

# Makefile of "Basics off"
#
# By Marcos Cruz (programandala.net), 2023, 2024, 2025, 2026
#
# Last modified 20260209T1325+0100.

# Requirements:
#   Asciidoctor
#       https://asciidoctor.org
#   Asciidoctor PDF
#       https://github.com/asciidoctor/asciidoctor-pdf
#   D
#       https://dlang.org
#   fish
#       https://fishshell.com/
#   gnuplot
#       http://www.gnuplot.info
#   Julia
#       https://julialang.org

commits_plot_targets = \
    target/activity__commits_plot_with_linespoints__en.png \
    target/activity__commits_plot_rowstacked__en.png \
    target/activity__commits_plot_with_linespoints__eo.png \
    target/activity__commits_plot_rowstacked__eo.png \
    target/activity__commits_plot_with_linespoints__es.png \
    target/activity__commits_plot_rowstacked__es.png \
    target/activity__commits_plot_with_linespoints__ie.png \
    target/activity__commits_plot_rowstacked__ie.png

conversions_plot_targets = \
    target/activity__conversions_plot_with_linespoints__en.png \
    target/activity__conversions_plot_rowstacked__en.png \
    target/activity__conversions_plot_with_linespoints__eo.png \
    target/activity__conversions_plot_rowstacked__eo.png \
    target/activity__conversions_plot_with_linespoints__es.png \
    target/activity__conversions_plot_rowstacked__es.png \
    target/activity__conversions_plot_with_linespoints__ie.png \
    target/activity__conversions_plot_rowstacked__ie.png

plot_targets = $(commits_plot_targets) $(conversions_plot_targets)

activity__commits_targets = \
    target/activity__commits_summary.csv \
    target/activity__commits_table.csv \
    $(commits_plot_targets)

activity__conversions_targets = \
    target/activity__conversions_summary.csv \
    target/activity__conversions_table.csv \
    $(conversions_plot_targets)

activity_targets = $(activity__commits_targets) $(activity__conversions_targets)

conversion_targets = \
    target/conversions__language_ranking.adoc \
    target/conversions__language_ranking__en.html \
    target/conversions__language_ranking__en.pdf \
    target/conversions__language_ranking__eo.html \
    target/conversions__language_ranking__eo.pdf \
    target/conversions__language_ranking__es.html \
    target/conversions__language_ranking__es.pdf \
    target/conversions__language_ranking__ie.html \
    target/conversions__language_ranking__ie.pdf \
    target/conversions__programs_and_languages.adoc \
    target/conversions__programs_and_languages__en.html \
    target/conversions__programs_and_languages__en.pdf \
    target/conversions__programs_and_languages__eo.html \
    target/conversions__programs_and_languages__eo.pdf \
    target/conversions__programs_and_languages__es.html \
    target/conversions__programs_and_languages__es.pdf \
    target/conversions__programs_and_languages__ie.html \
    target/conversions__programs_and_languages__ie.pdf

size_targets = \
    target/sine_wave_executable_sizes__en.html \
    target/sine_wave_executable_sizes__en.pdf \
    target/sine_wave_executable_sizes__eo.html \
    target/sine_wave_executable_sizes__eo.pdf \
    target/sine_wave_executable_sizes__es.html \
    target/sine_wave_executable_sizes__es.pdf \
    target/sine_wave_executable_sizes__ie.html \
    target/sine_wave_executable_sizes__ie.pdf

all_targets := $(activity_targets) $(conversion_targets) $(size_targets)

.PHONY: all # build all targets
all: $(all_targets) pages

.PHONY: log # build log file of commits activity
log: tmp/activity__commits.log

.PHONY: activity__commits # build CSV files of commits activity
commits_activity: $(activity__commits_targets)

.PHONY: activity__conversions # build CSV files of conversions activity
conversions_activity: $(activity__conversions_targets)

.PHONY: activity # build all of the CSV files of activity
activity: activity__commits activity__conversions

.PHONY: conversion # build CSV files of conversions
conversion: $(conversion_targets)

.PHONY: plot # build PNG plot graphics
plot: $(plot_targets)

.PHONY: size # build "sine wave" size reports
size: $(size_targets)

.PHONY: clean # remove target and temporary files
clean:
    @rm --force $(all_targets) tmp/*

.PHONY: cleanactivity # remove CSV files of activity
cleanactivity:
    @rm --force $(activity_targets) tmp/*_activity.log

.PHONY: cleanconversion # remove CSV files of conversions
cleanconversion:
    @rm --force $(conversion_targets)

.PHONY: cleanplot # remove PNG plot graphics
cleanplot:
    @rm --force $(plot_targets)

.PHONY: pages # build programandala.net pages
pages:
    rdmd src/build_pages.d

.PHONY: help # list available recipes
help:
    @echo Available recipes:
    @grep "^\.PHONY:" Makefile | sed -e 's/\.PHONY:\s\+\([a-z_0-9]\+\) \+#/    \1 =/' | sort

target/%__en.html: src/%.adoc
    asciidoctor --attribute en --out-file $@ $<

target/%__eo.html: src/%.adoc
    asciidoctor --attribute eo --out-file $@ $<

target/%__es.html: src/%.adoc
    asciidoctor --attribute es --out-file $@ $<

target/%__ie.html: src/%.adoc
    asciidoctor --attribute ie --out-file $@ $<

target/%__ie.html: src/%.adoc
    asciidoctor --attribute ie --out-file $@ $<

target/%__en.pdf: src/%.adoc
    asciidoctor-pdf --attribute en --out-file $@ $<

target/%__eo.pdf: src/%.adoc
    asciidoctor-pdf --attribute eo --out-file $@ $<

target/%__es.pdf: src/%.adoc
    asciidoctor-pdf --attribute es --out-file $@ $<

target/%__ie.pdf: src/%.adoc
    asciidoctor-pdf --attribute ie --out-file $@ $<

target/%__en.html: target/%.adoc
    asciidoctor --attribute en --out-file $@ $<

target/%__eo.html: target/%.adoc
    asciidoctor --attribute eo --out-file $@ $<

target/%__es.html: target/%.adoc
    asciidoctor --attribute es --out-file $@ $<

target/%__ie.html: target/%.adoc
    asciidoctor --attribute ie --out-file $@ $<

target/%__ie.html: target/%.adoc
    asciidoctor --attribute ie --out-file $@ $<

target/%__en.pdf: target/%.adoc
    asciidoctor-pdf --attribute en --out-file $@ $<

target/%__eo.pdf: target/%.adoc
    asciidoctor-pdf --attribute eo --out-file $@ $<

target/%__es.pdf: target/%.adoc
    asciidoctor-pdf --attribute es --out-file $@ $<

target/%__ie.pdf: target/%.adoc
    asciidoctor-pdf --attribute ie --out-file $@ $<

target/%.html: target/%.adoc
    asciidoctor --out-file $@ $<

target/%.pdf: target/%.adoc
    asciidoctor-pdf --out-file $@ $<

tmp/activity__commits.log:
    julia ./src/calculate_activity.jl log --plot-type commits --output $@

target/activity__commits_summary.csv: tmp/activity__commits.log
    julia ./src/calculate_activity.jl summary --plot-type commits --input $< --output $@

target/activity__commits_table.csv: target/activity__commits_summary.csv
    julia ./src/calculate_activity.jl tabular --input $< --output $@

tmp/activity__conversions.log:
    julia ./src/calculate_activity.jl log --plot-type conversions --output $@

target/activity__conversions_summary.csv: tmp/activity__conversions.log
    julia ./src/calculate_activity.jl summary --plot-type conversions --input $< --output $@

target/activity__conversions_table.csv: target/activity__conversions_summary.csv
    julia ./src/calculate_activity.jl tabular --input $< --output $@

target/activity__commits_plot_with_linespoints__en.png: target/activity__commits_table.csv src/colors.gnuplot src/common.gnuplot src/make_commits_plot_with_linespoints.gnuplot
    gnuplot \
        -e "input_file='$<'; title_='Monthly activity of Basics off';  xlabel_='Month'; ylabel_='Commits'; set output '$@'" \
        src/colors.gnuplot \
        src/common.gnuplot \
        src/make_commits_plot_with_linespoints.gnuplot

target/activity__commits_plot_rowstacked__en.png: target/activity__commits_table.csv src/colors.gnuplot src/common.gnuplot src/make_commits_plot_rowstacked.gnuplot
    gnuplot \
        -e "input_file='$<'; title_='Monthly activity of Basics off';  xlabel_='Month'; ylabel_='Commits'; set output '$@'" \
        src/colors.gnuplot \
        src/common.gnuplot \
        src/make_commits_plot_rowstacked.gnuplot

target/activity__conversions_plot_with_linespoints__en.png: target/activity__conversions_table.csv src/colors.gnuplot src/common.gnuplot src/make_conversions_plot_with_linespoints.gnuplot
    gnuplot \
        -e "input_file='$<'; title_='Monthly activity of Basics off';  xlabel_='Month'; ylabel_='Conversions started'; set output '$@'" \
        src/colors.gnuplot \
        src/common.gnuplot \
        src/make_conversions_plot_with_linespoints.gnuplot

target/activity__conversions_plot_rowstacked__en.png: target/activity__conversions_table.csv src/colors.gnuplot src/common.gnuplot src/make_conversions_plot_rowstacked.gnuplot
    gnuplot \
        -e "input_file='$<'; title_='Monthly activity of Basics off'; xlabel_='Month'; ylabel_='Conversions started'; set output '$@'" \
        src/colors.gnuplot \
        src/common.gnuplot \
        src/make_conversions_plot_rowstacked.gnuplot

target/activity__commits_plot_with_linespoints__eo.png: target/activity__commits_table.csv src/colors.gnuplot src/common.gnuplot src/make_commits_plot_with_linespoints.gnuplot
    gnuplot \
        -e "input_file='$<'; title_='Monata aktiveco de Basics off';  xlabel_='Monato'; ylabel_='Enmetoj'; set output '$@'" \
        src/colors.gnuplot \
        src/common.gnuplot \
        src/make_commits_plot_with_linespoints.gnuplot

target/activity__commits_plot_rowstacked__eo.png: target/activity__commits_table.csv src/colors.gnuplot src/common.gnuplot src/make_commits_plot_rowstacked.gnuplot
    gnuplot \
        -e "input_file='$<'; title_='Monata aktiveco de Basics off';  xlabel_='Monato'; ylabel_='Enmetoj'; set output '$@'" \
        src/colors.gnuplot \
        src/common.gnuplot \
        src/make_commits_plot_rowstacked.gnuplot

target/activity__conversions_plot_with_linespoints__eo.png: target/activity__conversions_table.csv src/colors.gnuplot src/common.gnuplot src/make_conversions_plot_with_linespoints.gnuplot
    gnuplot \
        -e "input_file='$<'; title_='Monata aktiveco de Basics off';  xlabel_='Monato'; ylabel_='Konvertoj komencitaj'; set output '$@'" \
        src/colors.gnuplot \
        src/common.gnuplot \
        src/make_conversions_plot_with_linespoints.gnuplot

target/activity__conversions_plot_rowstacked__eo.png: target/activity__conversions_table.csv src/colors.gnuplot src/common.gnuplot src/make_conversions_plot_rowstacked.gnuplot
    gnuplot \
        -e "input_file='$<'; title_='Monata aktiveco de Basics off'; xlabel_='Monato'; ylabel_='Konvertoj komencitaj'; set output '$@'" \
        src/colors.gnuplot \
        src/common.gnuplot \
        src/make_conversions_plot_rowstacked.gnuplot

target/activity__commits_plot_with_linespoints__es.png: target/activity__commits_table.csv src/colors.gnuplot src/common.gnuplot src/make_commits_plot_with_linespoints.gnuplot
    gnuplot \
        -e "input_file='$<'; title_='Actividad mensual de Basics off';  xlabel_='Mes'; ylabel_='Enmetoj'; set output '$@'" \
        src/colors.gnuplot \
        src/common.gnuplot \
        src/make_commits_plot_with_linespoints.gnuplot

target/activity__commits_plot_rowstacked__es.png: target/activity__commits_table.csv src/colors.gnuplot src/common.gnuplot src/make_commits_plot_rowstacked.gnuplot
    gnuplot \
        -e "input_file='$<'; title_='Actividad mensual de Basics off';  xlabel_='Mes'; ylabel_='Entradas'; set output '$@'" \
        src/colors.gnuplot \
        src/common.gnuplot \
        src/make_commits_plot_rowstacked.gnuplot

target/activity__conversions_plot_with_linespoints__es.png: target/activity__conversions_table.csv src/colors.gnuplot src/common.gnuplot src/make_conversions_plot_with_linespoints.gnuplot
    gnuplot \
        -e "input_file='$<'; title_='Actividad mensual de Basics off';  xlabel_='Mes'; ylabel_='Conversiones comenzadas'; set output '$@'" \
        src/colors.gnuplot \
        src/common.gnuplot \
        src/make_conversions_plot_with_linespoints.gnuplot

target/activity__conversions_plot_rowstacked__es.png: target/activity__conversions_table.csv src/colors.gnuplot src/common.gnuplot src/make_conversions_plot_rowstacked.gnuplot
    gnuplot \
        -e "input_file='$<'; title_='Actividad mensual de Basics off'; xlabel_='Mes'; ylabel_='Conversiones comenzadas'; set output '$@'" \
        src/colors.gnuplot \
        src/common.gnuplot \
        src/make_conversions_plot_rowstacked.gnuplot

target/activity__commits_plot_with_linespoints__ie.png: target/activity__commits_table.csv src/colors.gnuplot src/common.gnuplot src/make_commits_plot_with_linespoints.gnuplot
    gnuplot \
        -e "input_file='$<'; title_='Activitá mensual de Basics off';  xlabel_='Mensu'; ylabel_='Depositiones'; set output '$@'" \
        src/colors.gnuplot \
        src/common.gnuplot \
        src/make_commits_plot_with_linespoints.gnuplot

target/activity__commits_plot_rowstacked__ie.png: target/activity__commits_table.csv src/colors.gnuplot src/common.gnuplot src/make_commits_plot_rowstacked.gnuplot
    gnuplot \
        -e "input_file='$<'; title_='Activitá mensual de Basics off';  xlabel_='Mensu'; ylabel_='Depositiones'; set output '$@'" \
        src/colors.gnuplot \
        src/common.gnuplot \
        src/make_commits_plot_rowstacked.gnuplot

target/activity__conversions_plot_with_linespoints__ie.png: target/activity__conversions_table.csv src/colors.gnuplot src/common.gnuplot src/make_conversions_plot_with_linespoints.gnuplot
    gnuplot \
        -e "input_file='$<'; title_='Activitá mensual de Basics off';  xlabel_='Mensu'; ylabel_='Conversiones comensat'; set output '$@'" \
        src/colors.gnuplot \
        src/common.gnuplot \
        src/make_conversions_plot_with_linespoints.gnuplot

target/activity__conversions_plot_rowstacked__ie.png: target/activity__conversions_table.csv src/colors.gnuplot src/common.gnuplot src/make_conversions_plot_rowstacked.gnuplot
    gnuplot \
        -e "input_file='$<'; title_='Activitá mensual de Basics off'; xlabel_='Mensu'; ylabel_='Conversiones comensat'; set output '$@'" \
        src/colors.gnuplot \
        src/common.gnuplot \
        src/make_conversions_plot_rowstacked.gnuplot

target/conversions__language_ranking.adoc: FORCE
    ./src/count_conversions.fish ranking > $@

target/conversions__programs_and_languages.adoc: FORCE
    ./src/count_conversions.fish programs > $@

FORCE:

Related pages

Basics of 8th
Conversion of old BASIC programs to 8th in order to learn the basics of this language.
Basics of Ada
Conversion of old BASIC programs to Ada in order to learn the basics of this language.
Basics of Arturo
Conversion of old BASIC programs to Arturo in order to learn the basics of this language.
Basics of C#
Conversion of old BASIC programs to C# in order to learn the basics of this language.
Basics of C3
Conversion of old BASIC programs to C3 in order to learn the basics of this language.
Basics of Chapel
Conversion of old BASIC programs to Chapel in order to learn the basics of this language.
Basics of Clojure
Conversion of old BASIC programs to Clojure in order to learn the basics of this language.
Basics of Crystal
Conversion of old BASIC programs to Crystal in order to learn the basics of this language.
Basics of D
Conversion of old BASIC programs to D in order to learn the basics of this language.
Basics of Elixir
Conversion of old BASIC programs to Elixir in order to learn the basics of this language.
Basics of F#
Conversion of old BASIC programs to F# in order to learn the basics of this language.
Basics of Factor
Conversion of old BASIC programs to Factor in order to learn the basics of this language.
Basics of FreeBASIC
Conversion of old BASIC programs to FreeBASIC in order to learn the basics of this language.
Basics of Gleam
Conversion of old BASIC programs to Gleam in order to learn the basics of this language.
Basics of Go
Conversion of old BASIC programs to Go in order to learn the basics of this language.
Basics of Hare
Conversion of old BASIC programs to Hare in order to learn the basics of this language.
Basics of Haxe
Conversion of old BASIC programs to Haxe in order to learn the basics of this language.
Basics of Icon
Conversion of old BASIC programs to Icon in order to learn the basics of this language.
Basics of Io
Conversion of old BASIC programs to Io in order to learn the basics of this language.
Basics of Janet
Conversion of old BASIC programs to Janet in order to learn the basics of this language.
Basics of Julia
Conversion of old BASIC programs to Julia in order to learn the basics of this language.
Basics of Kotlin
Conversion of old BASIC programs to Kotlin in order to learn the basics of this language.
Basics of Lobster
Conversion of old BASIC programs to Lobster in order to learn the basics of this language.
Basics of Lua
Conversion of old BASIC programs to Lua in order to learn the basics of this language.
Basics of Nature
Conversion of old BASIC programs to Nature in order to learn the basics of this language.
Basics of Neat
Conversion of old BASIC programs to Neat in order to learn the basics of this language.
Basics of Neko
Conversion of old BASIC programs to Neko in order to learn the basics of this language.
Basics of Nelua
Conversion of old BASIC programs to Nelua in order to learn the basics of this language.
Basics of Nim
Conversion of old BASIC programs to Nim in order to learn the basics of this language.
Basics of Nit
Conversion of old BASIC programs to Nit in order to learn the basics of this language.
Basics of Oberon-07
Conversion of old BASIC programs to Oberon-07 in order to learn the basics of this language.
Basics of OCaml
Conversion of old BASIC programs to OCaml in order to learn the basics of this language.
Basics of Odin
Conversion of old BASIC programs to Odin in order to learn the basics of this language.
Basics of Pike
Conversion of old BASIC programs to Pike in order to learn the basics of this language.
Basics of Pony
Conversion of old BASIC programs to Pony in order to learn the basics of this language.
Basics of Python
Conversion of old BASIC programs to Python in order to learn the basics of this language.
Basics of Racket
Conversion of old BASIC programs to Racket in order to learn the basics of this language.
Basics of Raku
Conversion of old BASIC programs to Raku in order to learn the basics of this language.
Basics of Retro
Conversion of old BASIC programs to Retro in order to learn the basics of this language.
Basics of Rexx
Conversion of old BASIC programs to Rexx in order to learn the basics of this language.
Basics of Ring
Conversion of old BASIC programs to Ring in order to learn the basics of this language.
Basics of Rust
Conversion of old BASIC programs to Rust in order to learn the basics of this language.
Basics of Scala
Conversion of old BASIC programs to Scala in order to learn the basics of this language.
Basics of Scheme
Conversion of old BASIC programs to Scheme in order to learn the basics of this language.
Basics of Styx
Conversion of old BASIC programs to Styx in order to learn the basics of this language.
Basics of Swift
Conversion of old BASIC programs to Swift in order to learn the basics of this language.
Basics of V
Conversion of old BASIC programs to V in order to learn the basics of this language.
Basics of Vala
Conversion of old BASIC programs to Vala in order to learn the basics of this language.
Basics of Zig
Conversion of old BASIC programs to Zig in order to learn the basics of this language.

External related links