do not edit — generated by btf.
git.druid.rocksindexdruid520mpmk/genconfdoc.pl

mk/genconfdoc.pl


#!/usr/bin/env perl
# generates docs/reference/*-example.btft from this repo's own root
# *.conf.example/*.env.example files -- a single source of truth
# instead of the example files' own comments and the rendered docs
# slowly drifting apart. run as part of "mk . d"/"sh mk/d.sh", same as
# every other doc-generation step; NEVER hand-edit the resulting
# *-example.btft files directly, edit the source .example file and
# regenerate instead (matching every other "do not edit, generated"
# convention this tree already uses for btf's own html/man output).
#
# a source file is a sequence of blank-line-delimited CHUNKS. within
# one chunk, each line is one of:
#   - a real, uncommented directive line (no leading "#" at all) --
#     e.g. mpx.conf.example's own "SHELL=\"/bin/sh\"" -- always CODE.
#   - a bare "#" (nothing after it) -- a paragraph break WITHIN the
#     chunk's own prose, not a new chunk (a chunk can hold more than
#     one paragraph under the same heading).
#   - "# " followed by text that's still indented, or that looks like
#     a KEY=value/KEY: directive -- an example embedded inside the
#     surrounding explanation (e.g. "#   REPO_main=git://...") -- CODE.
#   - "# " followed by ordinary prose -- PROSE.
# consecutive PROSE lines join into one flowing paragraph (the same
# "newline within a paragraph is just a soft wrap" rule every OTHER
# page in this docs tree already relies on); consecutive CODE lines
# join into one [code] block, each line's own indentation preserved
# past the leading "# " it's stripped of.
#
# the first chunk's own first prose line, up to its first ". " or ":"
# (matching this docs tree's own existing "topic. explanation" heading
# style -- see mp.conf.example's own "hold (inside a...)"/"hooks. global
# keys..." for real examples of the convention being extracted here,
# not invented for this script), becomes that chunk's [h2] title, kept
# exactly as-authored (lowercase, no title-casing) to match house
# style. a chunk with no clean short phrase to extract (no "." or ":"
# within a reasonable prefix) renders with no heading at all, exactly
# like this docs tree's own hand-written pages already do for an
# opening/preamble paragraph.
use v5.16;
use strict;
use warnings;
 
sub h { my $s = shift; $s =~ s/&/&amp;/g; $s =~ s/</&lt;/g; $s =~ s/>/&gt;/g; return $s; }
 
# a handful of common lead-in words read as a topic phrase by the same
# ".../ :" pattern a genuine heading uses (e.g. "example: export a var
# ..."), but are never actually meant as a section title of their own
# -- excluded explicitly rather than trying to out-clever every such
# case with a smarter pattern.
my %GENERIC_HEADING = map { $_ => 1 } qw(example examples note notes warning tip aside caveat);
 
sub extract_heading {
	my ($line) = @_;
	my $cand;
	# (?<!\be\.g)(?<!\bi\.e)(?<!\betc): a period ending a common
	# abbreviation ("e.g. cmake", "i.e. the default", "etc. and more")
	# is not a sentence boundary -- confirmed the hard way, "build vs
	# runtime deps: pkg_bdepend (build-only, e.g. cmake/autoconf)"
	# extracted a heading truncated at "...e.g" before this guard.
	if($line =~ /^(.{1,70}?)(?<!\be\.g)(?<!\bi\.e)(?<!\betc)\.\s/) { $cand = $1; }
	elsif($line =~ /^([a-zA-Z][a-zA-Z0-9_* <>%!=,\/'-]{1,40}):(?:\s|$)/) { $cand = $1; }
	return undef unless defined $cand;
	return undef if $GENERIC_HEADING{lc $cand};
	return $cand;
}
 
sub render_chunk {
	my ($lines) = @_;
	my @paras;    # each: { type => 'prose'|'code', text => [...] }
	for my $line (@$lines)
	{
		if($line !~ /^#/)
		{
			push @paras, { type => 'code', text => [] } unless @paras && $paras[-1]{type} eq 'code';
			push @{$paras[-1]{text}}, $line;
			next;
		}
		(my $rest = $line) =~ s/^#//;
		if($rest =~ /^\s*$/)
		{
			push @paras, { type => 'break' };
			next;
		}
		# the actual, consistent authoring convention across every one
		# of these files (confirmed directly, not assumed): PROSE is
		# always "# text" -- exactly one space after the hash, nothing
		# more. an embedded example/directive is one of:
		#   "#text"      no separating space at all (mpx.env.example's
		#                own commented-out "#export FOO=...", "#PATH=..."),
		#   "#   text"   a real space PLUS further indentation (mp.conf.
		#                example's own "#   REPO_main=git://..."), or
		#   "# KEY=..."  a normal single space, but the text itself is
		#                an all-caps KEY=/KEY (a definition-list TERM
		#                line, e.g. hooks.conf.example's own
		#                "# IF_USE=<flag> <flag> ..." heading for the
		#                indented explanation that follows it).
		my $is_code;
		if($rest !~ /^ /) { $is_code = 1; }
		elsif($rest =~ /^  /) { $is_code = 1; $rest =~ s/^ //; }
		else
		{
			$rest =~ s/^ //;
			$is_code = ($rest =~ /^[A-Z][A-Z0-9_]*[+-]?[=:]/) ? 1 : 0;
		}
		if($is_code)
		{
			push @paras, { type => 'code', text => [] } unless @paras && $paras[-1]{type} eq 'code';
			push @{$paras[-1]{text}}, $rest;
		}
		else
		{
			push @paras, { type => 'prose', text => [] } unless @paras && $paras[-1]{type} eq 'prose';
			push @{$paras[-1]{text}}, $rest;
		}
	}
 
	# pull the heading candidate off the very first prose paragraph's
	# own first line, WITHOUT consuming that whole line -- the rest of
	# it (and everything else in that paragraph) still renders as body.
	my $heading;
	for my $p (@paras)
	{
		next unless $p->{type} eq 'prose';
		$heading = extract_heading($p->{text}[0]);
		last;
	}
 
	my $out = '';
	$out .= "[h2]" . h($heading) . "[e]\n\n" if defined $heading;
	for my $p (@paras)
	{
		if($p->{type} eq 'break') { next; }
		elsif($p->{type} eq 'code')
		{
			$out .= "[code]\n" . join("\n", @{$p->{text}}) . "\n[e]\n\n";
		}
		else
		{
			$out .= h(join(' ', @{$p->{text}})) . "\n\n";
		}
	}
	return $out;
}
 
sub convert {
	my ($src, $title, $seename) = @_;
	open(my $fh, '<', $src) or die "err: cannot open $src: $!\n";
	my @lines = map { chomp; $_ } <$fh>;
	close($fh);
 
	my @chunks;
	my @cur;
	for my $line (@lines)
	{
		if($line eq '')
		{
			push @chunks, [@cur] if @cur;
			@cur = ();
		}
		else
		{
			push @cur, $line;
		}
	}
	push @chunks, [@cur] if @cur;
 
	my $body = '';
	$body .= render_chunk($_) for @chunks;
 
	return <<EOF;
[table class="topnav"]
[tr]
[td class="logotab"]mp[e]
[td][see name="index"]index[e][e]
[td][see name="guide/index"]guide[e][e]
[td][see name="reference/index"]reference[e][e]
[e]
[e]
 
[h1]${title}[e]
 
[quote class="note"]
generated from $src -- do not edit this page directly, edit that file and regenerate ("mk . d" / "sh mk/d.sh") instead. see [see name="reference/config"]reference/config[e] for the narrative tour this page doesn't replace.
[e]
 
$body
EOF
}
 
my %files = (
	'mp.conf.example'      => ['docs/reference/mp-conf-example.btft',      'mp.conf.example'],
	'mpx.conf.example'     => ['docs/reference/mpx-conf-example.btft',     'mpx.conf.example'],
	'mpx.env.example'      => ['docs/reference/mpx-env-example.btft',      'mpx.env.example'],
	'patches.conf.example' => ['docs/reference/patches-conf-example.btft', 'patches.conf.example'],
	'hooks.conf.example'   => ['docs/reference/hooks-conf-example.btft',   'hooks.conf.example'],
);
 
for my $src (sort keys %files)
{
	my ($dest, $title) = @{$files{$src}};
	my $content = convert($src, $title);
	open(my $out, '>', $dest) or die "err: cannot write $dest: $!\n";
	print $out $content;
	close($out);
	print "generated $dest from $src\n";
}
powered by btf.