#!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+ −
+ −