package DocSet::Util; use strict; use warnings; use Symbol (); use File::Basename (); use File::Copy (); use File::Path (); use File::Find (); use Data::Dumper; use Carp; use Template; use File::Spec; use File::Spec::Functions; require DocSet::RunTime; # interdependency with DocSet::Util use constant IS_WIN32 => $^O eq 'MSWin32'; use constant PERL_LT_580 => $] < 5.008; use vars qw(@ISA @EXPORT); @ISA = qw(Exporter); @EXPORT = qw(read_file read_file_paras copy_file gzip_file write_file create_dir filename filename_ext require_package dumper sub_trace note get_date get_timestamp proc_tmpl build_matchmany_sub banner confess cluck carp format_bytes expand_dir which path2uri); # copy_file($src_path, $dst_path); # copy a file at $src_path to $dst_path, # if one of the directories of the $dst_path doesn't exist -- it'll # be created. ############### sub copy_file { my ($src, $dst) = @_; die "$src doesn't exist" unless -e $src; my $mode = (stat _)[2]; # make sure that the directory exist or create one my $base_dir = File::Basename::dirname $dst; create_dir($base_dir) unless (-d $base_dir); # File::Copy::syscopy doesn't preserve the mode :( File::Copy::syscopy($src, $dst); chmod $mode, $dst; } # gzip_file($src_path); # gzip a file at $src_path ############### sub gzip_file { my ($src) = @_; system "gzip -f $src"; } # write_file($filename, $ref_to_array||scalar); # content will be written to the file from the passed array of # paragraphs ############### sub write_file { my ($filename, $content) = @_; # make sure that the directory exist or create one my $dir = File::Basename::dirname $filename; create_dir($dir) unless -d $dir; my $fh = Symbol::gensym; open $fh, ">$filename" or croak "Can't open $filename for writing: $!"; print $fh ref $content ? @$content : defined $content ? $content : ''; close $fh; } # recursively creates a multi-layer directory ############### sub create_dir { my $path = shift; return if !defined($path) || -e $path; # META: mode could be made configurable File::Path::mkpath($path, 0, 0755) or croak "Couldn't create $path: $!"; } # read_file($filename, $ref); # assign to a ref to a scalar ############### sub read_file { my ($filename, $r_content) = @_; my $fh = Symbol::gensym; open $fh, $filename or croak "Can't open $filename for reading: $!"; local $/; $$r_content = <$fh>; close $fh; } # read_file_paras($filename, $ref_to_array); # read by paragraph # content will be set into a ref to an array ############### sub read_file_paras { my ($filename, $ra_content) = @_; my $fh = Symbol::gensym; open $fh, $filename or croak "Can't open $filename for reading: $!"; local $/ = ""; @$ra_content = <$fh>; close $fh; } # return the filename part of the path sub filename { my ($path) = @_; return File::Basename::basename($path); } # return the passed file's extension or '' if there is no one # note: that '/foo/bar.conf.in' returns an extension: 'conf.in'; # note: a hidden file .foo will be recognized as an extension 'foo' sub filename_ext { my ($filename) = @_; my $ext = (File::Basename::fileparse($filename, '\.[^\.]*'))[2] || ''; $ext =~ s/^\.(.*)/lc $1/e; $ext; } # since on non-Unix platforms the fs path's separator don't match the # URI separator ('/'), we need to rewrite those paths # accept a relative native path # return relative URI sub path2uri { return unless defined $_[0]; return join '/', File::Spec->splitdir(shift); } # File::Spec->abs2rel doesn't strip the volume (e.g. /^c:/) before # Perl v5.8.0 on Win32. This function fixes this bug. # # Make sure to call this function as DocSet::Util::abs2rel, especially # in the code that already uses File::Spec functions. sub abs2rel { my $res = File::Spec->abs2rel(@_); $res =~ s/^[a-zA-Z]:// if IS_WIN32 && PERL_LT_580 && defined $res; $res; } sub get_date { sprintf "%s %d, %d", (split /\s+/, scalar localtime)[1,2,4]; } sub get_timestamp { my ($mon,$day,$year) = (localtime ( time ) )[4,3,5]; return scalar gmtime() . ' GMT'; } my %require_seen = (); # convert Foo::Bar into Foo/Bar.pm and require sub require_package { my $package = shift; die "no package passed" unless $package; return if $require_seen{$package}; $require_seen{$package} = 1; $package =~ s|::|/|g; $package .= '.pm'; require $package; } # convert the template into the release version # $tmpl_root: a ref to an array of tmpl base dirs # tmpl_file: which template file to process # mode : in what mode (html, ps, ...) # vars : ref to a hash with vars to path to the template # # returns the processed template ################### sub proc_tmpl { my ($tmpl_root, $tmpl_file, $mode, $vars) = @_; # append the specific rendering mode, so the correct template will # be picked (e.g. in 'ps' mode, the ps sub-dir(s) will be searched # first) my $search_path = join ':', map { ("$_/$mode", "$_/common", "$_") } (ref $tmpl_root ? @$tmpl_root : $tmpl_root); my $template = Template->new ({ INCLUDE_PATH => $search_path, RECURSION => 1, PLUGINS => { cnavigator => 'DocSet::Template::Plugin::NavigateCache', }, }) || die $Template::ERROR, "\n"; # use Data::Dumper; # print Dumper \@search_path; my $output; $template->process($tmpl_file, $vars, \$output) || die "error: ", $template->error(), "\n"; return $output; } sub banner { my ($string) = @_; my $len = length($string) + 8; note( "#" x $len, "### $string ###", "#" x $len, ); } # see DocSet::Config::files_to_copy() for usage ######################### sub build_matchmany_sub { my $ra_regex = shift; my $expr = join '||', map { "\$_[0] =~ m/$_/o" } @$ra_regex; # note $expr; my $matchsub = eval "sub { ($expr) ? 1 : 0}"; die "Failed in building regex [@$ra_regex]: $@" if $@; $matchsub; } use constant KBYTE => 1024; use constant MBYTE => 1048576; use constant GBYTE => 1073741824; # compacts numbers like 1200234 => 1.2M, so they always fit into 4 chars. ################# sub format_bytes { my $bytes = shift || 0; if ($bytes < KBYTE) { return sprintf "%5dB", $bytes; } elsif (KBYTE <= $bytes and $bytes < MBYTE) { return sprintf "%4.@{[int($bytes/KBYTE) < 10 ? 1 : 0]}fKiB", $bytes/KBYTE; } elsif (MBYTE <= $bytes and $bytes < GBYTE) { return sprintf "%4.@{[int($bytes/MBYTE) < 10 ? 1 : 0]}fMiB", $bytes/MBYTE; } elsif (GBYTE <= $bytes) { return sprintf "%4.@{[int($bytes/GBYTE) < 10 ? 1 : 0]}fGiB", $bytes/GBYTE; } else { # shouldn't happen die "cannot handle formatting of $bytes" } } sub expand_dir { my @files = (); if ($] >= 5.006) { File::Find::find(sub {push @files, $File::Find::name}, $_[0]); } else { # perl 5.005_03 on FreeBSD doesn't set the dir it chdir'ed to # need to move this to compat level? require Cwd; File::Find::find(sub {push @files, catfile(Cwd::cwd(), $_)}, $_[0]); } return \@files; } my @path_ext = (''); if (IS_WIN32) { if ($ENV{PATHEXT}) { push @path_ext, split ';', $ENV{PATHEXT}; } else { push @path_ext, map { ".$_" } qw(com exe bat); # Win9X } } sub which { for my $base (map { catfile $_, $_[0] } File::Spec->path()) { for my $ext (@path_ext) { return $base.$ext if -x $base.$ext; } } return ''; } sub dumper { print Dumper @_; } #sub sub_trace { ## my ($package) = (caller(0))[0]; # my ($sub) = (caller(1))[3]; # print "=> $sub: @_\n"; #} *confess = \*Carp::confess; *cluck = \*Carp::cluck; *carp = \*Carp::carp; sub note { return unless DocSet::RunTime::get_opts('verbose'); print join("\n", @_), "\n"; } #sub error { # return unless DocSet::RunTime::get_opts('verbose'); # cluck(join("\n", @_), "\n"); #} 1; __END__ =head1 NAME C - Commonly used functions =head1 SYNOPSIS use DocSet::Util; copy_file($src, $dst); write_file($filename, $content); create_dir($path); read_file($filename, $r_content); read_file_paras($filename, $ra_content); my $ext = filename_ext($filename); my $date = get_date(); my $timestamp = get_timestamp(); my $uri = path2uri($os_path); require_package($package); my $output = proc_tmpl($tmpl_root, $tmpl_file, $mode, $vars); banner($string); my $sub_ref = build_matchmany_sub($ra_regex); dumper($ref); confess($string); note($string); my $exec_path = which('perldoc'); =head1 DESCRIPTION All the functions are exported by default. =head2 METHODS META: to be completed (see SYNOPSIS meanwhile) =over =item * copy_file =item * write_file =item * create_dir =item * read_file =item * read_file_paras =item * filename_ext =item * path2uri =item * get_date =item * get_timestamp =item * require_package =item * proc_tmpl =item * banner =item * build_matchmany_sub Since the patterns are compiled by insertion into m//, make sure that any C are escaped. Be careful with using quotemeta() for this, since you don't want to espace special regex char, e.g. C<^>, C<$>, etc. =item * which my $exec_path = which('perldoc'); a portable function to search for executables on the system. Accepts a single argument which is the name of the executable to search for. Returns the full path to the executable if found, an empty string otherwise. Parts of the implementation are borrowed from I and modified to take into an account Win32's C environment variable or the hardcoded list of known executable extensions for Win9x which doesn't have this variable. =item * dumper =item * confess =item * cluck =item * carp =item * note =back =head1 AUTHORS Stas Bekman Estas (at) stason.orgE =cut