Changing Behaviour of Perl's FindBin - perl

I've got a collection of Perl scripts which use the FindBin module. There are some data files which the scripts use, and are expecting to find them in $FindBin::Bin/../share/somedir/. I'm customizing the installation so those files now exist is /some/other/share/dir/. Is there any way to customize the behaviour of FindBin with something like, say, an environment variable?

I don't think you can solve this problem with FindBin module, because it's designed to solve particular problem: locate directory of original perl script.
You should implement your own module to getting configuration parameters. This can look like:
package MyConfig;
use strict; use warnings;
use FindBin;
sub get_data_folder {
return ($ENV{'DEV_ENVIROMENT'} ? ## create this variable in your development
$FindBin::Bin."/../share/somedir/" :
"/some/other/share/dir/"
);
}
1;
This will allow you to control all your configuration path in one place. But you'll need to change exists code.

Related

Including a module from another module [duplicate]

I don't know how to do one thing in Perl and I feel I am doing something fundamentally wrong.
I am doing a larger project, so I split the task into different modules. I put the modules into the project directory, in the "modules/" subdirectory, and added this directory to PERL5LIB and PERLLIB.
All of these modules use some configuration, saved in external file in the main project directory - "../configure.yaml" if you look at it from the module file perspective.
But, right now, when I use module through "use", all relative paths in the module are taken as from the current directory of the script using these modules, not from the directory of the module itself. Not even when I use FindBin or anything.
How do I load a file, relative from the module path? Is that even possible / advisable?
Perl stores where modules are loaded from in the %INC hash. You can load things relative to that:
package Module::Foo;
use File::Spec;
use strict;
use warnings;
my ($volume, $directory) = File::Spec->splitpath( $INC{'Module/Foo.pm'} );
my $config_file = File::Spec->catpath( $volume, $directory, '../configure.yaml' );
%INC's keys are based on a strict translation of :: to / with .pm appended, even on
Windows, VMS, etc.
Note that the values in %INC may be relative to the current directory if you put relative directories in #INC, so be careful if you change directories between the require/use and checking %INC.
The global %INC table contains an entry for every module you have use'd or require'd, associated with the place that Perl found that module.
use YAML;
print $INC{"YAML.pm"};
>> /usr/lib/perl5/site_perl/5.8/YAML.pm
Is that more helpful?
There's a module called File::ShareDir that exists to solve this problem. You were on the right track trying FindBin, but FindBin always finds the running program, not the module that's using it. ShareDir does something quite similar to ysth's solution, except wrapped up in a nice interface.
Usage is as simple as
my $filename = File::ShareDir::module_file(__PACKAGE__,
'my/data.txt');
# and then open $filename or whatever else.
or
my $dirname = File::ShareDir::module_dir(__PACKAGE__);
# Play ball!
Change your use Module call to require Module (or require Module; Module->import(LIST)). Then use the debugger to step through the module loading process and see where Perl thinks it is loading the files from.

I want to create a word count module and I want to reuse it further

I want to create a module in Perl. The below code is not working properly. I want to create a word count module and I want to reuse it further. Can anyone help me out to create this module? This is my first attempt to create a module so kindly help me out.
package My::count
use Exporter qw(import);
our #Export_ok = qw(line_count);
sub line_count {
my $line = #_;
return $line;
}
I saved the above code in count.pm
use My::count qw(line_count);
open INPUT, "<filename.txt";
$line++;
print line count is $line \n";
I saved the above script in .pi extension.
This code is showing error when I run it on an Ubuntu platform. Kindly help me to fix this errors.
Perl scripts are stored with .pl extension. As you say use My::count qw(line_count); Perl tries to search the modules from the directories stored in #INC variable. You can run it with the -I flag to specify the directory to search the custom packages. Refer to this question for more info.
By convention Perl packages usually have a capitalized first letter, so My::count is more in keeping with convention if you call it package My::Count;. Typically lower-cased module names are reserved for pragmas such as 'strict' and 'warnings'. So go ahead and change the name to My::Count.
Next, save the module in a path such as lib/My/Count.pm. lib is by convention as well.
Then you have to tell your script where to find package My::Count.
Let's assume you're storing your module and your executable like this:
~/project/lib/My/Count.pm
~/project/bin/count.pl
Notice I also used a .pl extension for the executable. This is another convention. Often on Unix-like systems people omit the .pl extension altogether.
Finally, in your count.pl file you need to tell perl where to find the library. Often that is done like this:
#!/usr/bin/env perl
use strict;
use warnings;
use FindBin qw($Bin);
use lib "$Bin/../lib";
use My::Count 'line_count';
# The rest goes here...
As you can see, we're using FindBin to locate where the executable is stored, and then telling perl that it should look (among other places) in the lib folder stored in a relative location to the executable.
Naturally, as this is Perl, this is not the only way to do it. But it's one common idiom.
You need to move your count.pm file into a directory called My. So you have the following.
./count.pl
./My/count.pm

Using custom modules in PERL

I made myself a custom PERL module and it works when called by a script in the same directory, but not from outside the directory for somewhat obvious reasons. How do I use the module without installing it? eg:
use 5.012;
use warnings;
use Y:/my/dir/to/module.pm;
use lib 'Y:/my/dir/to';
use module; # BAD name for module, lowercase is reserved for pragmas...
If you only have one module, instead of using lib, you can do this :
BEGIN {
unshift #INC,"dir";
##INC is the directory list, where perl searches for .pm files
}
use Foo::Bar; #dir/Foo/Bar.pm
#or
do "dir/Foo/Bar.pm"; #perldoc -f do

Using FindBin in more than one module

Here is the scenario, I have two files:
1. dir/A.pm
2. dir/new_dir/a.t
This is how A.pm looks like:
package A;
use FindBin;
use Test::More;
is (FindBin->again, 'dir', 'got dir');
1;
This is how a.t looks like:
use FindBin;
use Test::More qw(no_plan);
use A;
is (FindBin->again, 'dir/new_dir', 'got dir/new_dir');
So I ran file a.t with perl new_dir/a.t and expect my tests to pass. But this is my test result:
not ok 1 - got dir
# Failed test 'got fir'
# at A.pm line 6.
# got: 'dir/new_dir'
# expected: 'dir'
ok 2 - got dir/new_dir
1..2
Am I doing anything wrong? I am very new to perl. Please help!!
As Dave Sherohman notes, FindBin is for finding the location of the main script, not individual modules. From the documentation:
NAME
FindBin - Locate directory of original perl script
(Admittedly, the documentation does, somewhat confusingly, refer to "modules" in the "KNOWN ISSUES" section, but it doesn't really mean what you think it means by that.)
Anyway, if you look at the source with perldoc -m FindBin, you'll see that FindBin obtains the path to the script from the $0 variable. If you're interested in finding the location of a module included via use (or require), you should look under %INC instead, something like this:
package Foo::Bar;
use File::Spec;
my ($vol, $dir, $file) = File::Spec->splitpath( $INC{'Foo/Bar.pm'} );
FindBin finds the location of the file Perl launched, not of file currently executing.
I don't see why you'd need the path to a module — File::ShareDir can be used to access your module's data files — but the following will find it:
use Cwd qw( realpath );
use File::Basename qw( dirname );
my $module_dir = dirname(realpath(__FILE__));
The same caveat as Find::Bin applies: This only works if chdir hasn't been changed.
If I understand the question correctly, a.t is in the directory dir/new_dir/ and you're using new_dir/a.t to run it from dir/, right?
If so, then it is doing the right thing. Since a.t is in dir/new_dir, you should always get dir/new_dir from FindBin - its job is to Find the Binary (program), not to find the file it's being called from, so the result will be the same in A.pm as it is in a.t.
The ->again function is for running instances of completely different programs from within the same perl interpreter, such as what mod_perl does, not for just using different modules within a single program.

How can I split my Perl code across multiple files?

My scripts are getting too long. How do I split my code (procedural subs) into multiple Perl files and tell the interpreter to make sense of them?
Kind of like:
# -> main.pl
#include "foo.pl"
say_hello();
and:
# -> foo.pl
sub say_hello {print "hello!"}
What you want to do is create one or more modules. Start by looking over perlmod, especially the Perl Modules section.
Since you say you're writing procedural code, you'll want to export functions from your modules. The traditional way to do that is to use Exporter (which comes with Perl), although Sub::Exporter is a newer CPAN module that allows for some nice things. (See also its Sub::Exporter::Tutorial for an introduction to exporting functions.)
Modules can be placed in any of the directories listed in the #INC variable. Try perl -V to get a list. You can also use lib to add directories at runtime. One trick is to use the FindBin module to find the location of your script, and then add a directory relative to that:
use FindBin; # Suppose my script is /home/foo/bin/main.pl
use lib "$FindBin::Bin/lib"; # Add /home/foo/bin/lib to search path
Your sample code, converted to a module:
In main.pl:
#! /usr/bin/perl
use strict;
use warnings;
use Foo;
say_hello();
In Foo.pm:
package Foo;
use strict;
use warnings;
use Exporter 'import';
our $VERSION = '1.00';
our #EXPORT = qw(say_hello);
sub say_hello {print "hello!"}
1; # A module must end with a true value or "use" will report an error
I think you may be looking for do? http://perldoc.perl.org/functions/do.html
put it in the same folder as your class and add use ClassName to the top of the calling file.
Also check the Perl OOP tutorial.