Current File : //usr/tmp/par-6c6566/cache-9ab9fe47781b3f807c8b3ee838b58c73318324f4/inc/lib/Config/Crontab.pm
#line 1 "Config/Crontab.pm"
############################################################
############################################################
##
## Scott Wiersdorf
## Created: Fri May  9 14:03:01 MDT 2003
## Updated: $Id: Crontab.pm,v 1.8 2014/01/09 04:50:04 scott Exp $
##
## Config::Crontab - a crontab(5) parser
##
## This file contains the following classes:
##
## - Config::Crontab - the top level crontab object
## - Config::Crontab::Block - crontab block (paragraph) handling
## - Config::Crontab::Event - "5 0 * * * /bin/command"
## - Config::Crontab::Env - "VAR=value"
## - Config::Crontab::Comment - "## a comment"
## - Config::Crontab::Base - base class from which all other
##     Config::Crontab classes inherit
## - Config::Crontab::Container - base class from which Crontab and
##     Block classes inherit
##
############################################################
############################################################

## to do: if -file = /etc/crontab, set system => 1
## to do: if adding a non-block to a $ct file, make a block for us automatically

## a crontab object is a list of Block objects (see below) This class
## (Config::Crontab) is for working with crontab files as a whole.
package Config::Crontab;
use strict;
use warnings;
use Carp;
use 5.006_001;

our @ISA = qw(Config::Crontab::Base Config::Crontab::Container);

## these two are for the 'write' method
use Fcntl;
use File::Temp qw(:POSIX);

our $VERSION = '1.40';

sub init {
    my $self = shift;
    my %args = @_;

    $self->file('');
    $self->mode('block');
    $self->squeeze(1);     ## only in block mode
    $self->strict(0);
    $self->blocks([]);
    $self->error('');
    $self->system(0);
    $self->owner('');
    $self->owner_re( '[^a-zA-Z0-9\._-]' );

    $self->file(     $args{'-file'})     if exists $args{'-file'};
    $self->mode(     $args{'-mode'})     if exists $args{'-mode'};
    $self->squeeze(  $args{'-squeeze'})  if exists $args{'-squeeze'};
    $self->strict(   $args{'-strict'})   if exists $args{'-strict'};
    $self->system(   $args{'-system'})   if exists $args{'-system'};
    $self->owner(    $args{'-owner'})    if exists $args{'-owner'};
    $self->owner_re( $args{'-owner_re'}) if exists $args{'-owner_re'};

    ## auto-parse if file is specified
    $self->read if $self->file;

    return 1;
}

sub read {
    my $self = shift;
    my %args = @_;

    $self->file(     $args{'-file'})     if exists $args{'-file'};
    $self->mode(     $args{'-mode'})     if exists $args{'-mode'};
    $self->squeeze(  $args{'-squeeze'})  if exists $args{'-squeeze'};
    $self->strict(   $args{'-strict'})   if exists $args{'-strict'};
    $self->system(   $args{'-system'})   if exists $args{'-system'};
    $self->owner(    $args{'-owner'})    if exists $args{'-owner'};
    $self->owner_re( $args{'-owner_re'}) if exists $args{'-owner_re'};

    ## set default system crontab
    if( $self->system && ! $self->file ) {
	$self->file('/etc/crontab');
    }

    ## parse the file accordingly
    if( $self->file ) {
	open FILE, $self->file
	  or do {
	      $self->error($!);
	      if( $self->strict ) {
		  croak "Could not open " . $self->file . ": " . $self->error . "\n";
	      }
	      return;
	  }
    }

    else {
	my $crontab_cmd = "crontab -l 2>/dev/null|";
	if( $self->owner ) {
            if( $^O eq 'SunOS' ) {
                $crontab_cmd = "crontab -l " . $self->owner . " 2>/dev/null|";
            }
            else {
                $crontab_cmd = "crontab -u " . $self->owner . " -l 2>/dev/null|";
            }
	}
	open FILE, $crontab_cmd
	  or do {
	      $self->error($!);
	      if( $self->strict ) {
		  croak "Could not open pipe from crontab: " . $self->error . "\n";
	      }
	      return;
	  }
    }

    ## reset internal block list and errors
    $self->blocks([]);
    $self->error('');

  PARSE: {
	local $/;

	## each line is a block
	if( $self->mode eq 'line' ) {
	    $/ = "\n";
	}

	## whole file is a block
	elsif( $self->mode eq 'file' ) {
	    $/ = undef;
	}

	## each paragraph (\n\n+) is a block
	else {
	    $/ = ( $self->squeeze ? '' : "\n\n" );
	}

	local $_;
	while( <FILE> ) {
	    chomp;
	    $self->last( new Config::Crontab::Block( -system => $self->system,
						     -data   => $_ ) );
	}
    }
    close FILE;
}

## this is needed for Config::Crontab::Container class methods
*elements = \&blocks;

sub blocks {
    my $self   = shift;
    my $blocks = shift;

    if( ref($blocks) eq 'ARRAY' ) {
	$self->{'_blocks'} = $blocks;
    }

    ## return only blocks (in case of accidental non-block pushing)
    return grep { UNIVERSAL::isa($_, 'Config::Crontab::Block') }
      grep { ref($_) } @{$self->{'_blocks'}};
}

sub select {
    my $self = shift;
    my @results = ();
    push @results, $_->select(@_) for $self->blocks;
    @results;
}

sub select_blocks {
    my $self = shift;
    my %crit = @_;
    my @results = ();

    unless( keys %crit ) {
	@results = $self->blocks;
    }

    while( my($key, $value) = each %crit ) {
	$key =~ s/^\-//;  ## strip leading hyphen

	if( $key eq 'index' ) {
	    unless( defined $value ) {
		if( $self->strict ) {
		    carp "index value undefined\n";
		}
		next;
	    }

	    ## a list ref of integers
	    if( ref($value) eq 'ARRAY' ) {
		push @results, @{$self->{'_blocks'}}[@$value];
	    }

	    ## an integer
	    elsif( $value =~ /^\d+$/ ) {
		push @results, @{$self->{'_blocks'}}[$value];
	    }

	    else {
		if( $self->strict ) {
		    carp "index value not recognized\n";
		}
	    }
	}

	else {
	    if( $self->strict ) {
		carp "Unknown block selection type '$key'\n";
	    }
	}
    }
    @results;
}

sub block {
    my $self = shift;
    my $obj  = shift
      or return;
    my $rblock;

  BLOCK: for my $block ( $self->blocks ) {
	for my $line ( $block->lines ) {
	    if( $line == $obj ) {
		$rblock = $block;
		last BLOCK;
	    }
	}
    }

    return $rblock;
}

sub remove {
    my $self = shift;
    my @objs = @_;

    if( @objs ) {
	for my $obj ( @objs ) {
	    next unless defined $obj && ref($obj);

	    unless( UNIVERSAL::isa($obj, 'Config::Crontab::Block') ) {
		if( $self->block($obj) ) {
		    $self->block($obj)->remove($obj);
		}

		## a non-block object in our crontab file!
		else {
		    undef $obj;
		}
		next;
	    }

	    for my $block ( @{$self->{'_blocks'}} ) {
		next unless defined $block && ref($block);
		if( $block == $obj ) {
		    undef $block;
		}
	    }
	}

	## strip out undefined objects
	$self->blocks([ grep { defined } $self->elements ]);
    }

    return $self->elements;
}

## same as 'crontab -u user file'
sub write {
    my $self = shift;
    my $file = shift;

    ## see if a file is present, allow for ''
    if( defined $file ) {
	$self->file($file);
    }

    if( $self->file ) {
	open CT, ">" . $self->file
	  or croak "Could not open " . $self->file . ": $!\n";
	print CT $self->dump;
	close CT;
    }

    ## use a temporary filename
    else {
	my $tmpfile;
	do { $tmpfile = tmpnam() }
	  until sysopen(CT, $tmpfile, O_RDWR|O_CREAT|O_EXCL);
	print CT $self->dump;
	close CT;

	my $crontab;
	if( my $owner = $self->owner ) {
	    $crontab = `crontab -u $owner $tmpfile 2>&1`;
	}
	else {
	    $crontab = `crontab $tmpfile 2>&1`;
	}
	chomp $crontab;
	unlink $tmpfile;

	if( $crontab || $? ) {
	    $self->error($crontab);
	    if( $self->strict ) {
		carp "Error writing crontab (crontab exited with status " . 
		  ($? >> 8) . "): " . $self->error;
	    }
	    return;
	}
    }

    return 1;
}

sub remove_tab {
    my $self = shift;
    my $file = shift;

    ## see if a file is present, allow for ''
    if( defined $file ) {
	$self->file($file);
    }

    if( $self->file ) {
	unlink $self->file;
    }

    else {
	my $output = '';
	if( my $owner = $self->owner ) {
	    $output = `crontab -u $owner -r 2>&1`;
	}
	else {
	    $output = `yes | crontab -r 2>&1`;
	}
	chomp $output;

	## FIXME: what if no $output, but only '$?' ?
	if( $output || $? ) {
	    $self->error($output);
	    if( $self->strict ) {
		carp "Error removing crontab (crontab exited with status " .
		  ($? >> 8) ."): " . $self->error;
	    }
	    return;
	}
    }

    return 1;
}

sub dump {
    my $self = shift;
    my $ret  = '';

    for my $block ( $self->blocks ) {
	$ret .= "\n" if $ret && $block->dump;  ## empty blocks should not invoke a newline
	$ret .= $block->dump;
    }

    return $ret;
}

sub owner {
    my $self = shift;
    if( @_ ) {
	my $owner = shift;
	if( $owner ) {
	    unless( defined( getpwnam($owner) ) ) {
		$self->error("Unknown user: $owner");
		if( $self->strict ) {
		    croak $self->error;
		}
		return;
	    }

	    if( $owner =~ $self->owner_re ) {
		$self->error("Illegal username: $owner");
		if( $self->strict ) {
		    croak $self->error;
		}
		return;
	    }
	}
	$self->{_owner} = $owner;
    }
    return ( defined $self->{_owner} ? $self->{_owner} : '' );
}

sub owner_re {
    my $self = shift;
    if( @_ ) {
	my $re = shift;
	$self->{_owner_re} = qr($re);
    }
    return ( defined $self->{_owner_re} ? $self->{_owner_re} : qr() );
}

############################################################
############################################################

#line 991

## FIXME: need to say something about squeeze here, but squeeze(0)
## doesn't seem to work correctly (i.e., it still squeezes the file)

#line 1302

############################################################
############################################################

package Config::Crontab::Block;
use strict;
use warnings;
use Carp;

our @ISA = qw(Config::Crontab::Base Config::Crontab::Container);

sub init {
    my $self = shift;
    my %args = @_;

    $self->lines([]);  ## initialize
    $self->strict(0);
    $self->system(0);

    $self->lines($args{'-lines'})   if defined $args{'-lines'};
    $self->strict($args{'-strict'}) if defined $args{'-strict'};
    $self->system($args{'-system'}) if defined $args{'-system'};

    my $rv = 1;
    if( defined $args{'-data'} ) {
	$self->lines([]);
	$rv = $self->data($args{'-data'});
    }

    return ( defined $rv ? 1 : undef );
}

sub data {
    my $self  = shift;
    my $data  = shift;
    my @lines = ();

    if( defined $data ) {
	if( ref($data) eq 'ARRAY' ) {
	    @lines = @$data;
	}

	elsif( $data ) {
	    @lines = split(/\n/, $data);
	}

	elsif( $data eq '' ) {
	    @lines = ($data);
	}

	else {
	    @lines = ();
	}

	for my $line ( @lines ) {
	    my $obj;
	    if( $obj = new Config::Crontab::Event(-data => $line, 
						  -system => $self->system) ) {
	    }

	    elsif( $obj = new Config::Crontab::Env(-data => $line) ) {
	    }

	    elsif( $obj = new Config::Crontab::Comment(-data => $line) ) {
	    }

	    else {
		if( $self->strict ) {
		    carp "Skipping illegal line in block: $line\n";
		}
		next;
	    }

	    $self->last($obj);
	}
    }

    my $ret = '';
    for my $obj ( $self->lines ) {
	$ret .= "\n" if $ret; ## empty objects are empty lines, so we do a newline always
	$ret .= $obj->dump;
    }
    $ret .= "\n" if $ret;

    return $ret;
}

## this is needed for Config::Crontab::Container class methods
*elements = \&lines;

sub lines {
    my $self = shift;
    my $objs = shift;

    if( ref($objs) eq 'ARRAY' ) {
	$self->{'_lines'} = $objs;
    }

    return @{$self->{'_lines'}};
}

sub select {
    my $self = shift;
    my %crit = @_;

    ## return all lines unless criteria specified
    return $self->lines
      unless scalar keys %crit;

    my @results = ();
  LINE: for my $line ( $self->lines ) {
	my $j = scalar keys %crit;  ## reset keys
	while( my($key,$value) = each %crit ) {
	    $key =~ s/^\-//;  ## strip leading hyphen

	    ## FIXME: would be nice to have a negated 'type' option or a re

	    ## special case for 'type'
	    if( $key eq 'type' ) {
		if( $value eq 'event' ) {
		    next LINE unless UNIVERSAL::isa($line, 'Config::Crontab::Event');
		}
		elsif( $value eq 'env' ) {
		    next LINE unless UNIVERSAL::isa($line, 'Config::Crontab::Env');
		}
		elsif( $value eq 'comment' ) {
		    next LINE unless UNIVERSAL::isa($line, 'Config::Crontab::Comment');
		}
		else {
		    if( $self->strict ) {
			carp "Unknown object type '$value'\n";
		    }
		    next LINE;
		}
	    }

	    ## not special 'type' case
	    else {
		no strict 'refs';
		if( $key =~ /^(.+)_re$/ ) {
		    next LINE unless $line->$1() =~ qr($value);
		}
		elsif( $key =~ /^(.+)_nre$/ ) {
		    next LINE unless $line->$1() !~ qr($value);
		}
		else {
		    next LINE unless $line->$key() eq $value;
		}
	    }

	}
	push @results, $line;
    }

    return @results;
}

sub remove {
    my $self = shift;
    my @objs = @_;

    if( @objs ) {
	for my $obj ( @objs ) {
	    next unless defined $obj && ref($obj);
	    for my $line ( @{$self->{'_lines'}} ) {
		next unless defined $line && ref($line);
		if( $line == $obj ) {
		    undef $line;
		}
	    }
	}

	## strip out undefined objects
	$self->elements([ grep { defined } $self->elements ]);
    }

    return $self->elements;
}

sub active {
    my $self   = shift;

    return 1 unless @_;

    my $active = shift;
    local $_;
    $_->active($active) for $self->select(-type => 'env');
    $_->active($active) for $self->select(-type => 'event');

    return $active;
}

sub nolog {
    my $self = shift;
    return 1 unless @_;

    my $nolog = shift;
    local $_;
    $_->nolog($nolog) for $self->select(-type => 'event');

    return $nolog;
}

############################################################
############################################################

#line 1876

############################################################
############################################################

package Config::Crontab::Event;
use strict;
use warnings;
use Carp;

our @ISA = qw(Config::Crontab::Base);

use constant RE_DT        => '(?:\d+|\*)(?:[-,\/]\d+)*';
use constant RE_DTLIST    => RE_DT . '(?:,' . RE_DT . ')*';
use constant RE_DM        => '\w{3}(?:,\w{3})*';
use constant RE_DTELEM    => '(?:\*|' . RE_DTLIST . ')';
use constant RE_DTMOY     => '(?:\*|' . RE_DTLIST . '|' . RE_DM . ')';
use constant RE_DTDOW     => RE_DTMOY;
use constant RE_ACTIVE    => '^\s*(\#*)\s*';
use constant RE_NOLOG     => '(-?)';  ## SuSE-specific extension
use constant RE_SPECIAL   => '(\@(?:reboot|midnight|(?:year|annual|month|week|dai|hour)ly))';
use constant RE_DATETIME  => '(' . RE_DTELEM . ')' .
                          '\s+(' . RE_DTELEM . ')' .
                          '\s+(' . RE_DTELEM . ')' .
                          '\s+(' . RE_DTMOY  . ')' .
                          '\s+(' . RE_DTDOW  . ')';
use constant RE_USER      => '\s+(\S+)';
use constant RE_COMMAND   => '\s+(.+?)\s*$';
use constant SPECIAL      => RE_ACTIVE . RE_NOLOG . RE_SPECIAL  . RE_COMMAND;
use constant DATETIME     => RE_ACTIVE . RE_NOLOG . RE_DATETIME . RE_COMMAND;
use constant SYS_SPECIAL  => RE_ACTIVE . RE_NOLOG . RE_SPECIAL  . RE_USER . RE_COMMAND;
use constant SYS_DATETIME => RE_ACTIVE . RE_NOLOG . RE_DATETIME . RE_USER . RE_COMMAND;

sub init {
    my $self = shift;
    my %args = @_;
    my $rv = 1;

    ## set defaults
    $self->active(1);
    $self->nolog(0);
    $self->system(0);

    $self->special(undef);
    $self->minute('*');
    $self->hour('*');
    $self->dom('*');
    $self->month('*');
    $self->dow('*');
    $self->user('');

    ## get arguments and set new defaults
    $self->system($args{'-system'})     if defined $args{'-system'};  ## -system arg overrides implicits
    unless( $args{'-data'} ) {
	$self->minute($args{'-minute'})     if defined $args{'-minute'};
	$self->hour($args{'-hour'})         if defined $args{'-hour'};
	$self->dom($args{'-dom'})           if defined $args{'-dom'};
	$self->month($args{'-month'})       if defined $args{'-month'};
	$self->dow($args{'-dow'})           if defined $args{'-dow'};

	$self->user($args{'-user'})         if defined $args{'-user'};
	$self->system(1)                    if defined $args{'-user'};

	$self->special($args{'-special'})   if defined $args{'-special'};
	$self->datetime($args{'-datetime'}) if defined $args{'-datetime'};
	$self->command($args{'-command'})   if $args{'-command'};
	$self->active($args{'-active'})     if defined $args{'-active'};
        $self->nolog($args{'-nolog'})       if defined $args{'-nolog'};
    }
    $rv = $self->data($args{'-data'})   if defined $args{'-data'};

    return ( defined $rv ? 1 : undef );
}

## returns the crontab line w/o '(in)?active' pound sign (#)
sub data {
    my $self = shift;
    my $data = '';

    if( @_ ) {
	$data = shift;
	$data = '' unless $data;  ## normalize false values

	my @matches = ();

	## system (user) syntax
	if( $self->system ) {
	    if( @matches = $data =~ SYS_SPECIAL or
		@matches = $data =~ SYS_DATETIME ) {
		my $active = shift @matches;
                my $nolog = shift @matches;
		$self->active( ($active ? 0 : 1) );
                $self->nolog( ($nolog ? 1 : 0) );
		$self->command( pop @matches );
		$self->user( pop @matches );
		$self->datetime( \@matches );
	    }

	    ## not a good -data value
	    else {
		return;
	    }
	}

	## non-system (regular user crontab style) syntax
	else {
	    ## is a command
	    if( @matches = $data =~ SPECIAL or
		@matches = $data =~ DATETIME ) {
		my $active = shift @matches;
                my $nolog = shift @matches;
		$self->active( ($active ? 0 : 1) );
                $self->nolog( ($nolog ? 1 : 0) );
		$self->command( pop @matches );
		$self->user('');
		$self->datetime( \@matches );
	    }

	    ## not a good -data value
	    else {
		return;
	    }
	}
    }

    my $fmt = "%s";
    $fmt .= ( $self->command
	      ? ( $self->system 
		  ? ($self->special ? "\t\t\t\t\t%s" : "\t%s") . ( $self->user ? "\t%s" : '' )
		  : " %s" )
	      : '' );

    return sprintf($fmt, ( $self->command
			   ? ( $self->datetime, ($self->system && $self->user ? $self->user : ()))
			   : () ), $self->command )
}

sub datetime {
    my $self = shift;
    my $data = shift;
    my @matches = ();

    if( $data ) {
	## an array reference: when called from 'data' method
	if( ref($data) eq 'ARRAY' ) {
	    @matches = @$data;

	    ## likely special datetime format (e.g., @reboot, etc.)
	    if( scalar(@matches) == 1 ) {
		$self->special( @matches );
		$self->minute(  '*' );
		$self->hour(    '*' );
		$self->dom(     '*' );
		$self->month(   '*' );
		$self->dow(     '*' );
	    }

	    ## likely standard datetime format (e.g., '6 1 * * Fri', etc.)
	    elsif( scalar @matches ) {
		$self->special( undef);
		$self->minute(  shift @matches );
		$self->hour(    shift @matches );
		$self->dom(     shift @matches );
		$self->month(   shift @matches );
		$self->dow(     shift @matches );
	    }
	    else {
		## empty array ref
		carp "No data in array constructor\n";
		return;
	    }
	}

	## not a reference: when called as a method directly (e.g., 'init' method)
	else {
	    ## special datetime format (@reboot, @daily, etc.)
	    if( @matches = $data =~ RE_SPECIAL ) {
		$self->special( @matches );
		$self->minute(  '*' );
		$self->hour(    '*' );
		$self->dom(     '*' );
		$self->month(   '*' );
		$self->dow(     '*' );
	    }

	    ## standard datetime format ("0 5 * * Fri", etc.)
	    elsif( @matches = $data =~ RE_DATETIME ) {
		$self->special( undef);
		$self->minute(  shift @matches );
		$self->hour(    shift @matches );
		$self->dom(     shift @matches );
		$self->month(   shift @matches );
		$self->dow(     shift @matches );
	    }

	    ## not a valid datetime format
	    else {
		## some bad data
		carp "Bad datetime spec: $data\n";
		return;
	    }
	}
    }

    if( $self->special ) {
	return $self->special;
    }

    my $fmt = ( $self->system 
		? "%s\t%s\t%s\t%s\t%s"
		: "%s %s %s %s %s" );

    return sprintf( $fmt, $self->minute, $self->hour, $self->dom, $self->month, $self->dow);
}

## this is duplicated in AUTOLOAD, but we need to set system also
sub user {
    my $self = shift;
    if( @_ ) {  ## setting a value, set system too
	$self->system($_[0] ? 1 : 0);
	$self->{_user} = shift;
    }
    return ( defined $self->{_user} ? $self->{_user} : '' );
}

sub dump {
    my $self = shift;
    my $rv   = '';

    $rv .= ( $self->active
             ? '' 
             : '#' );
    $rv .= ( $self->nolog
             ? '-'
             : '' );
    $rv .= $self->data;
    return $rv;
}

############################################################
############################################################

#line 2580

############################################################
############################################################

## env objects are a few lines of comments followed by a variable assignment
package Config::Crontab::Env;
use strict;
use warnings;

our @ISA = qw(Config::Crontab::Base);

use constant RE_ACTIVE   => '^\s*(\#*)\s*';
use constant RE_VAR      => q!(["']?[^=]+?['"]?)\s*=\s*(.*)$!;
use constant RE_VARIABLE => RE_ACTIVE . RE_VAR;

sub init {
    my $self = shift;
    my %args = @_;

    $self->active(1);

    $self->active($args{'-active'})  if defined $args{'-active'};
    $self->name($args{'-name'})      if $args{'-name'};
    $self->value($args{'-value'})    if defined $args{'-value'};

    my $rv = 1;
    if( defined $args{'-data'} ) {
	$rv = $self->data($args{'-data'});
    }

    return ( defined $rv ? 1 : undef );
}

sub data {
    my $self = shift;
    my $data = '';

    if( @_ ) {
	$data = shift;
	$data = '' unless $data;  ## normalize false values

	my @matches = ();
	if( @matches = $data =~ RE_VARIABLE ) {
	    my $active = shift @matches;
	    $self->active( ($active ? 0 : 1) );
	    $self->name(   shift @matches );
	    $self->value(  shift @matches );
	}

	## not a valid Env object
	else {
	    return;
	}
    }

    return ( $self->name 
	     ? $self->name . '=' . $self->value 
	     : $self->name );
}

sub inactive {
    my $self = shift;
    return ( $self->active ? 0 : 1 );
}

sub dump {
    my $self = shift;
    my $ret  = '';
    
    if( $self->name ) {
	$ret .= ( $self->active
		  ? ''
		  : '#' );
    }

    $ret .= $self->data;
}

############################################################
############################################################

#line 2827

############################################################
############################################################

## comment objects are empty lines (lines containing only whitespace)
## or lines beginning with # and which do not match an event or
## environment pattern
package Config::Crontab::Comment;
use strict;
use warnings;

our @ISA = qw(Config::Crontab::Base);

sub init {
    my $self = shift;
    my %args = ( @_ == 1 ? ('-data' => @_) : @_ );
    my $data = '';

    if( exists $args{'-data'} ) {
	$data = $args{'-data'};
    }

    ## no '-data' tag, just the data
    elsif( @_ ) {
	$data = shift;
    }

    chomp $data if $data;

    my $rv = $self->data($data);

    return ( defined $rv ? 1 : undef );
}

sub data {
    my $self = shift;
    my $data = '';

    if( @_ ) {
	$data = shift;
	$data = '' unless $data;  ## normalize false values

	unless( $data =~ /^\s*$/ || $data =~ /^\s*\#/ ) {
	    return;
	}

	$self->{'_data'} = $data;
    }

    return ( defined $self->{'_data'} ? $self->{'_data'} : $data );
}

############################################################
############################################################

#line 2961

############################################################
############################################################

## a virtual base class for top-level container classes
package Config::Crontab::Container;
use strict;
use warnings;
use Carp;

sub up {
    my $self = shift;
    my $targ = shift;

    return unless ref($targ);

    my @objs = $self->elements;

    my $found;
    for my $i ( 0..$#objs ) {
	if( $objs[$i] == $targ ) {
	    ($objs[$i], $objs[$i-1]) = ($objs[$i-1], $objs[$i])  ## swap...
	      unless $i == 0;                                    ## unless already first
	    $found = 1;
	    last;
	}
    }

    unshift @objs, $targ unless $found;
    $self->elements( \@objs );
}

sub down {
    my $self = shift;
    my $targ = shift;

    return unless ref($targ);

    my @objs = $self->elements;

    my $found;
    for my $i ( 0..$#objs ) {
	if( $objs[$i] == $targ ) {
	    ($objs[$i], $objs[$i+1]) = ($objs[$i+1], $objs[$i])  ## swap...
	      unless $i == $#objs;                               ## unless already last
	    $found = 1;
	    last;
	}
    }

    push @objs, $targ unless $found;
    $self->elements( \@objs );
}

sub first {
    my $self = shift;
    my @targ = grep { ref($_) } @_;

    $self->remove(@targ);
    $self->elements( [@targ, $self->elements] );
}

sub last {
    my $self = shift;
    my @targ = grep { ref($_) } @_;

    $self->remove(@targ);
    $self->elements( [$self->elements, @targ] );
}

sub before {
    my $self = shift;
    my $ref  = shift;
    my @targ = @_;

    $self->remove(@targ);

    my @objs  = ();
    my $found = 0;
    for my $obj ( $self->elements ) {
	if( ! $found && $ref && ($obj == $ref) ) {
	    push @objs, @targ;
	    $found = 1;
	}
	push @objs, $obj;
    }

    unshift @objs, @targ unless $found;

    $self->elements(\@objs);
}

sub after {
    my $self = shift;
    my $ref  = shift;
    my @targ = @_;

    $self->remove(@targ);

    my @objs  = ();
    my $found = 0;
    for my $obj ( $self->elements ) {
	push @objs, $obj;
	if( ! $found && ($obj == $ref) ) {
	    push @objs, @targ;
	    $found = 1;
	}
    }

    push @objs, @targ unless $found;

    $self->elements(\@objs);
}

sub replace {
    my $self = shift;
    my $old  = shift;
    my $new  = shift;

    return unless ref($old) && ref($new);

    my @objs = $self->elements;
    my $found;
    for my $i ( 0..$#objs ) {
	if( $objs[$i] == $old ) {
	    $objs[$i] = $new;
	    $found = 1;
	    last;
	}
    }

    $self->elements( \@objs );
    return ( $found ? $old : undef );
}

############################################################
############################################################

## the virtual base class of all Config::Crontab classes
package Config::Crontab::Base;
use strict;
use warnings;
use Carp;

our $AUTOLOAD;

sub new {
    my $self  = { };
    my $proto = shift;
    my $class = ref($proto) || $proto;

    bless $self, $class;

    my $rv = $self->init(@_);
    $self->flag('');

    return ( $rv ? $self : undef );
}

## boolean: if returns false, 'new' will return undef, $self otherwise
sub init {
    my $self = shift;
    my %args = @_;

    return 1;
}

sub dump {
    my $self = shift;
    return $self->data;  ## this will AUTOLOAD if not present
}

sub flag {
    my $self = shift;
    $self->{'_flag'} = shift if @_;
    return $self->{'_flag'};
}

sub AUTOLOAD {
    my $self = shift or return;

    my $sub = $AUTOLOAD;
    $sub =~ s/^.*:://;
    return if $sub eq 'DESTROY';

    my $foni;

    ## new accessor
    if( $sub =~ /^(\w+)$/ ) {
	my $subname = $1;
	$foni = sub {
	    my $self = shift;
	    $self->{"_$subname"} = shift if @_;
	    return ( defined $self->{"_$subname"} ? $self->{"_$subname"} : '' );
	};
    }

    else {
	croak "Undefined subroutine '$sub'";
    }

    ## do magic
  SYMBOLS: {
	no strict 'refs';
	*$AUTOLOAD = $foni;
    }
    unshift @_, $self;  ## put me back on call stack
    goto &$AUTOLOAD;    ## jump to me
}

1;
__END__

############################################################
############################################################

#line 3314