1package DocSet::RunTime; 2 3# META: this class acts as Singleton, consider to actually use 4# Class::Singleton 5 6use strict; 7use warnings; 8 9use File::Spec::Functions qw(catdir catfile splitdir); 10use File::Find; 11 12use DocSet::Util; 13use Carp; 14 15use vars qw(@ISA @EXPORT %opts); 16@ISA = qw(Exporter); 17@EXPORT = qw(get_opts find_src_doc set_render_obj get_render_obj unset_render_obj); 18 19my %registers = (); 20 21my @search_paths = (); 22my %src_docs = (); 23my %exts = (); 24my $render_obj; 25 26sub registers_reset { 27 %registers = (); 28} 29 30sub register { 31 my ($register, $key, $val) = @_; 32 push @{$registers{$register}{$key}}, $val; 33 return @{ $registers{$register}{$key} || []}; 34} 35 36 37sub set_opt { 38 my (%args) = (); 39 if (@_ == 1) { 40 my $arg = shift; 41 my $ref = ref $arg; 42 if ($ref) { 43 %args = $ref eq 'HASH' ? %$arg : @$arg; 44 } else { 45 die "must be a ref to or an array/hash"; 46 } 47 } else { 48 %args = @_; 49 } 50 @opts{keys %args} = values %args; 51} 52 53sub get_opts { 54 my $opt = shift; 55 exists $opts{$opt} ? $opts{$opt} : ''; 56} 57 58# check whether we have a Storable avalable 59use constant HAS_STORABLE => eval { require Storable; }; 60sub has_storable_module { 61 return HAS_STORABLE; 62} 63 64# check for existence of html2ps and ps2pdf 65 66my $html2ps_exec = which('html2ps'); 67sub can_create_ps { 68 # ps2html is bundled, so we can always create PS 69 return $html2ps_exec if $html2ps_exec; 70 71 print 'It seems that you do not have html2ps installed! You have', 72 'to install it if you want to generate the PDF file'; 73 return 0; 74 75 # if you unbundle it make sure you write here a code similar to 76 # can_create_pdf() 77} 78 79my $ps2pdf_exec = which('ps2pdf'); 80sub can_create_pdf { 81 # check whether ps2pdf exists 82 return $ps2pdf_exec if $ps2pdf_exec; 83 84 print 'It seems that you do not have ps2pdf installed! You have', 85 'to install it if you want to generate the PDF file'; 86 return 0; 87} 88 89sub scan_src_docs { 90 my ($base, $ra_search_paths, $ra_search_exts) = @_; 91 92 @search_paths = @{$ra_search_paths || []}; 93 94 # .cfg is for matching config.cfg to become index.html 95 %exts = map {$_ => 1} @{$ra_search_exts || []}, 'cfg'; 96 97 my @ext_accept_pattern = map {quotemeta($_)."\$"} keys %exts; 98 my $rsub_keep_ext = 99 build_matchmany_sub(\@ext_accept_pattern); 100 101 my %seen; 102 for my $rel_path (@search_paths) { 103 my $full_base_path = catdir $base, $rel_path; 104 die "'search_paths' attr: $full_base_path is not a dir" 105 unless -d $full_base_path; 106 107 my @seen_pattern = map {"^".quotemeta($_)} keys %seen; 108 my $rsub_skip_seen = build_matchmany_sub(\@seen_pattern); 109 110 my $rel_uri = path2uri($rel_path); 111 $src_docs{$rel_uri} = { 112 # autogenerated index.html 113 map { s/config\.cfg$/index.html/; ($_ => 1) } 114 # full path => relative uri 115 map path2uri( DocSet::Util::abs2rel($_, $full_base_path) ), 116 grep $rsub_keep_ext->($_), # get files with wanted exts 117 grep !$rsub_skip_seen->($_), # skip seen base dirs 118 @{ expand_dir($full_base_path) } 119 }; 120 121 note "Scanning for src files: $full_base_path"; 122 $seen{$full_base_path}++; 123 } 124 125# dumper \%src_docs; 126} 127 128# this function returns a URI, so its separators are always / 129sub find_src_doc { 130 my ($resource_rel_path) = @_; 131 132 # push the html extension, because of autogenerated index.html, 133 # which should be found automatically 134 $exts{html} = 1 unless exists $exts{html}; 135 136 for my $path (keys %src_docs) { 137 if (my $found_path = match_in_doc_src_subset($path, 138 $resource_rel_path)) { 139 return path2uri($found_path); 140 } 141 } 142 143 # if we didn't find anything so far, it's possible that the path was 144 # specified with a longer prefix, that was needed (the above 145 # searches only the end leaves), so try locate the segments of the 146 # search path and search within maching sub-sets 147 for my $path (@search_paths) { 148 if ($resource_rel_path =~ m|^$path/(.*)|) { 149 if (my $found_path = match_in_doc_src_subset($path, $1)) { 150 return path2uri($found_path); 151 } 152 } 153 } 154 155#dumper $src_docs{"docs/1.0"}; 156 return; 157} 158 159# accepts the base_path (from the @search_paths) and the rel_path as 160# args, then it tries to find the match by applying known extensions. 161# 162# if matched, returns the whole path relative to the root, otherwise 163# returns undef 164sub match_in_doc_src_subset { 165 my ($base_path, $rel_path) = @_; 166 for my $ext (keys %exts) { 167#print qq{Try: $base_path :: $rel_path.$ext\n}; 168 if (exists $src_docs{$base_path}{"$rel_path.$ext"}) { 169#print qq{Found $base_path/$rel_path.$ext\n}; 170 return catdir $base_path, "$rel_path.$ext"; 171 } 172 } 173 return; 174} 175 176# set render object: sort of Singleton, it'll complain aloud if the 177# object is set over the existing object, without first unsetting it 178sub set_render_obj { 179 Carp::croak("usage: set_render_obj(\$obj) ") unless @_; 180 Carp::croak("unset render_obj before setting a new one") if $render_obj; 181 Carp::croak("undefined render_obj passed") unless defined $_[0]; 182 $render_obj = shift; 183} 184 185sub get_render_obj { 186 Carp::croak("render_obj is not available") unless $render_obj; 187 188 return $render_obj; 189} 190 191sub unset_render_obj { 192 Carp::croak("render_obj is not set") unless $render_obj; 193 194 undef $render_obj; 195} 196 197 1981; 199__END__ 200 201=head1 NAME 202 203C<DocSet::RunTime> - RunTime Configuration 204 205=head1 SYNOPSIS 206 207 use DocSet::RunTime; 208 209 # run time options 210 DocSet::RunTime::set_opt(\%args); 211 if (get_opts('verbose') { 212 print "verbose mode"; 213 } 214 215 # hosting system capabilities testing 216 DocSet::RunTime::has_storable_module(); 217 DocSet::RunTime::can_create_ps(); 218 DocSet::RunTime::can_create_pdf(); 219 220 # source documents lookup 221 DocSet::RunTime::scan_src_docs($base_path, \@search_paths, \@search_exts); 222 my $full_src_path = find_src_doc($resource_rel_path); 223 224 # rendering object singleton 225 set_render_obj($obj); 226 unset_render_obj(); 227 $obj = get_render_obj(); 228 229=head1 DESCRIPTION 230 231This module is a part of the docset application, and it stores the run 232time arguments, caches results of expensive calls and provide 233Singleton-like service to the whole system. 234 235=head1 FUNCTIONS 236 237META: To be completed, see SYNOPSIS 238 239=head2 Run Time Options 240 241Only get_opts() method is exported by default. 242 243=over 244 245=item * registers_reset() 246 247This function resets various run-time registers, used for validations. 248 249If the runtime is run more than once remember to always run first this 250function and even better always run it before using the runtime. e.g.: 251 252 DocSet::RunTime::registers_reset(); 253 my $docset = DocSet::DocSet::HTML->new($config_file); 254 $docset->set_dir(abs_root => "."); 255 $docset->scan; 256 $docset->render; 257 258=item * register 259 260 my @entries = register($register_name, $key, $val); 261 262Push into the register for a given key the supplied value. 263 264Return an array of the given register's key. 265 266For example used to track duplicated docset ids with: 267 268 my @entries = DocSet::RunTime::register('unique_docset_id', $id, 269 $self->{config_file}); 270 die if @entries > 1; 271 272because if the register returns two value for the same key, someone 273has already registered that key before. The values in C<@entries> 274include the config files in this example. 275 276=item * set_opt(\%args) 277 278 279=item * get_opts() 280 281 282=back 283 284=head2 Hosting System Capabilities Testing 285 286These methods test the capability of the system and are a part of the 287runtime system to perform the checking only once. 288 289=over 290 291=item * has_storable_module 292 293 294=item * can_create_ps 295 296 297=item * can_create_pdf 298 299=back 300 301=head2 Source Documents Lookup 302 303A system for mapping L<> escapes to the located of the rendered 304files. This system scans once the C<@search_paths> for files with 305C<@search_exts> starting from C<$base_path> using scan_src_docs(). The 306C<@search_paths> and C<@search_exts> are configured in the 307I<config.cfg> file. For example: 308 309 dir => { 310 # search path for pods, etc. must put more specific paths first! 311 search_paths => [qw( 312 foo/bar 313 foo 314 . 315 )], 316 # what extensions to search for 317 search_exts => [qw(pod pm html)], 318 }, 319 320So for example if the base path is I<~/myproject/src>, the files with 321extensions I<.pod>, I<.pm> and I<.html> will be searched in 322I<~/myproject/src/foo/bar>, I<~/myproject/src/foo> and 323I<~/myproject/src>. 324 325Notice that you must specify more specific paths first, since for 326optimization the seen paths are skipped. Therefore in our example the 327more explicit path I<foo/bar> was listed before the more general 328I<foo>. 329 330When the POD parser finds a L<> sequence it indentifies the resource 331part and passes it to the find_src_doc() which looks up for this file 332in the cache and returns its original (src) location, which can be 333then easily converted to the final location and optionally adjusting 334the extension, e.g. when the POD file is converted to HTML. 335 336As a special extension this function automatically assumes that 337C<index.html> will be generated in each directory containing items of 338an interest. Therefore in find_src_doc() it'll automatically find 339things like: L<the guide|guide::index>, even though there was no 340source I<index.pod> or I<index.html> in first place. 341 342Only the find_src_doc() function is exported by default. 343 344=over 345 346=item * scan_src_docs($base_path, \@search_paths, \@search_exts); 347 348=item * find_src_doc($resource_rel_path); 349 350returns C<undef> if nothing was found. See the description above. 351 352=back 353 354 355=head2 Rendering Object Singleton 356 357Since the rendering process may happen by a third party system, into 358which we provide hooks or overload some of its methods, it's quite 359possible that we won't be able to access the current document (or 360better rendering) object. One solution would be to have a global 361package variable, but that's very error-prone. Therefore the used 362solution is to provide a hook into a RunTime environment setting the 363current rendering object when the rendering of a single page starts 364via C<set_render_obj($obj)> and unsetting it when it's finished via 365unset_render_obj(). Between these two moments the current rendering 366object can be retrieved with get_render_obj() method. 367 368Notice that this is all possible in the program which is not threaded, 369or/and only one rendering process exists at any given time from its 370start to its end. 371 372All three methods are exported by default. 373 374=over 375 376=item * set_render_obj($obj) 377 378Sets the current rendering object. 379 380You cannot set a new rendering object before the previous one is 381unset. This is in order to make sure that one document won't use by 382mistake a rendering object of another document. So when the rendering 383is done remember to call the unset_render_obj() function. 384 385=item * unset_render_obj() 386 387Unsets the currently set rendering object. 388 389=item * get_render_obj() 390 391Retrieves the currently set rendering object or complains aloud if it 392cannot find one. 393 394=back 395 396=head1 AUTHORS 397 398Stas Bekman E<lt>stas (at) stason.orgE<gt> 399 400=cut 401