[ic] What Does it Take to Create Wizards

Jim Balcom interchange-users@interchange.redhat.com
Wed Sep 26 18:40:00 2001


On Wed, 26 Sep 2001, Zack Johnson wrote:

ZJ>>Many thanks!  Once I downloaded 'te' (which I could only on Google's cache:
ZJ>>http://www.google.com/search?q=cache:vf5RIgpu40Q:www.akopia.com/~jon/te+Jon+
ZJ>>interchange+te+editor&hl=en), it was a piece of cake.  I found Ed's thread

te is hard to find????

This is slicker than snot on a brass doorknob!

Here it is! Cut and paste into a file and make it executable!
-------------------------
#!/usr/bin/perl -w

=pod

=head1 NAME

te - table editor front-end for tab-delimited ASCII databases

=head1 SYNOPSIS

B<te> I<file1> [ I<file2> ... ]

=head1 DESCRIPTION

This program makes it easier to edit tab-delimited ASCII databases,
such as are used with Red Hat Interchange.

It converts tab-delimited ASCII files that have one record per line into
temporary files with one field per line, each line beginning with the
field name. It then sends each file to your favorite text editor. After
you exit your editor, it checks to see if you changed anything in the
file, and if so, it converts the data back to the tab-delimited format
with one record per line, and replaces the original file.

The first line of each input file must contain the field names,
tab-delimited, that apply for that file.

Editing is pretty straightforward when you see it in action. The
rules are:

Empty lines and comment lines (beginning with C<#>) are ignored.

Any space left after the field name and colon (like C<fieldname:>) will be
included as part of the field. Any tabs you put in the field data itself
will be converted to spaces (as they would corrupt the table otherwise).

A new record begins immediately after the previous one ends; no special
record-separation marker is used. All fields must be represented for each
record, even fields with no data. A record can be deleted by removing
all its fields. A new record can be added by inserting a new block of
all fields at a record boundary.

If any errors are encountered, such as non-existent field names, fields
out of order, or lines that don't follow the prescribed format, processing
aborts immediately and the original file is left untouched.

You can edit several files in succession by naming each on the command
line. The editor will be called for each one independently. If you start
editing many files and decide you want to stop, add a line C<#DONE>
anywhere in the temporary file and save it. The current file will be
processed and saved, but the rest will be skipped.

As is customary with many Unix applications, you can set the environment
variable EDITOR to point to your favorite text editor. If EDITOR is not
set, my favorite editor, B<vi>(1) is used.

=head1 LIMITATIONS

There is currently no way to add or delete entire columns from the
file. I recommend using B<cut>(1) for this.

=head1 AUTHOR

Jon Jensen <jon@redhat.com>

=head1 COPYRIGHT

Copyright (C) 2001 Red Hat, Inc., http://www.redhat.com/

This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 2 of the License, or (at your
option) any later version.

This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
at http://www.fsf.org/copyleft/gpl.html for more details.

=head1 CHANGELOG

2001-04-26. Initial release.

2001-05-04. Make temporary file in working directory instead of using
POSIX::tmpnam. Keep ownership and permissions of original file. Fixed
bug that caused fields with number "0" to be output as empty strings.

2001-05-06. Fix problem with how temporary file names were generated.

=cut

use strict;
use Digest::MD5 'md5';
use File::Basename 'fileparse';

die "Usage: $0 tablefile1 [tablefile2 ...]\n" unless @ARGV;

my $EDITOR = $ENV{EDITOR} || 'vi';

# I tried keeping the whole file in memory instead of first writing to
# disk, but doing it this way turned out to be about 5 times faster and
# used 1/10th the memory on large files. (My benchmark was a 12 MB
# products.txt database for Interchange.)

for my $filename (@ARGV) {
	my (@fieldnames, $fieldcount, @fields);
	my ($name, $path, $tmpfile, $newfile, $digest1, $digest2);
	unless (-e $filename) {
		warn "Skipping '$filename': file does not exist\n";
		next;
	}
	unless (-f $filename) {
		warn "Skipping '$filename': not a regular file\n";
		next;
	}
	unless (open IN, "<$filename") {
		warn "Error 'opening' $filename for reading: $!\n";
		next;
	}
	chomp ($_ = <IN>);
	die "Error in '$filename' header: null field name found\n" if /\t\t/;
	$fieldcount = tr/\t/\t/ + 1;
	@fieldnames = split /\t/, $_, $fieldcount;
	($name, $path) = fileparse($filename);
	$tmpfile = "$path.$name.tmp.$$";
	open OUT, ">$tmpfile" or die "Error opening '$tmpfile' for writing: $!\n";
	print STDERR "Prettifying $filename\n";
	print OUT <<EOF;
#
# This is a temporary file, automatically generated from the database file:
#
# $filename
#
# If you change anything in it, it will be converted back into the original
# format and will replace the original file.
#
EOF
	while (<IN>) {
		chomp;
		@fields = split /\t/, $_, $fieldcount;
		for (my $i = 0; $i < @fieldnames; $i++) {
			print OUT $fieldnames[$i], ":",
				defined $fields[$i] ? $fields[$i] : '', "\n";
		}
		print OUT "#\n";
	}
	if (@fields) {
		print OUT <<EOF;
# You can uncomment the following lines to use as a template for inserting
# a new row into the table. Copy as many times as needed to add many rows.
#
EOF
	} else {
		print OUT <<EOF;
# Your file was empty -- it had no data rows, only field definitions.
# You can copy the following empty row template as many times as needed
# to add new rows to the table.
#
EOF
	}
	print OUT join("\n", map { (@fields ? '#' : '') . $_ . ":" } @fieldnames);
	print OUT "\n#\n";
	close IN;
	print OUT <<EOF;
# end of file
#
EOF
	close OUT or die "Error closing '$tmpfile' after writing: $!\n";
	{
		local $/;
		open IN, "<$tmpfile" or die "Error opening '$tmpfile' for reading: $!\n";
		my $file = <IN>;
		close IN;
		$digest1 = md5($file);
		system ($EDITOR, $tmpfile) == 0
			or die "Error calling editor '$EDITOR' with '$tmpfile': $!\n";
		open IN, "<$tmpfile" or die "Error opening $tmpfile for reading: $!\n";
		$file = <IN>;
		close IN;
		$digest2 = md5($file);
	}
	if ($digest1 eq $digest2) {
		print STDERR "No changes made; '$filename' untouched\n";
		unlink $tmpfile;
		next;
	}
	open IN, "<$tmpfile" or die "Error opening '$tmpfile' for reading: $!\n";
	print STDERR "Importing changes back into '$filename'\n";
	$newfile = "$path.$name.new.$$";
	open OUT, ">$newfile" or die "Error opening '$newfile' for writing: $!\n";
	print OUT join("\t", @fieldnames), "\n";
	my $tabcounter = 0;
	my $fieldpos = 0;
	my $done;
	@fields = ();
	while (<IN>) {
		$done = 1 if /^#\s*DONE/;
		next if /^\s*#/ || /^\s*$/;
		/^([^:]+):(.*)$/ or
			die "Error parsing '$tmpfile': line format unknown:\n$_";
		$1 eq $fieldnames[$fieldpos] or
			die "Error parsing '$tmpfile': expected field name '$fieldnames[$fieldpos]', found '$1'\n";
		$_ = $2;
		$tabcounter += s/\t/ /g;
		push @fields, $_;
		if (++$fieldpos >= $fieldcount) {
			print OUT join("\t", @fields), "\n";
			@fields = ();
			$fieldpos = 0;
		}
	}
	print STDERR "$tabcounter tab character",
		$tabcounter == 1 ? ' was' : 's were',
		" found in the data! Each tab was replaced with a space.\n"
		if $tabcounter;
	close OUT or die "Error closing '$filename.new' after writing: $!\n";
	close IN or die "Error closing '$tmpfile' after reading: $!\n";
	my ($mode, $uid, $gid) = (stat($filename))[2,4,5];
	chmod $mode, $newfile;
	chown $uid, $gid, $newfile if $> == 0;
	rename $newfile, $filename or
		die "Error renaming '$newfile' to '$filename': $!\n";
	unlink $tmpfile;
	if ($done) {
		print STDERR "Found 'DONE' command; skipping rest of files.\n";
		last;
	}
}

-------------------------------------

-= Jim =-

----------------------------------------------------------------
Jim's Linux-Operated Underground Bomb Shelter

Tagline for Wednesday, September 26, 2001 at 18:40 PM:
Birds are trapped by their feet, people by their tongues.

----------------------------------------------------------------
This Linux System has been up 49 hours

My web page: http://www.idk-enterprises.com
----------------------------------------------------------------