Irssi Loop Through Users in Channel - perl

I am trying to piece together other scripts i have seen to be able to loop through a list of users on the channel.
Here is what i have come up with
my $channel = #_;
foreach my $nick ($channel->nicks()) {
$server->command("msg $chatchannel $nick->{nick}");
}
But all i get from that is
Can't call method "nicks" without a package or object reference at
/root/.irssi/scripts/test.pl line 64.
which is referring to
$channel->nicks()
Am i going about this the wrong way? or should this be working? I have seen many other scripts using $channel->nicks() so i know it must work?
Edit
I should also mention that this is already define further up in the code
my ($server, $msg, $target, $channel, $chatnet) = #_;
But when i try it with that $channel variable i get
Can't locate object method "nicks" via package
"mattigins#mattigins.tmi.twitch.tv" (perhaps you forgot to load
"mattigins#mattigins.tmi.twitch.tv"?) at /root/.irssi/scripts/test.pl
line 64.

Since the left hand side (LHS) of my $channel = #_; is a scalar it imposes scalar context on the #_ array. This means that the length of the array gets assigned to $channel. You want to assign with my ($channel) = #_; so that the LHS is in list context and that the first element in the #_ array gets assigned to your scalar.
Ref:
What is the difference between the scalar and list contexts in Perl?
Scalar and List context in Perl

I figured out how to do it.
$chan = $server->channel_find('#channel');
foreach my $nick ($chan->nicks()) {
$nickname = $nick->{nick};
}

Related

Perl return list of array refs of unknown length

I have a sub in Perl that needs to return a list of array refs to fit in with the rest of the package. The problem is that I don't know in advance how many array refs I will generate. My usual method of pushing the array refs that I generate into an array and returning a reference to that doesn't work with the rest of the code, which I can't change without breaking some legacy stuff.
sub subTrackTable {
my ($self, $experimentName, $subTrackAttr) = #_;
# return nothing if no subtracks required
if ($subTrackAttr eq 'no_sub') {
return;
}
# get distinct values for subtrack attr (eg antibody) from db
my $dbh = $self->dbh();
my $sh = $dbh->prepare("SELECT DISTINCT * blah sql");
$sh->execute();
my #subtrackTable;
while (my ($term, $value) = $sh->fetchrow_array()) {
my $subtrack = [':$value', $value];
push (#subtrackTable, $subtrack);
}
$sh->finish();
# this is hard-coded for one experiment and does what I want
# Want to loop through #subtrackTable and return a list of all the array refs it contains
# Returning nested array refs doesn't work with external code
return ([":H3K4me3", "H3K4me3"],[":H4K20me3", "H4K20me3"]);
}
The problem is that because I am dynamically getting values from a database, I don't know how many there will be. Just returning \#subtrackTable, which would be my usual strategy breaks the rest of the code. If I knew in advance how many there would be I could also do something like
my $a1 = [":$value1", $value1];
my $a2 = [":$value2", $value2];
...
my $an = [":$valuen", $valuen];
return($a1, $a2,...$an);
but I can't see how to make this work with an unknown number of arrayrefs.
Help appreciated!
It looks like you just need to
return #subtrackTable;
Also, this line
my $subtrack = [':$value', $value];
must be changed to use double quotes, like this
my $subtrack = [ ":$value", $value ];

Warning: 'Use of uninitialized value in numeric eq' in Perl

I am getting a warning as:
Use of uninitialized value in numeric eq (==) at script.pl line 53; line 53 isif statement`
My code snippet:
foreach(#array)
{
push #gene2refseq, $_;
}
foreach(#::ids)
{
if($_ == $gene2refseq[1])
{
push #::prot_gi, $gene2refseq[6];
}
}
Now if I declare $gene2refseq[1] before foreach(#::ids) the same error persists; but if I initialize it before foreach(#::ids) viz. $gene2refseq[1] = 0 it didn't give the error but also no results; as the value is initialized to 0.
I think I am initializing it at a wrong place, but then where have I to initialize it? As I can't initialize it before or in foreach(#array)
Disclaimer: I am not very good with use warnings and use strict
Edit: Solved
Thanks for the help; I just declared the #gene2refseq and initialized $gene2refseq[1] = 0 before the foreach(#array), and it worked fine.
Also thanks for correcting me on foreach usage.
A question:
What if I have to access multiple indexes of an array? Do I have to initialize them all? As here I need to access only a single index, so I initialized it.
If #gene2refeq is empty and #array is empty before the start of that code snippet, then #gene2refeq will be empty by line 53.
To find out, print the contents of #array and #gene2refeq. It's also possible #::ids contains uninitialized values, check that too. Add a separator so you can see what's in them.
print "\#array is ".join(", ", #array)."\n";
print "\#gene2refeq is ".join(", ", #gene2refeq)."\n";
print "\#::ids is ".join(", ", #::ids)."\n";
As to your question about when to initialize things, there's a difference between declaring a variable and initializing it. my and our declare a variable to exist lexically and globally respectively. Then you can populate (initialize) it.
The basic pattern of your program might look something like this.
my #ids;
...code to populate #ids...
my #gene2refeq;
...code to populate #gene2refeq...
my #array;
...code to populate #array...
push #gene2refeq, #array;
die "\#gene2refeq is not long enough, ".join(", ", #gene2refeq)
unless #gene2refeq >= 7;
my #prot_gi;
foreach my $id (#ids) {
if($id == $gene2refseq[1])
{
push #prot_gi, $gene2refseq[6];
}
}
A few other points. That first foreach loop is better written as simply...
push #gene2refseq, #array;
Using #::ids and #::prot_gi is odd. That's shorthand for getting the global variable #ids in the main package. It shouldn't be necessary and smells like cargo culting.

Why is this defined value not recognized as a package or object reference?

I have the code below:
my $content = $response->decoded_content((charset => 'UTF-8'));
my $feed = XML::Feed->parse(\$content) || $logger->error("When retrieving $URL: ", XML::Feed->errstr);
if (defined $feed) {
for my $entry ($feed->entries) {
#DO SOMETHING
}
}
For some site, XML::FEED saying that it can't detect the feed type. This is something I have to look at but this is not my question at the moment.
This sample code is inside a while loop has I'm retrieving different RSS and I would like to have the script running even when some URLs failed.
The defined function seems to not work as I get the error message:
Can't call method "entries" without a package or object reference
Can someone tell me what is the right way to handle the test?
You first have to check the value of $feed.
The error message you describe is obvious: $feed is not a package / object reference, but it can be a simple hash for instance. So it's defined.
Add my favourite debugging line right in front of if(defined):
warn Data::Dumper->new([ $feed ],[ '*feed' ])->Sortkeys(1)->Dump();use Data::Dumper;
and you'll see the value in a nice way.
Without testing I'd say that $feed contains the result of your logger, which might be 1 or 0 or something like that, because you set the value of $feed to XML::Feed->parse, and if this is not successful (undefined) it's the result of $logger->error.
You'd better write it like:
my $feed = XML::Feed->parse(\$content);
if (defined $feed) {
for my $entry ($feed->entries) {
#DO SOMETHING
}
}
else {
$logger->error("When retrieving $URL: ", XML::Feed->errstr);
}
because parse is said to return an object, and I guess it returns undef on error.
The error message means what it says: $feed is neither a package nor an object reference. It passes the defined test because there are many defined values which are neither packages nor object references.
In this particular case, you're seeing this error because you are misuing ||:
my $feed = XML::Feed->parse(\$content) || $logger->error("When retrieving $URL: ", XML::Feed->errstr);
If the parse call should fail and return undef, this evaluates to
my $feed = ( undef || $logger->error("When retrieving $URL: ", XML::Feed->errstr) );
which evaluates to
my $feed = $logger->error("When retrieving $URL: ", XML::Feed->errstr);
. The return value of $logger->error is unknown to me, but presumably it is neither a package nor an object reference. And if it were one, it probably would be the wrong one to put in a variable named $feed.
The documentation for XML::Feed mentions parsing with a construct like
my $feed = XML::Feed->parse(URI->new('http://example.com/atom.xml'))
or die XML::Feed->errstr;
This is not the same thing. Their respective precedence rules make || and or suitable for different applications; specifically, you should only use || when you want the value on the right-hand side for something. Do not use it only for the short-circuit side effect.
You can solve this by replacing the || with or to get the right evaluation order. While you are there, you probably should also eliminate the redundant defined test.

Access an object within an object? (Perl)

I'm pretty new to perl. I have an SomeItem object that contains an array of InnerObjects, of which I want to call the "foo" method on.
foreach $obj (# { $self->{InnerObjects} }) {
$obj->foo();
}
This doesn't work. Here's the error I get:
Can't call method "foo" without a package or object reference
The InnerObject class is in the same file as SomeItem, and would prefer to keep it that way if possible, so how can I access the InnerObject class/package from the SomeItem class/package?
Here's how I declare the array in the constructor:
$self->{InnerObjects} = [];
and set it:
sub set {
my ($self, #items) = #_;
#{ $self->{InnerObjects} } = #items;
}
Your code so far looks legitimate. Therefore the error MAY be in the data passed to set().
Please add the following to set code:
sub set {
my ($self, #items) = #_;
#{ $self->{InnerObjects} } = #items;
print "OBJECT To work on: " . ref($self) . "\n";
print "TOTAL objects passed: " . scalar(#items) . "\n";
foreach my $obj (#items) { print "REF: " . ref($obj) . "\n" };
}
This will show how many objects you passed and whether they are indeed objects of correct class (ref should print class name)
Also, please be aware that #{ $self->{InnerObjects} } = #items; copies over the array of object references, instead of storing the reference to the original array #items - this is NOT the reason for your problem at all but causes you to basically allocate 2 arrays instead of one. Not a major problem memory-management wise unless the array is very large, but still wasteful (#items array would need to be garbage collected after set() is done).
I apologize for putting what was essentially comment-content as an answer but it's too big to be a comment.
My solution to the problem ended up creating a hash that contained the ID of the SomeItem which points to a reference of an array of InnerObjects. This hash is created by the manipulating classlike so,
%SomeItemInnerObjects; # Hash of SomeItem IDs=>InnerObject array references
$SomeItemInnerObjects{ $currentSomeItem->{ID} } = \#myInnerObjects;
and gets used as follows:
foreach $item (#{ $SomeItemInnerObjects{$currentSomeItem->{ID} } }) {
# item is an InnerObject
$item->foo($currentSomeItem->{ID});
}
So SomeItem no longer contains InnerObjects. I know this doesn't answer the question per se, but presents an alternate solution.

Perl subroutine array and scalar variable parameters

How exactly can I pass both scalar variables and array variables to a subroutine in Perl?
my $currVal = 1;
my $currValTwo = 1;
my #currArray = ('one','two','three');
my #currArrayTwo =('one','two','three');
&mysub($currVal, $currValTwo,\#currArray, \#currArrayTwo);
sub mysub() {
# That doesn't work for the array as I only get the first element of the array
my($inVal, $inValTwo, #inArray, #inArrayTwo) = #_;
}
You need to fetch them as references because you've already passed them as references (by using the \ operator):
my($inVal, $inValTwo, $inArray, $inArrayTwo) = #_;
and then use the references as arrays:
#{$inArray}
You pass the arguments as references, so you need to dereference them to use the values. Be careful about whether you want to change the original array or not.
sub mysub {
my($inVal, $inValTwo, $inArray, $inArrayTwo) = #_;
#{$inArrayTwo} = ('five','six','seven');
}
This will change the original #currArrayTwo, which might not be what you want.
sub mysub {
my($inVal, $inValTwo, $inArray, $inArrayTwo) = #_;
my #ATwo = #{$inArrayTwo};
#ATwo = ('five','six','seven');
}
This will only copy the values and leave the original array intact.
Also, you do not need the ampersand in front of the sub name, from perldoc perlsub:
If a subroutine is called using the &
form, the argument list is optional,
and if omitted, no #_ array is set up
for the subroutine: the #_ array at
the time of the call is visible to
subroutine instead. This is an
efficiency mechanism that new users
may wish to avoid.
You do not need empty parens after your sub declaration. Those are used to set up prototypes, which is something you do not need to do, unless you really want to.
So, for example: This is a using statement to search something in an array:
use List::Util qw(first);
This is the sub declaration:
sub GetIndex($$$);
This is the call to the sub (last parameter is: Default index value to give back if not found)
$searchedIndex = GetIndex(\#theArr, "valuesearched", 1);
This is the routine:
sub GetIndex($$$)
{
my $inArray=shift;
my #theArray= #{$inArray};
my $searchedTag= shift;
my $defaultVal= shift;
my $retVal = first { $theArray[$_] eq $searchedTag} 0 .. $#theArray;
if ((! defined $retVal)|| ($retVal<0)||($retVal>#theArray))
{
$retVal = $defaultVal;
}
return $retVal;
}