package DSSP; use strict; use warnings; use Carp; use IO::String; use UNIVERSAL qw(isa); use base qw(Root Read); =head1 DSSP Object to hold some of the information found in a DSSP file. =head2 my $dssp = DSSP->new; Creates new object. Any accessor method is valid to use as an argument in a hash key/value set of pairs. =head2 $dssp->read($string); Reads in the DSSP file contained within the $string. Returns true if it there is information in the file, otherwise false. =cut sub read { my ($self, $data) = @_; defined $data or croak "No filehandle passed\n"; local $/ = "\n"; my $flag = 0; # Test to see if there's any information in the file while (<$data>) { if (/^ # RESIDUE AA STRUCTURE/) { $flag = 1; my $chain; while (<$data>) { chomp; my $aa_pri = substr($_, 13, 1); # Chain break, has to come here in order to remember what the # chain was if ($aa_pri eq '!') { $self->add_position( DSSP::Position->new( chain => $chain, break => 1 ) ); next; } # If there isn't a chain break, continue as normal my $aa_sec = substr($_, 16, 1); my $aa_acc = substr($_, 35, 3); $chain = substr($_, 11, 1); # Field is six wide to include alternatives, but I'm ignoring # those my $pos = substr($_, 5, 6); my $seq_pos = substr($_, 0, 5); # Replace empty chain defn with NULL $chain = 'NULL' if $chain eq ' '; # Replace lower case letters (cystine # bridge pairs) with 'c' $aa_pri =~ tr/[a-z]/c/; $aa_sec =~ tr/ /-/; # Remove any padding whitespace for ($aa_pri, $aa_sec, $aa_acc, $chain, $pos, $seq_pos) { s/ //g } $self->add_position( DSSP::Position->new( chain => $chain, pri => $aa_pri, sec => $aa_sec, acc => $aa_acc, off => $pos, seq_off => $seq_pos, break => undef ) ); } } } return $flag; } =head2 $dssp->add_position($DSSP::Position); Adds a position object. =cut sub add_position { my ($self, $pos) = @_; unless (defined $pos and isa $pos, 'DSSP::Position') { croak "No object passed" } my ($chain, $offset); defined($chain = $pos->chain) or croak "Chain not set\n"; unless ($pos->break) { defined($offset = $pos->off) or croak "Offset not set\n"; } # Use a stack based system now push @{ $self->{__PACKAGE__."pos"}{$chain} }, $pos; # This is for backwards compatibility, shudder, also required changes in # get_position() and position() unless ($pos->break) { $self->{__PACKAGE__."offset_compat"}{offset}{$chain}{$offset} = \$self->{__PACKAGE__."pos"}{$chain}->[ -1 ]; if (exists $self->{__PACKAGE__."pos"}{offset}{$chain}{$offset}) { my $out = "Overwriting DSSP position $offset in chain $chain"; $out .= $self->path ? " for file ".$self->path."\n" : "\n"; warn $out; } } # Add the chain if not already present my @chains = grep { defined } $self->chains; unless (grep { $chain eq $_ } @chains) { $self->chains(@chains, $chain); } } =head2 $DSSP::Position = $dssp->get_chain_defs($chain) New method to replace get_position() and positions(). Returns all of the DSSP::Position objects for a particular chain in the order they are seen in the DSSP file. Then you have to decide what to do with them. =cut sub get_chain_defs { my ($self, $chain) = @_; croak "Chain not recognised" unless grep { $_ eq $chain } $self->chains; return @{ $self->{__PACKAGE__."pos"}{$chain} }; } =head2 $DSSP::Position = $dssp->get_position($chain, $offset) Depricated! Don't use this methods as it's broken, it's available for backwards compatibility. Returns the position object for a particular location. =cut sub get_position { my ($self, $chain, $offset) = @_; unless (defined $chain) { croak "No chain given" } unless (defined $offset) { croak "No offset given" } confess("No such chain '$chain'") and return unless exists $self->{__PACKAGE__."pos"}{$chain}; confess("No such offset $offset in chain $chain") and return #unless exists $self->{__PACKAGE__."pos"}{$chain}; unless exists $self->{__PACKAGE__."offset_compat"}{offset}{$chain}{$offset}; return ${ $self->{__PACKAGE__."offset_compat"}{offset}{$chain}{$offset} }; } =head2 $DSSP::Position = $dssp->positions($chain); Depricated! Don't use this methods as it's broken, it's available for backwards compatibility. Returns all the offsets that are available for a particular chain, sorted by offset. =cut sub positions { my ($self, $chain) = @_; unless (defined $chain) { croak "No chain given" } croak "No such chain $chain" unless exists $self->{__PACKAGE__."pos"}{$chain}; no warnings; return map { join "", @{$_} } sort { $a->[0] <=> $b->[0] || $a->[1] cmp $b->[1] } map { [ /^(-?\d+)(\D+)?$/ ] } #keys %{$self->{__PACKAGE__."pos"}{$chain}}; keys %{$self->{__PACKAGE__."offset_compat"}{offset}{$chain}}; } =head2 @chains = $dssp->chains; Finds the chains in the structure, or alters them, but don't do that. Returns an empty list if no chains have been found. =cut sub chains { my ($self, @chains) = @_; if (@chains) { @{$self->{__PACKAGE__."chains"}} = @chains } elsif ($self->{__PACKAGE__."chains"}) { return @{ $self->{__PACKAGE__."chains"} }; } else { return (); } } =head1 DSSP::Position Object to hold information on each position of a DSSP output. =head2 my $dssp_pos = DSSP::Position->new; Create a new object. =head2 Accessors =over =item * chain Holds the chain ID. =item * pri Holds the primary structre. =item * sec Holds the secondary structre. =item * acc Holds the solvent structre. =item * off Holds the position in the PDB structure. =back =cut use Class::Struct "DSSP::Position" => { chain => '$', pri => '$', sec => '$', acc => '$', off => '$', seq_off => '$', break => '$' }; 1;