Today I learned the hard way that file handle names in Perl can conflict with package names of Perl modules.
I was adding emoticon support to my Siikir CMS ( :-) ), and so I had downloaded this open source Tango emoticon set that came with an emoticons.txt
file that described what trigger texts correspond to each PNG image. So, I wanted to write a quick Perl script that would use the JSON module and convert this plain text emoticon list into a JSON file that the Siikir CMS could use.
For some slight backstory, Siikir uses JSON all over the place. The entire database system is using JSON, and so Siikir has a "JsonDB" plugin that manages all database access. It looks like this:
my $db = $self->Master->JsonDB->getDocument("users/by-name/kirsle");
Anyway, the JsonDB plugin creates a JSON object as follows:
$self->{json} = JSON->new->utf8->pretty();
This has always worked fine. But for some odd reason, my little Perl script that converted my emoticons to JSON for me was throwing this bizarre error message from the same constructor:
Can't locate object method "new" via package "IO::File" at mkjson.pl line 4.
This made no sense to me. What, was IO::File failing to load? So I add an explicit "use IO::File;
" in my code (ordinarily, if JSON requires this module, it should've auto-loaded when I did "use JSON;
", but something was wrong here). This turns the error message into this:
Can't locate object method "utf8" via package "GLOB" at mkjson.pl line 4.
WTF. I try to debug this weird error message in all the usual ways (like using Data::Dumper
to dump the contents of %INC, and verify that the JSON.pm being loaded is the same one the Siikir CMS uses; it was). After running out of ideas, I scrapped using JSON.pm in this script and just hard-coded it to write JSON code to the output file directly.
Then it was time to create the Emoticons plugin for Siikir, which would be a centralized piece of code to render emoticons for every page that wants them. I couldn't use the JsonDB to manage the emoticons now, because each emoticon "theme" is supposed to keep its own "emoticons.json" file within itself, instead of putting them in my global database directory. So, Emoticons.pm needed to use its own JSON object.
I was bizarrely running into the same dumb error messages in my Emoticons plugin too. I thought for a minute that maybe
, the environment of my bash terminal running my small script was different than the Apache server's environment, because my JsonDB plugin never threw these sorts of errors. But now my Emoticons plugin was throwing errors just like my small script was!
Well, here would be the full source of my small script (I added in the exit 1
right after the JSON constructor line just to test the JSON line without the chance of running the entire script again should the error miraculously disappear):
#!/usr/bin/perl -w
use strict;
use warnings;
use JSON;
use Data::Dumper;
#die Dumper(\%INC);
my $json = JSON->new->utf8->pretty();
exit 1;
open (EMO, "emoticons.txt");
open (JSON, ">emoticons.json");
print JSON "{\n"
. "\tname: 'Tango',\n"
. "\tsource: 'http://digsbies.org/site/content/project/tango-emoticons-big-pack',\n"
. "\tmap: {\n";
while (my $line = <EMO>) {
chomp $line;
$line =~ s/[\x0D\x0A]+//g;
next unless length $line;
my ($img,@codes) = split(/\s+/, $line);
my @escaped;
foreach my $c (@codes) {
$c =~ s/\'/\\'/g;
push (@escaped, $c);
}
my $escaped = join(", ", map { qq{'$_'} } @escaped);
print JSON "\t\t\"$img\": [ $escaped ],\n";
}
print JSON "\t}\n}";
close (JSON);
close (EMO);
See the problem? Apparently, because I had named my output filehandle "JSON", this was conflicting with the package named JSON. If I cut out all of the code after the "exit 1", the error went away and the code would compile and run just fine. The same problem occurred in my Emoticons plugin because I was using "JSON" as the filehandle name when reading the JSON text from disk. Changing the name for the filehandle fixed my problem.
My JsonDB module used the names READ and WRITE for its filehandles, which is why this problem didn't occur there.
So, apparently, filehandles must share the same namespace as packages. The annoying thing is that the errors given are completely misleading. :(
There are 3 comments on this page. Add yours.
It's not that filehandles and packages share a namespace, it's that the use of barewords in Perl 5 is ambiguous. If you use lexical filehandles, there's no possibility of conflict--that's why lexical filehandles exist.
Nice post! In the future, please consider using lexical filehandles instead of barewords - it will save you a lot of headache. While we're at it, 3 arguments open and testing file operations (open, close, etc) lets you avoid a lot of other potential pitfalls.
Just in case it's not clear enough, I mean instead of writing:
open (JSON, ">emoticons.json");
writing:
use autodie; open my $json_file, '>', 'emoticons.json';
Hope this helps!
If you write to a filehandle you must test close() for success. Any errors writing to the filehandle such as "disk full" will propagate to the close().
Something like this:
close $json_file or die $!;
0.0115s
.