[BACK]Return to Todo.pm CVS log [TXT][DIR] Up to [local] / todotxt / Text-Todo / lib / Text

Annotation of todotxt/Text-Todo/lib/Text/Todo.pm, Revision 1.18

1.1       andrew      1: package Text::Todo;
                      2:
1.18    ! andrew      3: # $AFresh1: Todo.pm,v 1.17 2010/01/12 20:09:02 andrew Exp $
1.1       andrew      4:
                      5: use warnings;
                      6: use strict;
                      7: use Carp;
                      8:
1.18    ! andrew      9: use Class::Std;
1.2       andrew     10: use Text::Todo::Entry;
1.5       andrew     11: use File::Spec;
                     12:
1.1       andrew     13: use version; our $VERSION = qv('0.0.1');
                     14:
1.2       andrew     15: {
1.18    ! andrew     16:     my ( %path_of, %list_of, %loaded_of, ) : ATTR;
1.2       andrew     17:
1.18    ! andrew     18:     sub BUILD {
        !            19:         my ( $self, $ident, $options ) = @_;
1.2       andrew     20:
1.5       andrew     21:         $path_of{$ident} = {
1.14      andrew     22:             todo_dir  => undef,
                     23:             todo_file => 'todo.txt',
                     24:             done_file => undef,
1.5       andrew     25:         };
                     26:
                     27:         if ($options) {
                     28:             if ( ref $options eq 'HASH' ) {
                     29:                 foreach my $opt ( keys %{$options} ) {
                     30:                     if ( exists $path_of{$ident}{$opt} ) {
                     31:                         $self->_path_to( $opt, $options->{$opt} );
                     32:                     }
                     33:                     else {
1.14      andrew     34:
1.13      andrew     35:                         #carp "Invalid option [$opt]";
1.5       andrew     36:                     }
                     37:                 }
                     38:             }
                     39:             else {
                     40:                 if ( -d $options ) {
                     41:                     $self->_path_to( 'todo_dir', $options );
                     42:                 }
                     43:                 elsif ( $options =~ /\.txt$/ixms ) {
                     44:                     $self->_path_to( 'todo_file', $options );
                     45:                 }
                     46:                 else {
                     47:                     carp "Unknown options [$options]";
                     48:                 }
                     49:             }
                     50:         }
                     51:
                     52:         my $file = $self->_path_to('todo_file');
                     53:         if ( defined $file && -e $file ) {
                     54:             $self->load();
                     55:         }
1.2       andrew     56:
                     57:         return $self;
                     58:     }
                     59:
1.5       andrew     60:     sub _path_to {
                     61:         my ( $self, $type, $path ) = @_;
                     62:         my $ident = ident($self);
                     63:
                     64:         if ( $type eq 'todo_dir' ) {
                     65:             if ($path) {
                     66:                 $path_of{$ident}{$type} = $path;
                     67:             }
                     68:             return $path_of{$ident}{$type};
                     69:         }
                     70:
                     71:         if ($path) {
                     72:             my ( $volume, $directories, $file )
                     73:                 = File::Spec->splitpath($path);
                     74:             $path_of{$ident}{$type} = $file;
                     75:
                     76:             if ($volume) {
                     77:                 $directories = File::Spec->catdir( $volume, $directories );
                     78:             }
                     79:
                     80:             # XXX Should we save complete paths to each file, mebbe only if
                     81:             # the dirs are different?
                     82:             if ($directories) {
                     83:                 $path_of{$ident}{todo_dir} = $directories;
                     84:             }
                     85:         }
                     86:
                     87:         if ( $type =~ /(todo|done|report)_file/xms ) {
                     88:             if ( my ( $pre, $post )
                     89:                 = $path_of{$ident}{$type} =~ /^(.*)$1(.*)\.txt$/ixms )
                     90:             {
                     91:                 foreach my $f qw( todo done report ) {
                     92:                     if ( !defined $path_of{$ident}{ $f . '_file' } ) {
                     93:                         $path_of{$ident}{ $f . '_file' }
                     94:                             = $pre . $f . $post . '.txt';
                     95:                     }
                     96:                 }
                     97:             }
                     98:         }
                     99:
                    100:         if ( defined $path_of{$ident}{todo_dir} ) {
                    101:             return File::Spec->catfile( $path_of{$ident}{todo_dir},
                    102:                 $path_of{$ident}{$type} );
                    103:         }
                    104:
                    105:         return;
                    106:     }
                    107:
1.3       andrew    108:     sub file {
1.2       andrew    109:         my ( $self, $file ) = @_;
                    110:         my $ident = ident($self);
                    111:
1.5       andrew    112:         if ( defined $file && exists $path_of{$ident}{$file} ) {
                    113:             $file = $self->_path_to($file);
                    114:         }
                    115:         else {
                    116:             $file = $self->_path_to( 'todo_file', $file );
1.2       andrew    117:         }
                    118:
1.5       andrew    119:         return $file;
1.3       andrew    120:     }
                    121:
                    122:     sub load {
                    123:         my ( $self, $file ) = @_;
                    124:         my $ident = ident($self);
                    125:
1.8       andrew    126:         $loaded_of{$ident} = undef;
                    127:
1.9       andrew    128:         $file = $self->file($file);
                    129:
1.8       andrew    130:         if ( $list_of{$ident} = $self->listfile($file) ) {
                    131:             $loaded_of{$ident} = $file;
                    132:             return 1;
                    133:         }
                    134:
                    135:         return;
                    136:     }
                    137:
                    138:     sub listfile {
                    139:         my ( $self, $file ) = @_;
                    140:
1.5       andrew    141:         $file = $self->file($file);
                    142:
                    143:         if ( !defined $file ) {
1.8       andrew    144:             carp q{file can't be found};
                    145:             return;
1.5       andrew    146:         }
                    147:
                    148:         if ( !-e $file ) {
1.8       andrew    149:             carp "file [$file] does not exist";
1.5       andrew    150:             return;
                    151:         }
1.2       andrew    152:
                    153:         my @list;
                    154:         open my $fh, '<', $file or croak "Couldn't open [$file]: $!";
                    155:         while (<$fh>) {
                    156:             s/\r?\n$//xms;
1.18    ! andrew    157:             push @list, Text::Todo::Entry->new({ text => $_ });
1.2       andrew    158:         }
                    159:         close $fh or croak "Couldn't close [$file]: $!";
                    160:
1.8       andrew    161:         return wantarray ? @list : \@list;
1.2       andrew    162:     }
                    163:
                    164:     sub save {
                    165:         my ( $self, $file ) = @_;
                    166:         my $ident = ident($self);
                    167:
1.5       andrew    168:         $file = $self->file($file);
                    169:         if ( !defined $file ) {
1.6       andrew    170:             croak q{todo file can't be found};
1.5       andrew    171:         }
1.2       andrew    172:
                    173:         open my $fh, '>', $file or croak "Couldn't open [$file]: $!";
                    174:         foreach my $e ( @{ $list_of{$ident} } ) {
1.3       andrew    175:             print {$fh} $e->text . "\n"
                    176:                 or croak "Couldn't print to [$file]: $!";
1.2       andrew    177:         }
                    178:         close $fh or croak "Couldn't close [$file]: $!";
                    179:
1.9       andrew    180:         $loaded_of{$ident} = $file;
                    181:
1.2       andrew    182:         return 1;
                    183:     }
                    184:
                    185:     sub list {
1.3       andrew    186:         my ($self) = @_;
1.2       andrew    187:         my $ident = ident($self);
1.6       andrew    188:
1.2       andrew    189:         return if !$list_of{$ident};
1.6       andrew    190:         return wantarray ? @{ $list_of{$ident} } : $list_of{$ident};
1.5       andrew    191:     }
                    192:
                    193:     sub listpri {
1.14      andrew    194:         my ( $self, $pri ) = @_;
1.5       andrew    195:
1.14      andrew    196:         my @list;
                    197:         if ($pri) {
                    198:             $pri = uc $pri;
                    199:             if ( $pri !~ /^[A-Z]$/xms ) {
1.17      andrew    200:                 croak 'PRIORITY must a single letter from A to Z.';
1.14      andrew    201:             }
                    202:             @list = grep { defined $_->priority && $_->priority eq $pri }
                    203:                 $self->list;
                    204:         }
                    205:         else {
                    206:             @list = grep { $_->priority } $self->list;
                    207:         }
1.5       andrew    208:
                    209:         return wantarray ? @list : \@list;
1.2       andrew    210:     }
1.1       andrew    211:
1.3       andrew    212:     sub add {
                    213:         my ( $self, $entry ) = @_;
                    214:         my $ident = ident($self);
                    215:
1.5       andrew    216:         if ( !ref $entry ) {
1.18    ! andrew    217:             $entry = Text::Todo::Entry->new({ text => $entry });
1.5       andrew    218:         }
                    219:         elsif ( ref $entry ne 'Text::Todo::Entry' ) {
                    220:             croak(
                    221:                 'entry is a ' . ref($entry) . ' not a Text::Todo::Entry!' );
                    222:         }
                    223:
                    224:         push @{ $list_of{$ident} }, $entry;
                    225:
                    226:         return $entry;
                    227:     }
                    228:
1.6       andrew    229:     sub del {
1.5       andrew    230:         my ( $self, $src ) = @_;
                    231:         my $ident = ident($self);
                    232:
1.6       andrew    233:         my $id = $self->_find_entry_id($src);
1.5       andrew    234:
                    235:         my @list = $self->list;
1.6       andrew    236:         my $entry = splice @list, $id, 1;
1.5       andrew    237:         $list_of{$ident} = \@list;
                    238:
                    239:         return $entry;
                    240:     }
                    241:
                    242:     sub move {
                    243:         my ( $self, $entry, $dst ) = @_;
                    244:         my $ident = ident($self);
                    245:
                    246:         my $src  = $self->_find_entry_id($entry);
                    247:         my @list = $self->list;
                    248:
1.6       andrew    249:         splice @list, $dst, 0, splice @list, $src, 1;
1.5       andrew    250:
                    251:         $list_of{$ident} = \@list;
                    252:
                    253:         return 1;
                    254:     }
                    255:
1.6       andrew    256:     sub listproj {
1.17      andrew    257:         my ($self) = @_;
1.14      andrew    258:         return $self->listtag('project');
                    259:     }
                    260:
                    261:     sub listcon {
1.17      andrew    262:         my ($self) = @_;
1.14      andrew    263:         return $self->listtag('context');
                    264:     }
                    265:
                    266:     sub listtag {
                    267:         my ( $self, $tag ) = @_;
1.5       andrew    268:         my $ident = ident($self);
1.17      andrew    269:
1.14      andrew    270:         my $accessor = $tag . 's';
1.5       andrew    271:
1.14      andrew    272:         my %available;
1.6       andrew    273:         foreach my $e ( $self->list ) {
1.14      andrew    274:             foreach my $p ( $e->$accessor ) {
                    275:                 $available{$p} = 1;
1.5       andrew    276:             }
                    277:         }
                    278:
1.14      andrew    279:         my @tags = sort keys %available;
1.5       andrew    280:
1.17      andrew    281:         return wantarray ? @tags : \@tags;
1.5       andrew    282:     }
                    283:
1.9       andrew    284:     sub archive {
                    285:         my ($self) = @_;
                    286:         my $ident = ident($self);
                    287:
                    288:         if ( !defined $loaded_of{$ident}
                    289:             || $loaded_of{$ident} ne $self->file('todo_file') )
                    290:         {
                    291:             carp 'todo_file not loaded';
                    292:             return;
                    293:         }
                    294:
1.12      andrew    295:         my $changed = 0;
1.9       andrew    296:     ENTRY: foreach my $e ( $self->list ) {
                    297:             if ( $e->done ) {
                    298:                 if ( $self->addto( 'done_file', $e ) && $self->del($e) ) {
1.12      andrew    299:                     $changed++;
1.9       andrew    300:                 }
                    301:                 else {
                    302:                     carp q{Couldn't archive entry [} . $e->text . ']';
                    303:                     last ENTRY;
                    304:                 }
                    305:             }
1.14      andrew    306:             elsif ( $e->text eq q{} ) {
                    307:                 if ( $self->del($e) ) {
1.12      andrew    308:                     $changed++;
                    309:                 }
                    310:                 else {
                    311:                     carp q{Couldn't delete blank entry};
                    312:                     last ENTRY;
                    313:                 }
                    314:             }
1.9       andrew    315:         }
                    316:
1.12      andrew    317:         if ($changed) {
1.9       andrew    318:             $self->save;
                    319:         }
                    320:
1.12      andrew    321:         return $changed;
1.9       andrew    322:     }
1.8       andrew    323:
                    324:     sub addto {
                    325:         my ( $self, $file, $entry ) = @_;
                    326:         my $ident = ident($self);
                    327:
                    328:         $file = $self->file($file);
                    329:         if ( !defined $file ) {
                    330:             croak q{file can't be found};
                    331:         }
                    332:
1.9       andrew    333:         if ( ref $entry ) {
                    334:             if ( ref $entry eq 'Text::Todo::Entry' ) {
                    335:                 $entry = $entry->text;
                    336:             }
                    337:             else {
                    338:                 carp 'Unknown ref [' . ref($entry) . ']';
                    339:                 return;
                    340:             }
                    341:         }
                    342:
1.8       andrew    343:         open my $fh, '>>', $file or croak "Couldn't open [$file]: $!";
                    344:         print {$fh} $entry, "\n"
                    345:             or croak "Couldn't print to [$file]: $!";
                    346:         close $fh or croak "Couldn't close [$file]: $!";
                    347:
                    348:         if ( defined $loaded_of{$ident} && $file eq $loaded_of{$ident} ) {
                    349:             return $self->load($file);
                    350:         }
                    351:
                    352:         return 1;
                    353:     }
1.5       andrew    354:
                    355:     sub _find_entry_id {
                    356:         my ( $self, $entry ) = @_;
                    357:         my $ident = ident($self);
                    358:
1.3       andrew    359:         if ( ref $entry ) {
                    360:             if ( ref $entry ne 'Text::Todo::Entry' ) {
                    361:                 croak(    'entry is a '
                    362:                         . ref($entry)
                    363:                         . ' not a Text::Todo::Entry!' );
                    364:             }
1.5       andrew    365:
                    366:             my @list = $self->list;
                    367:             foreach my $id ( 0 .. $#list ) {
                    368:                 if ( $list[$id] eq $entry ) {
                    369:                     return $id;
                    370:                 }
                    371:             }
1.3       andrew    372:         }
1.5       andrew    373:         elsif ( $entry =~ /^\d+$/xms ) {
                    374:             return $entry;
1.3       andrew    375:         }
                    376:
1.5       andrew    377:         croak "Invalid entry [$entry]!";
1.3       andrew    378:     }
1.2       andrew    379: }
1.1       andrew    380:
1.2       andrew    381: 1;    # Magic true value required at end of module
1.1       andrew    382: __END__
                    383:
                    384: =head1 NAME
                    385:
1.4       andrew    386: Text::Todo - Perl interface to todo_txt files
1.1       andrew    387:
1.10      andrew    388:
1.6       andrew    389: =head1 VERSION
                    390:
1.10      andrew    391: Since the $VERSION can't be automatically included,
                    392: here is the RCS Id instead, you'll have to look up $VERSION.
1.6       andrew    393:
1.18    ! andrew    394:     $Id: Todo.pm,v 1.17 2010/01/12 20:09:02 andrew Exp $
1.1       andrew    395:
                    396: =head1 SYNOPSIS
                    397:
                    398:     use Text::Todo;
1.10      andrew    399:
                    400:     my $todo = Text::Todo->new('todo/todo.txt');
                    401:
                    402:     foreach my $e (sort { lc($_->text) cmp lc($e->text)} $todo->list) {
                    403:         print $e->text, "\n";
                    404:     }
                    405:
1.1       andrew    406:
                    407: =head1 DESCRIPTION
                    408:
1.10      andrew    409: This module is a basic interface to the todo.txt files as described by
                    410: Lifehacker and extended by members of their community.
                    411:
1.4       andrew    412: For more information see L<http://todotxt.com>
1.1       andrew    413:
1.10      andrew    414: This module supports the 3 axes of an effective todo list.
                    415: Priority, Project and Context.
                    416:
                    417: It does not support other notations or many of the more advanced features of
                    418: the todo.sh like plugins.
                    419:
                    420: It should be extensible, but and hopefully will be before a 1.0 release.
                    421:
                    422:
1.1       andrew    423: =head1 INTERFACE
                    424:
1.18    ! andrew    425: =head2 BUILD
1.2       andrew    426:
1.10      andrew    427:     new({
                    428:         [ todo_dir    => 'directory', ]
                    429:         [ todo_file   => 'filename in todo_dir', ]
                    430:         [ done_file   => 'filename in todo_dir', ]
                    431:         [ report_file => 'filename in todo_dir', ]
                    432:         });
                    433:
                    434: Allows you to set each item individually.  todo_file defaults to todo.txt.
                    435:
1.18    ! andrew    436:     new({ todo_file => 'path/to/todo.txt');
1.10      andrew    437:
                    438: Automatically sets todo_dir to 'path/to', todo_file to 'todo.txt'
                    439:
                    440: If you what you set matches (.*)todo(.*).txt it will automatically set
                    441: done_file to $1done$2.txt
                    442: and
                    443: report_file to $1report$2.txt.
                    444:
                    445: For example, new('todo/todo.shopping.txt') will set
                    446: todo_dir to 'todo',
                    447: todo_file to 'todo.shopping.txt',
                    448: done_file to 'done.shopping.txt',
                    449: and
                    450: report_file to 'report.shopping.txt'.
                    451:
1.9       andrew    452: =head2 file
                    453:
1.10      andrew    454: Allows you to read the paths to the files in use.
                    455: If as in the SYNOPSIS above you used $todo = new('todo/todo.txt').
                    456:
                    457:     $todo_file = $todo->file('todo_file');
                    458:
                    459: then, $todo_file eq 'todo/todo.txt'
                    460:
1.2       andrew    461: =head2 load
1.16      andrew    462: - Reads a list from a file into the current object.
1.2       andrew    463:
1.10      andrew    464: Allows you to load a different file into the object.
                    465:
                    466:     $todo->load('done_file');
                    467:
                    468: This effects the other functions that act on the list.
                    469:
1.2       andrew    470: =head2 save
1.16      andrew    471: - Writes the list to disk.
1.2       andrew    472:
1.10      andrew    473:     $todo->save(['new/path/to/todo']);
                    474:
1.16      andrew    475: Either writes the current working file or the passed in argument
1.10      andrew    476: that can be recognized by file().
                    477:
                    478: If you specify a filename it will save to that file and update the paths.
                    479: Additional changes to the object work on that file.
                    480:
1.9       andrew    481: =head2 list
1.16      andrew    482: - get the curently loaded list
1.9       andrew    483:
1.10      andrew    484:     my @todo_list = $todo->list;
                    485:
1.16      andrew    486: In list context returns a list, it scalar context returns an array reference to the list.
                    487:
1.9       andrew    488: =head2 listpri
1.16      andrew    489: - get the list items that are marked priority
1.3       andrew    490:
1.10      andrew    491: Like list, but only returns entries that have priority set.
                    492:
                    493:     my @priority_list = $todo->listpri;
                    494:
1.16      andrew    495: Since this is so easy to write as:
                    496:
                    497:     my @priority_list = grep { $_->priority } $todo->list;
                    498:
                    499: I think it may become depreciated unless there is demand.
                    500:
                    501: =head2 listtag
                    502:
                    503: Returns tags found in the list sorted by name.
1.3       andrew    504:
1.16      andrew    505: If there were projects +GarageSale and +Shopping then
1.10      andrew    506:
1.16      andrew    507:     my @projects = $todo->listtag('project');
1.10      andrew    508:
                    509: is the same as
                    510:
                    511:     @projects = ( 'GarageSale', 'Shopping' );
1.16      andrew    512:
                    513: =head2 listcon
                    514: - Shortcut to listtag('context')
                    515:
                    516: =head2 listproj
                    517: - Shortcut to listtag('project')
1.10      andrew    518:
1.3       andrew    519: =head2 add
1.9       andrew    520:
1.10      andrew    521: Adds a new entry to the list.
                    522: Can either be a Text::Todo::Entry object or plain text.
                    523:
                    524:     $todo->add('new todo entry');
                    525:
                    526: It then becomes $todo->list->[-1];
                    527:
1.9       andrew    528: =head2 del
                    529:
1.10      andrew    530: Remove an entry from the list, either the reference or by number.
                    531:
                    532:     $removed_entry = $todo->del($entry);
                    533:
                    534: $entry can either be an Text::Todo::Entry in the list or the index of the
                    535: entry to delete.
                    536:
                    537: Note that entries are 0 indexed (as expected in perl) not starting at line 1.
                    538:
1.9       andrew    539: =head2 move
                    540:
1.10      andrew    541:     $todo->move($entry, $new_pos);
                    542:
                    543: $entry can either be the number of the entry or the actual entry.
                    544: $new_pos is the new position to put it.
                    545:
                    546: Note that entries are 0 indexed (as expected in perl) not starting at line 1.
                    547:
1.9       andrew    548: =head2 archive
                    549:
1.10      andrew    550:     $todo->archive
                    551:
                    552: Iterates over the list and for each done entry,
                    553: addto('done_file')
                    554: and
                    555: del($entry).
                    556: If any were archived it will then
                    557: save()
                    558: and
                    559: load().
                    560:
1.9       andrew    561: =head2 addto
                    562:
1.10      andrew    563:     $todo->addto($file, $entry);
1.1       andrew    564:
1.10      andrew    565: Appends text to the file.
                    566: $file can be anyting recognized by file().
                    567: $entry can either be a Text::Todo::Entry or plain text.
1.1       andrew    568:
1.10      andrew    569: =head2 listfile
1.1       andrew    570:
1.10      andrew    571:     @list = $todo->listfile($file);
1.1       andrew    572:
1.10      andrew    573: Read a file and returns a list like $todo->list but does not update the
                    574: internal list that is being worked with.
                    575: $file can be anyting recognized by file().
1.1       andrew    576:
                    577:
1.10      andrew    578: =head1 DIAGNOSTICS
1.1       andrew    579:
1.10      andrew    580: Most methods return undef on failure.
1.1       andrew    581:
1.10      andrew    582: Some more important methods are fatal.
1.1       andrew    583:
                    584:
                    585: =head1 CONFIGURATION AND ENVIRONMENT
                    586:
                    587: Text::Todo requires no configuration files or environment variables.
                    588:
1.10      andrew    589: Someday it should be able to read and use the todo.sh config file.  This may
                    590: possibly be better done in a client that would use this module.
1.4       andrew    591:
1.1       andrew    592:
                    593: =head1 DEPENDENCIES
                    594:
1.18    ! andrew    595: Class::Std
1.10      andrew    596: File::Spec
                    597: version
1.1       andrew    598:
                    599:
                    600: =head1 INCOMPATIBILITIES
                    601:
                    602: None reported.
                    603:
                    604:
                    605: =head1 BUGS AND LIMITATIONS
                    606:
                    607: No bugs have been reported.
1.11      andrew    608:
                    609: Limitations:
                    610:
                    611: Currently there isn't an easy way to print out line numbers with the entry.
1.1       andrew    612:
                    613: Please report any bugs or feature requests to
                    614: C<bug-text-todo@rt.cpan.org>, or through the web interface at
                    615: L<http://rt.cpan.org>.
                    616:
                    617:
                    618: =head1 AUTHOR
                    619:
                    620: Andrew Fresh  C<< <andrew@cpan.org> >>
                    621:
                    622:
                    623: =head1 LICENSE AND COPYRIGHT
                    624:
                    625: Copyright (c) 2009, Andrew Fresh C<< <andrew@cpan.org> >>. All rights reserved.
                    626:
                    627: This module is free software; you can redistribute it and/or
                    628: modify it under the same terms as Perl itself. See L<perlartistic>.
                    629:
                    630:
                    631: =head1 DISCLAIMER OF WARRANTY
                    632:
                    633: BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
                    634: FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
                    635: OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
                    636: PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
                    637: EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
                    638: WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
                    639: ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
                    640: YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
                    641: NECESSARY SERVICING, REPAIR, OR CORRECTION.
                    642:
                    643: IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
                    644: WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
                    645: REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
                    646: LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
                    647: OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
                    648: THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
                    649: RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
                    650: FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
                    651: SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
                    652: SUCH DAMAGES.

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>