FAQ Title: Interweaving cfengine, documentation, and perl Keywords: documentation perl template weave Classes: any Section: Configuration

This script converts a .pt (perl template) file into a cfengine configuration file and a cfengine documentation file. If you just write the file as ordinary text, it will produce a cfengine config file exactly the same (and an empty doco file). If you have certain sections between two lines marked =cfdoc start =cfdoc end ...then those parts get dropped into the documentation file. And if you have parts between <? and ?> then the perl code between these gets run, and the return value gets put in the cfengine config file.

#!/usr/bin/perl -I /usr/lib/perl5/site_perl/5.005

use	Text::Template;
use	Getopt::Long;
use	File::Basename;

### Deal with command-line options
Getopt::Long::Configure('bundling', 'no_ignore_case');
GetOptions(\%ARGH,
	'd=s', 'D|define=s' => \%defines,
);
if(! $ARGH{'d'}) { $ARGH{'d'} = "#"; }

if($#ARGV < 0) { die "Error: List files to be processed on the command line\n"; }

### Set up paths
$pathsearch = '/var/cfengine/';
$pathreplace = '/var/www/html/internal/cfengine-auto/';
$ourpath = `pwd`; chomp $ourpath;

### Set up documentation headers and footers
$xhtml_head = <<EOT;
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html>
<head>
<title>#TITLE#</title>
<link rel="stylesheet" href="http://www.sunet.com.au/include/dococolors.css" type="text/css"/>
<!-- #MESSAGE# -->
</head>
<body>
<h1>#TITLE#</h1>
<hr>
EOT

$xhtml_foot = <<EOT;
<hr>
</body>
</html>
EOT

$message = "Do not edit this file; it is generated with templateconv";

### Process each file
foreach $file (@ARGV) {
	### Ensure that we're processing the right file, and set up the names
	if($file !~ /\.pt$/) { $file .= '.pt'; }
	if(! -e $file) { die "File $file does not exist\n"; }
	$targetfile = $file; $targetfile =~ s/\.pt$//;

	### Set up documentation filename
	$docofile = $targetfile . '.cfdoc.html';
	if($docofile !~ m#^/#) { $docofile = "$ourpath/$docofile"; }
	$docofile =~ s#$pathsearch#$pathreplace#;
	$docopath = dirname($docofile);
	mkdir($docopath);

	### Suck in template
	$text = join '', getfile($file);

	### Extract doco
	while($text =~ s/\n=cfdoc start\n(.*?)\n=cfdoc end\n//s) {
		$docotext .= $1;
	}
	### Print to doco file
	if($docotext !~ /^\s*$/) {
		print "Doco: $docofile\n";
		$docotext = "$xhtml_head$docotext$xhtml_foot";
		$docotext =~ s/#TITLE#/Documentation for $targetfile/g;
		$docotext =~ s/#MESSAGE#/$message/g;
		writefile($docofile, $docotext);
	}

	### Set up and run template conversion
	$template = new Text::Template(
		TYPE => STRING, 
		SOURCE => $text,
		DELIMITERS => ['<?', '?<'],
	) or die "Can't create template: $Text::Template::ERROR\n";

	$text = $template->fill_in(
		BROKEN => \&callback
	);

	### Write to output file
	print "Output: $targetfile\n";
	$text = "$ARGH{'d'} $message\n$text";
	writefile($targetfile, $text);
}

exit $templateretval ? $templateretval : 0;

### Call back for Text::Template function
sub	callback {
	my(%hash) = @_;
	die "Error at $hash{'lineno'}: " . $hash{'error'} . "\n";
}

### Returns the passed $filename in an array of @lines
sub	getfile {
	my($filename) = @_;
	my(@lines);

	open(FILE, $filename) or die "Can't open file $filename: $!";
	@lines = <FILE>;
	close(FILE);

	return(@lines);
}

### Writes the $text to the specified $filename
sub	writefile {
	my($filename, $text) = @_;

	open(FILE, '>' . $filename) or die "Can't open file $filename: $!";
	print FILE $text;
	close(FILE);
}