lib::local code
[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
22sub compose;
23
24sub compose {
25 my @methods = @_;
26 my $last = pop(@methods);
27 if (@methods) {
28 \sub {
29 my ($obj, @args) = @_;
30 $obj->${compose @methods}(
31 $obj->$last(@args)
32 );
33 };
34 } else {
35 \sub {
36 shift->$last(@_);
37 };
38 }
39}
40
41=for test
42
43package local::lib;
44
45{ package Foo; sub foo { -$_[1] } sub bar { $_[1]+2 } sub baz { $_[1]+3 } }
46my $foo = bless({}, 'Foo');
47ok($foo->${compose qw(foo bar baz)}(10) == -15);
48
49=cut
50
51sub resolve_path {
52 my ($class, $path) = @_;
53 $class->${compose qw(
54 resolve_relative_path
55 resolve_home_path
56 resolve_empty_path
57 )}($path);
58}
59
60sub resolve_empty_path {
61 my ($class, $path) = @_;
62 if (defined $path) {
63 $path;
64 } else {
65 '~/perl5';
66 }
67}
68
69=for test classmethod setup
70
71my $c = 'local::lib';
72
73=cut
74
75=for test classmethod
76
77is($c->resolve_empty_path, '~/perl5');
78is($c->resolve_empty_path('foo'), 'foo');
79
80=cut
81
82sub resolve_home_path {
83 my ($class, $path) = @_;
84 return $path unless ($path =~ /^~/);
85 my ($user) = ($path =~ /^~([^\/]+)/); # can assume ^~ so undef for 'us'
86 my $tried_file_homedir;
87 my $homedir = do {
88 if (eval { require File::HomeDir } && $File::HomeDir::VERSION >= 0.65) {
89 $tried_file_homedir = 1;
90 if (defined $user) {
91 File::HomeDir->users_home($user);
92 } else {
93 File::HomeDir->my_homedir;
94 }
95 } else {
96 if (defined $user) {
97 (getpwnam $user)[7];
98 } else {
99 if (defined $ENV{HOME}) {
100 $ENV{HOME};
101 } else {
102 (getpwuid $<)[7];
103 }
104 }
105 }
106 };
107 unless (defined $homedir) {
108 Carp::croak(
109 "Couldn't resolve homedir for "
110 .(defined $user ? $user : 'current user')
111 .($tried_file_homedir ? '' : ' - consider installing File::HomeDir')
112 );
113 }
114 $path =~ s/^~[^\/]*/$homedir/;
115 $path;
116}
117
118sub resolve_relative_path {
119 my ($class, $path) = @_;
120 File::Spec->rel2abs($path);
121}
122
123=for test classmethod
124
125local *File::Spec::rel2abs = sub { shift; 'FOO'.shift; };
126is($c->resolve_relative_path('bar'),'FOObar');
127
128=cut
129
130sub setup_local_lib_for {
131 my ($class, $path) = @_;
132 $class->ensure_dir_structure_for($path);
133 if ($0 eq '-') {
134 $class->print_environment_vars_for($path);
135 exit 0;
136 } else {
137 $class->setup_env_hash_for($path);
138 }
139}
140
141sub modulebuildrc_path {
142 my ($class, $path) = @_;
143 File::Spec->catfile($path, '.modulebuildrc');
144}
145
146sub install_base_bin_path {
147 my ($class, $path) = @_;
148 File::Spec->catdir($path, 'bin');
149}
150
151sub install_base_perl_path {
152 my ($class, $path) = @_;
153 File::Spec->catdir($path, 'lib', 'perl5');
154}
155
156sub install_base_arch_path {
157 my ($class, $path) = @_;
158 File::Spec->catdir($class->install_base_perl_path($path), $Config{archname});
159}
160
161sub ensure_dir_structure_for {
162 my ($class, $path) = @_;
163 unless (-d $path) {
164 warn "Attempting to create directory ${path}\n";
165 }
166 File::Path::mkpath($path);
167 my $modulebuildrc_path = $class->modulebuildrc_path($path);
168 if (-e $modulebuildrc_path) {
169 unless (-f _) {
170 Carp::croak("${modulebuildrc_path} exists but is not a plain file");
171 }
172 } else {
173 warn "Attempting to create file ${modulebuildrc_path}\n";
174 open MODULEBUILDRC, '>', $modulebuildrc_path
175 || Carp::croak("Couldn't open ${modulebuildrc_path} for writing: $!");
176 print MODULEBUILDRC qq{--install_base ${path}\n}
177 || Carp::croak("Couldn't write line to ${modulebuildrc_path}: $!");
178 close MODULEBUILDRC
179 || Carp::croak("Couldn't close file ${modulebuildrc_path}: $@");
180 }
181}
182
183sub INTERPOLATE_PATH () { 1 }
184sub LITERAL_PATH () { 0 }
185
186sub print_environment_vars_for {
187 my ($class, $path) = @_;
188 my @envs = $class->build_environment_vars_for($path, LITERAL_PATH);
189 my $out = '';
190 while (@envs) {
191 my ($name, $value) = (shift(@envs), shift(@envs));
192 $value =~ s/(\\")/\\$1/g;
193 $out .= qq{export ${name}="${value}"\n};
194 }
195 print $out;
196}
197
198sub setup_env_hash_for {
199 my ($class, $path) = @_;
200 my %envs = $class->build_environment_vars_for($path, INTERPOLATE_PATH);
201 @ENV{keys %envs} = values %envs;
202}
203
204sub build_environment_vars_for {
205 my ($class, $path, $interpolate) = @_;
206 return (
207 MODULEBUILDRC => $class->modulebuildrc_path($path),
208 PERL_MM_OPT => "INSTALL_BASE=${path}",
209 PERL5LIB => join(':',
210 $class->install_base_perl_path($path),
211 $class->install_base_arch_path($path),
212 ),
213 PATH => join(':',
214 $class->install_base_bin_path($path),
215 ($interpolate == INTERPOLATE_PATH
216 ? $ENV{PATH}
217 : '$PATH')
218 ),
219 )
220}
221
222=for test classmethod
223
224File::Path::rmtree('t/var/splat');
225
226$c->resolve_relative_path('t/var/splat');
227
228ok(-d 't/var/splat');
229
230ok(-f 't/var/splat/.modulebuildrc');
231
232=head1 NAME
233
234local::lib - create and use a local lib/ for perl modules with PERL5LIB
235
236=head1 SYNOPSIS
237
238In code -
239
240 use local::lib; # sets up a local lib at ~/perl5
241
242 use local::lib '~/foo'; # same, but ~/foo
243
244From the shell -
245
246 $ perl -Mlocal::lib
247 export MODULEBUILDRC=/home/username/perl/.modulebuildrc
248 export PERL_MM_OPT='INSTALL_BASE=/home/username/perl'
249 export PERL5LIB='/home/username/perl/lib/perl5:/home/username/perl/lib/perl5/i386-linux'
250 export PATH="/home/username/perl/bin:$PATH"
251
252=head1 AUTHOR
253
254Matt S Trout <mst@shadowcat.co.uk> http://www.shadowcat.co.uk/
255
256=head1 LICENSE
257
258This library is free software under the same license as perl itself
259
260=cut
261
2621;