#!/usr/bin/env perl
#
# make-index-html
# John Simpson 2023-07-23
#
# Create one or more 'index.html' files for use with web hosts that don't
# automatically generate index pages (such as Keybase Sites)
#
# 2023-08-13 jms1 - add support for per-directory `.title` files
#
# 2023-11-11 jms1 - added "generated" timestamp at end of page
#
# 2023-12-19 jms1 - no functional change, just adding a note to remind myself
# in the future ... tried to add timestamps when listing child directories,
# you can't just use the directory's mtime since writing a new `index.html`
# file within a directory updates the directory's mtime ... you'll need to
# scan and remember the timestamps of every file *within* that directory,
# and use the highest timestamp *within* a directory, and you'll need to
# scan all files recursively *before* generating *any* `index.html` files,
# and this is more than i feel like doing right now.
#
# 2025-01-03 jms1
# - made the green "alternating lines" colour more faded (#c0f0c0 -> #e0f0e0)
# - added to generated pages
#
# 2025-01-18 jms1
# - changing logic around options, `-T` is no longer needed
# - removed '-w' from shebang line, since some 'env' executables don't work
# correctly if it's there. The 'use warnings' line below does the same thing.
require 5.005 ;
use strict ;
use warnings ;
use Getopt::Std ;
########################################
# Globals
my @dirs = () ; # list of directories waiting to be processed
my $root = '' ; # original root directory
########################################
# getopts
my %opt = () ;
my $sorted = 0 ; # -s generate "sorted" index pages
my $recursive = 0 ; # -r process child directories
my $overwrite = 0 ; # -f overwrite existing index pages
my $set_title = '' ; # -t: manual title of root index page
###############################################################################
#
# usage
sub usage(;$)
{
my $msg = ( shift || '' ) ;
print <
my $t_title = $p_title ;
$t_title =~ s|<.*?>||gs ;
########################################
# Unless we've been told to overwrite files, make sure this file
# doesn't already exist
unless( $overwrite )
{
if ( -f $outfile )
{
print " EXISTS $outfile\n" ;
return ;
}
}
########################################
# Create the file
print " writing $outfile\n" ;
open( my $fh , '>' , $outfile )
or die "ERROR: can't write \"$outfile\": $!\n" ;
########################################
# Write the header
my $t = time() ;
my @d = gmtime( $t ) ;
my $now = sprintf( '%04d-%02d-%02dT%02d:%02d:%02d+00:00' ,
$d[5]+1900 , $d[4]+1 , $d[3] , $d[2] , $d[1] , $d[0] ) ;
print $fh <
$t_title
$p_title
$header
EOF
########################################
# Start the table
print $fh <
EOF
########################################
# Build possible links within header row
my $nl = '' ;
my $sl = '' ;
my $dl = '' ;
my $el = '' ;
if ( $sorted )
{
$nl = "" ;
$sl = "" ;
$dl = "" ;
$el = '' ;
}
########################################
# Write table header row
print $fh <
$nlName$el |
|
$slSize$el |
|
$dlDate$el |
EOF
########################################
# Write rows for child directories
if ( $show_parent )
{
print $fh <
../ |
EOF
}
for my $n ( sort keys %{$r_dirs} )
{
print $fh <
$n/ |
|
|
|
|
EOF
}
########################################
# Figure out what order to show the files
my @show = sort keys %{$r_files} ;
if ( $sort_by eq 'size' )
{
@show = sort { $r_files->{$a}->{'size'} <=> $r_files->{$b}->{'size'} }
keys %{$r_files} ;
}
elsif ( $sort_by eq 'date' )
{
@show = sort { $r_files->{$a}->{'mtime'} <=> $r_files->{$b}->{'mtime'} }
keys %{$r_files} ;
}
########################################
# Write rows for files
for my $n ( @show )
{
my $size = add_commas( $r_files->{$n}->{'size'} ) ;
my $mtime = $r_files->{$n}->{'mtime'} ;
my @d = gmtime( $mtime ) ;
my $date = sprintf( '%04d-%02d-%02d %02d:%02d:%02d UTC' ,
$d[5]+1900 , $d[4]+1 , $d[3] , $d[2] , $d[1] , $d[0] ) ;
print $fh <
$n |
|
$size |
|
$date |
EOF
}
########################################
# Finish the page
print $fh <
Generated $now
$footer
EOF
close $fh ;
}
###############################################################################
#
# Read the contents of a directory and generate one or more index file(s).
sub process_dir($)
{
my $dir = shift ;
my $header = '' ;
my $footer = '' ;
############################################################
# Read directory's contents into memory
my %ff = () ; # filename => { (mtime|size) => value }
my %dd = () ; # dirname => 1
opendir( D , $dir )
or die "ERROR: opendir(\"$dir\"): $!\n" ;
while( my $f = readdir( D ) )
{
########################################
# Certain filenames need to be ignored
next if ( $f =~ m|^\.| ) ;
next if ( $f eq 'index.html' ) ;
next if ( $f eq 'index-s.html' ) ;
next if ( $f eq 'index-d.html' ) ;
########################################
# If a HEADER.html or FOOTER.html file exsits, remember its contents
# and don't show the file in the output.
if ( $f eq 'HEADER.html' )
{
print " reading HEADER.html\n" ;
open( H , '<' , "$dir/$f" )
or die "ERROR: can't read \"$dir/$f\": $!\n" ;
undef $/ ;
$header = ;
close H ;
next ;
}
if ( $f eq 'FOOTER.html' )
{
print " reading FOOTER.html\n" ;
open( H , '<' , "$dir/$f" )
or die "ERROR: can't read \"$dir/$f\": $!\n" ;
undef $/ ;
$footer = ;
close H ;
next ;
}
########################################
# Read/remember info about this entry
if ( -f "$dir/$f" )
{
my @s = stat( "$dir/$f" ) ;
$ff{$f}->{'size'} = $s[7] ;
$ff{$f}->{'mtime'} = $s[9] ;
}
elsif ( -d "$dir/$f" )
{
my @s = stat( "$dir/$f" ) ;
$dd{$f}->{'mtime'} = $s[9] ;
if ( $recursive )
{
push( @dirs , "$dir/$f" ) ;
}
}
}
closedir D ;
############################################################
# Build the title for this index
my $sub = $dir ;
$sub =~ s|^$root|| ;
my $p_title = '' ;
if ( $set_title ne '' )
{
$p_title = $set_title . $sub ;
}
elsif ( -f "$dir/.title" )
{
$p_title = '' ;
open( T , '<' , "$dir/.title" )
or die "ERROR: can't read \"$dir/.title\": $!\n" ;
while ( my $line = )
{
$p_title .= $line ;
}
close T ;
$p_title =~ s|\s+$||s ;
}
else
{
$p_title = "Index of $root$sub" ;
}
########################################
# Write 'index.html'
my $sp = ( $dir ne $root ) ? 1 : 0 ;
write_index( "$dir/index.html" , $p_title , \%dd , \%ff ,
$header , $footer , '' , $sp ) ;
########################################
# If we're doing "sorted by" versions, generate those pages as well
if ( $sorted )
{
write_index( "$dir/index-s.html" , $p_title , \%dd , \%ff ,
$header , $footer , 'size' , $sp ) ;
write_index( "$dir/index-d.html" , $p_title , \%dd , \%ff ,
$header , $footer , 'date' , $sp ) ;
}
}
###############################################################################
###############################################################################
###############################################################################
#
# Process the command line
getopts( 'hsrft:' , \%opt ) ;
$opt{'h'} && usage() ;
$sorted = ( $opt{'s'} ? 1 : 0 ) ;
$recursive = ( $opt{'r'} ? 1 : 0 ) ;
$overwrite = ( $opt{'f'} ? 1 : 0 ) ;
$set_title = ( $opt{'t'} || '' ) ;
$root = ( shift || '.' ) ;
$root =~ s|/$|| ;
push( @dirs , $root ) ;
########################################
# Process the list of directories.
#
# When the script starts, the list only contains the directory that was
# specified on the command line.
#
# If $recursive is true, other directories *may* be added to the list as
# they are found.
while ( my $d = shift @dirs )
{
print "processing '$d'\n" ;
process_dir( $d ) ;
}