extension-convert.pl 7.65 KB
Newer Older
1
#!/usr/bin/perl
2 3 4
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
#
6 7
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
8

9
use 5.10.1;
10 11
use strict;
use warnings;
12

13 14 15 16 17 18 19 20 21 22 23
use lib qw(. lib);

use Bugzilla;
use Bugzilla::Constants;
use Bugzilla::Util qw(trim);

use File::Basename;
use File::Copy qw(move);
use File::Find;
use File::Path qw(mkpath rmtree);

24
my $from = $ARGV[0] or die <<END;
25 26 27 28 29 30 31 32 33
You must specify the name of the extension you are converting from,
as the first argument.
END
my $extension_name = ucfirst($from);

my $extdir = bz_locations()->{'extensionsdir'};

my $from_dir = "$extdir/$from";
if (!-d $from_dir) {
34
  die "$from_dir does not exist.\n";
35 36 37 38
}

my $to_dir = "$extdir/$extension_name";
if (-d $to_dir) {
39
  die "$to_dir already exists, not converting.\n";
40 41 42
}

if (ON_WINDOWS) {
43 44 45 46 47 48

  # There's no easy way to recursively copy a directory on Windows.
  print "WARNING: This will modify the contents of $from_dir.\n",
    "Press Ctrl-C to stop or any other key to continue...\n";
  getc;
  move($from_dir, $to_dir) || die "rename of $from_dir to $to_dir failed: $!";
49 50
}
else {
51 52
  print "Copying $from_dir to $to_dir...\n";
  system("cp", "-r", $from_dir, $to_dir);
53 54
}

55
# Make sure we don't accidentally modify the $from_dir anywhere else
56 57 58 59
# in this script.
undef $from_dir;

if (!-d $to_dir) {
60
  die "$to_dir was not created.\n";
61 62 63 64 65 66 67 68 69 70 71
}

my $version = get_version($to_dir);
move_template_hooks($to_dir);
rename_module_packages($to_dir, $extension_name);
my $install_requirements = get_install_requirements($to_dir);
my ($modules, $subs) = code_files_to_subroutines($to_dir);

my $config_pm = <<END;
package Bugzilla::Extension::$extension_name;
use strict;
72 73
use warnings;

74 75 76 77 78 79 80 81
use constant NAME => '$extension_name';
$install_requirements
__PACKAGE__->NAME;
END

my $extension_pm = <<END;
package Bugzilla::Extension::$extension_name;
use strict;
82 83
use warnings;

84
use parent qw(Bugzilla::Extension);
85 86 87 88 89 90 91 92 93 94 95 96 97

$modules

our \$VERSION = '$version';

$subs

__PACKAGE__->NAME;
END

open(my $config_fh, '>', "$to_dir/Config.pm") || die "$to_dir/Config.pm: $!";
print $config_fh $config_pm;
close($config_fh);
98
open(my $extension_fh, '>', "$to_dir/Extension.pm")
99 100 101 102 103 104 105 106 107 108 109 110
  || die "$to_dir/Extension.pm: $!";
print $extension_fh $extension_pm;
close($extension_fh);

rmtree("$to_dir/code");
unlink("$to_dir/info.pl");

###############
# Subroutines #
###############

sub rename_module_packages {
111 112 113 114 115 116 117 118 119 120
  my ($dir, $name) = @_;
  my $lib_dir = "$dir/lib";

  # We don't want things like Bugzilla::Extension::Testopia::Testopia.
  if (-d "$lib_dir/$name") {
    print "Moving contents of $lib_dir/$name into $lib_dir...\n";
    foreach my $file (glob("$lib_dir/$name/*")) {
      my $dirname  = dirname($file);
      my $basename = basename($file);
      rename($file, "$dirname/../$basename") || warn "$file: $!\n";
121
    }
122
  }
123

124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
  my @modules;
  find({wanted => sub { $_ =~ /\.pm$/i and push(@modules, $_) }, no_chdir => 1},
    $lib_dir);
  my %module_rename;
  foreach my $file (@modules) {
    open(my $fh, '<', $file) || die "$file: $!";
    my $content = do { local $/ = undef; <$fh> };
    close($fh);
    if ($content =~ /^package (\S+);/m) {
      my $package  = $1;
      my $new_name = $file;
      $new_name =~ s/^$lib_dir\///;
      $new_name =~ s/\.pm$//;
      $new_name = join('::', File::Spec->splitdir($new_name));
      $new_name = "Bugzilla::Extension::${name}::$new_name";
      print "Renaming $package to $new_name...\n";
      $content =~ s/^package \Q$package\E;/package \Q$new_name\E;/;
      open(my $write_fh, '>', $file) || die "$file: $!";
      print $write_fh $content;
      close($write_fh);
      $module_rename{$package} = $new_name;
145
    }
146
  }
147

148 149 150
  print "Renaming module names inside of library and code files...\n";
  my @code_files = glob("$dir/code/*.pl");
  rename_modules_internally(\%module_rename, [@modules, @code_files]);
151 152 153
}

sub rename_modules_internally {
154 155 156 157 158 159 160 161 162 163 164
  my ($rename, $files) = @_;

  # We can't use \b because :: matches \b.
  my $break = qr/^|[^\w:]|$/;
  foreach my $file (@$files) {
    open(my $fh, '<', $file) || die "$file: $!";
    my $content = do { local $/ = undef; <$fh> };
    close($fh);
    foreach my $old_name (keys %$rename) {
      my $new_name = $rename->{$old_name};
      $content =~ s/($break)\Q$old_name\E($break)/$1$new_name$2/gms;
165
    }
166 167 168 169
    open(my $write_fh, '>', $file) || die "$file: $!";
    print $write_fh $content;
    close($write_fh);
  }
170 171 172
}

sub get_version {
173 174 175 176 177 178 179 180 181
  my ($dir) = @_;
  print "Getting version info from info.pl...\n";
  my $info;
  {
    local @INC = ("$dir/lib", @INC);
    $info = do "$dir/info.pl";
    die $@ if $@;
  }
  return $info->{version};
182 183 184
}

sub get_install_requirements {
185 186 187 188 189 190 191 192 193 194 195 196
  my ($dir) = @_;
  my $file = "$dir/code/install-requirements.pl";
  return '' if !-f $file;

  print "Moving install-requirements.pl code into Config.pm...\n";
  my ($modules, $code) = process_code_file($file);
  $modules = join('', @$modules);
  $code    = join('', @$code);
  if ($modules) {
    return "$modules\n\n$code";
  }
  return $code;
197 198 199
}

sub process_code_file {
200 201 202 203 204 205 206 207 208 209 210 211 212
  my ($file) = @_;
  open(my $fh, '<', $file) || die "$file: $!";
  my $stuff_started;
  my (@modules, @code);
  foreach my $line (<$fh>) {
    $stuff_started = 1 if $line !~ /^#/;
    next if !$stuff_started;
    next if $line =~ /^use (warnings|strict|lib|Bugzilla)[^\w:]/;
    if ($line =~ /^(?:use|require)\b/) {
      push(@modules, $line);
    }
    else {
      push(@code, $line);
213
    }
214 215 216
  }
  close $fh;
  return (\@modules, \@code);
217 218 219
}

sub code_files_to_subroutines {
220 221 222 223 224 225 226 227
  my ($dir) = @_;

  my @dir_files = glob("$dir/code/*.pl");
  my (@all_modules, @subroutines);
  foreach my $file (@dir_files) {
    next if $file =~ /install-requirements/;
    print "Moving $file code into Extension.pm...\n";
    my ($modules, $code) = process_code_file($file);
228
    my @code_lines  = map {"    $_"} @$code;
229 230 231 232 233 234 235 236 237 238
    my $code_string = join('', @code_lines);
    $code_string =~ s/Bugzilla->hook_args/\$args/g;
    $code_string =~ s/my\s+\$args\s+=\s+\$args;//gs;
    chomp($code_string);
    push(@all_modules, @$modules);
    my $name = basename($file);
    $name =~ s/-/_/;
    $name =~ s/\.pl$//;

    my $subroutine = <<END;
239 240 241 242 243
sub $name {
    my (\$self, \$args) = \@_;
$code_string
}
END
244 245
    push(@subroutines, $subroutine);
  }
246

247
  my %seen_modules      = map { trim($_) => 1 } @all_modules;
248 249 250
  my $module_string     = join("\n", sort keys %seen_modules);
  my $subroutine_string = join("\n", @subroutines);
  return ($module_string, $subroutine_string);
251 252 253
}

sub move_template_hooks {
254 255 256 257 258 259 260 261 262 263 264 265 266
  my ($dir) = @_;
  foreach my $lang (glob("$dir/template/*")) {
    next if !_file_matters($lang);
    my $hook_container = "$lang/default/hook";
    mkpath($hook_container) || warn "$hook_container: $!";

    # Hooks can be in all sorts of weird places, including
    # template/default/hook.
    foreach my $file (glob("$lang/*")) {
      next if !_file_matters($file, 1);
      my $dirname = basename($file);
      print "Moving $file to $hook_container/$dirname...\n";
      rename($file, "$hook_container/$dirname") || die "move failed: $!";
267
    }
268
  }
269 270 271
}

sub _file_matters {
272 273 274 275 276 277 278 279 280 281 282 283
  my ($path, $tmpl) = @_;
  my @ignore = qw(default custom CVS);
  my $file   = basename($path);
  return 0 if grep(lc($_) eq lc($file), @ignore);

  # Hidden files
  return 0 if $file =~ /^\./;
  if ($tmpl) {
    return 1 if $file =~ /\.tmpl$/;
  }
  return 0 if !-d $path;
  return 1;
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
}

__END__

=head1 NAME

extension-convert.pl - Convert extensions from the pre-3.6 format to the 
3.6 format.

=head1 SYNOPSIS

 contrib/extension-convert.pl name

 Converts an extension in the F<extensions/> directory into the new
 extension layout for Bugzilla 3.6.