Thoughts on using Text::Template for our autogenerated code?

Started by Corey Huinkeralmost 3 years ago11 messages
#1Corey Huinker
corey.huinker@gmail.com

Is there a barrier to us using non-core perl modules, in this case
Text::Template?

I think it would be a tremendous improvement in readability and
maintainability over our current series of print statements, some
multiline, some not.

The module itself works like this https://www.perlmonks.org/?node_id=33296

Some other digging around shows that the module has been around since 1996
(Perl5 was 1994) and hasn't had a feature update (or any update for that
matter) since 2003. So it should meet our baseline 5.14 requirement, which
came out in 2011.

I'm happy to proceed with a proof-of-concept so that people can see the
costs/benefits, but wanted to first make sure it wasn't a total non-starter.

#2Tom Lane
tgl@sss.pgh.pa.us
In reply to: Corey Huinker (#1)
Re: Thoughts on using Text::Template for our autogenerated code?

Corey Huinker <corey.huinker@gmail.com> writes:

Is there a barrier to us using non-core perl modules, in this case
Text::Template?

Use for what exactly?

I'd be hesitant to require such things to build from a tarball,
or to run regression tests. If it's used to build a generated file
that we include in tarballs, that might be workable ... but I'd bet
a good fraction of the buildfarm falls over (looks like all four of
my animals would), and you might get push-back from developers too.

I think it would be a tremendous improvement in readability and
maintainability over our current series of print statements, some
multiline, some not.

I suspect it'd have to be quite a remarkable improvement to justify
adding a new required build tool ... but show us an example.

regards, tom lane

#3Andres Freund
andres@anarazel.de
In reply to: Corey Huinker (#1)
Re: Thoughts on using Text::Template for our autogenerated code?

Hi,

On 2023-03-30 13:06:46 -0400, Corey Huinker wrote:

Is there a barrier to us using non-core perl modules, in this case
Text::Template?

I don't think we should have a hard build-time dependency on non-core perl
modules. On some operating systems having to install such dependencies is
quite painful (e.g. windows).

I think it would be a tremendous improvement in readability and
maintainability over our current series of print statements, some
multiline, some not.

I think many of those could just be replaced by multi-line strings and/or here
documents to get most of the win.

Greetings,

Andres Freund

#4Daniel Gustafsson
daniel@yesql.se
In reply to: Corey Huinker (#1)
Re: Thoughts on using Text::Template for our autogenerated code?

On 30 Mar 2023, at 19:06, Corey Huinker <corey.huinker@gmail.com> wrote:

Some other digging around shows that the module has been around since 1996 (Perl5 was 1994) and hasn't had a feature update (or any update for that matter) since 2003. So it should meet our baseline 5.14 requirement, which came out in 2011.

I assume you then mean tying this to 1.44 (or another version?), since AFAICT
there has been both features and bugfixes well after 2003?

https://metacpan.org/dist/Text-Template/changes

--
Daniel Gustafsson

#5Andrew Dunstan
andrew@dunslane.net
In reply to: Daniel Gustafsson (#4)
Re: Thoughts on using Text::Template for our autogenerated code?

On 2023-03-30 Th 15:06, Daniel Gustafsson wrote:

On 30 Mar 2023, at 19:06, Corey Huinker<corey.huinker@gmail.com> wrote:
Some other digging around shows that the module has been around since 1996 (Perl5 was 1994) and hasn't had a feature update (or any update for that matter) since 2003. So it should meet our baseline 5.14 requirement, which came out in 2011.

I assume you then mean tying this to 1.44 (or another version?), since AFAICT
there has been both features and bugfixes well after 2003?

https://metacpan.org/dist/Text-Template/changes

I don't think that's remotely a starter. Asking people to install an old
and possibly buggy version of a perl module is not something we should do.

I think the barrier for this is pretty high. I try to keep module
requirements for the buildfarm client pretty minimal, and this could
affect a much larger group of people.

cheers

andrew

--
Andrew Dunstan
EDB:https://www.enterprisedb.com

#6Corey Huinker
corey.huinker@gmail.com
In reply to: Andres Freund (#3)
Re: Thoughts on using Text::Template for our autogenerated code?

I think many of those could just be replaced by multi-line strings and/or
here
documents to get most of the win.

I agree that a pretty good chunk of it can be done with here-docs, but
template files do have attractive features (separation of concerns, syntax
highlighting, etc) that made it worth asking.

#7Corey Huinker
corey.huinker@gmail.com
In reply to: Andrew Dunstan (#5)
Re: Thoughts on using Text::Template for our autogenerated code?

I don't think that's remotely a starter. Asking people to install an old
and possibly buggy version of a perl module is not something we should do.

I think the barrier for this is pretty high. I try to keep module
requirements for the buildfarm client pretty minimal, and this could affect
a much larger group of people.

Those are good reasons.

For those who already responded, are your concerns limited to the
dependency issue? Would you have concerns about a templating library that
was developed by us and included in-tree? I'm not suggesting suggesting we
write one at this time, at least not until after we make a here-doc-ing
pass first, but I want to understand the concerns before embarking on any
refactoring.

#8Andres Freund
andres@anarazel.de
In reply to: Corey Huinker (#7)
Re: Thoughts on using Text::Template for our autogenerated code?

Hi,

On 2023-03-30 17:15:20 -0400, Corey Huinker wrote:

For those who already responded, are your concerns limited to the
dependency issue? Would you have concerns about a templating library that
was developed by us and included in-tree? I'm not suggesting suggesting we
write one at this time, at least not until after we make a here-doc-ing
pass first, but I want to understand the concerns before embarking on any
refactoring.

The dependency is/was my main issue. But I'm also somewhat doubtful that what
we do warrants the use of a template library in the first place, but I could
be convinced by a patch showing it to be a significant improvement.

Greetings,

Andres Freund

#9Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andres Freund (#8)
Re: Thoughts on using Text::Template for our autogenerated code?

Andres Freund <andres@anarazel.de> writes:

On 2023-03-30 17:15:20 -0400, Corey Huinker wrote:

For those who already responded, are your concerns limited to the
dependency issue? Would you have concerns about a templating library that
was developed by us and included in-tree? I'm not suggesting suggesting we
write one at this time, at least not until after we make a here-doc-ing
pass first, but I want to understand the concerns before embarking on any
refactoring.

The dependency is/was my main issue. But I'm also somewhat doubtful that what
we do warrants the use of a template library in the first place, but I could
be convinced by a patch showing it to be a significant improvement.

Yeah, it's somewhat hard to believe that the cost/benefit ratio would be
attractive. But maybe you could mock up some examples of what the input
could look like, and get people on board (or not) before writing any
code.

regards, tom lane

#10John Naylor
john.naylor@enterprisedb.com
In reply to: Corey Huinker (#7)
Re: Thoughts on using Text::Template for our autogenerated code?

On Fri, Mar 31, 2023 at 4:15 AM Corey Huinker <corey.huinker@gmail.com>
wrote:

For those who already responded, are your concerns limited to the

dependency issue? Would you have concerns about a templating library that
was developed by us and included in-tree?

Libraries (and abstractions in general) require some mental effort to
interface with them (that also means debugging when the output fails to
match expectations), not to mention maintenance cost. There has to be a
compensating benefit in return. The cost-to-benefit ratio here seems
unfavorable -- seems like inventing a machine that ties shoelaces, but we
usually wear sandals.

--
John Naylor
EDB: http://www.enterprisedb.com

#11Corey Huinker
corey.huinker@gmail.com
In reply to: Tom Lane (#9)
4 attachment(s)
Re: Thoughts on using Text::Template for our autogenerated code?

Yeah, it's somewhat hard to believe that the cost/benefit ratio would be
attractive. But maybe you could mock up some examples of what the input
could look like, and get people on board (or not) before writing any
code.

tl;dr - I tried a few things, nothing that persuades myself let alone the
community, but perhaps some ideas for the future.

I borrowed Bertrand's ongoing work for waiteventnames.* because that is
what got me thinking about this in the first place. I considered a few
different templating libraries:

There is no perl implementation of the golang template library (example of
that here: https://blog.gopheracademy.com/advent-2017/using-go-templates/ )
that I could find.

Text::Template does not support loops, and as such it is no better than
here-docs.

Template Toolkit seems to do what we need, but it has a kitchen sink of
dependencies that make it an unattractive option, so I didn't even attempt
it.

HTML::Template has looping and if/then/else constructs, and it is a single
standalone library. It also does a "separation of concerns" wherein you
pass in parameter names and values, and some parameters can be for loops,
which means you pass an arrayref of hashrefs that the template engine loops
over. That's where the advantages stop, however. It is fairly verbose, and
because it is HTML-centric it isn't very good about controlling whitespace,
which leads to piling template directives onto the same line in order to
avoid spurious newlines. As such I cannot recommend it.

My ideal template library would have text something like this:

[% loop events %]
[% $enum_value %]
[% if __first__ +%] = [%+ $inital_value %][% endif %]
[% if ! __last__ %],[% endif +%]
[% end loop %]
[% loop xml_blocks indent: relative,spaces,4 %]

<row>

<SomeElement attrib=[%attrib_val%]>[%element_body%]/>

</row>

[% end loop %]

[%+ means "leading whitespace matters", +%] means "trailing whitespace
matters"
That pseudocode is a mix of ASP, HTML::Template. The special variables
__first__ and __last__ refer to which iteration of the loop we are on. You
would pass it a data structure like this:

{events: [ { enum_value: "abc", initial_value: "def"}, ... { enum_value:
"wuv", initial_value: "xyz" } ],
xml_block: [ {attrib_val: "one", element_body: "two"} ]
}

I did one initial pass with just converting printf statements to here-docs,
and the results were pretty unsatisfying. It wasn't really possible to
"see" the output files take shape.

My next attempt was to take the "separation of concerns" code from the
HTML::Template version, constructing the nested data structure of resolved
output values, and then iterating over that once per output file. This
resulted in something cleaner, partly because we're only writing one file
type at a time, partly because the interpolated variables have names much
closer to their output meaning.

In doing this, it occurred to me that a lot of this effort is in getting
the code to conform to our own style guide, at the cost of the generator
code being less readable. What if we wrote the generator and formatted the
code in a way that made sense for the generator, and then pgindented it.
That's not the workflow right now, but perhaps it could be.

Conclusions:
- There is no "good enough" template engine that doesn't require big
changes in dependencies.
- pgindent will not save you from a run-on sentence, like putting all of
a typdef enum values on one line
- There is some clarity value in either separating input processing from
the output processing, or making the input align more closely with the
outputs
- Fiddling with indentation and spacing detracts from legibility no matter
what method is used.
- here docs are basically ok but they necessarily confuse output
indentation with code indentation. it is possible to de-indent them them
with <<~ but that's a 5.25+ feature.
- Any of these principles can be applied at any time, with no overhaul
required.

"sorted-" is the slightly modified version of Bertrand's code.
"eof-as-is-" is a direct conversion of the original but using here-docs.
"heredoc-fone-file-at-a-time-" first generates an output-friendly data
structure
"needs-pgindent-" is what is possible if we format for our own readability
and make pgindent fix the output, though it was not a perfect output match

Attachments:

sorted-generate-waiteventnames.plapplication/x-perl; name=sorted-generate-waiteventnames.plDownload
#!/usr/bin/perl
#----------------------------------------------------------------------
#
# Generate wait events support files from waiteventnames.txt:
# - waiteventnames.h
# - waiteventnames.c
# - waiteventnames.sgml
#
# Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
# Portions Copyright (c) 1994, Regents of the University of California
#
# src/backend/utils/activity/generate-waiteventnames.pl
#
#----------------------------------------------------------------------

use strict;
use warnings;
use Getopt::Long;

my $output_path = '.';

my $continue    = "\n";
my %hashwe;
my $waitclass;
my @wait_classes = ("PG_WAIT_ACTIVITY", "PG_WAIT_CLIENT", "PG_WAIT_IPC", "PG_WAIT_TIMEOUT", "PG_WAIT_IO");

GetOptions(
	'outdir:s'       => \$output_path);

open my $waiteventnames, '<', $ARGV[0] or die;

# Include PID in suffix in case parallel make runs this multiple times.
my $htmp = "$output_path/waiteventnames.h.tmp$$";
my $ctmp = "$output_path/waiteventnames.c.tmp$$";
my $stmp = "$output_path/waiteventnames.s.tmp$$";
open my $h, '>', $htmp or die "Could not open $htmp: $!";
open my $c, '>', $ctmp or die "Could not open $ctmp: $!";
open my $s, '>', $stmp or die "Could not open $stmp: $!";

my $header_comment =
  '/*-------------------------------------------------------------------------
 *
 * %s
 *    Generated wait events infrastructure code
 *
 * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * NOTES
 *  ******************************
 *  *** DO NOT EDIT THIS FILE! ***
 *  ******************************
 *
 *  It has been GENERATED by src/backend/utils/activity/generate-waiteventnames.pl
 *
 *-------------------------------------------------------------------------
 */
';

printf $h $header_comment, 'waiteventnames.h';
printf $h "#ifndef WAITEVENTNAMES_H\n";
printf $h "#define WAITEVENTNAMES_H\n\n";
printf $h "#include \"utils/wait_event.h\"\n\n";

printf $c $header_comment, 'waiteventnames.c';

# Read the input file and populate the hash table
while (<$waiteventnames>)
{
	chomp;

	# Skip comments
	next if /^#/;
	next if /^\s*$/;

	die "unable to parse waiteventnames.txt"
	  unless /^(\w+)\t+(\w+)\t+("\w+")\t+("\w.*\.")$/;

	(my $waitclassname, my $waiteventenumname, my $waiteventdescription, my $waitevendocsentence) = ($1, $2, $3, $4);

	my @waiteventlist = [$waiteventenumname, $waiteventdescription, $waitevendocsentence];
	my $trimmedwaiteventname = $waiteventenumname;
	$trimmedwaiteventname =~ s/^WAIT_EVENT_//;
	die "wait event names must start with 'WAIT_EVENT_'" if $trimmedwaiteventname eq $waiteventenumname;
	$continue    = ",\n";
	push(@{ $hashwe{$waitclassname} }, @waiteventlist);
}

# Generate the output files
foreach $waitclass (sort keys %hashwe) {
	my $last = $waitclass;
	$last =~ s/^WaitEvent//;
	my $lastuc = uc $last;
	my $lastlc = lc $last;
	my $firstpass = 1;
	my $pg_wait_class;

	printf $h "typedef enum\n{\n";

	printf $c "static const char *\npgstat_get_wait_$lastlc($waitclass w)\n{\n";
	printf $c "\tconst char *event_name = \"unknown wait event\";\n\n";
	printf $c "\tswitch (w)\n\t{\n";

	printf $s "  <table id=\"wait-event-%s-table\">\n", $lastlc;
	printf $s "   <title>Wait Events of Type <literal>%s</literal></title>\n", ucfirst($lastlc);
	printf $s "   <tgroup cols=\"2\">\n";
	printf $s "    <thead>\n";
	printf $s "     <row>\n";
	printf $s "      <entry><literal>Activity</literal> Wait Event</entry>\n";
	printf $s "      <entry>Description</entry>\n";
    printf $s "     </row>\n";
	printf $s "    </thead>\n\n";
	printf $s "    <tbody>\n";

	foreach my $wev (@{$hashwe{$waitclass}}) {
		if ($firstpass) {
			$pg_wait_class = "PG_WAIT_".$lastuc;
			die "waitclass $pg_wait_class does not exist" unless grep( /^$pg_wait_class$/, @wait_classes );
			printf $h "\t%s = %s", $wev->[0], $pg_wait_class;
			$continue = ",\n";
		} else {
			printf $h "%s\t%s", $continue, $wev->[0];
			$continue = ",\n";
		}
		$firstpass = 0;

		printf $c "\t\t case %s:\n", $wev->[0];
		printf $c "\t\t\t event_name = %s;\n\t\t\t break;\n", $wev->[1];

		printf $s "     <row>\n";
		printf $s "      <entry><literal>%s</literal></entry>\n", substr $wev->[1], 1, -1;
		printf $s "      <entry>%s</entry>\n", substr $wev->[2], 1, -1;
		printf $s "     </row>\n";
	}

	printf $h "\n} $waitclass;\n\n";

	printf $c "\t\t\t /* no default case, so that compiler will warn */\n";
	printf $c "\t}\n\n";
	printf $c "\treturn event_name;\n";
	printf $c "\n}\n";

	printf $s "    </tbody>\n";
	printf $s "   </tgroup>\n";
	printf $s "  </table>\n";
}

printf $h "#endif                          /* WAITEVENTNAMES_H */";
close $h;
close $c;
close $s;

rename($htmp, "$output_path/waiteventnames.h") || die "rename: $htmp to $output_path/waiteventnames.h: $!";
rename($ctmp, "$output_path/waiteventnames.c") || die "rename: $ctmp: $!";
rename($stmp, "$output_path/waiteventnames.sgml") || die "rename: $ctmp: $!";

close $waiteventnames;
eof-as-is-generate-waiteventnames.plapplication/x-perl; name=eof-as-is-generate-waiteventnames.plDownload
#!/usr/bin/perl
#----------------------------------------------------------------------
#
# Generate wait events support files from waiteventnames.txt:
# - waiteventnames.h
# - waiteventnames.c
# - waiteventnames.sgml
#
# Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
# Portions Copyright (c) 1994, Regents of the University of California
#
# src/backend/utils/activity/generate-waiteventnames.pl
#
#----------------------------------------------------------------------

use strict;
use warnings;
use Getopt::Long;

my $output_path = '.';

my $continue    = "\n";
my %hashwe;
my $waitclass;
my @wait_classes = ("PG_WAIT_ACTIVITY", "PG_WAIT_CLIENT", "PG_WAIT_IPC", "PG_WAIT_TIMEOUT", "PG_WAIT_IO");

GetOptions(
	'outdir:s'       => \$output_path);

open my $waiteventnames, '<', $ARGV[0] or die;

# Include PID in suffix in case parallel make runs this multiple times.
my $htmp = "$output_path/waiteventnames.h.tmp$$";
my $ctmp = "$output_path/waiteventnames.c.tmp$$";
my $stmp = "$output_path/waiteventnames.s.tmp$$";
open my $h, '>', $htmp or die "Could not open $htmp: $!";
open my $c, '>', $ctmp or die "Could not open $ctmp: $!";
open my $s, '>', $stmp or die "Could not open $stmp: $!";

my $header_comment =
  '/*-------------------------------------------------------------------------
 *
 * waiteventnames.%s
 *    Generated wait events infrastructure code
 *
 * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * NOTES
 *  ******************************
 *  *** DO NOT EDIT THIS FILE! ***
 *  ******************************
 *
 *  It has been GENERATED by src/backend/utils/activity/generate-waiteventnames.pl
 *
 *-------------------------------------------------------------------------
 */';

my $h_header_comment = sprintf $header_comment, 'h';
my $c_header_comment = sprintf $header_comment, 'c';


print $h <<"EOF";
$h_header_comment
#ifndef WAITEVENTNAMES_H
#define WAITEVENTNAMES_H

#include "utils/wait_event.h"

EOF

print $c <<"EOF";
$c_header_comment
EOF

# Read the input file and populate the hash table
while (<$waiteventnames>)
{
	chomp;

	# Skip comments
	next if /^#/;
	next if /^\s*$/;

	die "unable to parse waiteventnames.txt"
	  unless /^(\w+)\t+(\w+)\t+("\w+")\t+("\w.*\.")$/;

	(my $waitclassname, my $waiteventenumname, my $waiteventdescription, my $waitevendocsentence) = ($1, $2, $3, $4);

	my @waiteventlist = [$waiteventenumname, $waiteventdescription, $waitevendocsentence];
	my $trimmedwaiteventname = $waiteventenumname;
	$trimmedwaiteventname =~ s/^WAIT_EVENT_//;
	die "wait event names must start with 'WAIT_EVENT_'" if $trimmedwaiteventname eq $waiteventenumname;
	$continue    = ",\n";
	push(@{ $hashwe{$waitclassname} }, @waiteventlist);
}

# Generate the output files
foreach $waitclass (sort keys %hashwe) {
	my $last = $waitclass;
	$last =~ s/^WaitEvent//;
	my $lastuc = uc $last;
	my $lastlc = lc $last;
	my $firstpass = 1;
	my $pg_wait_class;

	print $h <<"EOF";
typedef enum
{
EOF

	print $c <<"EOF";
static const char *
pgstat_get_wait_$lastlc($waitclass w)
{
	const char *event_name = "unknown wait event";

	switch (w)
	{
EOF

	my $ucfirst_lastlc = ucfirst($lastlc);
	print $s <<"EOF";
  <table id="wait-event-$ucfirst_lastlc-table">
   <title>Wait Events of Type <literal>$ucfirst_lastlc</literal></title>
   <tgroup cols="2">
    <thead>
     <row>
      <entry><literal>Activity</literal> Wait Event</entry>
      <entry>Description</entry>
     </row>
    </thead>

    <tbody>
EOF

	foreach my $wev (@{$hashwe{$waitclass}}) {

		my $wev0 = $wev->[0];
		my $wev1 = $wev->[1];
		my $wev1_substr = substr $wev->[1], 1, -1;
		my $wev2_substr = substr $wev->[2], 1, -1;

		if ($firstpass) {
			$pg_wait_class = "PG_WAIT_".$lastuc;
			die "waitclass $pg_wait_class does not exist" unless grep( /^$pg_wait_class$/, @wait_classes );
			printf $h "\t%s = %s", $wev->[0], $pg_wait_class;
			$continue = ",\n";
		} else {
			printf $h "%s\t%s", $continue, $wev->[0];
			$continue = ",\n";
		}
		$firstpass = 0;

		print $c <<"EOF";
		 case $wev0:
			 event_name = $wev1;
			 break;
EOF

		print $s <<"EOF";
     <row>
      <entry><literal>$wev1_substr</literal></entry>
      <entry>$wev2_substr</entry>
     </row>
EOF
	}

	printf $h "\n} $waitclass;\n\n";

	print $c <<"EOF";
			 /* no default case, so that compiler will warn */
	}

	return event_name;

}
EOF

print $s <<"EOF";
    </tbody>
   </tgroup>
  </table>
EOF
}

printf $h "#endif                          /* WAITEVENTNAMES_H */";
close $h;
close $c;
close $s;

rename($htmp, "$output_path/waiteventnames.h") || die "rename: $htmp to $output_path/waiteventnames.h: $!";
rename($ctmp, "$output_path/waiteventnames.c") || die "rename: $ctmp: $!";
rename($stmp, "$output_path/waiteventnames.sgml") || die "rename: $ctmp: $!";

close $waiteventnames;
heredoc-one-file-at-a-time-generate-waiteventnames.plapplication/x-perl; name=heredoc-one-file-at-a-time-generate-waiteventnames.plDownload
#!/usr/bin/perl
#----------------------------------------------------------------------
#
# Generate wait events support files from waiteventnames.txt:
# - waiteventnames.h
# - waiteventnames.c
# - waiteventnames.sgml
#
# Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
# Portions Copyright (c) 1994, Regents of the University of California
#
# src/backend/utils/activity/generate-waiteventnames.pl
#
#----------------------------------------------------------------------

use strict;
use warnings;
use Getopt::Long;

my $output_path = '.';

my %hashwe;
my $waitclass;
my @wait_classes = ("PG_WAIT_ACTIVITY", "PG_WAIT_CLIENT", "PG_WAIT_IPC", "PG_WAIT_TIMEOUT", "PG_WAIT_IO");


GetOptions(
	'outdir:s'       => \$output_path);

open my $waiteventnames, '<', $ARGV[0] or die;

# Read the input file and populate the hash table
while (<$waiteventnames>)
{
	chomp;

	# Skip comments
	next if /^#/;
	next if /^\s*$/;

	die "unable to parse waiteventnames.txt"
	  unless /^(\w+)\t+(\w+)\t+("\w+")\t+("\w.*\.")$/;

	(my $waitclassname, my $waiteventenumname, my $waiteventdescription, my $waitevendocsentence) = ($1, $2, $3, $4);

	my @waiteventlist = [$waiteventenumname, $waiteventdescription, $waitevendocsentence];
	my $trimmedwaiteventname = $waiteventenumname;
	$trimmedwaiteventname =~ s/^WAIT_EVENT_//;
	die "wait event names must start with 'WAIT_EVENT_'" if $trimmedwaiteventname eq $waiteventenumname;
	push(@{ $hashwe{$waitclassname} }, @waiteventlist);
}
close $waiteventnames;

# Generate the output data
my @classes;
foreach $waitclass (sort keys %hashwe) {
	my $last = $waitclass;
	$last =~ s/^WaitEvent//;
	my $lastuc = uc $last;
	my $lastlc = lc $last;
	my $pg_wait_class = "PG_WAIT_".$lastuc;
	die "waitclass $pg_wait_class does not exist" unless grep( /^$pg_wait_class$/, @wait_classes );

	my @events;
	foreach my $wev (@{$hashwe{$waitclass}}) {
		push @events, {
			PG_WAIT_CLASS => $pg_wait_class,
			ENUM_NAME => $wev->[0],
			WAIT_EVENT_ENUM => $wev->[1],
			WAIT_EVENT_NAME => substr($wev->[1], 1, -1),
			WAIT_EVENT_DOC =>  substr($wev->[2], 1, -1)
			};
	}
	push @classes, {
		WAIT_CLASS => $waitclass,
		DISPLAY_NAME => ucfirst($lastlc),
		LASTLC => $lastlc,
		EVENTS => \@events
		};
}

my $header_comment =
  '/*-------------------------------------------------------------------------
 *
 * waiteventnames.%s
 *    Generated wait events infrastructure code
 *
 * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * NOTES
 *  ******************************
 *  *** DO NOT EDIT THIS FILE! ***
 *  ******************************
 *
 *  It has been GENERATED by src/backend/utils/activity/generate-waiteventnames.pl
 *
 *-------------------------------------------------------------------------
 */
';



# Include PID in suffix in case parallel make runs this multiple times.
my $htmp = "$output_path/waiteventnames.h.tmp$$";
open my $h, '>', $htmp or die "Could not open $htmp: $!";
my $ctmp = "$output_path/waiteventnames.c.tmp$$";
open my $c, '>', $ctmp or die "Could not open $ctmp: $!";
my $stmp = "$output_path/waiteventnames.s.tmp$$";
open my $s, '>', $stmp or die "Could not open $stmp: $!";

printf $h $header_comment, 'h';
print $h <<'H1';
#ifndef WAITEVENTNAMES_H
#define WAITEVENTNAMES_H

#include "utils/wait_event.h"

H1

foreach my $cl (@classes) {
	my $wait_class = $cl->{WAIT_CLASS};
	print $h <<'HC1';
typedef enum
{
HC1
	my $evs = $cl->{EVENTS};
	my $last_event = scalar(@$evs) - 1;
	my $ectr = 0;
	for my $event (@$evs) {
		printf $h "\t%s%s%s\n",
			$event->{ENUM_NAME},
			($ectr == 0) ? " = $event->{PG_WAIT_CLASS}" : "",
			($ectr == $last_event) ? "" : ",";
		$ectr++;
	}
	print $h <<"HC2";
} $wait_class;

HC2
}

print $h <<'H2';
#endif                          /* WAITEVENTNAMES_H */
H2

close $h;


printf $c $header_comment, 'c';
foreach my $cl (@classes) {
	my $wait_class = $cl->{WAIT_CLASS};
	my $lastlc = $cl->{LASTLC};
	print $c <<"CC1";
static const char *
pgstat_get_wait_$lastlc($wait_class w)
{
	const char *event_name = "unknown wait event";

	switch (w)
	{
CC1
	my $evs = $cl->{EVENTS};
	for my $event (@$evs) {
		my $enum_name = $event->{ENUM_NAME};
		my $wait_event_enum = $event->{WAIT_EVENT_ENUM};
		print $c <<"CCEV"
		 case $enum_name:
			 event_name = $wait_event_enum;
			 break;
CCEV
	}
	print $c <<"CC2";
			 /* no default case, so that compiler will warn */
	}

	return event_name;

}
CC2
}

close $c;

foreach my $cl (@classes) {
	my $display_name = $cl->{DISPLAY_NAME};
	my $lastlc = $cl->{LASTLC};
	print $s <<"SC1";
  <table id="wait-event-$lastlc-table">
   <title>Wait Events of Type <literal>$display_name</literal></title>
   <tgroup cols="2">
    <thead>
     <row>
      <entry><literal>Activity</literal> Wait Event</entry>
      <entry>Description</entry>
     </row>
    </thead>

    <tbody>
SC1
	my $evs = $cl->{EVENTS};
	for my $event (@$evs) {
		my $wait_event_name = $event->{WAIT_EVENT_NAME};
		my $wait_event_doc = $event->{WAIT_EVENT_DOC};
		print $s <<"SCEV";
     <row>
      <entry><literal>$wait_event_name</literal></entry>
      <entry>$wait_event_doc</entry>
     </row>
SCEV
	}
	print $s <<"SC2";
    </tbody>
   </tgroup>
  </table>
SC2
}

close $s;

rename($htmp, "$output_path/waiteventnames.h") || die "rename: $htmp to $output_path/waiteventnames.h: $!";
rename($ctmp, "$output_path/waiteventnames.c") || die "rename: $ctmp: $!";
rename($stmp, "$output_path/waiteventnames.sgml") || die "rename: $ctmp: $!";

needs-pgindent-generate-waiteventnames.plapplication/x-perl; name=needs-pgindent-generate-waiteventnames.plDownload
#!/usr/bin/perl
#----------------------------------------------------------------------
#
# Generate wait events support files from waiteventnames.txt:
# - waiteventnames.h
# - waiteventnames.c
# - waiteventnames.sgml
#
# Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
# Portions Copyright (c) 1994, Regents of the University of California
#
# src/backend/utils/activity/generate-waiteventnames.pl
#
#----------------------------------------------------------------------

use strict;
use warnings;
use Getopt::Long;

my $output_path = '.';

my %hashwe;
my $waitclass;
my @wait_classes = ("PG_WAIT_ACTIVITY", "PG_WAIT_CLIENT", "PG_WAIT_IPC", "PG_WAIT_TIMEOUT", "PG_WAIT_IO");


GetOptions(
	'outdir:s'       => \$output_path);

open my $waiteventnames, '<', $ARGV[0] or die;

# Read the input file and populate the hash table
while (<$waiteventnames>)
{
	chomp;

	# Skip comments
	next if /^#/;
	next if /^\s*$/;

	die "unable to parse waiteventnames.txt"
	  unless /^(\w+)\t+(\w+)\t+("\w+")\t+("\w.*\.")$/;

	(my $waitclassname, my $waiteventenumname, my $waiteventdescription, my $waitevendocsentence) = ($1, $2, $3, $4);

	my @waiteventlist = [$waiteventenumname, $waiteventdescription, $waitevendocsentence];
	my $trimmedwaiteventname = $waiteventenumname;
	$trimmedwaiteventname =~ s/^WAIT_EVENT_//;
	die "wait event names must start with 'WAIT_EVENT_'" if $trimmedwaiteventname eq $waiteventenumname;
	push(@{ $hashwe{$waitclassname} }, @waiteventlist);
}
close $waiteventnames;

# Generate the output data
my @classes;
foreach $waitclass (sort keys %hashwe) {
	my $last = $waitclass;
	$last =~ s/^WaitEvent//;
	my $lastuc = uc $last;
	my $lastlc = lc $last;
	my $pg_wait_class = "PG_WAIT_".$lastuc;
	die "waitclass $pg_wait_class does not exist" unless grep( /^$pg_wait_class$/, @wait_classes );

	my @events;
	foreach my $wev (@{$hashwe{$waitclass}}) {
		push @events, {
			PG_WAIT_CLASS => $pg_wait_class,
			ENUM_NAME => $wev->[0],
			WAIT_EVENT_ENUM => $wev->[1],
			WAIT_EVENT_NAME => substr($wev->[1], 1, -1),
			WAIT_EVENT_DOC =>  substr($wev->[2], 1, -1)
			};
	}
	push @classes, {
		WAIT_CLASS => $waitclass,
		DISPLAY_NAME => ucfirst($lastlc),
		LASTLC => $lastlc,
		EVENTS => \@events
		};
}

my $header_comment =
  '/*-------------------------------------------------------------------------
 *
 * waiteventnames.%s
 *    Generated wait events infrastructure code
 *
 * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * NOTES
 *  ******************************
 *  *** DO NOT EDIT THIS FILE! ***
 *  ******************************
 *
 *  It has been GENERATED by src/backend/utils/activity/generate-waiteventnames.pl
 *
 *-------------------------------------------------------------------------
 */
';



# Include PID in suffix in case parallel make runs this multiple times.
my $htmp = "$output_path/waiteventnames.h.tmp$$";
open my $h, '>', $htmp or die "Could not open $htmp: $!";
my $ctmp = "$output_path/waiteventnames.c.tmp$$";
open my $c, '>', $ctmp or die "Could not open $ctmp: $!";
my $stmp = "$output_path/waiteventnames.s.tmp$$";
open my $s, '>', $stmp or die "Could not open $stmp: $!";

printf $h $header_comment, 'h';
print $h qq[
	#ifndef WAITEVENTNAMES_H
	#define WAITEVENTNAMES_H
	#include "utils/wait_event.h"
	];

foreach my $cl (@classes) {
	my $wait_class = $cl->{WAIT_CLASS};
	print $h qq[
		typedef enum
		{
		];
	my $evs = $cl->{EVENTS};
	my $last_event = scalar(@$evs) - 1;
	my $ectr = 0;
	for my $event (@$evs) {
		printf $h "%s%s%s\n",
			$event->{ENUM_NAME},
			($ectr == 0) ? " = $event->{PG_WAIT_CLASS}" : "",
			($ectr == $last_event) ? "" : ",";
		$ectr++;
	}
	print $h qq[
		} $wait_class;

		];
}

print $h '#endif                          /* WAITEVENTNAMES_H */';
close $h;


printf $c $header_comment, 'c';
foreach my $cl (@classes) {
	my $wait_class = $cl->{WAIT_CLASS};
	my $lastlc = $cl->{LASTLC};
	print $c qq[
		static const char *
		pgstat_get_wait_$lastlc($wait_class w)
		{
			const char *event_name = "unknown wait event";
			switch (w)
			{
		];
	my $evs = $cl->{EVENTS};
	for my $event (@$evs) {
		my $enum_name = $event->{ENUM_NAME};
		my $wait_event_enum = $event->{WAIT_EVENT_ENUM};
		print $c qq[
			case $enum_name:
				event_name = $wait_event_enum;
				break;
			];
	}
	print $c qq[
				/* no default case, so that compiler will warn */
				}
				return event_name;
			}
			];
}

close $c;

foreach my $cl (@classes) {
	my $display_name = $cl->{DISPLAY_NAME};
	my $lastlc = $cl->{LASTLC};
	print $s <<"SC1";
  <table id="wait-event-$lastlc-table">
   <title>Wait Events of Type <literal>$display_name</literal></title>
   <tgroup cols="2">
    <thead>
     <row>
      <entry><literal>Activity</literal> Wait Event</entry>
      <entry>Description</entry>
     </row>
    </thead>

    <tbody>
SC1
	my $evs = $cl->{EVENTS};
	for my $event (@$evs) {
		my $wait_event_name = $event->{WAIT_EVENT_NAME};
		my $wait_event_doc = $event->{WAIT_EVENT_DOC};
		print $s <<"SCEV";
     <row>
      <entry><literal>$wait_event_name</literal></entry>
      <entry>$wait_event_doc</entry>
     </row>
SCEV
	}
	print $s <<"SC2";
    </tbody>
   </tgroup>
  </table>
SC2
}

close $s;

rename($htmp, "$output_path/waiteventnames.h") || die "rename: $htmp to $output_path/waiteventnames.h: $!";
rename($ctmp, "$output_path/waiteventnames.c") || die "rename: $ctmp: $!";
rename($stmp, "$output_path/waiteventnames.sgml") || die "rename: $ctmp: $!";