1 | use strict; |
---|
2 | use warnings; |
---|
3 | |
---|
4 | package BarnOwl::ModuleLoader; |
---|
5 | |
---|
6 | use PAR; |
---|
7 | |
---|
8 | our %modules; |
---|
9 | |
---|
10 | sub load_all { |
---|
11 | my $class = shift; |
---|
12 | $class->rescan_modules; |
---|
13 | PAR::reload_libs(); |
---|
14 | |
---|
15 | for my $mod (keys %modules) { |
---|
16 | if(!defined eval "use BarnOwl::Module::$mod") { |
---|
17 | BarnOwl::error("Unable to load module $mod: \n$@\n") if $@; |
---|
18 | } |
---|
19 | } |
---|
20 | |
---|
21 | $BarnOwl::Hooks::startup->add(\®ister_keybindings); |
---|
22 | } |
---|
23 | |
---|
24 | =h2 rescan_modules |
---|
25 | |
---|
26 | Re-compute the list of available modules, and add the necessary items to @INC |
---|
27 | and @PAR_INC. |
---|
28 | |
---|
29 | We load modules from two directories, the system module dir, and the user module |
---|
30 | directory. Modules can be in either of two forms: ${modname}.par, or else a |
---|
31 | ${modname}/ directory containing lib/${modname}. |
---|
32 | |
---|
33 | We prefer to load modules from the user's directory, and if a module exists in |
---|
34 | both packed and unpacked form in the same directory, we prefer the unpacked |
---|
35 | module. |
---|
36 | |
---|
37 | We walk the module directories in order of ascending priority -- user directory, |
---|
38 | and then system directory. |
---|
39 | |
---|
40 | When we walk the directories, we first check all things that are not named |
---|
41 | Foo.par, add them to @INC, and add them to the module list. We then walk the |
---|
42 | .par files and add them to @PAR_INC and update the module list. |
---|
43 | |
---|
44 | It is important that we never add a module to @INC (or @PAR_INC) if we already |
---|
45 | have a source for it, in order to get priorities right. The reason is that @INC |
---|
46 | is processed before @PAR_INC, so if we had an unpacked system module and a |
---|
47 | packed local module, if we added both, the system module would take priority. |
---|
48 | |
---|
49 | =cut |
---|
50 | |
---|
51 | sub rescan_modules { |
---|
52 | @PAR::PAR_INC = (); |
---|
53 | |
---|
54 | %modules = (); |
---|
55 | |
---|
56 | my @moddirs = (); |
---|
57 | push @moddirs, BarnOwl::get_config_dir() . "/modules"; |
---|
58 | push @moddirs, BarnOwl::get_data_dir() . "/modules"; |
---|
59 | |
---|
60 | for my $dir (@moddirs) { |
---|
61 | # Strip defunct entries from @INC |
---|
62 | @INC = grep {!/^\Q$dir/} @INC; |
---|
63 | |
---|
64 | opendir(my $dh, $dir) or next; |
---|
65 | my @ents = grep {!/^\./} readdir($dh); |
---|
66 | |
---|
67 | # Walk unpacked modules |
---|
68 | for my $f (grep {!/\.par$/} @ents) { |
---|
69 | if(-d "$dir/$f" && -d "$dir/$f/lib") { |
---|
70 | next if $modules{$f}; |
---|
71 | |
---|
72 | unshift @INC, "$dir/$f/lib" unless grep m{^$dir/$f/lib$}, @INC; |
---|
73 | $modules{$f} = 1; |
---|
74 | } |
---|
75 | } |
---|
76 | |
---|
77 | # Walk parfiles |
---|
78 | for my $f (grep /\.par$/, @ents) { |
---|
79 | if(-f "$dir/$f" && $f =~ /^(.+)\.par$/) { |
---|
80 | next if $modules{$1}; |
---|
81 | |
---|
82 | PAR->import("$dir/$f"); |
---|
83 | $modules{$1} = 1; |
---|
84 | } |
---|
85 | } |
---|
86 | |
---|
87 | closedir($dh); |
---|
88 | } |
---|
89 | } |
---|
90 | |
---|
91 | sub reload_module { |
---|
92 | my $class = shift; |
---|
93 | my $module = shift; |
---|
94 | |
---|
95 | $class->rescan_modules(); |
---|
96 | |
---|
97 | for my $m (keys %INC) { |
---|
98 | delete $INC{$m} if $m =~ m{^BarnOwl/Module/$module}; |
---|
99 | } |
---|
100 | |
---|
101 | my $parfile; |
---|
102 | for my $p (@PAR::PAR_INC) { |
---|
103 | if($p =~ m/\Q$module\E[.]par$/) { |
---|
104 | $parfile = $p; |
---|
105 | last; |
---|
106 | } |
---|
107 | } |
---|
108 | |
---|
109 | local $SIG{__WARN__} = \&squelch_redefine; |
---|
110 | |
---|
111 | if(defined $parfile) { |
---|
112 | PAR::reload_libs($parfile); |
---|
113 | $class->run_startup_hooks(); |
---|
114 | } elsif(!defined eval "use BarnOwl::Module::$module") { |
---|
115 | BarnOwl::error("Unable to load module $module: \n$@\n") if $@; |
---|
116 | } |
---|
117 | } |
---|
118 | |
---|
119 | sub reload_module_cmd { |
---|
120 | my $class = shift; |
---|
121 | shift; # Command |
---|
122 | my $module = shift; |
---|
123 | unless(defined($module)) { |
---|
124 | die("Usage: reload-module [MODULE]"); |
---|
125 | }; |
---|
126 | $class->reload_module($module); |
---|
127 | } |
---|
128 | |
---|
129 | sub register_keybindings { |
---|
130 | BarnOwl::new_command('reload-modules', sub {BarnOwl::ModuleLoader->reload}, { |
---|
131 | summary => 'Reload all modules', |
---|
132 | usage => 'reload-modules', |
---|
133 | description => q{Reloads all modules located in ~/.owl/modules and the system modules directory} |
---|
134 | }); |
---|
135 | BarnOwl::new_command('reload-module', sub {BarnOwl::ModuleLoader->reload_module_cmd(@_)}, { |
---|
136 | summary => 'Reload one module', |
---|
137 | usage => 'reload-module [MODULE]', |
---|
138 | description => q{Reloads a single module located in ~/.owl/modules or the system modules directory} |
---|
139 | }); |
---|
140 | } |
---|
141 | |
---|
142 | sub reload { |
---|
143 | my $class = shift; |
---|
144 | for my $m (keys %INC) { |
---|
145 | delete $INC{$m} if $m =~ m{^BarnOwl/}; |
---|
146 | } |
---|
147 | # Restore core modules from perlwrap.pm |
---|
148 | $INC{$_} = 1 for (qw(BarnOwl.pm BarnOwl/Hooks.pm |
---|
149 | BarnOwl/Message.pm BarnOwl/Style.pm)); |
---|
150 | $class->run_startup_hooks(); |
---|
151 | } |
---|
152 | |
---|
153 | sub run_startup_hooks { |
---|
154 | my $class = shift; |
---|
155 | local $SIG{__WARN__} = \&squelch_redefine; |
---|
156 | $class->load_all; |
---|
157 | $BarnOwl::Hooks::startup->run(1); |
---|
158 | BarnOwl::startup() if *BarnOwl::startup{CODE}; |
---|
159 | } |
---|
160 | |
---|
161 | sub squelch_redefine { |
---|
162 | my $warning = shift; |
---|
163 | warn $warning unless $warning =~ /^Subroutine .+ redefined at/; |
---|
164 | } |
---|
165 | |
---|
166 | 1; |
---|