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