File Coverage

blib/lib/lib/remote.pm
Criterion Covered Total %
statement 30 85 35.2
branch 3 46 6.5
condition 0 26 0.0
subroutine 8 12 66.6
pod 3 3 100.0
total 44 172 25.5


line stmt bran cond sub pod time code
1             package lib::remote;
2              
3 1     1   47520 use 5.006;
  1         4  
  1         40  
4 1     1   6 use strict;
  1         2  
  1         34  
5 1     1   5 use warnings;
  1         6  
  1         28  
6 1     1   1080 use LWP::UserAgent;
  1         3224752  
  1         39  
7             #~ use Data::Dumper;
8 1     1   12 use Carp qw(croak carp);
  1         2  
  1         673  
9              
10             =head1 VERSION
11              
12             Version 0.11
13              
14             =cut
15              
16             our $VERSION = '0.11';
17             our $CODE_NAME = 'Gloria';
18             my $pkg = __PACKAGE__;
19              
20             my $url_re = qr#^(https?|ftp|file)://#i;
21              
22             my $config = {#сохранение списка пар "Имя::модуля"=>{opt_name => ..., ..., ...}
23             $pkg => {
24             ua => LWP::UserAgent->new,
25             require=>1,
26             cache=>1,
27             debug=>0,
28             _INC=> [],# список общих путей like @INC
29             },
30             };
31              
32             my $module_content = sub {# @INC диспетчер
33             my $arg = shift;#Имя/Модуля.pm
34            
35             my $path = my $mod = $arg;
36             $mod =~ s|/+|::|g;
37             $mod =~ s|\.pm$||g;
38             $path =~ s|::|/|g;
39             $path =~ s|\.pm$||;
40             $path .= '.pm';
41            
42             my $conf = $config->{$mod};
43             my $debug = ($conf && $conf->{debug}) // $config->{$pkg}{debug};
44             my $cache = ($conf && $conf->{cache}) // $config->{$pkg}{cache};
45            
46             carp "$pkg->INC_dispatcher: try dispatch of [$mod][path=$path][arg=$arg]]" if $debug;
47            
48             if ($cache && $conf && $conf->{_content}) {
49             carp "$pkg->INC_dispatcher: get cached content of [$mod]" if $debug;
50             return $conf->{_content};
51             }
52              
53             my $content;
54            
55             if ($conf && $conf->{url} && $conf->{url} =~ /$url_re/) {# конкретный модуль
56             my $url = $conf->{url};
57             $url .= $conf->{url_suffix} if $conf->{url_suffix};
58             $content = _lwpget($url);
59             carp "$pkg->INC_dispatcher: success LWP get content [$mod] by url=[ $url ]" if $debug && $content;
60             carp "$pkg->INC_dispatcher: couldn't get content the module [$mod] by url=[ $url ]" if $debug && !$content;
61             }
62             unless ($content) {# перебор удаленных папок
63             for (@{$config->{$pkg}{_INC}}) {
64             s|/$||;
65             my $url = "$_/$path";
66             $url .= $conf->{url_suffix} if $conf->{url_suffix};
67             #~ carp "$pkg: try get [$_/$path] for [$mod]" if $debug;#": get [$mod] content";
68             $content = _lwpget($url);
69             if ($content) {
70             carp "$pkg->INC_dispatcher: success LWP get content [$mod] by url [ $url ]" if $debug;#": get [$mod] content";
71             last;
72             }
73             }
74             }
75            
76             $config->{$mod}{_content} = $content if $cache && $content;
77              
78             return $content;
79             };
80              
81             BEGIN {
82             push @INC, sub {# диспетчер
83 1         3 my $self = shift;# эта функция CODE(0xf4d728) вроде не нужна
84 1 50       8 my $content = $module_content->(@_)
85             or return undef;
86 0 0       0 open my $fh, '<', \$content or die "Cant open: $!";
87 0         0 return $fh;
88 1     1   1227 };
89             }
90              
91             sub import { # это разбор аргументов после строк use lib::remote ...
92 1     1   15 my $pkg_or_obj = shift;
93 1 50       7 carp "$pkg->import: incoming args = [ @_ ]" if $config->{$pkg}{debug};
94 1         6 $pkg->config(@_);
95            
96 1         1 my $module;
97 1         4 for my $module (@{$config->{$pkg}{_last_config_modules}}) {
  1         17  
98 0   0     0 my $require = $config->{$module}{require} // $config->{$pkg}{require};
99 0   0     0 my $debug = $config->{$module}{debug} // $config->{$pkg}{debug};
100 0 0       0 if ( $require ) {
101             #~ eval "use $module;";# вот сразу заход в диспетчер @INC
102 0         0 eval {require $module};
  0         0  
103 0 0       0 if ($@) {
    0          
104 0         0 carp "$pkg->import: возможно проблемы с модулем [$module]: $@";
105             } elsif ($debug) {
106 0 0       0 carp "$pkg->import: success done [require $module]\n" if $debug;
107 0         0 $config->{$module}{_require_ok}++;
108             }
109             }
110 0         0 my $import = $config->{$module}{import};# || $config->{$pkg}{import};
111            
112 0 0 0     0 if ($require && $import && @$import) {
      0        
113 0         0 eval {$module->import(@$import)};
  0         0  
114 0 0       0 if ($@) {
115 0         0 carp "$pkg->import: возможно проблемы с импортом [$module]: $@";
116             } else {
117 0 0       0 carp "$pkg->import: success done [$module->import(@{[@$import]})]\n" if $debug;
  0         0  
118 0         0 $config->{$module}{_import_ok}++;
119             }
120             }
121             }
122             }
123              
124             sub config {
125 1     1 1 3 my $pkg_or_obj = shift;
126 1         3 my $module;
127 1         3 delete $config->{$pkg}{_last_config_modules};
128 1         6 for my $arg (@_) {
129 0         0 my $opt = _opt($arg);
130 0 0 0     0 if ($module) {
    0          
    0          
131 0 0       0 if ( $module eq $pkg ) {
132 0         0 my $url = delete $opt->{url};
133 0 0 0     0 push @{$config->{$pkg}{_INC}}, $url if $url && $url =~ /$url_re/ && !($url ~~ @{$config->{$pkg}{_INC}});
  0   0     0  
  0         0  
134             } else {
135 0         0 push @{$config->{$pkg}{_last_config_modules}}, $module;
  0         0  
136             }
137 0         0 @{$config->{$module}}{keys %$opt} = values %$opt;
  0         0  
138 0         0 $module = undef; # done pair
139             } elsif ($opt->{url} && $opt->{url} =~ /$url_re/) {
140 0 0       0 push @{$config->{$pkg}{_INC}}, $opt->{url} unless $opt->{url} ~~ @{$config->{$pkg}{_INC}};#$unique{$arg}++;
  0         0  
  0         0  
141             } elsif (!ref($arg)) {
142 0         0 $module = $arg;
143             } else {
144 0         0 @{$config->{$pkg}}{keys %$opt} = values %$opt; # [] {}
  0         0  
145             }
146             }
147 1 50       5 push @{$config->{$pkg}{_last_config_modules}}, $module if $module;
  0         0  
148 1         3 return $config;
149             }
150              
151             sub new {
152 0     0 1   my $pkg = shift;
153 0           bless $pkg->config(@_);
154             }
155              
156             sub module {
157 0     0 1   my $pkg_or_obj = shift;
158 0           $pkg_or_obj->import(@_);
159 0           return $config->{$pkg}{_last_config_modules}[0];
160             }
161              
162             sub _opt {
163 0     0     my $arg = shift;
164 0 0         return {} unless defined $arg;
165 0 0         my $ret = {url=>$arg,} unless ref($arg);
166 0 0 0       $ret ||= {$arg->[0] =~ /$url_re/ ? (url=>@$arg) : @$arg,} if ref($arg) eq 'ARRAY';
    0          
167 0 0 0       $ret ||= $arg if ref($arg) eq 'HASH';
168 0           return $ret;
169             }
170              
171             sub _lwpget {
172 0     0     my $url = shift;
173 0           my $get = $config->{$pkg}{ua}->get($url);
174 0 0         if ( $get->is_success ) {
175 0           return $get->decoded_content();# ??? ->content нужно отладить charset=>'cp-1251'
176             } else {
177 0           return undef;
178             }
179             }
180              
181             =encoding utf8
182              
183             =head1 ПРИВЕТСТВИЕ SALUTE
184              
185             Доброго всем! Доброго здоровья! Доброго духа!
186              
187             Hello all! Nice health! Good thinks!
188              
189              
190             =head1 NAME
191              
192             lib::remote - pragma, functional and object interface for load and use/require modules from remote sources without installation basically throught protocols like http (LWP). One dispather on @INC - C This dispather will return filehandle for downloaded content of a module from remote server.
193              
194             lib::remote - Удаленная загрузка и использование модулей. Загружает модули с удаленного сервера. Только один диспетчер в @INC- C. Диспетчер возвращает filehandle для контента, полученного удаленно. Смотреть perldoc -f require.
195              
196             Идея из L
197              
198             Кто-то еще стырил L (поздняя дата и есть ошибки)
199              
200              
201             =head1 FAQ
202              
203             Q: Зачем? Why?
204              
205             A: За лосем. For elk.
206              
207             Q: Почему? And so why?
208              
209             A: По кочану. For head of cabbage.
210              
211             Q: Как? How?
212              
213             A: Да вот так. Da vot tak.
214              
215              
216             =head1 SYNOPSIS
217              
218             Все просто. По аналогии с локальным вариантом:
219              
220             use lib '/to/any/local/lib';
221              
222             указываем урл:
223              
224             # pragma interface at compile time
225             use lib::remote 'http://<хост(host)>/site-perl/.../';
226             use My::Module1;
227             ...
228              
229             Искомый модуль будет запрашиваться как в локальном варианте, дописывая в конце URL: http://<хост(host)>/site-perl/.../My/Module1.pm
230              
231             Допустим, УРЛ сложнее, не содержит имени модуля или используются параметры: https://<хост>/.../?key=ede35ac1208bbf479&...
232              
233             Тогда делаем пары ключ->значение, указывая КОНКРЕТНЫЙ урл для КОНКРЕТНОГО модуля, например:
234              
235             use lib::remote
236             'Some::Module1'=>'https://<хост>/.../?key=ede35ac1208bbf479&...',
237             'SomeModule2'=>'ssh://user:pass@host:/..../SomeModule2.pm',
238             ;
239             #use Some::Module1; не нужно, уже сделано require (см. "Опцию [require] расширенного синтаксиса")
240             use SomeModule2 qw(func1 func2), [, ...];# только, если нужно что-то импортировать очень сложное (см. "Опцию [import] расширенного синтаксиса")
241             use parent 'Some::Module1'; # такое нужно
242             ...
243              
244              
245             B<Внимание>
246              
247             Конкретно указанный модуль (через пару) будет искаться сначала в своем урл, а потом во всех заданных урлах глобального конфига.
248              
249             При многократном вызове use lib::remote все параметры и урлы сохраняются, аналогично use lib '';, но естественно не в @INC. Повторюсь, в @INC помещается только один диспетчер.
250              
251             =head2 Расширенный синтаксис Extended syntax
252              
253             =head3 Pragma variant
254              
255             use lib::remote
256             # global config for modules unless them have its own
257             'http://....', # push to search list urls
258             ['http://....', opt1 =>..., opt2 =>..., ....], # push to search list urls
259             {url=>'http://....', opt1 =>..., opt2 =>..., ....}, # push to search list urls
260              
261             and per module personal options
262              
263             'Some::Module1'=> 'http://....',
264             'Some::Module2'=>['http://...', opt1 =>..., opt2 =>..., ....],
265             'Some::Module3'=>{url => 'http://...', opt1 =>..., opt2 =>..., ....},
266             'SomeModule1'=>['ssh://user@host:/..../SomeModule2.pm', 'pass'=>..., ...],
267             'SomeModule2'=>{url => 'ssh://user@host:/..../SomeModule2.pm', 'pass'=>..., ...},
268             ;
269              
270              
271             =head3 Functional variant - is runtime and you cant import symbols
272              
273             use lib::remote;
274             my $conf = lib::remote->config('http://....');
275             # DONT WORK -> lib::remote::module('Foo::');
276             # OK
277             lib::remote->module('Foo::One'=>'http://...', )::foo(...);
278            
279              
280             =head3 Object variant - is runtime and you cant import symbols
281              
282             use lib::remote;
283             my $dispatcher = lib::remote->new();
284             my $foo2 = $dispatcher->module('Foo::Two')->new();
285              
286              
287              
288             =head2 Опции Options
289              
290             Не трудно догадаться, что вычленение пар в общем списке import/config происходит по специфике URI.
291              
292             =over 4
293              
294             =item * url => '>schema://>...' Это основной параметр. На уровне глобальной конфигурации сохраняется список всех урлов, к которым добавляется путь Some/Module.pm
295              
296             =item * url_suffix
297              
298             =item * charset => 'utf8', Задать кодировку урла. Если веб-сервер правильно выдает C, тогда не нужно, ->decoded_content сработает. Помнить про C
299              
300             =item * require => 1|0 Cрабатывает require Some::Module1; Поэтому не нужно делать строку use|require Some::Module;, если только нет хитрых импортов (см. опцию import ниже)
301              
302             =item * import => [qw(), ...]. The import spec for loaded module. Disadvantage!!! Work on list of scalars only!!! Просто вызывается Some::Module1->import(...);
303              
304             =item * cache => 1|0 Content would be cached
305              
306             =item * debug => 0|1 warn messages
307              
308             =item * что еще?
309              
310             =back
311              
312              
313             Можно многократно вызывать use lib::remote ...; и тем самым изменять настройки модулей и глобальные опции.
314              
315             Url может возвращать сразу пачку модулей (package). В этом случае писать ключом один модуль и дополнительно вызывать use/require для остальных модулей.
316              
317             =head1 EXPORT
318              
319             Ничего не экспортируется.
320              
321             =head1 SUBROUTINES/METHODS
322              
323             This is runtime.
324              
325             =head2 new() Create lib::remote object and apply/merge options. All created objects are one variable.
326              
327             =head2 module() Try to load and require modules. Return the name of first parsed module in list of options.
328              
329             =head2 config() Apply/merge options to lib::remote package.
330              
331             =head1 Требования REQUIRES
332              
333             Если урлы 'http://...', 'https://...', 'ftp://...', 'file://...', то нужен LWP::UserAgent
334              
335             Если 'ssh://...' - TODO
336              
337             =head1 Пример конфига для NGINX, раздающего модули:
338              
339             ...
340             server {
341             listen 81;
342             # server_name localhost;
343              
344              
345             location / {
346             charset utf-8;
347             charset_types *;
348             root /home/perl/lib-remote/;
349             index index.html index.htm;
350             }
351              
352             }
353             ...
354              
355              
356             =head1 AUTHOR
357              
358             Mikhail Che, C<< >>
359              
360             =head1 BUGS
361              
362             Пишите.
363              
364             Please report any bugs or feature requests to C, or through
365             the web interface at L. I will be notified, and then you'll
366             automatically be notified of progress on your bug as I make changes.
367              
368              
369              
370              
371             =head1 SUPPORT
372              
373             You can find documentation for this module with the perldoc command.
374              
375             perldoc lib::remote
376              
377              
378             You can also look for information at:
379              
380             =over 4
381              
382             =item * RT: CPAN's request tracker (report bugs here)
383              
384             L
385              
386             =item * AnnoCPAN: Annotated CPAN documentation
387              
388             L
389              
390             =item * CPAN Ratings
391              
392             L
393              
394             =item * Search CPAN
395              
396             L
397              
398             =back
399              
400              
401             =head1 ACKNOWLEDGEMENTS
402              
403             Не знаю.
404              
405             =head1 SEE ALSO
406              
407             perldoc -f require.
408              
409             Глянь L
410              
411             Глянь L
412              
413             Глянь L
414              
415             Глянь L
416              
417             =head1 LICENSE AND COPYRIGHT
418              
419             Copyright 2012-2013 Mikhail Che.
420              
421             This program is free software; you can redistribute it and/or modify it
422             under the terms of either: the GNU General Public License as published
423             by the Free Software Foundation; or the Artistic License.
424              
425             See http://dev.perl.org/licenses/ for more information.
426              
427             =head1 DISTRIB
428              
429             $ module-starter --module=lib::remote --author=”Mikhail Che” --email=”m.che@cpan.org" --builder=Module::Build --license=perl --verbose
430              
431             $ perl Build.PL
432              
433             $ ./Build test
434              
435             $ ./Build dist
436              
437              
438             =cut
439              
440             1; # End of lib::remote