git/list[1] front-page[2] threads[3] people[4] search[5] about
 

WIP: asciidoc replacement

From
Johannes Schindelin <johannes.schindelin@gmx.de>
Date
Oct 3, 2007, 00:42 UTC
Message-ID
<Pine.LNX.4.64.0710030133020.28395@racer.site>
Hi,

I do not want to depend on more than necessary in msysGit, and therefore I started to write an asciidoc replacement.

So here it is: a perl script that does a good job on many .txt files in Documentation/, although for some it deviates from "make man"'s output, and for others it is outright broken. It is meant to be run in Documentation/.

My intention is not to fix the script for all cases, but to make patches to Documentation/*.txt themselves, so that they are more consistent (and incidentally nicer to the script).

Now, I hear you already moan: "But Dscho, you know you suck at Perl!"

Yeah, I know, but maybe instead of bashing on me (pun intended), you may want to enlighten me with tips how to make it nicer to read. (Yes, there are no comments; yes, I will gladly add them where appropriate; yes, html is just a stub.)

So here, without further ado, da script:

-- snip -- #!/usr/bin/perl

$conv = new man_page(); $conv->{manual} = 'Git Manual'; $conv->{git_version} = 'Git ' . `cat ../GIT-VERSION-FILE`; $conv->{git_version} =~ s/GIT_VERSION = //; $conv->{git_version} =~ s/-/\\-/; $conv->{git_version} =~ s/\n//; $conv->{date} = `date +%m/%d/%Y`; $conv->{date} =~ s/\n//;

$par = ''; handle_file($ARGV[0]); $conv->finish();

sub handle_text {
	if ($par =~ /^\. /s) {
		my @lines = split(/^\. /m, $par);
		shift @lines;
		$conv->enumeration(\@lines);
	} elsif ($par =~ /^\* /s) {
		my @lines = split(/^\* /m, $par);
		shift @lines;
		$conv->enumeration(\@lines, 'unnumbered');
	} elsif ($par =~ /^\[verse\]/) {
		$par =~ s/\[verse\] *\n?//;
		$conv->verse($par);
	} elsif ($par =~ /^(\t|  +)/s) {
		$par =~ s/^$1//mg;
		$par =~ s/^\+$//mg;
		$conv->indent($par);
	} elsif ($par =~ /^([^\n]*)::\n((\t|  +).*)$/s) {
		my ($first, $rest, $indent) = ($1, $2, $3);
		$rest =~ s/^\+$//mg;
		while ($rest =~ /^(.*?\n\n)--+\n(.*?\n)--+\n\n(.*)$/s) {
			my ($pre, $verb, $post) = ($1, $2, $3);
			$pre =~ s/^(\t|$indent)//mg;
			if ($first ne '') {
				$conv->begin_item($first, $pre);
				$first = '';
			} else {
				$conv->normal($pre);
			}
			$conv->verbatim($verb);
			$rest = $post;
		}
		$rest =~ s/^(\t|$indent)//mg;
		if ($first ne '') {
			$conv->begin_item($first, $rest);
		} else {
			$conv->normal($rest);
		}
		$conv->end_item();
	} elsif ($par =~ /^-+\n(.*\n)-+\n$/s) {
		$conv->verbatim($1);
	} else {
		$conv->normal($par);
	}
	$par = '';
}
sub handle_file {
	my $in;
	open($in, '<' . $_[0]);
	while (<$in>) {
		if (/^=+$/) {
			if ($par ne '' && length($_) >= length($par)) {
				$conv->header($par);
				$par = '';
				next;
			}
		} elsif (/^-+$/) {
			if ($par ne '' && length($_) >= length($par)) {
				$conv->section($par);
				$par = '';
				next;
			}
		} elsif (/^~+$/) {
			if ($par ne '' && length($_) >= length($par)) {
				$conv->subsection($par);
				$par = '';
				next;
			}
		} elsif (/^\[\[(.*)\]\]$/) {
			handle_text();
			$conv->anchor($1);
			next;
		} elsif (/^$/) {
			if ($par =~ /^-+\n.*[^-]\n$/s) {
				# fallthru; is verbatim, but needs more.
			} elsif ($par =~ /::\n$/s) {
				# is item, but needs more.
				next;
			} else {
				handle_text();
				next;
			}
		} elsif (/^include::(.*)\[\]$/) {
			handle_text();
			handle_file($1);
			next;
		}
		# convert "\--" to "--"
		s/\\--/--/g;
		# convert "\*" to "*"
		s/\\\*/*/g;
		# handle gitlink:
		s/gitlink:([^\[ ]*)\[(\d+)\]/sprintf "%s",
			$conv->get_link($1, $2)/ge;
		# handle link:
		s/link:([^\[ ]*)\[(.+)\]/sprintf "%s",
			$conv->get_link($1, $2, 'external')/ge;
		$par .= $_;
	}
	close($in);
	handle_text();
}
package man_page;
sub new {
	my ($class) = @_;
	my $self = {
		sep => '',
		links => [],
#		generator => 'Home grown git txt2man converter'
		generator => 'DocBook XSL Stylesheets v1.71.1 <http://docbook.sf.net/>'
	};
	bless $self, $class;
	return $self;
}
sub header {
	my ($self, $text) = @_;
	$text =~ s/-/\\-/g;
	if ($self->{preamble_shown} == undef) {
		$title = $text;
		$title =~ s/\(\d+\)$//;
		print '.\"     Title: ' . $title
			. '.\"    Author: ' . "\n"
			. '.\" Generator: ' . $self->{generator} . "\n"
			. '.\"      Date: ' . $self->{date} . "\n"
			. '.\"    Manual: ' . $self->{manual} . "\n"
			. '.\"    Source: ' . $self->{git_version} . "\n"
			. '.\"' . "\n";
	}
	$text =~ tr/a-z/A-Z/;
	my $suffix = "\"$self->{date}\" \"$self->{git_version}\""
		. " \"$self->{manual}\"";
	$text =~ s/^(.*)\((\d+)\)$/.TH "\1" "\2" $suffix/;
	print $text;
	if ($self->{preamble_shown} == undef) {
		print '.\" disable hyphenation' . "\n"
			. '.nh' . "\n"
			. '.\" disable justification (adjust text to left'
				. ' margin only)' . "\n"
			. '.ad l' . "\n";
		$self->{preamble_shown} = 1;
	}
	$self->{last_op} = 'header';
}
sub section {
	my ($self, $text) = @_;
	$text =~ tr/a-z/A-Z/;
	$text =~ s/^(.*)$/.SH "\1"/;
	print $text;
	$self->{last_op} = 'section';
}
sub subsection {
	my ($self, $text) = @_;
	$text =~ s/^(.*)$/.SS "\1"/;
	print $text;
	$self->{last_op} = 'subsection';
}
sub get_link {
	my ($self, $command, $section, $option) = @_;
	if ($option eq 'external') {
		my $links = $self->{links};
		push(@$links, $command);
		$command =~ s/\.html$//;
		$command =~ s/-/ /g;
		push(@$links, $command);
		return '\fI' . $command . '\fR\&[1]';
	} else {
		return '\fB' . $command . '\fR(' . $section . ')';
	}
}
sub common {
	my ($self, $text, $option) = @_;
	# escape backslashes, but not in "\n", "\&" or "\fB"
	$text =~ s/\\(?!n|f[A-Z]|&)/\\\\/g;
	# escape "-"
	$text =~ s/-/\\-/g;
	# handle ...
	$text =~ s/(\.\.\.)/\\&\1/g;
	# remove double space after full stop or comma
	$text =~ s/([\.,])  /\1 /g;
	if ($option ne 'no-markup') {
		# make 'italic'
		$text =~ s/'([^'\n]*)'/\\fI\1\\fR/g;
		# ignore `
		$text =~ s/`//g;
		# make *bold*
		$text =~ s/\*([^\*\n]*)\*/\\fB\1\\fR/g;
		# handle <<sections>
		$text =~ s/<<([^>]*)>>/the section called \\(lq\1\\(rq/g;
	}
	return $text;
}
sub normal {
	my ($self, $text) = @_;
	if ($text eq "") {
		return;
	}
	$text = $self->common($text);
	$text =~ s/ *\n(.)/ \1/g;
	if ($self->{last_op} eq 'normal') {
		print "\n";
	}
	print $text;
	$self->{last_op} = 'normal';
}
sub verse {
	my ($self, $text) = @_;
	$text = $self->common($text);
	$text =~ s/^\t/        /mg;
	print ".sp\n.RS 4\n.nf\n" . $text . ".fi\n.RE\n";
	$self->{last_op} = 'verse';
}
sub enumeration {
	my ($self, $text, $option) = @_;
	my $counter = 0;
	foreach $line (@$text) {
		$counter++;
		print ".TP 4\n"
			. ($option eq 'unnumbered' ? '\(bu' : $counter . '.')
			. "\n"
			. $self->common($line);
	}
	$self->{last_op} = 'enumeration';
}
sub begin_item {
	my ($self, $item, $text) = @_;
	$item = $self->common($item);
	$text = $self->common($text);
	$text =~ s/([^\n]) *\n([^\n])/\1 \2/g;
	print ".PP\n" . $item . "\n.RS 4\n" . $text;
	$self->{last_op} = 'item'; 
}
sub end_item {
	my ($self) = @_;
	print ".RE\n";
	$self->{last_op} = 'end_item';
}
sub indent {
	my ($self, $text) = @_;
	$text = $self->common($text, 'no-markup');
	$text =~ s/^\t/        /mg;
	if ($self->{last_op} eq 'normal') {
		print "\n";
	}
	print ".sp\n.RS 4\n.nf\n" . $text . ".fi\n.RE\n";
	$self->{last_op} = 'indent';
}
sub verbatim {
	my ($self, $text) = @_;
	$text = $self->common($text, 'no-markup');
	# convert tabs to spaces
	$text =~ s/^\t/        /mg;
	# remove trailing empty lines
	$text =~ s/\n\n*$/\n/;
	if ($self->{last_op} eq 'normal') {
		print "\n";
	}
	print ".sp\n.RS 4\n.nf\n.ft C\n" . $text . ".ft\n\n.fi\n.RE\n";
	$self->{last_op} = 'verbatim';
}
sub anchor {
	my ($self, $text) = @_;
	$self->{last_op} = 'anchor';
}
sub finish {
	my ($self) = @_;
	my $links = $self->{links};
	if ($#$links >= 0) {
		print '.SH "REFERENCES"' . "\n";
		my $i = 1;
		while ($#$links >= 0) {
			my $ref = shift(@$links);
			$ref =~ s/-/\\-/g;
			my $label = shift(@$links);
			printf (".IP \"% 2d.\" 4\n%s\n.RS 4\n\\%%%s\n.RE\n",
				$i++, $label, $ref);
		}
	} else {
		print "\n";
	}
}
package html_page;
sub new {
	my ($class) = @_;
	my $self = {};
	bless $self, $class;
	return $self;
}
-- snap --

Ciao, Dscho

P.S.: I need to catch some Zs, and do some real work, so do not be surprised if I do not respond within the next 24 hours.

Next: Sam Vilain
Message 1 of 25 in “WIP: asciidoc replacement”
  1. Johannes SchindelinOct 3, 2007
  2. Sam VilainOct 3, 2007
  3. Johannes SchindelinOct 3, 2007
  4. Jeff KingOct 3, 2007
  5. J. Bruce FieldsOct 3, 2007
  6. Sam VilainOct 4, 2007
  7. Johannes SchindelinOct 4, 2007
  8. Wincent ColaiutaOct 3, 2007
  9. Junio C HamanoOct 3, 2007
  10. Wincent ColaiutaOct 3, 2007
  11. David KastrupOct 3, 2007
  12. Wincent ColaiutaOct 3, 2007
  13. David KastrupOct 3, 2007
  14. Sam RavnborgOct 3, 2007
  15. J. Bruce FieldsOct 3, 2007
  16. David KastrupOct 3, 2007
  17. Junio C HamanoOct 3, 2007
  18. Sam RavnborgOct 3, 2007
  19. Johannes SchindelinOct 3, 2007
  20. Sam RavnborgOct 3, 2007
  21. Martin LanghoffOct 4, 2007
  22. David KastrupOct 4, 2007
  23. Martin LanghoffOct 4, 2007
  24. Johannes SchindelinOct 3, 2007
  25. David KastrupOct 3, 2007

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.