views:

79

answers:

4

when we use Expires header for text files like js, css, contents are cached in the browser, to get new content we need to change in the html file the new names in the link and script tag. When we add changes. How can we automate it.

In a Windows Box, I may have some bunch of html files in multiple folders also in subdirectories.

There would be a text file

filelist.txt

OldName                 NewName
oldfile1-ver-1.0.js      oldfile1-ver-2.0.js  
oldfile2-ver-1.0.js      oldfile2-ver-2.0.js  
oldfile3-ver-1.0.js      oldfile3-ver-2.0.js  
oldfile4-ver-1.0.js      oldfile4-ver-2.0.js  

The script should change all the oldfile1-ver-1.0.js into oldfile1-ver-2.0.js in the html, php files

I would run this script before i start uploading.

Finally the script could create a list of files and line number where it made the update.

The solution can be in Perl/PHP/BATCH or anything thats nice and elegant

A: 

It's probably more elegant to create a symlink pointing to the current version, and point the pages to that link, then change the link where necessary, rather than edit file names in code:

ln -s whatever-1.0.js whatever.js

Then use whatever.js in your HTML files. To update,

rm whatever.js; ln -s whatever-1.1.js whatever.js
Delan Azabani
Really Nice way, but normally when we don't have shell access to available hosting server. This is not possible. I m looking for something with Windows environment.
nepsdotin
You host your website on a Windows server? That's a nice invitation for hacking... ;P
Delan Azabani
I am building my website on windows and hosting it on Linux. I never take a chance.
nepsdotin
This breaks the whole reason for putting a version number in the filename. The name change **must** be in the HTML pages, or the browser won't know that it needs to fetch the new version of the code.
cjm
Browsers don't just blindly use the cached file, you know. Browsers check if the file is the same, only using the cached if there is no new version.
Delan Azabani
The question strongly implied that they use an Expires header with a date far in the future to prevent the browser from needing to check whether the cached file has been changed. This avoids a request/response and improves page-loading speeds for repeat visitors. But it requires you to change the filename if you modify the file. If they weren't using this trick, there'd be no need for the version numbers in the filename, and they could just edit the original file instead of bothering with symlinks.
cjm
Ah, I didn't see that, thanks.
Delan Azabani
+2  A: 

There are almost certainly more elegant solutions, but this command line hack gets the job done.

$ perl -i~ -pe 's/ver-1.0.js/ver-2.0.js/g' *.html

That will make the change in all of the html files in the current directory. And you'll have a set of back-up files with the extension .html~.

davorg
+1  A: 

The code below uses Perl's in-place editing feature to modify only those files that need changing and leaves backups of the originals. For example, if it updates foo.php, the original will be in foo.php.bak.

We begin with typical front matter. We'll use the File::Find module to search for HTML and PHP files at any depth, starting by default in the current directory or in all directories named on the command line.

#! /usr/bin/perl

use warnings;
use strict;

use File::Find;

sub usage { "Usage: $0 [ directory .. ]\n" }

In read_filelist, we get the list of transformations use them to generate two bits of code:

  1. a compiled regex that matches any line containing at least one old filename, and
  2. a compiled sub that replaces old names with new.

Note the use of \b which matches only at word boundaries and quotemeta, which escapes any regex metacharacters. These constrain the replacements to only literal occurrences of the old filenames.

It's a bit lengthy, so pardon the scrollbar.

sub read_filelist {
  my $path = "filelist.txt";
  open my $fh, "<", $path
    or die "$0: open $path: $!";

  my $code = q[
    sub {
      while (<>) {
  ];

  my @old;
  my $errors;
  while (<$fh>) {
    next if $. == 1 && /OldName.*NewName/;

    my($old,$new) = split;
    unless (defined($old) && defined($new)) {
      warn "$0: $path:$.: missing filename\n";
      ++$errors;
    }

    my($qmold,$qmnew) = map quotemeta($_) => $old, $new;
    $code .= "        s/\\b$qmold\\b/$qmnew/gi;\n";

    push @old, $old;
  }

  die "$0: will not continue\n" if $errors;

  $code .= q[        print; 
      }
    }];

  local $@;
  print $code, "\n";
  my $sub = eval $code;
  die "$0: should not happen: $@" if $@;

  my $pattern = '\b(' .
                join("|", map quotemeta, @old) .
                ')\b';

  #print $pattern, "\n";
  my $regex = eval { qr/$pattern/oi };
  die "$0: should not happen: $@" if $@;

  ($regex, $sub);
}

Here we remember the root directories to begin searching and read filelist.txt. Perl's in-place editing requires the files to be updated to be in the special @ARGV array.

my @dirs = @ARGV ? @ARGV : ".";
my($has_old,$replace) = read_filelist;

The sub needs_update is the worker that File::Find::find uses to check whether a given file should be modified. We could place all PHP and HTML files in @ARGV, but in cases when the code modifies nothing, you'd still get backup files for everything.

sub needs_update {
  return unless /\.(?:php|html)$/i;

  open my $fh, "<", $_
    or warn("$0: open $_: $!"), return;

  while (<$fh>) {
    if (/$has_old/) {
      push @ARGV, $File::Find::name;
      return;
    }
  }
}, 

For the main event, we clear @ARGV, use needs_update to add the appropriate PHP and HTML files, and unleash the compiled sub on them.

# set up in-place editing
@ARGV = ();
find \&needs_update, @dirs;

die "$0: nothing to do!\n" unless @ARGV;
$^I = ".bak";
$replace->();

__END__

A couple of notes:

  • The code above attempts to make all replacements in the order given in filelist.txt.
  • It makes no attempt to limit scope to <link> and <script> elements. Doing this correctly requires an HTML parser. If names of Javascript sources in filelist.txt should not be replaced sometimes, I can update this answer with the extra machinery, but doing would add even more code.

On Windows, paste the code (minus my running commentary) into a file with an extension of .pl, and then run it as

C:\> fixjs.pl

to patch HTML and PHP files beneath the current directory. To process one or more directories elsewhere, run

C:\> fixjs.pl D:\some\dir E:\another
Greg Bacon
A: 

Why not modify your system so that your files are templates, and then use a library like Template Toolkit to select your files at build time. You can also manage deployment specific information the same way.

It would be a simple thing to have a make target that copies your raw files and applies template processing based on configuration data stored in a file or pulled from the environment.

daotoad