Integrate generated content

It is trivial to use Foswiki with topics stored as text files and it is straightforward to produce text files to be used as Foswiki topics, especially if the conversion is uni-directional or once-off.

Meta data - TOPICINFO

Foswiki assumes that every topic has a %META:TOPICINFO{...}% tag that specifies the revision info, who last edit the topic, etc. To see this metadata, append ?raw=debug to the URL, e.g. for this topic. You can get away without this, but the resulting topics look odd compared to regular topics (e.g. the topic date looks wrong).

It is really easy to generate this in perl. The date format is as produced by perl's time() in scalar context. I generate it like this:
  print $fh "\%META:TOPICINFO{author=\"GeneratedContent\" date=\"".time()."\" format=\"1.1\" version=\"1.1\"}\%\n";
Foswiki expects a username, so consider adding a user called GeneratedContent or something similar, so that it is obvious when content is computer-generated. And then create the corresponding user topic to explain this.

Revision history with RCS

You can also get a revision history on your generated topics if you are using an RCS-based store (either RcsWrap or RcsLite) and RCS is available to your scripts.

The RCS commands below are based on (default) values from my LocalSite.cfg, and assume a (debian/ubuntu) linux platform, although they should also be portable to Windows simply by changing the paths - YMMV.

The RCS file must be initialised before the first checkin:
/usr/bin/rcs -i -t-none -ko /path/to/foswiki/data/Web/NewTopic.txt,v

If the RCS file already exists (because the topic was checked in before), then the RCS file must be locked:
/usr/bin/rcs -l /path/to/foswiki/data/Web/NewTopic.txt

Then check in the file:
/usr/bin/ci -mPutACommentHereIfYouLike -t-none -u -wGeneratedContent /path/to/foswiki/data/Web/NewTopic.txt

If you are checking your generated topics into RCS, then the topic version in %META:TOPICINFO{...}% should be correct. Parse the output from RCS rlog /usr/bin/rlog -h /path/to/foswiki/data/Web/NewTopic.txt and extract the current version from the head: line.

Example code

Here is how all this might be done:

Here is a routine that writes a %META:TOPICINFO{...}% tag to a file, using the next version if the file was previously checked in:
sub write_next_version_meta_topicinfo
{
  my $fh = shift; # filehandle of temporary output file to write to
  my $filename = shift; # /path/to/foswiki/data/Web/NewTopic.txt
  
  my $version = "1.1";

  if (-e "$filename,v")
  {
    # file was previously checked in
    my $rlog_command = "/usr/bin/rlog -h $filename";
    my @rlog_output = `$rlog_command`;
    if ($? == -1)
    {
      die "Cannot run '$rlog_command': $!";
    }
    for (@rlog_output)
    {
      if (/^head: 1\.(\d+)/)
      {
        my $revision = $1;
        $revision++;
        $version = "1.$revision";
        last;
      }
    }
  }
  print $fh "\%META:TOPICINFO{author=\"GeneratedContent\" date=\"".time()."\" format=\"1.1\" version=\"$version\"}\%\n";
}

After generating a topic into a temporary file, you may want to compare it with the existing topic and only replace the existing topic if the content actually changed. The %META:TOPICINFO{...}% contains the date, so here is a routine for comparing two files but ignoring this tag:
sub did_content_change
{
  my ($old, $new) = @_; # filenames

  # Read in the content of the files
  local *OLD;
  open OLD, "<", $old or die "Cannot open '$old' to read: $!";
  my $old_content = '';
  $old_content .= $_ while (<OLD>);
  close OLD or die "Error closing '$old': $!";

  local *NEW;
  open NEW, "<", $new or die "Cannot open '$new' to read: $!";
  my $new_content = '';
  $new_content .= $_ while (<NEW>);
  close NEW or die "Error closing '$new': $!";

  # Remove topicinfo metadata, since that can change even when the content does not
  $old_content =~ s/^\%META:TOPICINFO{.*?}\%//;
  $new_content =~ s/^\%META:TOPICINFO{.*?}\%//;
  return ($old_content ne $new_content);
}

Here is a routine for checking in a topic. It initialises the RCS file if checking in for the first time:
sub checkin
{
  my $txt_file = shift;

  my $rcs_file = "$txt_file,v";
  if (not -e $rcs_file)
  {
    # Not previously checked in, so initialise the RCS file
    my $init_command = "/usr/bin/rcs -i -t-none -ko $rcs_file 2>&1";
    my $init_result = `$init_command`;
    if ($? == -1)
    {
      die "Cannot run '$init_command': $!";
    }
    my $return_code = $? >> 8;
    if ($return_code != 0)
    {
      die "$init_result\n'$init_command' returned $return_code";
    }
  }
  else
  {
    # Previously checked in, so lock before checkin
    my $lock_command = "/usr/bin/rcs -l $txt_file 2>&1";
    my $lock_result = `$lock_command`;
    if ($? == -1)
    {
      die "Cannot run '$lock_command': $!";
    }
    my $return_code = $? >> 8;
    if ($return_code != 0)
    {
      die "$lock_result\n'$lock_command' returned $return_code";
    }
  }

  my $ci_command = "/usr/bin/ci -mgenerated -t-none -u -wGeneratedContent $txt_file 2>&1";
  my $ci_result = `$ci_command`;
  if ($? == -1)
  {
    die "Cannot run '$ci_command': $!";
  }
  my $return_code = $? >> 8;
  if ($return_code != 0)
  {
    die "$ci_result\n'$ci_command' returned $return_code";
  }
}

-- MichaelTempest - 15 Apr 2010
Topic revision: r1 - 15 Apr 2010, MichaelTempest
The copyright of the content on this website is held by the contributing authors, except where stated elsewhere. See Copyright Statement. Creative Commons License    Legal Imprint    Privacy Policy