redid test setup to be sane pod-wise
[p5sagit/local-lib.git] / lib / local / lib.pm
CommitLineData
b5cc15f7 1use strict;
2use warnings;
3
4package local::lib;
5
6use 5.8.1; # probably works with earlier versions but I'm not supporting them
7 # (patches would, of course, be welcome)
8
9use File::Spec ();
10use File::Path ();
11use Carp ();
12use Config;
13
14our $VERSION = '1.000000'; # 1.0.0
15
16sub import {
17 my ($class, $path) = @_;
18 $path = $class->resolve_path($path);
19 $class->setup_local_lib_for($path);
20}
21
5b94dce5 22sub pipeline;
b5cc15f7 23
5b94dce5 24sub pipeline {
b5cc15f7 25 my @methods = @_;
26 my $last = pop(@methods);
27 if (@methods) {
28 \sub {
29 my ($obj, @args) = @_;
5b94dce5 30 $obj->${pipeline @methods}(
b5cc15f7 31 $obj->$last(@args)
32 );
33 };
34 } else {
35 \sub {
36 shift->$last(@_);
37 };
38 }
39}
40
275c9dae 41=begin testing
42
43#:: test pipeline
b5cc15f7 44
45package local::lib;
46
47{ package Foo; sub foo { -$_[1] } sub bar { $_[1]+2 } sub baz { $_[1]+3 } }
48my $foo = bless({}, 'Foo');
4c375968 49Test::More::ok($foo->${pipeline qw(foo bar baz)}(10) == -15);
b5cc15f7 50
275c9dae 51=end testing
52
b5cc15f7 53=cut
54
55sub resolve_path {
56 my ($class, $path) = @_;
5b94dce5 57 $class->${pipeline qw(
b5cc15f7 58 resolve_relative_path
59 resolve_home_path
60 resolve_empty_path
61 )}($path);
62}
63
64sub resolve_empty_path {
65 my ($class, $path) = @_;
66 if (defined $path) {
67 $path;
68 } else {
69 '~/perl5';
70 }
71}
72
275c9dae 73=begin testing
74
75#:: test classmethod setup
b5cc15f7 76
77my $c = 'local::lib';
78
275c9dae 79=end testing
80
81=begin testing
b5cc15f7 82
275c9dae 83#:: test classmethod
b5cc15f7 84
85is($c->resolve_empty_path, '~/perl5');
86is($c->resolve_empty_path('foo'), 'foo');
87
275c9dae 88=end testing
89
b5cc15f7 90=cut
91
92sub resolve_home_path {
93 my ($class, $path) = @_;
94 return $path unless ($path =~ /^~/);
95 my ($user) = ($path =~ /^~([^\/]+)/); # can assume ^~ so undef for 'us'
96 my $tried_file_homedir;
97 my $homedir = do {
98 if (eval { require File::HomeDir } && $File::HomeDir::VERSION >= 0.65) {
99 $tried_file_homedir = 1;
100 if (defined $user) {
101 File::HomeDir->users_home($user);
102 } else {
dc8ddd06 103 File::HomeDir->my_home;
b5cc15f7 104 }
105 } else {
106 if (defined $user) {
107 (getpwnam $user)[7];
108 } else {
109 if (defined $ENV{HOME}) {
110 $ENV{HOME};
111 } else {
112 (getpwuid $<)[7];
113 }
114 }
115 }
116 };
117 unless (defined $homedir) {
118 Carp::croak(
119 "Couldn't resolve homedir for "
120 .(defined $user ? $user : 'current user')
121 .($tried_file_homedir ? '' : ' - consider installing File::HomeDir')
122 );
123 }
124 $path =~ s/^~[^\/]*/$homedir/;
125 $path;
126}
127
128sub resolve_relative_path {
129 my ($class, $path) = @_;
130 File::Spec->rel2abs($path);
131}
132
275c9dae 133=begin testing
134
135#:: test classmethod
b5cc15f7 136
137local *File::Spec::rel2abs = sub { shift; 'FOO'.shift; };
138is($c->resolve_relative_path('bar'),'FOObar');
139
275c9dae 140=end testing
141
b5cc15f7 142=cut
143
144sub setup_local_lib_for {
145 my ($class, $path) = @_;
146 $class->ensure_dir_structure_for($path);
147 if ($0 eq '-') {
148 $class->print_environment_vars_for($path);
149 exit 0;
150 } else {
151 $class->setup_env_hash_for($path);
f9c6b7ff 152 unshift(@INC, split(':', $ENV{PERL5LIB}));
b5cc15f7 153 }
154}
155
156sub modulebuildrc_path {
157 my ($class, $path) = @_;
158 File::Spec->catfile($path, '.modulebuildrc');
159}
160
161sub install_base_bin_path {
162 my ($class, $path) = @_;
163 File::Spec->catdir($path, 'bin');
164}
165
166sub install_base_perl_path {
167 my ($class, $path) = @_;
168 File::Spec->catdir($path, 'lib', 'perl5');
169}
170
171sub install_base_arch_path {
172 my ($class, $path) = @_;
173 File::Spec->catdir($class->install_base_perl_path($path), $Config{archname});
174}
175
176sub ensure_dir_structure_for {
177 my ($class, $path) = @_;
178 unless (-d $path) {
179 warn "Attempting to create directory ${path}\n";
180 }
181 File::Path::mkpath($path);
182 my $modulebuildrc_path = $class->modulebuildrc_path($path);
183 if (-e $modulebuildrc_path) {
184 unless (-f _) {
185 Carp::croak("${modulebuildrc_path} exists but is not a plain file");
186 }
187 } else {
188 warn "Attempting to create file ${modulebuildrc_path}\n";
189 open MODULEBUILDRC, '>', $modulebuildrc_path
190 || Carp::croak("Couldn't open ${modulebuildrc_path} for writing: $!");
18bb63e0 191 print MODULEBUILDRC qq{install --install_base ${path}\n}
b5cc15f7 192 || Carp::croak("Couldn't write line to ${modulebuildrc_path}: $!");
193 close MODULEBUILDRC
194 || Carp::croak("Couldn't close file ${modulebuildrc_path}: $@");
195 }
196}
197
198sub INTERPOLATE_PATH () { 1 }
199sub LITERAL_PATH () { 0 }
200
201sub print_environment_vars_for {
202 my ($class, $path) = @_;
203 my @envs = $class->build_environment_vars_for($path, LITERAL_PATH);
204 my $out = '';
0353dbc0 205 # rather basic csh detection, goes on the assumption that something won't
206 # call itself csh unless it really is. also, default to bourne in the
207 # pathological situation where a user doesn't have $ENV{SHELL} defined.
208 # note also that shells with funny names, like zoid, are assumed to be
209 # bourne.
210 my $shellbin = 'sh';
211 if(defined $ENV{'SHELL'}) {
212 my @shell_bin_path_parts = File::Spec->splitpath($ENV{'SHELL'});
213 $shellbin = $shell_bin_path_parts[-1];
214 }
b5cc15f7 215 while (@envs) {
216 my ($name, $value) = (shift(@envs), shift(@envs));
217 $value =~ s/(\\")/\\$1/g;
618272fe 218
618272fe 219 if($shellbin =~ /csh/) {
220 $out .= qq{setenv ${name} "${value}"\n};
221 }
222 else {
223 $out .= qq{export ${name}="${value}"\n};
224 }
b5cc15f7 225 }
226 print $out;
227}
228
229sub setup_env_hash_for {
230 my ($class, $path) = @_;
231 my %envs = $class->build_environment_vars_for($path, INTERPOLATE_PATH);
232 @ENV{keys %envs} = values %envs;
233}
234
235sub build_environment_vars_for {
236 my ($class, $path, $interpolate) = @_;
237 return (
238 MODULEBUILDRC => $class->modulebuildrc_path($path),
239 PERL_MM_OPT => "INSTALL_BASE=${path}",
240 PERL5LIB => join(':',
241 $class->install_base_perl_path($path),
242 $class->install_base_arch_path($path),
243 ),
244 PATH => join(':',
245 $class->install_base_bin_path($path),
246 ($interpolate == INTERPOLATE_PATH
247 ? $ENV{PATH}
248 : '$PATH')
249 ),
250 )
251}
252
275c9dae 253=begin testing
254
255#:: test classmethod
b5cc15f7 256
257File::Path::rmtree('t/var/splat');
258
4c375968 259$c->ensure_dir_structure_for('t/var/splat');
b5cc15f7 260
261ok(-d 't/var/splat');
262
263ok(-f 't/var/splat/.modulebuildrc');
264
275c9dae 265=end testing
266
b5cc15f7 267=head1 NAME
268
269local::lib - create and use a local lib/ for perl modules with PERL5LIB
270
271=head1 SYNOPSIS
272
273In code -
274
275 use local::lib; # sets up a local lib at ~/perl5
276
277 use local::lib '~/foo'; # same, but ~/foo
278
279From the shell -
280
281 $ perl -Mlocal::lib
282 export MODULEBUILDRC=/home/username/perl/.modulebuildrc
283 export PERL_MM_OPT='INSTALL_BASE=/home/username/perl'
284 export PERL5LIB='/home/username/perl/lib/perl5:/home/username/perl/lib/perl5/i386-linux'
285 export PATH="/home/username/perl/bin:$PATH"
286
bc30e1d5 287To bootstrap if you don't have local::lib itself installed -
288
e423efce 289 $ perl -MCPAN -eshell # you only need to do this if you don't have a ~/.cpan
290 cpan> exit
291 <download local::lib tarball from CPAN, unpack and cd into dir>
bc30e1d5 292 $ perl Makefile.PL --bootstrap
293 $ make test && make install
dc8ddd06 294 $ echo 'eval $(perl -I$HOME/perl5/lib/perl5 -Mlocal::lib)' >>~/.bashrc
618272fe 295 # Or for C shells...
296 $ /bin/csh
297 % echo $SHELL
298 /bin/csh
299 % perl -I$HOME/perl5/lib/perl5 -Mlocal::lib >> ~/.cshrc
dc8ddd06 300
301You can also pass --boostrap=~/foo to get a different location (adjust the
618272fe 302bashrc / cshrc line appropriately)
303
304=head1 DESCRIPTION
305
306This module provides a quick, convenient way of bootstrapping a user-local Perl
307module library located within the user's home directory. It also constructs and
308prints out for the user the list of environment variables using the syntax
309appropriate for the user's current shell (as specified by the C<SHELL>
310environment variable), suitable for directly adding to one's shell configuration
311file.
dc8ddd06 312
313=head1 LIMITATIONS
314
618272fe 315Rather basic shell detection. Right now anything with csh in its name is
316assumed to be a C shell or something compatible, and everything else is assumed
317to be Bourne.
dc8ddd06 318
319Bootstrap is a hack and will use CPAN.pm for ExtUtils::MakeMaker even if you
320have CPANPLUS installed.
321
322Kills any existing PERL5LIB, PERL_MM_OPT or MODULEBUILDRC.
323
e423efce 324Should probably auto-fixup CPAN config if not already done.
325
dc8ddd06 326Patches very much welcome for any of the above.
bc30e1d5 327
618272fe 328=head1 ENVIRONMENT
329
330=over 4
331
332=item SHELL
333
334local::lib looks at the user's C<SHELL> environment variable when printing out
335commands to add to the shell configuration file.
336
337=back
338
b5cc15f7 339=head1 AUTHOR
340
341Matt S Trout <mst@shadowcat.co.uk> http://www.shadowcat.co.uk/
342
d6b71a2d 343auto_install fixes kindly sponsored by http://www.takkle.com/
344
b5cc15f7 345=head1 LICENSE
346
347This library is free software under the same license as perl itself
348
349=cut
350
3511;