tools/createsrc
changeset 0 7f656887cf89
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tools/createsrc	Wed Jun 23 15:52:26 2010 +0100
@@ -0,0 +1,658 @@
+#!perl
+# createsrc
+# 
+# Copyright (c) 2009 - 2010 Accenture. All rights reserved.
+# This component and the accompanying materials are made available
+# under the terms of the "Eclipse Public License v1.0"
+# which accompanies this distribution, and is available
+# at the URL "http://www.eclipse.org/legal/epl-v10.html".
+# 
+# Initial Contributors:
+# Accenture - Initial contribution
+#
+
+use strict;
+use Getopt::Long;
+use Cwd;
+use FindBin;
+
+# forward decls
+sub homeDir();
+sub processCommandLine();
+sub Usage();
+sub make4CharId($);
+sub caseRename($$$);
+sub renameFileCopy($$);
+sub GetMacroValue($);
+sub PromptMacro($);
+sub CheckMacro($$);
+sub CheckUid($);
+sub CheckIdentifier($);
+sub listTemplates($);
+sub getTemplates();
+sub query($$);
+sub getConfigFileName();
+sub getConfig($);
+sub setConfig($$);
+sub setCopyright($);
+
+
+#globals
+my $template;
+my $newName;
+my $templateDir;
+my $templateName = "SkeletonTemplate";
+my $dest;
+my $help;
+my $verbose = 0;
+my %macroValue;
+my %macroDescription = ( 'UID' => 'a UID', 'COPYRIGHT' => 'a copyright notice' );
+my $homeDir;
+my $listTemplates;
+my $overwrite = 0;
+my $addToPerforce = 0;
+my $addToMercurial = 0;
+my $interactive = 0;
+
+processCommandLine();
+
+unless (defined $macroValue{'COPYRIGHT'}) {
+	$macroValue{'COPYRIGHT'} = getConfig('copyright');
+	$macroValue{'COPYRIGHT'} = "Copyright (c) [[YEAR]] Accenture. All rights reserved." unless $macroValue{'COPYRIGHT'};
+}
+
+
+$homeDir = homeDir() unless defined ($homeDir);
+
+my $year = [localtime()]->[5]+1900;
+$macroValue{'COPYRIGHT'} =~ s|\[\[YEAR\]\]|$year|g;
+
+if ($interactive) {
+  my $templates = getTemplates();
+  print "Available Templates:\n";
+  my $count = 1;
+  my $options="";
+  foreach my $template (@$templates) {
+    print "$count) $template->[0] - $template->[1]\n";
+    $options=$options . $count;
+    $count++;
+  }
+  do {
+  	print "Which template do you want to use? ";
+  	my $in = <>;
+  	chomp $in;
+  	my $choice;
+  	$choice = eval "int($in)";
+  	unless ($@) {
+  	  if (($choice>0) && ($choice <= scalar(@$templates))) {
+  	    $template = $templates->[$choice-1]->[0];
+  	  }
+  	}
+  	my $max = $count-1;
+    print "Please enter a number between 1 and $max\n" unless defined $template;
+  }	until (defined $template);
+  print "Using template $template\n" if $verbose;
+  
+  do {
+    print "Name for the new code? ";
+    $newName = <>;
+    chomp $newName;
+    
+    unless (CheckIdentifier($newName)) {
+      print "$newName is not a valid C++ identifier\n";
+      undef $newName;
+    }
+   
+  } until ((defined $newName) && (length $newName));
+  
+   my $cwd = cwd();
+   print "Directoy to create new code in? [$cwd] ";
+   $dest = <>;
+   chomp $dest;
+   $dest = $cwd unless (length $dest);
+  
+} else {
+
+  $dest = cwd() unless $dest;
+  
+}
+
+$templateDir = $homeDir."/createsrc-templates/$template";
+die "ERROR: Template $template not found at $templateDir\n" unless -d $templateDir;
+
+$dest =~ s|\\|/|g;
+$dest =~ s|/$||;
+print "Creating $newName at $dest from template $template at $templateDir\n" if ($verbose);
+
+# read [[MACRO]] descriptions from .txt file
+my $infoFile;
+my $infoFileName = "$templateDir.txt";
+if ((-f $infoFileName) && (open $infoFile, $infoFileName)) {
+	print "Reading $infoFileName\n" if ($verbose);
+	<$infoFile>; # skip template description
+	while (my $line = <$infoFile>) {
+		chomp $line;
+		$line =~ s|#.*||; # remove perl style comments
+		if ($line =~ m|^\s*(\w[\w\d]*)\s*\:\s*(.*)$|) {
+			my ($macro, $desc) = ($1, $2);
+			print "Macro '$macro': '$desc'\n" if ($verbose>1);
+			$macroDescription{$macro} = $desc;
+		} elsif ($line =~ m|^\s*$|) {
+			# blank line
+		} else {
+			die "Error: cannot parse line $. of $infoFileName\n";
+		}
+	}
+	
+	close $infoFile;
+}
+
+unless (-d $dest) {
+  mkdir($dest) or die "ERROR: Couldn't create directory $dest: $!\n";
+  print "Created directory $dest\n" if ($verbose>1);
+}
+
+my @dirStack;
+push @dirStack, "";
+
+
+while ($#dirStack>=0) {
+  my $dir = pop(@dirStack);
+
+  my $dirName = $dir;
+  $dir = "/$dir" unless ($dir eq "") || ($dir =~ m|^/|);
+
+  # create destination directory
+  unless (-d "$dest$dir") {
+    mkdir "$dest$dir" or die "ERROR: cannot create directory $dest$dir: $!\n";
+    print "Created directory $dest$dir\n" if ($verbose);
+  }
+
+  # read template directory contents
+  opendir DIR, "$templateDir$dir";
+  my @dirContents = readdir(DIR);
+  closedir(DIR);
+
+  foreach my $f (@dirContents) {
+    if (-f "$templateDir$dir/$f") {
+      my $newFile = caseRename($f, $templateName, $newName);
+      print "Renaming from $templateDir$dir/$f to $dest$dir/$newFile\n" if ($verbose);
+      renameFileCopy("$templateDir$dir/$f", "$dest$dir/$newFile"); 
+    } elsif (-d "$templateDir$dir/$f") {
+      unless ($f =~ m|^\.\.?$|) {
+        print "Adding $dirName/$f to stack\n" if ($verbose);
+        push @dirStack, "$dirName/$f";
+      }
+    } else {
+      print "WARNING: $templateDir$dir/$f is neither a file nor a directory";
+    }
+  }
+
+}
+
+
+
+sub homeDir() {
+  my $homeDir = $FindBin::Bin;
+  print "homeDir=$homeDir\n" if ($verbose > 1);
+  return $homeDir;
+}
+
+sub processCommandLine() {
+  my $setCopyright = undef;
+  Getopt::Long::Configure("bundling");
+  my @macroDefs;
+  GetOptions('h'   => \$help,
+             'v+'  => \$verbose,
+             'd=s' => \$dest,
+             's=s' => \$homeDir,
+             't=s' => \$templateName,
+             'l'   => \$listTemplates,
+             'o'   => \$overwrite,
+             'p'   => \$addToPerforce,
+	     'm'   => \$addToMercurial,
+             'i'   => \$interactive,
+             'c=s' => \$setCopyright,
+	     'D=s' => \@macroDefs);
+
+  Usage() if ($help);
+
+  listTemplates(1) if ($listTemplates);
+  
+  setCopyright($setCopyright) if (defined $setCopyright);
+
+  unless ($interactive) {
+    if ($#ARGV < 1) {
+      print "Insufficient arguments; entering interactive mode\n";
+      $interactive = 1;
+    } else {
+      $template = shift(@ARGV);
+      $newName = shift(@ARGV);
+      die "$newName is not a valid C++ identifier\n" unless CheckIdentifier($newName);
+    }
+  }
+  foreach my $d (@macroDefs) {
+	if ($d =~ m|^(\w[\w\d]*)=(.*)$|) {
+		my ($macro, $value) = ($1, $2);
+		print "Got macro '$macro'='$value' from command line\n" if ($verbose>1);
+		die "Error: '$value' is not a valid value for $macro\n" unless CheckMacro($macro, $value);
+		$macroValue{$macro} = $value;
+	} else {
+		die "Error: Incorrect usage of -D: $d: expected a string of the format 'id=value'\n";
+	}
+  }  
+  print "Warning: ignoring " . scalar(@ARGV) . " extra arguments\n" if scalar(@ARGV);
+}
+
+sub Usage() {
+  require Pod::Text;
+  my $parser = Pod::Text->new();
+  $parser->parse_from_file($0);
+  exit;
+}
+
+sub make4CharId($) {
+	my $longId = shift;
+	while (length($longId)<2) {
+		$longId = $longId . '_';
+	}
+	return uc( substr($longId, 0, 2) . substr($longId, length($longId)-2, 2) );
+}
+
+sub caseRename($$$) {
+  my $string = shift;
+  my $oldName = shift;
+  my $newName = shift;
+
+  my $oldUpper = $oldName;
+  $oldUpper =~ tr/a-z/A-Z/;
+
+  my $oldLower = $oldName;
+  $oldLower =~ tr/A-Z/a-z/;
+
+  my $oldLowerFirst = "\l$oldName;\E";
+
+  my $uppercase = $newName;
+  $uppercase =~ tr/a-z/A-Z/;
+
+  my $lowercase = $newName;
+  $lowercase =~ tr/A-Z/a-z/;
+
+  my $lowerFirst = "\l$newName\E";
+  
+  my $old4Char = make4CharId($oldName);
+  my $new4Char = make4CharId($newName);
+
+  $string =~ s/$oldName/$newName/g;
+  $string =~ s/$oldUpper/$uppercase/g;
+  $string =~ s/$oldLower/$lowercase/g;
+  $string =~ s/$oldLowerFirst/$lowerFirst/g;
+  $string =~ s/$old4Char/$new4Char/g;
+  return $string;
+}
+
+sub renameFileCopy($$) {
+  my $src = shift;
+  my $dest = shift;
+  open SOURCE, "<$src" or die "ERROR: can't open $src for reading: $!\n";
+  unless ($overwrite) {
+    die "ERROR: $dest already exists. Use -o to overwrite. Aborting.\n" if (-f $dest);
+  }
+  open DEST, ">$dest" or die "ERROR: can't open $dest for writing: $!\n";
+  while (my $line = <SOURCE>) {
+    while ($line =~ m|\[\[(\w[\w\d]*)\]\]|) {
+	my $id = $1;
+	my $value = GetMacroValue($id);
+	$line =~ s|\[\[$id\]\]|$value|g;
+    }
+    print DEST caseRename($line, $templateName, $newName);
+    
+  }
+  close (SOURCE);
+  close (DEST);
+  if ($addToPerforce) {
+    print "p4 add $dest\n";
+    `p4 add $dest` ;
+  }
+  if ($addToMercurial) {
+    print "hg add $dest\n";
+    `hg add $dest` ;
+  }
+}
+
+sub GetMacroValue($) {
+	my $macro = shift;
+	if (!defined($macroValue{$macro})) {
+		$macroValue{$macro} = PromptMacro($macro);
+	}
+	return $macroValue{$macro};
+}
+
+sub PromptMacro($) {
+	my $macro = shift;
+	my $description = $macroDescription{$macro};
+	$description = $macro unless defined $description;		
+	
+	print "Enter $description for $newName: ";
+	my $value;
+	do  {
+		$value = <>;
+		chomp $value;
+	} while (!CheckMacro($macro, $value));
+	return $value;
+}
+
+sub CheckMacro($$) {
+	my ($macro, $value) = @_;
+	if ($macro eq 'UID') {
+		return CheckUid($value);
+	}
+	return 1;
+}
+
+sub CheckUid($) {
+  my $uid = shift;
+  return 1 if ($uid =~ m|^0x[0-9a-f]{1,8}$|i);
+  return 1 if ($uid =~ m|^[0-9]{1,10}$|);
+  return 0;
+}
+
+sub CheckIdentifier($)	{
+  my $id = shift;
+  return ($id =~ m|[A-Za-z_][A-Za-z_0-9]*|);
+}
+
+sub getTemplates() {
+  $homeDir = homeDir() unless defined ($homeDir);
+  opendir TEMPLATES, "$homeDir/createsrc-templates" or die "Can't read directory $homeDir/createsrc-templates: $!\n";
+  my @templateNames = grep((-d "$homeDir/createsrc-templates/$_")&&!(m|\.\.?|), readdir(TEMPLATES));
+  close TEMPLATES;
+  die "No templates found at $homeDir/createsrc-templates\n" unless scalar(@templateNames);
+  my @templates = ();
+  foreach my $template (@templateNames) {
+  	my $desc;
+    my $descFile = "$homeDir/createsrc-templates/$template.txt";
+    if ((-f $descFile)&&(open DESC, $descFile)) {
+      $desc = <DESC>;
+      chomp $desc;
+      close DESC;
+    } else {
+     $desc = "<no description>";
+    }
+    push @templates, [$template, $desc];
+  }
+  @templates = sort {$a->[0] cmp $b->[0]} @templates;
+  return \@templates;
+}
+
+sub listTemplates($) {
+  my $exitWhenDone = shift;
+  my $templates = getTemplates();
+  foreach my $template (@$templates) {
+    print $template->[0] . " - " . $template->[1] . "\n";
+  }
+  exit if $exitWhenDone;
+}
+
+sub getConfigFileName() {
+  my $fn;
+  if (defined $ENV{'USERPROFILE'}) {
+    $fn = $ENV{'USERPROFILE'};
+  } else {
+    $fn = homeDir();
+  }
+  $fn =~ s|[/\\]$||;
+  $fn .= "\\createsrc.cfg";
+  return $fn;
+}
+
+sub getConfig($) {
+  my $findKey = shift;
+  my $cfg = getConfigFileName();
+  my $foundValue = undef;
+  open CFGFILE, "<$cfg" or return undef;
+  print "Reading $cfg\n" if ($verbose);
+  while (my $line = <CFGFILE>) {
+    chomp $line;
+    if ($line =~ m|^(.*?)\s*=\s*(.*)$|)	{
+      my ($key, $value) = ($1, $2);
+      print "Read '$key'='$value'\n" if ($verbose>1);
+      $foundValue = $value if ($key eq $findKey);
+    } else {
+      print "ignoring line '$line'\n" if ($verbose>1);
+    }
+  }
+  return $foundValue;
+  close CFGFILE;  
+}
+
+sub setConfig($$) {
+  my ($key, $value) = @_;
+  my $cfg = getConfigFileName();
+  my @lines;
+  if (open CFGFILE, "<$cfg") {
+    print "Reading $cfg\n";
+    @lines = <CFGFILE>;
+    close CFGFILE;
+  }
+  open CFGFILE, ">$cfg" or die "Cannot write to $cfg: $!\n";
+  print "Writing $cfg\n" if ($verbose);
+  my $written = 0;
+  foreach my $line (@lines) {
+    if ($line =~ m|^(.*?)\s*=\s*(.*)$|)	{
+      my ($foundKey, $foundValue) = ($1, $2);
+      print "Read '$foundKey'='$foundValue'\n" if ($verbose>1);
+      if ($key eq $foundKey) {
+        $foundValue = $value;
+        $written = 1;
+        print "Updated key $foundKey to '$foundValue'\n" if ($verbose>1);
+      }
+      print CFGFILE "$foundKey=$foundValue\n";
+    } else {
+      print "ignoring line '$line'\n" if ($verbose>1);
+      print CFGFILE $line;
+    }
+  }
+  unless ($written) {
+    print "Adding $key=$value\n";
+    print CFGFILE "$key=$value\n";
+  }
+  close CFGFILE;
+  
+}
+
+sub setCopyright($) {
+  my $copyright =  shift;
+  print "Setting copyright notice to '$copyright'\n" if ($verbose);
+  setConfig('copyright', $copyright);
+  exit(1);
+}
+
+
+__END__
+
+=head1 NAME
+
+createsrc - creates new code from templates
+
+=head1 SYNOPSIS
+
+	createsrc [options] [TemplateName NewName]
+
+options:
+
+=over
+
+=item -i
+
+Use L<Interactive mode>. This will be the default if C<TemplateName> and C<NewName> are not given.
+
+=item -d <directory>
+
+Put the new code in the specified directory. Defaults to current directory.
+
+=item -DMACRO=value
+
+Specfies a value for a macro. See "Macros" section below.
+
+=item -c "Copyright Notice"
+
+Sets the default copyright to use when none other is specified. See L<Copyright Notices> below.
+
+=item -o
+
+Overwrites any existing files when creating the new code.
+
+=item -p
+
+Add the new files to perforce as they are created.
+
+=item -m
+
+Add the new files to mercurial as they are created.
+
+=item -s <directory>
+
+Specifies the home directory of the script, where the templates are located. Defaults to the directory where the script is located.
+
+=item -t <TemplateText>
+
+Specified the text to be replaced in the template. Defaults to C<SkeletonTemplate>. It's not usually necessary to change this.
+
+=item -l
+
+List available templates, then exits.
+
+=item -h
+
+Shows this help.
+
+=item -v
+
+Verbose mode (-vv very verbose).
+
+=back
+
+=head1 DESCRIPTION
+
+This script creates new projects or code snippets from templates. For example, assuming you have a template C<UiqBasic> that consists of a basic UIQ application, you could create a HelloWorld application as follows:
+
+	createsrc UiqBasic HelloWorld
+
+When creating the new code, it performs the following steps:
+
+=over
+
+=item 1.
+
+Copies and renames the files as appropriate.
+
+=item 2.
+
+Replaces text within the files as appropriate.
+
+=item 3.
+
+Substitutes macros enclosed in double square brackets, C<[[MACRO]]>.
+
+=back
+
+When renaming files or substituting text withing the files, case is preseved as follows:
+
+=over
+
+=item 1.
+
+C<MixedCaseText> is preserved - i.e. C<TemplateText> becomes C<NewText>.
+
+=item 2.
+
+C<lowercasetext> is preserved - i.e. C<templatetext> becomes C<newtext>.
+
+=item 3.
+
+C<UPPERCASETEXT> is preserved - i.e. C<TEMPLATETEXT> becomes C<NEWTEXT>.
+
+=item 4.
+
+C<lowerCaseFirst> is preserved - i.e. C<templateText> becomes C<newText>.
+
+=back
+
+This should cover all forms of capitalisation used within normal Symbian code.
+
+In addition, the capitalised first and last two letters of C<TemplateText> is replaced with the same letters of C<NewText> - i.e. C<TEXT> becomes C<NEXT>. This is to support the C<NAME> value in an applications resource file, which must always have 4 letters.
+
+If the new text has less than 4 characters, it will be padded with underscores. For example, an application called C<A> will be given a C<NAME> of C<A_A_>; C<Ap> becomes C<APAP>; C<HelloWorld> becomes C<HELD> and so on.
+
+=head2 Interactive mode
+
+If the C<-i> option is given, of if no C<TemplateName> and C<TemplateText> are given, interactive mode will be used:
+
+	D:\work\new>createsrc
+	Insufficient arguments; entering interactive mode
+	Available Templates:
+	1) UiqBasic - Basic UIQ application
+	2) UiqImaging - Basic UIQ application with one large control, suitable for displaying an image, a video, etc.
+	3) UiqList - Basic UIQ application with a list in the view.
+	4) server - Symbian 9 transient server
+	Which template do you want to use? 1
+	Name for the new code? HelloWorld
+	Directoy to create new code in? [D:/work/new]
+	Enter a UID for HelloWorld: 0x10002000
+
+When prompted for the directory to create the code in, simply hit enter to use the suggestion in square brackets. Or you can enter a relative or absolute path name to use instead.
+
+=head2 Macros
+
+Macros allow extra custom strings to be inserted into the generate code. Macro values can be specified on the command line using the C<-D> option; if no value is specified then it will be prompted for at runtime.
+
+There are two macros that are used by all (or most) templates:
+
+=over
+
+=item C<COPYRIGHT>
+
+Defines a copyright notice for the code, usually inserted in a comment at the top of each source file. See L<Copyright Notices> below.
+
+=item C<UID>
+
+Specifies a UID for the new code. This will be verified as a valid integer.
+
+=back
+
+Some templates may define other, custom macros. To define a macro value on the command line, use -D syntax in a similar way the a C preprocessor:
+
+	createsrc -DUID=0x01234567
+
+
+=head2 Copyright Notices
+
+Copyright notices are automatically inserted into the source files, including the current year. When using the C<-DCOPYRIGHT=...> and C<-c> options, the copyright text may include C<[[YEAR]]>, which will be replaced with the current year when the code was created.
+
+For example,
+
+	createsrc -DCOPYRIGHT="Copyright (c) [[YEAR]] Joe Bloggs. All rights reserved"
+
+The default copyright notice is C<Copyright (c) [[YEAR]] Accenture. All rights reserved.>
+
+When the C<-c> option is used, the copyright notice is stored in C<%USERPROFILE%\createsrc.cfg> and so is persisted for future runs of C<createsrc>.
+
+=head2 Templates
+
+The templates are located in F<\epoc32\tools\Templates\TemplateName>. Each template usually contains an entire buildable project, but this may vary depending upon the nature of the template. The text that is replaced in template filenames and files is C<SkeletonTemplate> by default, but this can be modified with the C<-t TemplateText> option (although it is not recommended).
+
+In the template, macros are enclosed in double square brackets, for example C<[[UID]]>. Templates should make use of the standard C<[[UID]]> and C<[[COPYRIGHT]]> macros and can use others if necessary.
+
+If a F<TemplateName.txt> file is found in F<\epoc32\tools\Templates>, the first line of this file will be used as the description of the template when the C<-l> flag is given. The remainder of the file is expected to contain lines of the format:
+
+	MACRO: description
+
+giving descriptions for any custom macros used in the project. The descriptions will be used instead of the macro name when prmopting the user for a value at runtime.
+
+=head1 COPYRIGHT
+
+Copyright (c) 2008-2010 Accenture. All rights reserved.
+
+=cut
+