#!/usr/bin/env perl # SPDX-FileCopyrightText: 2026 GPForum Contributors # SPDX-License-Identifier: BSD-3-Clause package main; use v5.40; use Carp qw(croak); use Const::Fast; use English qw(-no_match_vars); our $VERSION = '0.001'; # Writes the index of docs/adr/ into docs/adr/README.md, between two markers. # # There were 103 ADRs and a README that indexed none of them, so the only way # to find the decision on, say, effective visibility was to guess a filename. # A hand-kept list would drift the day the next ADR landed; this one is # generated, and `--check` fails when the README no longer matches the files, # which the test suite runs. const my $DIRECTORY => 'docs/adr'; const my $README => "$DIRECTORY/README.md"; const my $START => ''; const my $END => ''; my $check = grep { $_ eq '--check' } @ARGV; my $index = _index(); my $current = _slurp($README); my $wanted = _with_index( $current, $index ); if ($check) { if ( $wanted ne $current ) { _say( *STDERR, "adr-index: $README is stale; run script/adr-index" ); exit 1; } _say( *STDOUT, 'adr-index status=ok' ); exit 0; } if ( $wanted ne $current ) { open my $handle, '>', $README or croak "open $README: $ERRNO"; print {$handle} $wanted or croak "write $README: $ERRNO"; close $handle or croak "close $README: $ERRNO"; } _say( *STDOUT, "adr-index wrote $README" ); exit 0; sub _index { opendir my $dir, $DIRECTORY or croak "opendir $DIRECTORY: $ERRNO"; my @files = sort grep { /\A [[:digit:]]{4} - .+ [.]md \z/msx } readdir $dir; closedir $dir or croak "closedir $DIRECTORY: $ERRNO"; my @rows; my %seen; for my $file (@files) { my ($number) = $file =~ /\A ([[:digit:]]{4})/msx; next if $number eq '0000'; $seen{ $number + 0 } = 1; my $text = _slurp("$DIRECTORY/$file"); my ($title) = $text =~ /\A [#] \s* ADR \s* [[:digit:]]+ : \s* ([^\n]+)/msx; my ($status) = $text =~ /^[#][#] \s* Status \s*\n+ \s* ([[:alpha:]]+)/msx; $title //= $file; $status //= 'Unstated'; push @rows, "| [$number]($file) | $title | $status |"; } my @gaps = grep { !$seen{$_} } 1 .. ( reverse sort { $a <=> $b } keys %seen )[0]; my $note = q{}; if (@gaps) { $note = "\nNumbers " . join( q{, }, map { sprintf '%04d', $_ } @gaps ) . " were never assigned. They are left unused rather than\n" . "renumbered, because other documents cite ADRs by number.\n"; } return join "\n", '| ADR | Decision | Status |', '| --- | --- | --- |', @rows, q{}, 'Generated by `script/adr-index` from the files in this' . ' directory; `script/adr-index --check` fails when it is stale.' . $note; } sub _with_index { my ( $text, $table ) = @_; my $block = "$START\n$table\n$END"; if ( $text =~ /\Q$START\E .*? \Q$END\E/msx ) { $text =~ s/\Q$START\E .*? \Q$END\E/$block/msx; return $text; } return $text . "\n## Index\n\n$block\n"; } sub _say { my ( $handle, $line ) = @_; print {$handle} "$line\n" or croak "write: $ERRNO"; return; } sub _slurp { my ($path) = @_; open my $handle, '<', $path or croak "open $path: $ERRNO"; local $INPUT_RECORD_SEPARATOR = undef; my $text = <$handle>; close $handle or croak "close $path: $ERRNO"; return $text; }