Added ENotifyKeypresses and ECaptureCtrlC flags to CCommandBase.
Commands can now get keypresses and handle ctrl-C via callbacks instead of having to implement custom active objects. As part of this extended the CCommandBase extension interface to MCommandExtensionsV2 for the new virtual functions KeyPressed(TUint aKeyCode, TUint aModifiers) and CtrlCPressed(). sudo now cleans up correctly by using ECaptureCtrlC.
#!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