From: Tara L Andrews Date: Sat, 31 Dec 2011 23:22:45 +0000 (+0100) Subject: make the stemma a property of the tradition X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=scpubgit%2Fstemmatology.git;a=commitdiff_plain;h=56cf65bd9ea030e1e7e0cc0bd77673e4f5f00bc8 make the stemma a property of the tradition --- diff --git a/lib/Text/Tradition.pm b/lib/Text/Tradition.pm index e10198d..bbdb45b 100644 --- a/lib/Text/Tradition.pm +++ b/lib/Text/Tradition.pm @@ -3,6 +3,7 @@ package Text::Tradition; use Module::Load; use Moose; use Text::Tradition::Collation; +use Text::Tradition::Stemma; use Text::Tradition::Witness; use vars qw( $VERSION ); @@ -32,6 +33,12 @@ has 'name' => ( isa => 'Str', default => 'Tradition', ); + +has 'stemma' => ( + is => 'ro', + isa => 'Text::Tradition::Stemma', + writer => '_add_stemma', + ); # Create the witness before trying to add it around 'add_witness' => sub { @@ -276,6 +283,40 @@ sub BUILD { } } +=head2 add_stemma( $dotfile ) + +Initializes a Text::Tradition::Stemma object from the given dotfile, +and associates it with the tradition. + +=begin testing + +use Text::Tradition; + +my $t = Text::Tradition->new( + 'name' => 'simple test', + 'input' => 'Tabular', + 'file' => 't/data/simple.txt', + ); + +my $s; +ok( $s = $t->add_stemma( 't/data/simple.dot' ), "Added a simple stemma" ); +is( ref( $s ), 'Text::Tradition::Stemma', "Got a stemma object returned" ); +is( $t->stemma, $s, "Stemma is the right one" ); + +=end testing + +=cut + +sub add_stemma { + my( $self, $dot ) = @_; + open my $stemma_fh, '<', $dot or warn "Could not open file $dot"; + my $stemma = Text::Tradition::Stemma->new( + 'collation' => $self->collation, + 'dot' => $stemma_fh ); + $self->_add_stemma( $stemma ) if $stemma; + return $stemma; +} + no Moose; __PACKAGE__->meta->make_immutable; diff --git a/lib/Text/Tradition/Analysis.pm b/lib/Text/Tradition/Analysis.pm index 4842b69..56b943c 100644 --- a/lib/Text/Tradition/Analysis.pm +++ b/lib/Text/Tradition/Analysis.pm @@ -11,10 +11,17 @@ use vars qw/ @EXPORT_OK /; @EXPORT_OK = qw/ run_analysis group_variants wit_stringify /; sub run_analysis { - my( $tradition, $stemma ) = @_; + my( $tradition ) = @_; # What we will return my $variants = []; my $data = {}; + + # We need a stemma in order to run this... + unless( $tradition->has_stemma ) { + warn "Tradition '" . $tradition->name . "' has no stemma to analyze"; + return undef; + } + my $stemma = $tradition->stemma; # We have the collation, so get the alignment table with witnesses in rows. # Also return the reading objects in the table, rather than just the words. diff --git a/lib/Text/Tradition/Directory.pm b/lib/Text/Tradition/Directory.pm index 3487d90..42c39f2 100644 --- a/lib/Text/Tradition/Directory.pm +++ b/lib/Text/Tradition/Directory.pm @@ -21,14 +21,11 @@ Text::Tradition::Directory - a KiokuDB interface for storing and retrieving trad ); my $tradition = Text::Tradition->new( @args ); + my $stemma = $tradition->add_stemma( $dotfile ); $d->save_tradition( $tradition ); - my $stemma = Text::Tradition::Stemma->new( - 'dot' => $dotfile, 'collation' => $tradition->collation ); - $d->save_stemma( $stemma ); foreach my $id ( $d->traditions ) { print $d->tradition( $id )->name; - print $d->stemma( $id )->as_svg; } =head1 DESCRIPTION @@ -39,14 +36,7 @@ Text::Tradition::Directory is an interface for storing and retrieving text tradi =head2 new -Returns a Directory object. Apart from those documented in L, -options include: - -=over - -=item * preload - Load all traditions and stemmata into memory upon instantiation. Defaults to true. (TODO manage on-demand loading) - -=back +Returns a Directory object. =head2 tradition_ids @@ -56,23 +46,15 @@ Returns the ID of all traditions in the database. Returns the Text::Tradition object of the given ID. -=head2 stemma( $id ) - -Returns the Text::Tradition::Stemma object associated with the given tradition ID. - -=head2 save_tradition( $tradition ) - -Writes the given tradition to the database, returning its UUID. +=head2 save( $tradition ) -=head2 save_stemma( $stemma ) - -Writes the given stemma to the database, returning its UUID. +Writes the given tradition to the database, returning its ID. =begin testing +use Test::Warn; use File::Temp; use Text::Tradition; -use Text::Tradition::Stemma; use_ok 'Text::Tradition::Directory'; my $fh = File::Temp->new(); @@ -81,64 +63,41 @@ $fh->close; my $dsn = "dbi:SQLite:dbname=$file"; my $d = Text::Tradition::Directory->new( 'dsn' => $dsn, - 'extra_args' => { 'create' => 1 } ); + 'extra_args' => { 'create' => 1 } ); is( ref $d, 'Text::Tradition::Directory', "Got directory object" ); +my $scope = $d->new_scope; my $t = Text::Tradition->new( - 'name' => 'inline', - 'input' => 'Tabular', - 'file' => 't/data/simple.txt', - ); -my $uuid = $d->save_tradition( $t ); + 'name' => 'inline', + 'input' => 'Tabular', + 'file' => 't/data/simple.txt', + ); +my $uuid = $d->save( $t ); ok( $uuid, "Saved test tradition" ); -my $s = Text::Tradition::Stemma->new( - 'collation' => $t->collation, - 'dotfile' => 't/data/simple.dot' ); -my $sid = $d->save_stemma( $s ); -ok( $sid, "Saved test stemma" ); - +my $s = $t->add_stemma( 't/data/simple.dot' ); +ok( $d->save( $t ), "Updated tradition with stemma" ); is( $d->tradition( $uuid ), $t, "Correct tradition returned for id" ); -is( $d->stemma( $uuid ), $s, "Correct stemma returned for id" ); -is( scalar $d->tradition_ids, 1, "Only one tradition in DB" ); +is( $d->tradition( $uuid )->stemma, $s, "...and it has the correct stemma" ); +warning_like { $d->save( $s ) } qr/not a Text::Tradition/, "Correctly failed to save stemma directly"; -# Connect to a new instance my $e = Text::Tradition::Directory->new( 'dsn' => $dsn ); -is( scalar $e->tradition_ids, 1, "One tradition preloaded from DB" ); +$scope = $e->new_scope; +is( scalar $e->tradition_ids, 1, "Directory index has our tradition" ); my $te = $e->tradition( $uuid ); -is( $te->name, $t->name, "New instance returns correct tradition" ); -my $se = $e->stemma( $uuid ); -is( $se->graph, $s->graph, "New instance returns correct stemma" ); -is( $e->tradition( 'NOT-A-UUID' ), undef, "Undef returned for non-tradition" ); -is( $e->stemma( 'NOT-A-UUID' ), undef, "Undef returned for non-stemma" ); -$te->name( "Changed name" ); -my $new_id = $e->save_tradition( $te ); -is( $new_id, $uuid, "Updated tradition ID did not change" ); - -my $f = Text::Tradition::Directory->new( 'dsn' => $dsn, 'preload' => 0 ); -is( scalar $f->tradition_ids, 0, "No traditions preloaded from DB" ); -### TODO This doesn't work, as I cannot get an object scope in the -### 'tradition' wrapper. -# my $tf = $f->tradition( $uuid ); -# is( $tf->name, $t->name, "Next instance returns correct tradition" ); -# is( $tf->name, "Changed name", "Change to tradition carried through" ); +is( $te->name, $t->name, "Retrieved the tradition from a new directory" ); +my $sid = $e->object_to_id( $te->stemma ); +warning_like { $e->tradition( $sid ) } qr/not a Text::Tradition/, "Did not retrieve stemma via tradition call"; +warning_like { $e->delete( $sid ) } qr/Cannot directly delete non-Tradition object/, "Stemma object not deleted from DB"; +$e->delete( $uuid ); +ok( !$e->exists( $uuid ), "Object is deleted from DB" ); +is( scalar $e->tradition_ids, 0, "Object is deleted from index" ); + =end testing =cut -has data_hash => ( - traits => ['Hash'], - default => sub { {} }, - handles => { - tradition => 'get', - stemma => 'get', - add_tradition => 'set', - add_stemma => 'set', - tradition_ids => 'keys', - }, -); - has +typemap => ( is => 'rw', isa => 'KiokuDB::TypeMap', @@ -152,142 +111,72 @@ has +typemap => ( }, ); -has preload => ( - is => 'ro', - isa => 'Bool', - default => 1, - ); - -around 'tradition' => sub { - my( $orig, $self, @arg ) = @_; - my $data = $self->$orig( @arg ); - unless( $data ) { - # Connect to the DB and fetch the thing. - $self->new_scope; - my $id = shift @arg; - my $trad = $self->lookup( $id ); - if( ref( $trad ) eq 'Text::Tradition' ) { - $self->add_tradition( $id => $trad ); - return $trad; - } - # If we got this far... - return undef; - } - return $data->{'object'}; -}; - -around 'stemma' => sub { - my( $orig, $self, @arg ) = @_; - my $data = $self->$orig( @arg ); - unless( $data ) { - # Connect to the DB and fetch the thing. - $self->new_scope; - my $id = shift @arg; - my $trad = $self->lookup( $id ); - if( ref( $trad ) eq 'Text::Tradition' ) { - # Add it - $self->add_tradition( $id => $trad ); - # Find the stemma whose collation belongs to $trad - my $ret = $self->grep( sub { $_->collation eq $trad->collation } ); - my $stemma; - until ( $ret->is_done ) { - foreach my $st ( $ret->items ) { - warn "Found two saved stemmas for tradition $id" if $stemma; - $stemma = $st; - } - } - if( $stemma ) { - $self->add_stemma( $stemma ); - return $stemma; - } - } - # If we got this far... - return undef; - } - return $data->{'stemma'}; -}; - -around 'add_tradition' => sub { - my( $orig, $self, $id, $obj ) = @_; - $self->$orig( $id => { 'object' => $obj } ); -}; - -around 'add_stemma' => sub { - my( $orig, $self, $id, $obj ) = @_; - $self->{data_hash}->{$id}->{'stemma'} = $obj; -}; - -# Load all the relevant data from the DSN we were passed. +has tradition_index => ( + traits => ['Hash'], + isa => 'HashRef[Str]', + handles => { + add_index => 'set', + del_index => 'delete', + name => 'get', + tradition_ids => 'keys', + }, + default => sub { {} }, + ); +# Populate the tradition index. sub BUILD { my $self = shift; - my $args = shift; - - $self->fetch_all if( $self->dsn && $self->preload ); -} - -# Connect to self, get the traditions and stemmas, and save them -# in the directory. -sub fetch_all { - my $self = shift; - my $scope = $self->new_scope; my $stream = $self->root_set; - my %stemmata; until( $stream->is_done ) { foreach my $obj ( $stream->items ) { my $uuid = $self->object_to_id( $obj ); if( ref( $obj ) eq 'Text::Tradition' ) { - $self->add_tradition( $uuid => $obj ); - } elsif( ref( $obj ) eq 'Text::Tradition::Stemma' ) { - $stemmata{$obj->collation} = $obj; + $self->add_index( $uuid => $obj->name ); } else { - warn "Found root object in DB that is neither tradition nor stemma: $obj"; + warn "Found root object in DB that is not a Text::Tradition"; } } } - # Now match the stemmata to their traditions. - foreach my $id ( $self->tradition_ids ) { - my $c = $self->tradition( $id )->collation; - if( exists $stemmata{$c} ) { - $self->add_stemma( $id => $stemmata{$c} ); - } - } + return $self; } - -sub save_tradition { - my( $self, $tradition ) = @_; - # Write the thing to the db and return its ID. - unless( ref( $tradition ) eq 'Text::Tradition' ) { - warn "Object $tradition is not a Text::Tradition"; - return undef; +# If a tradition is deleted, remove it from the index. +around delete => sub { + my $orig = shift; + my $self = shift; + warn "Only the first object will be deleted" if @_ > 1; + my $arg = shift; + my $obj = ref( $arg ) ? $arg : $self->lookup( $arg ); + my $id = ref( $arg ) ? $self->object_to_id( $arg ) : $arg; + unless( ref $obj eq 'Text::Tradition' ) { + warn "Cannot directly delete non-Tradition object $obj"; + return; + } + $self->$orig( $arg ); + $self->del_index( $id ); +}; + +sub save { + my( $self, $obj ) = @_; + unless( ref( $obj ) eq 'Text::Tradition' ) { + warn "Object $obj is not a Text::Tradition"; + return; } - my $scope = $self->new_scope; - my $uuid = $self->store( $tradition ); - $self->add_tradition( $uuid => $tradition ); + my $uuid = $self->store( $obj ); + $self->add_index( $uuid => $obj->name ) if $uuid; return $uuid; } -sub save_stemma { - my( $self, $stemma ) = @_; - unless( ref( $stemma ) eq 'Text::Tradition::Stemma' ) { - warn "Object $stemma is not a Text::Tradition::Stemma"; - return undef; - } - my $scope = $self->new_scope; - # Get the tradition to which this stemma belongs. - my $tradition = $stemma->collation->tradition; - # Make sure the tradition is in the DB. - my $tid = $self->save_tradition( $tradition ); - unless( $tid ) { - warn "Could not access this stemma's tradition; aborting"; - return undef; + +sub tradition { + my( $self, $id ) = @_; + my $obj = $self->lookup( $id ); + unless( ref( $obj ) eq 'Text::Tradition' ) { + warn "Retrieved object is a " . ref( $obj ) . ", not a Text::Tradition"; + return; } - my $sid = $self->store( $stemma ); - $self->add_stemma( $tid => $stemma ); - return $tid; + return $obj; } - 1; diff --git a/t/text_tradition.t b/t/text_tradition.t index 2c36c23..eae4e3a 100644 --- a/t/text_tradition.t +++ b/t/text_tradition.t @@ -47,5 +47,23 @@ is( scalar $s->witnesses, 3, "object has three witnesses again" ); +# =begin testing +{ +use Text::Tradition; + +my $t = Text::Tradition->new( + 'name' => 'simple test', + 'input' => 'Tabular', + 'file' => 't/data/simple.txt', + ); + +my $s; +ok( $s = $t->add_stemma( 't/data/simple.dot' ), "Added a simple stemma" ); +is( ref( $s ), 'Text::Tradition::Stemma', "Got a stemma object returned" ); +is( $t->stemma, $s, "Stemma is the right one" ); +} + + + 1; diff --git a/t/text_tradition_directory.t b/t/text_tradition_directory.t index e2c2667..efc4f23 100644 --- a/t/text_tradition_directory.t +++ b/t/text_tradition_directory.t @@ -8,9 +8,9 @@ $| = 1; # =begin testing { +use Test::Warn; use File::Temp; use Text::Tradition; -use Text::Tradition::Stemma; use_ok 'Text::Tradition::Directory'; my $fh = File::Temp->new(); @@ -19,47 +19,35 @@ $fh->close; my $dsn = "dbi:SQLite:dbname=$file"; my $d = Text::Tradition::Directory->new( 'dsn' => $dsn, - 'extra_args' => { 'create' => 1 } ); + 'extra_args' => { 'create' => 1 } ); is( ref $d, 'Text::Tradition::Directory', "Got directory object" ); +my $scope = $d->new_scope; my $t = Text::Tradition->new( - 'name' => 'inline', - 'input' => 'Tabular', - 'file' => 't/data/simple.txt', - ); -my $uuid = $d->save_tradition( $t ); + 'name' => 'inline', + 'input' => 'Tabular', + 'file' => 't/data/simple.txt', + ); +my $uuid = $d->save( $t ); ok( $uuid, "Saved test tradition" ); -my $s = Text::Tradition::Stemma->new( - 'collation' => $t->collation, - 'dotfile' => 't/data/simple.dot' ); -my $sid = $d->save_stemma( $s ); -ok( $sid, "Saved test stemma" ); - +my $s = $t->add_stemma( 't/data/simple.dot' ); +ok( $d->save( $t ), "Updated tradition with stemma" ); is( $d->tradition( $uuid ), $t, "Correct tradition returned for id" ); -is( $d->stemma( $uuid ), $s, "Correct stemma returned for id" ); -is( scalar $d->tradition_ids, 1, "Only one tradition in DB" ); +is( $d->tradition( $uuid )->stemma, $s, "...and it has the correct stemma" ); +warning_like { $d->save( $s ) } qr/not a Text::Tradition/, "Correctly failed to save stemma directly"; -# Connect to a new instance my $e = Text::Tradition::Directory->new( 'dsn' => $dsn ); -is( scalar $e->tradition_ids, 1, "One tradition preloaded from DB" ); +$scope = $e->new_scope; +is( scalar $e->tradition_ids, 1, "Directory index has our tradition" ); my $te = $e->tradition( $uuid ); -is( $te->name, $t->name, "New instance returns correct tradition" ); -my $se = $e->stemma( $uuid ); -is( $se->graph, $s->graph, "New instance returns correct stemma" ); -is( $e->tradition( 'NOT-A-UUID' ), undef, "Undef returned for non-tradition" ); -is( $e->stemma( 'NOT-A-UUID' ), undef, "Undef returned for non-stemma" ); -$te->name( "Changed name" ); -my $new_id = $e->save_tradition( $te ); -is( $new_id, $uuid, "Updated tradition ID did not change" ); - -my $f = Text::Tradition::Directory->new( 'dsn' => $dsn, 'preload' => 0 ); -is( scalar $f->tradition_ids, 0, "No traditions preloaded from DB" ); -### TODO This doesn't work, as I cannot get an object scope in the -### 'tradition' wrapper. -# my $tf = $f->tradition( $uuid ); -# is( $tf->name, $t->name, "Next instance returns correct tradition" ); -# is( $tf->name, "Changed name", "Change to tradition carried through" ); +is( $te->name, $t->name, "Retrieved the tradition from a new directory" ); +my $sid = $e->object_to_id( $te->stemma ); +warning_like { $e->tradition( $sid ) } qr/not a Text::Tradition/, "Did not retrieve stemma via tradition call"; +warning_like { $e->delete( $sid ) } qr/Cannot directly delete non-Tradition object/, "Stemma object not deleted from DB"; +$e->delete( $uuid ); +ok( !$e->exists( $uuid ), "Object is deleted from DB" ); +is( scalar $e->tradition_ids, 0, "Object is deleted from index" ); }