diff -r 000000000000 -r 7f656887cf89 tools/createsrc --- /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 = ) { + 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 = ; + chomp $desc; + close DESC; + } else { + $desc = ""; + } + 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 = ) { + 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 = ; + 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. This will be the default if C and C are not given. + +=item -d + +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 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 + +Specifies the home directory of the script, where the templates are located. Defaults to the directory where the script is located. + +=item -t + +Specified the text to be replaced in the template. Defaults to C. 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 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 is preserved - i.e. C becomes C. + +=item 2. + +C is preserved - i.e. C becomes C. + +=item 3. + +C is preserved - i.e. C becomes C. + +=item 4. + +C is preserved - i.e. C becomes C. + +=back + +This should cover all forms of capitalisation used within normal Symbian code. + +In addition, the capitalised first and last two letters of C is replaced with the same letters of C - i.e. C becomes C. This is to support the C 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 will be given a C of C; C becomes C; C becomes C and so on. + +=head2 Interactive mode + +If the C<-i> option is given, of if no C and C 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 + +Defines a copyright notice for the code, usually inserted in a comment at the top of each source file. See L below. + +=item C + +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 + +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. + +=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 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 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 +