[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.21

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

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