2016-02-03 21:47:48 +00:00
#!/usr/bin/perl
use strict ;
use warnings ;
my $ topbip = 9999 ;
2016-12-15 05:04:28 +00:00
my $ include_layer = 1 ;
2016-02-03 21:47:48 +00:00
my % RequiredFields = (
BIP = > undef ,
Title = > undef ,
2025-04-11 20:08:17 -07:00
Authors = > undef ,
2016-02-03 21:47:48 +00:00
Status = > undef ,
Type = > undef ,
2025-10-08 14:59:36 -07:00
Assigned = > undef ,
2016-11-30 09:47:31 +00:00
# License => undef, (has exceptions)
2016-02-03 21:47:48 +00:00
) ;
my % MayHaveMulti = (
2025-04-11 20:08:17 -07:00
Authors = > undef ,
2025-04-14 11:44:23 -07:00
Deputies = > undef ,
2016-11-30 09:47:31 +00:00
'Comments-URI' = > undef ,
License = > undef ,
2017-10-29 04:04:07 +00:00
'License-Code' = > undef ,
2025-04-11 20:07:37 -07:00
'Discussion' = > undef ,
2016-02-03 21:47:48 +00:00
) ;
my % DateField = (
2025-10-08 14:59:36 -07:00
Assigned = > undef ,
2016-02-03 21:47:48 +00:00
) ;
my % EmailField = (
2025-04-11 20:08:17 -07:00
Authors = > undef ,
2016-02-03 21:47:48 +00:00
Editor = > undef ,
2025-04-14 11:44:23 -07:00
Deputies = > undef ,
2016-02-03 21:47:48 +00:00
) ;
my % MiscField = (
2025-04-14 11:44:23 -07:00
'Deputies' = > undef ,
2025-04-11 20:05:13 -07:00
'Comments-URI' = > undef ,
2016-11-30 09:47:31 +00:00
'Comments-Summary' = > undef ,
2025-04-11 20:07:37 -07:00
'Discussion' = > undef ,
2016-10-05 05:50:12 +00:00
'Replaces' = > undef ,
2020-01-19 14:24:30 -08:00
'Requires' = > undef ,
2025-04-11 18:17:21 -07:00
'Proposed-Replacement' = > undef ,
2016-02-03 21:47:48 +00:00
) ;
2025-07-08 14:49:40 +02:00
my % VersionField = (
'Version' = > undef ,
) ;
2025-07-10 01:36:29 +02:00
my @ FieldOrder = qw(
BIP
Layer
Title
Author
Authors
Editor
Deputies
Discussions - To
Comments - Summary
Comments - URI
Status
Type
2025-10-08 14:59:36 -07:00
Assigned
2025-07-10 01:36:29 +02:00
License
License - Code
Discussion
Post - History
Version
Requires
Replaces
Proposed - Replacement
Superseded - By
) ;
2016-02-03 21:47:48 +00:00
2016-11-30 09:45:33 +00:00
my % ValidLayer = (
'Consensus (soft fork)' = > undef ,
'Consensus (hard fork)' = > undef ,
'Peer Services' = > undef ,
'API/RPC' = > undef ,
'Applications' = > undef ,
) ;
2016-02-03 21:47:48 +00:00
my % ValidStatus = (
Draft = > undef ,
2025-04-11 18:02:31 -07:00
Complete = > "background-color: #ffffcf" ,
2025-04-11 18:08:44 -07:00
Deployed = > "background-color: #cfffcf" ,
2025-04-11 18:11:55 -07:00
Closed = > "background-color: #ffcfcf" ,
2016-02-03 21:47:48 +00:00
) ;
my % ValidType = (
2025-04-11 18:19:21 -07:00
'Specification' = > undef ,
2016-02-03 21:47:48 +00:00
'Informational' = > undef ,
'Process' = > undef ,
) ;
2025-04-14 11:45:46 -07:00
my % AcceptableLicenses = (
2016-11-30 09:47:31 +00:00
'BSD-2-Clause' = > undef ,
'BSD-3-Clause' = > undef ,
'CC0-1.0' = > undef ,
'GNU-All-Permissive' = > undef ,
2025-04-14 11:45:46 -07:00
'MIT' = > undef ,
'CC-BY-4.0' = > undef ,
2016-11-30 09:47:31 +00:00
'Apache-2.0' = > undef ,
'BSL-1.0' = > undef ,
2025-04-14 11:45:46 -07:00
) ;
my % DefinedLicenses = (
% AcceptableLicenses ,
2016-11-30 09:47:31 +00:00
'CC-BY-SA-4.0' = > undef ,
'AGPL-3.0' = > undef ,
'AGPL-3.0+' = > undef ,
'FDL-1.3' = > undef ,
'GPL-2.0' = > undef ,
'GPL-2.0+' = > undef ,
'LGPL-2.1' = > undef ,
'LGPL-2.1+' = > undef ,
'OPL' = > undef ,
'PD' = > undef ,
) ;
2016-12-15 04:11:06 +00:00
my % GrandfatheredPD = map { $ _ = > undef } qw( 9 36 37 38 42 49 50 60 65 67 69 74 80 81 83 90 99 105 107 109 111 112 113 114 122 124 125 126 130 131 132 133 140 141 142 143 144 146 147 150 151 152 ) ;
2025-04-14 11:45:46 -07:00
my % GrandfatheredCCBySA = map { $ _ = > undef } qw( 98 116 117 134 ) ;
2017-01-20 00:12:51 +00:00
my % TolerateMissingLicense = map { $ _ = > undef } qw( 1 10 11 12 13 14 15 16 21 31 33 34 35 39 43 44 45 47 61 64 68 70 71 72 73 101 102 106 120 121 ) ;
2023-03-28 09:23:35 +10:00
my % TolerateTitleTooLong = map { $ _ = > undef } qw( 39 44 45 47 49 60 67 68 69 73 74 75 80 81 99 105 106 109 113 122 126 131 143 145 147 173 327 ) ;
2016-02-03 21:47:48 +00:00
my % emails ;
my $ bipnum = 0 ;
while ( + + $ bipnum <= $ topbip ) {
my $ fn = sprintf "bip-%04d.mediawiki" , $ bipnum ;
2025-02-08 20:04:18 +10:00
my $ is_markdown = 0 ;
2024-05-01 17:02:36 -04:00
if ( ! - e $ fn ) {
$ fn = sprintf "bip-%04d.md" , $ bipnum ;
2025-02-08 20:04:18 +10:00
$ is_markdown = 1 ;
2024-05-01 17:02:36 -04:00
}
2016-02-03 21:47:48 +00:00
- e $ fn || next ;
open my $ F , "<$fn" ;
2025-02-08 20:04:18 +10:00
if ( $ is_markdown ) {
while ( <$F> !~ m [^(?:\xef\xbb\xbf)?```$] ) {
die "No ``` in $fn" if eof $ F ;
}
} else {
while ( <$F> !~ m [^(?:\xef\xbb\xbf)?<pre>$] ) {
2016-02-03 21:47:48 +00:00
die "No <pre> in $fn" if eof $ F ;
2025-02-08 20:04:18 +10:00
}
2016-02-03 21:47:48 +00:00
}
my % found ;
2025-04-11 20:08:17 -07:00
my ( $ title , $ authors , $ status , $ type , $ layer ) ;
2025-07-10 01:36:29 +02:00
my ( $ field , $ val , @ field_order ) ;
2016-02-03 21:47:48 +00:00
while ( <$F> ) {
2025-02-08 20:04:18 +10:00
last if ( $ is_markdown && m [^```$] ) ;
last if ( ! $ is_markdown && m [^</pre>$] ) ;
2016-02-03 21:47:48 +00:00
if ( m [^ ([\w-] + ) \ : ( . * \ S ) $] ) {
$ field = $ 1 ;
$ val = $ 2 ;
die "Duplicate $field field in $fn" if exists $ found { $ field } ;
2017-01-14 07:03:49 +00:00
die "Too many spaces in $fn" if $ val =~ /^\s/ ;
2016-02-03 21:47:48 +00:00
} elsif ( m [^ ( +)(.*\S)$] ) {
die "Continuation of non-field in $fn" unless defined $ field ;
die "Too many spaces in $fn" if length $ 1 != 2 + length $ field ;
die "Not allowed for multi-value in $fn" unless exists $ MayHaveMulti { $ field } ;
$ val = $ 2 ;
} else {
die "Bad line in $fn preamble" ;
}
die "Extra spaces in $fn" if $ val =~ /^\s/ ;
if ( $ field eq 'BIP' ) {
die "$fn claims to be BIP $val" if $ val ne $ bipnum ;
} elsif ( $ field eq 'Title' ) {
$ title = $ val ;
2017-05-23 13:18:52 +00:00
my $ title_len = length ( $ title ) ;
2025-04-14 11:45:12 -07:00
die "$fn has too-long Title ($title_len > 50 char max)" if $ title_len > 50 and not exists $ TolerateTitleTooLong { $ bipnum } ;
2025-04-11 20:08:17 -07:00
} elsif ( $ field eq 'Authors' ) {
$ val =~ m/^(\S[^<@>]*\S) \<([^@>]*\@[\w.-]+\.\w+)\>$/ or die "Malformed Authors line in $fn" ;
2016-02-03 21:47:48 +00:00
my ( $ authorname , $ authoremail ) = ( $ 1 , $ 2 ) ;
$ authoremail =~ s/(?<=\D)$bipnum(?=\D)/<BIPNUM>/g ;
$ emails { $ authorname } - > { $ authoremail } = undef ;
2025-04-11 20:08:17 -07:00
if ( defined $ authors ) {
$ authors . = ", $authorname" ;
2016-02-03 21:47:48 +00:00
} else {
2025-04-11 20:08:17 -07:00
$ authors = $ authorname ;
2016-02-03 21:47:48 +00:00
}
} elsif ( $ field eq 'Status' ) {
if ( $ bipnum == 38 ) { # HACK
$ val =~ s/\s+\(.*\)$// ;
}
die "Invalid status in $fn" unless exists $ ValidStatus { $ val } ;
$ status = $ val ;
} elsif ( $ field eq 'Type' ) {
die "Invalid type in $fn" unless exists $ ValidType { $ val } ;
if ( defined $ ValidType { $ val } ) {
$ type = $ ValidType { $ val } ;
} else {
$ type = $ val ;
}
2016-11-30 09:45:33 +00:00
} elsif ( $ field eq 'Layer' ) { # BIP 123
die "Invalid layer $val in $fn" unless exists $ ValidLayer { $ val } ;
$ layer = $ val ;
2017-10-29 04:04:07 +00:00
} elsif ( $ field =~ /^License(?:\-Code)?$/ ) {
2016-11-30 09:47:31 +00:00
die "Undefined license $val in $fn" unless exists $ DefinedLicenses { $ val } ;
2017-10-29 04:04:07 +00:00
if ( not $ found { $ field } ) {
2025-04-14 11:45:46 -07:00
die "Unacceptable license $val in $fn" unless exists $ AcceptableLicenses { $ val } or ( $ val eq 'PD' and exists $ GrandfatheredPD { $ bipnum } ) or ( $ val eq 'CC-BY-SA-4.0' and exists $ GrandfatheredCCBySA { $ bipnum } ) ;
2016-11-30 09:47:31 +00:00
}
} elsif ( $ field eq 'Comments-URI' ) {
2025-04-11 20:05:13 -07:00
if ( $ found { 'Comments-URI' } ) {
2017-05-07 22:24:47 +00:00
my $ first_comments_uri = sprintf ( 'https://github.com/bitcoin/bips/wiki/Comments:BIP-%04d' , $ bipnum ) ;
die "First Comments-URI must be exactly \"$first_comments_uri\" in $fn" unless $ val eq $ first_comments_uri ;
2016-11-30 09:47:31 +00:00
}
2016-02-03 21:47:48 +00:00
} elsif ( exists $ DateField { $ field } ) {
2025-10-03 18:13:15 +02:00
# Enforce date format 20XX-MM-DD, where XX is 00-99, MM is 01-12 and DD is 01-31
die "Invalid date format in $fn" unless $ val =~ /^20\d{2}\-(?:0[1-9]|1[0-2])\-(?:0[1-9]|[12]\d|30|31)$/ ;
2016-02-03 21:47:48 +00:00
} elsif ( exists $ EmailField { $ field } ) {
$ val =~ m/^(\S[^<@>]*\S) \<[^@>]*\@[\w.]+\.\w+\>$/ or die "Malformed $field line in $fn" ;
2025-07-08 14:49:40 +02:00
} elsif ( exists $ VersionField { $ field } ) {
$ val =~ m/^(\d+\.\d+\.\d+)$/ or die "Malformed $field line in $fn" ;
2016-02-03 21:47:48 +00:00
} elsif ( not exists $ MiscField { $ field } ) {
die "Unknown field $field in $fn" ;
}
2016-12-15 04:20:10 +00:00
+ + $ found { $ field } ;
2025-07-10 01:36:29 +02:00
push @ field_order , $ field unless @ field_order and $ field_order [ - 1 ] eq $ field ;
2016-02-03 21:47:48 +00:00
}
2016-11-30 09:47:31 +00:00
if ( not $ found { License } ) {
die "Missing License in $fn" unless exists $ TolerateMissingLicense { $ bipnum } ;
}
2016-02-03 21:47:48 +00:00
for my $ field ( keys % RequiredFields ) {
die "Missing $field in $fn" unless $ found { $ field } ;
}
2025-07-10 01:36:29 +02:00
my @ expected_field_order = grep { exists $ found { $ _ } } @ FieldOrder ;
if ( "@expected_field_order" ne "@field_order" ) {
die "Field order is incorrect in $fn, should be:\n\t" . join ( ", " , @ expected_field_order ) . "\nbut contains:\n\t" . join ( ", " , @ field_order ) ;
}
2016-02-03 21:47:48 +00:00
print "|-" ;
if ( defined $ ValidStatus { $ status } ) {
print " style=\"" . $ ValidStatus { $ status } . "\"" ;
}
print "\n" ;
print "| [[${fn}|${bipnum}]]\n" ;
2016-11-30 09:45:33 +00:00
if ( $ include_layer ) {
if ( defined $ layer ) {
print "| ${layer}\n" ;
} else {
print "|\n" ;
}
}
2016-02-03 21:47:48 +00:00
print "| ${title}\n" ;
2025-04-11 20:08:17 -07:00
print "| ${authors}\n" ;
2016-02-03 21:47:48 +00:00
print "| ${type}\n" ;
print "| ${status}\n" ;
close $ F ;
}
2025-04-11 20:08:17 -07:00
for my $ authors ( sort keys % emails ) {
my @ emails = sort keys % { $ emails { $ authors } } ;
2016-02-03 21:47:48 +00:00
my $ email_count = @ emails ;
next unless $ email_count > 1 ;
2025-04-11 20:08:17 -07:00
warn "NOTE: $authors has $email_count email addresses: @emails\n" ;
2016-02-03 21:47:48 +00:00
}