forked from qt-creator/qt-creator
Custom wizards: Enhance generator script
following suggestions. Introduce argument syntax in XML allowing for fine-grained control of handling (omitting empty values, use temporary files).
This commit is contained in:
@@ -34,45 +34,59 @@ use Getopt::Long;
|
||||
use IO::File;
|
||||
|
||||
my $optDryRun = 0;
|
||||
my $fieldClassName = 'MyClass';
|
||||
my $standardFieldProjectName = 'MyProject';
|
||||
my $standardFieldCppHeaderSuffix = 'h';
|
||||
my $standardFieldCppSourceSuffix = 'cpp';
|
||||
my $optHelp = 0;
|
||||
my $optClassName = 'MyClass';
|
||||
my $optProjectName = 'MyProject';
|
||||
my $optCppHeaderSuffix = 'h';
|
||||
my $optCppSourceSuffix = 'cpp';
|
||||
my $optDescription = '';
|
||||
|
||||
# -- Read in a file and return its lines
|
||||
sub readFile
|
||||
{
|
||||
my ($fileName) = @_;
|
||||
my @rc = ();
|
||||
my $fh = new IO::File('<' . $fileName) or die ('Unable to open for reading ' . $fileName . ' :' . $!);
|
||||
while (my $line = <$fh>) {
|
||||
chomp($line);
|
||||
push (@rc, $line);
|
||||
}
|
||||
$fh->close();
|
||||
return @rc;
|
||||
}
|
||||
|
||||
my $USAGE=<<EOF;
|
||||
Usage: generate.pl [--dry-run] <parameter-mappings>
|
||||
Usage: generate.pl [--help] | [--dry-run]
|
||||
[--class-name=<class name>]
|
||||
[--project-name=<project name>]
|
||||
[--header-suffix=<header suffix>]
|
||||
[--source-suffix=<source suffix>]
|
||||
[--description=<description-file>]
|
||||
|
||||
Custom wizard project generation example script.
|
||||
|
||||
Known parameters: ClassName=<value>
|
||||
EOF
|
||||
|
||||
if (!GetOptions("dry-run" => \$optDryRun)) {
|
||||
my $argCount = scalar(@ARGV);
|
||||
if ($argCount == 0
|
||||
|| !GetOptions("help" => \$optHelp,
|
||||
"dry-run" => \$optDryRun,
|
||||
"class-name:s" => \$optClassName,
|
||||
"project-name:s" => \$optProjectName,
|
||||
"header-suffix:s" => \$optCppHeaderSuffix,
|
||||
"source-suffix:s" => \$optCppSourceSuffix,
|
||||
"description:s" => \$optDescription)
|
||||
|| $optHelp != 0) {
|
||||
print $USAGE;
|
||||
exit (1);
|
||||
}
|
||||
|
||||
if (scalar(@ARGV) == 0) {
|
||||
print $USAGE;
|
||||
exit (1);
|
||||
}
|
||||
|
||||
# -- Parse the 'field=value' pairs
|
||||
foreach my $arg (@ARGV) {
|
||||
my ($key, $value) = split('=', $arg);
|
||||
$fieldClassName = $value if ($key eq 'ClassName');
|
||||
# -- Standard fields defined by the custom project wizard
|
||||
$standardFieldProjectName = $value if ($key eq 'ProjectName');
|
||||
$standardFieldCppHeaderSuffix = $value if ($key eq 'CppHeaderSuffix');
|
||||
$standardFieldCppSourceSuffix = $value if ($key eq 'CppSourceSuffix');
|
||||
}
|
||||
|
||||
# -- Determine file names
|
||||
my $baseFileName = lc($fieldClassName);
|
||||
my $sourceFileName = $baseFileName . '.' . $standardFieldCppSourceSuffix;
|
||||
my $headerFileName = $baseFileName . '.' . $standardFieldCppHeaderSuffix;
|
||||
my $mainSourceFileName = 'main.' . $standardFieldCppSourceSuffix;
|
||||
my $projectFileName = lc($standardFieldProjectName) . '.pro';
|
||||
my $baseFileName = lc($optClassName);
|
||||
my $sourceFileName = $baseFileName . '.' . $optCppSourceSuffix;
|
||||
my $headerFileName = $baseFileName . '.' . $optCppHeaderSuffix;
|
||||
my $mainSourceFileName = 'main.' . $optCppSourceSuffix;
|
||||
my $projectFileName = lc($optProjectName) . '.pro';
|
||||
|
||||
if ($optDryRun) {
|
||||
# -- Step 1) Dry run: Print file names along with attributes
|
||||
@@ -85,23 +99,29 @@ if ($optDryRun) {
|
||||
print 'Generating ', $headerFileName, ' ', $sourceFileName, ' ',
|
||||
$mainSourceFileName, ' ', $projectFileName, "\n";
|
||||
my $headerFile = new IO::File('>' . $headerFileName) or die ('Unable to open ' . $headerFileName . ' :' . $!);
|
||||
print $headerFile '#ifndef ', uc($fieldClassName), "_H\n#define ", uc($fieldClassName), "_H\n\n",
|
||||
'class ', $fieldClassName, "{\npublic:\n ", $fieldClassName, "();\n\n};\n\n#endif\n";
|
||||
print $headerFile '#ifndef ', uc($optClassName), "_H\n#define ", uc($optClassName), "_H\n\n",
|
||||
'class ', $optClassName, "{\npublic:\n ", $optClassName, "();\n\n};\n\n#endif\n";
|
||||
$headerFile->close();
|
||||
|
||||
my $sourceFile = new IO::File('>' . $sourceFileName) or die ('Unable to open ' . $sourceFileName . ' :' . $!);
|
||||
print $sourceFile '#include "', $headerFileName ,"\"\n\n",
|
||||
$fieldClassName,'::', $fieldClassName, "()\n{\n}\n";
|
||||
$optClassName,'::', $optClassName, "()\n{\n}\n";
|
||||
$sourceFile->close();
|
||||
|
||||
my $mainSourceFile = new IO::File('>' . $mainSourceFileName) or die ('Unable to open ' . $mainSourceFileName . ' :' . $!);
|
||||
print $mainSourceFile '#include "', $headerFileName ,"\"\n\n",
|
||||
"int main(int argc, char *argv[])\n{\n ", $fieldClassName,' ', lc($fieldClassName),
|
||||
print $mainSourceFile '#include "', $headerFileName ,"\"\n\n";
|
||||
# -- Write out description comments
|
||||
if ($optDescription ne '') {
|
||||
foreach my $description (readFile($optDescription)) {
|
||||
print $mainSourceFile '// ', $description, "\n";
|
||||
}
|
||||
}
|
||||
print $mainSourceFile "int main(int argc, char *argv[])\n{\n ", $optClassName,' ', lc($optClassName),
|
||||
";\n return 0;\n}\n";
|
||||
$mainSourceFile->close();
|
||||
|
||||
my $projectFile = new IO::File('>' . $projectFileName) or die ('Unable to open ' . $projectFileName . ' :' . $!);
|
||||
print $projectFile "TEMPLATE = app\nQT -= core\nCONFIG += console\nTARGET = ", $standardFieldProjectName,
|
||||
print $projectFile "TEMPLATE = app\nQT -= core\nCONFIG += console\nTARGET = ", $optProjectName,
|
||||
"\nSOURCES += ", $sourceFileName, ' ',$headerFileName, ' ', $mainSourceFileName,
|
||||
"\nHEADERS += ", $headerFileName,"\n";
|
||||
$projectFile->close();
|
||||
|
||||
@@ -34,8 +34,6 @@ Custom class wizard example configuration file. -->
|
||||
<description>Creates a simple project using a generator script</description>
|
||||
<displayname>Simple Script-Generated Project</displayname>;
|
||||
<displaycategory>Custom Projects</displaycategory>
|
||||
<!-- Specify the generator script -->
|
||||
<files generatorscript="generate.pl"/>
|
||||
<!-- Create parameter wizard page -->
|
||||
<fieldpagetitle>Simple Script-Generated Project Parameters</fieldpagetitle>
|
||||
<fields>
|
||||
@@ -43,5 +41,19 @@ Custom class wizard example configuration file. -->
|
||||
<fieldcontrol class="QLineEdit" validator="^[a-zA-Z0-9_]+$" defaulttext="MyClass" />
|
||||
<fielddescription>Class name:</fielddescription>
|
||||
</field>
|
||||
<!-- Description will be inserted as a multi-line C++-comment -->
|
||||
<field name="Description">
|
||||
<fieldcontrol class="QTextEdit" defaulttext="Enter description" />
|
||||
<fielddescription>Description:</fielddescription>
|
||||
</field>
|
||||
</fields>
|
||||
<!-- Specify the generator script -->
|
||||
<generatorscript binary="generate.pl">
|
||||
<argument value="--class-name=%ClassName%"/>
|
||||
<argument value="--project-name=%ProjectName%"/>
|
||||
<argument value="--header-suffix=%CppHeaderSuffix%" omit-empty="true"/>
|
||||
<argument value="--source-suffix=%CppSourceSuffix%" omit-empty="true"/>
|
||||
<!-- Multi-line description passed as temporary file unless empty -->
|
||||
<argument value="--description=%Description%" omit-empty="true" write-file="true"/>
|
||||
</generatorscript>
|
||||
</wizard>
|
||||
|
||||
@@ -225,7 +225,9 @@ bool CustomWizard::writeFiles(const Core::GeneratedFiles &files, QString *errorM
|
||||
}
|
||||
}
|
||||
// Run the custom script to actually generate the files.
|
||||
if (!Internal::runCustomWizardGeneratorScript(ctx->targetPath, d->m_parameters->filesGeneratorScriptFullPath(),
|
||||
if (!Internal::runCustomWizardGeneratorScript(ctx->targetPath,
|
||||
d->m_parameters->filesGeneratorScript,
|
||||
d->m_parameters->filesGeneratorScriptArguments,
|
||||
ctx->replacements, errorMessage))
|
||||
return false;
|
||||
// Paranoia: Check on the files generated by the script:
|
||||
@@ -233,7 +235,7 @@ bool CustomWizard::writeFiles(const Core::GeneratedFiles &files, QString *errorM
|
||||
if (generatedFile.attributes() & Core::GeneratedFile::CustomGeneratorAttribute)
|
||||
if (!QFileInfo(generatedFile.path()).isFile()) {
|
||||
*errorMessage = QString::fromLatin1("%1 failed to generate %2").
|
||||
arg(d->m_parameters->filesGeneratorScript, generatedFile.path());
|
||||
arg(d->m_parameters->filesGeneratorScript.back(), generatedFile.path());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -253,7 +255,8 @@ Core::GeneratedFiles CustomWizard::generateWizardFiles(QString *errorMessage) co
|
||||
// If generator script is non-empty, do a dry run to get it's files.
|
||||
if (!d->m_parameters->filesGeneratorScript.isEmpty()) {
|
||||
rc += Internal::dryRunCustomWizardGeneratorScript(ctx->targetPath,
|
||||
d->m_parameters->filesGeneratorScriptFullPath(),
|
||||
d->m_parameters->filesGeneratorScript,
|
||||
d->m_parameters->filesGeneratorScriptArguments,
|
||||
ctx->replacements, errorMessage);
|
||||
if (rc.isEmpty())
|
||||
return rc;
|
||||
|
||||
@@ -29,18 +29,24 @@
|
||||
|
||||
#include "customwizardparameters.h"
|
||||
#include "customwizardpreprocessor.h"
|
||||
#include "customwizardscriptgenerator.h"
|
||||
|
||||
#include <coreplugin/mimedatabase.h>
|
||||
#include <coreplugin/icore.h>
|
||||
#include <cpptools/cpptoolsconstants.h>
|
||||
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QLocale>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QFileInfo>
|
||||
#include <QtCore/QXmlStreamReader>
|
||||
#include <QtCore/QXmlStreamAttribute>
|
||||
#include <QtCore/QTemporaryFile>
|
||||
|
||||
#include <QtGui/QIcon>
|
||||
|
||||
enum { debug = 0 };
|
||||
@@ -64,6 +70,13 @@ static const char comboEntriesElementC[] = "comboentries";
|
||||
static const char comboEntryElementC[] = "comboentry";
|
||||
static const char comboEntryTextElementC[] = "comboentrytext";
|
||||
|
||||
static const char generatorScriptElementC[] = "generatorscript";
|
||||
static const char generatorScriptBinaryAttributeC[] = "binary";
|
||||
static const char generatorScriptArgumentElementC[] = "argument";
|
||||
static const char generatorScriptArgumentValueAttributeC[] = "value";
|
||||
static const char generatorScriptArgumentOmitEmptyAttributeC[] = "omit-empty";
|
||||
static const char generatorScriptArgumentWriteFileAttributeC[] = "write-file";
|
||||
|
||||
static const char fieldDescriptionElementC[] = "fielddescription";
|
||||
static const char fieldNameAttributeC[] = "name";
|
||||
static const char fieldMandatoryAttributeC[] = "mandatory";
|
||||
@@ -87,6 +100,8 @@ enum ParseState {
|
||||
ParseWithinComboEntryText,
|
||||
ParseWithinFiles,
|
||||
ParseWithinFile,
|
||||
ParseWithinScript,
|
||||
ParseWithinScriptArguments,
|
||||
ParseError
|
||||
};
|
||||
|
||||
@@ -136,6 +151,7 @@ void CustomWizardParameters::clear()
|
||||
files.clear();
|
||||
fields.clear();
|
||||
filesGeneratorScript.clear();
|
||||
filesGeneratorScriptArguments.clear();
|
||||
firstPageId = -1;
|
||||
}
|
||||
|
||||
@@ -276,6 +292,8 @@ static ParseState nextOpeningState(ParseState in, const QStringRef &name)
|
||||
return ParseWithinFields;
|
||||
if (name == QLatin1String(filesElementC))
|
||||
return ParseWithinFiles;
|
||||
if (name == QLatin1String(generatorScriptElementC))
|
||||
return ParseWithinScript;
|
||||
break;
|
||||
case ParseWithinFields:
|
||||
if (name == QLatin1String(fieldElementC))
|
||||
@@ -303,10 +321,15 @@ static ParseState nextOpeningState(ParseState in, const QStringRef &name)
|
||||
if (name == QLatin1String(fileElementC))
|
||||
return ParseWithinFile;
|
||||
break;
|
||||
case ParseWithinScript:
|
||||
if (name == QLatin1String(generatorScriptArgumentElementC))
|
||||
return ParseWithinScriptArguments;
|
||||
break;
|
||||
case ParseWithinFieldDescription: // No subelements
|
||||
case ParseWithinComboEntryText:
|
||||
case ParseWithinFile:
|
||||
case ParseError:
|
||||
case ParseWithinScriptArguments:
|
||||
break;
|
||||
}
|
||||
return ParseError;
|
||||
@@ -358,6 +381,14 @@ static ParseState nextClosingState(ParseState in, const QStringRef &name)
|
||||
if (name == QLatin1String(comboEntryTextElementC))
|
||||
return ParseWithinComboEntry;
|
||||
break;
|
||||
case ParseWithinScript:
|
||||
if (name == QLatin1String(generatorScriptElementC))
|
||||
return ParseWithinWizard;
|
||||
break;
|
||||
case ParseWithinScriptArguments:
|
||||
if (name == QLatin1String(generatorScriptArgumentElementC))
|
||||
return ParseWithinScript;
|
||||
break;
|
||||
case ParseError:
|
||||
break;
|
||||
}
|
||||
@@ -427,6 +458,11 @@ static inline QString localeLanguage()
|
||||
return name;
|
||||
}
|
||||
|
||||
GeneratorScriptArgument::GeneratorScriptArgument(const QString &v) :
|
||||
value(v), flags(0)
|
||||
{
|
||||
}
|
||||
|
||||
// Main parsing routine
|
||||
CustomWizardParameters::ParseResult
|
||||
CustomWizardParameters::parse(QIODevice &device,
|
||||
@@ -499,7 +535,6 @@ CustomWizardParameters::ParseResult
|
||||
}
|
||||
break;
|
||||
case ParseWithinFiles:
|
||||
filesGeneratorScript = attributeValue(reader, filesGeneratorScriptAttributeC);
|
||||
break;
|
||||
case ParseWithinFile: { // file attribute
|
||||
CustomWizardFile file;
|
||||
@@ -516,6 +551,26 @@ CustomWizardParameters::ParseResult
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ParseWithinScript:
|
||||
filesGeneratorScript = fixGeneratorScript(configFileFullPath, attributeValue(reader, generatorScriptBinaryAttributeC));
|
||||
if (filesGeneratorScript.isEmpty()) {
|
||||
*errorMessage = QString::fromLatin1("No binary specified for generator script.");
|
||||
return ParseFailed;
|
||||
}
|
||||
break;
|
||||
case ParseWithinScriptArguments: {
|
||||
GeneratorScriptArgument argument(attributeValue(reader, generatorScriptArgumentValueAttributeC));
|
||||
if (argument.value.isEmpty()) {
|
||||
*errorMessage = QString::fromLatin1("No value specified for generator script argument.");
|
||||
return ParseFailed;
|
||||
}
|
||||
if (booleanAttributeValue(reader, generatorScriptArgumentOmitEmptyAttributeC, false))
|
||||
argument.flags |= GeneratorScriptArgument::OmitEmpty;
|
||||
if (booleanAttributeValue(reader, generatorScriptArgumentWriteFileAttributeC, false))
|
||||
argument.flags |= GeneratorScriptArgument::WriteFile;
|
||||
filesGeneratorScriptArguments.push_back(argument);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -559,23 +614,26 @@ CustomWizardParameters::ParseResult
|
||||
return parse(configFile, configFileFullPath, bp, errorMessage);
|
||||
}
|
||||
|
||||
QString CustomWizardParameters::filesGeneratorScriptFullPath() const
|
||||
{
|
||||
if (filesGeneratorScript.isEmpty())
|
||||
return QString();
|
||||
QString rc = directory;
|
||||
rc += QLatin1Char('/');
|
||||
rc += filesGeneratorScript;
|
||||
return rc;
|
||||
}
|
||||
|
||||
QString CustomWizardParameters::toString() const
|
||||
{
|
||||
QString rc;
|
||||
QTextStream str(&rc);
|
||||
str << "Directory: " << directory << " Klass: '" << klass << "'\n";
|
||||
if (!filesGeneratorScript.isEmpty())
|
||||
str << "Script: '" << filesGeneratorScript << "'\n";
|
||||
if (!filesGeneratorScriptArguments.isEmpty()) {
|
||||
str << "Script:";
|
||||
foreach(const QString &a, filesGeneratorScript)
|
||||
str << " '" << a << '\'';
|
||||
str << "\nArguments: ";
|
||||
foreach(const GeneratorScriptArgument &a, filesGeneratorScriptArguments) {
|
||||
str << " '" << a.value << '\'';
|
||||
if (a.flags & GeneratorScriptArgument::OmitEmpty)
|
||||
str << " [omit empty]";
|
||||
if (a.flags & GeneratorScriptArgument::WriteFile)
|
||||
str << " [write file]";
|
||||
str << ',';
|
||||
}
|
||||
str << '\n';
|
||||
}
|
||||
foreach(const CustomWizardFile &f, files) {
|
||||
str << " File source: " << f.source << " Target: " << f.target;
|
||||
if (f.openEditor)
|
||||
@@ -603,8 +661,16 @@ QString CustomWizardParameters::toString() const
|
||||
|
||||
// ------------ CustomWizardContext
|
||||
|
||||
void CustomWizardContext::replaceFields(const FieldReplacementMap &fm, QString *s)
|
||||
static inline QString passThrough(const QString &in) { return in; }
|
||||
|
||||
// Do field replacements applying modifiers and string transformation
|
||||
// for the value
|
||||
template <class ValueStringTransformation>
|
||||
bool replaceFieldHelper(ValueStringTransformation transform,
|
||||
const CustomWizardContext::FieldReplacementMap &fm,
|
||||
QString *s)
|
||||
{
|
||||
bool nonEmptyReplacements = false;
|
||||
if (debug) {
|
||||
qDebug().nospace() << "CustomWizardContext::replaceFields with " <<
|
||||
fm << *s;
|
||||
@@ -633,7 +699,7 @@ void CustomWizardContext::replaceFields(const FieldReplacementMap &fm, QString *
|
||||
modifier = fieldSpec.at(fieldSpecSize - 1).toLatin1();
|
||||
fieldSpec.truncate(fieldSpecSize - 2);
|
||||
}
|
||||
const FieldReplacementMap::const_iterator it = fm.constFind(fieldSpec);
|
||||
const CustomWizardContext::FieldReplacementMap::const_iterator it = fm.constFind(fieldSpec);
|
||||
if (it == fm.constEnd()) {
|
||||
pos = nextPos; // Not found, skip
|
||||
continue;
|
||||
@@ -655,9 +721,64 @@ void CustomWizardContext::replaceFields(const FieldReplacementMap &fm, QString *
|
||||
default:
|
||||
break;
|
||||
}
|
||||
s->replace(pos, nextPos - pos, replacement);
|
||||
if (!replacement.isEmpty())
|
||||
nonEmptyReplacements = true;
|
||||
// Apply transformation to empty values as well.
|
||||
s->replace(pos, nextPos - pos, transform(replacement));
|
||||
nonEmptyReplacements = true;
|
||||
pos += replacement.size();
|
||||
}
|
||||
return nonEmptyReplacements;
|
||||
}
|
||||
|
||||
bool CustomWizardContext::replaceFields(const FieldReplacementMap &fm, QString *s)
|
||||
{
|
||||
return replaceFieldHelper(passThrough, fm, s);
|
||||
}
|
||||
|
||||
// Transformation to be passed to replaceFieldHelper(). Writes the
|
||||
// value to a text file and returns the file name to be inserted
|
||||
// instead of the expanded field in the parsed template,
|
||||
// used for the arguments of a generator script.
|
||||
class TemporaryFileTransform {
|
||||
public:
|
||||
typedef CustomWizardContext::TemporaryFilePtr TemporaryFilePtr;
|
||||
typedef CustomWizardContext::TemporaryFilePtrList TemporaryFilePtrList;
|
||||
|
||||
explicit TemporaryFileTransform(TemporaryFilePtrList *f);
|
||||
|
||||
QString operator()(const QString &) const;
|
||||
|
||||
private:
|
||||
TemporaryFilePtrList *m_files;
|
||||
QString m_pattern;
|
||||
};
|
||||
|
||||
TemporaryFileTransform::TemporaryFileTransform(TemporaryFilePtrList *f) :
|
||||
m_files(f), m_pattern(QDir::tempPath())
|
||||
{
|
||||
if (!m_pattern.endsWith(QLatin1Char('/')))
|
||||
m_pattern += QLatin1Char('/');
|
||||
m_pattern += QLatin1String("qtcreatorXXXXXX.txt");
|
||||
}
|
||||
|
||||
QString TemporaryFileTransform::operator()(const QString &value) const
|
||||
{
|
||||
TemporaryFilePtr temporaryFile(new QTemporaryFile(m_pattern));
|
||||
QTC_ASSERT(temporaryFile->open(), return QString(); )
|
||||
|
||||
temporaryFile->write(value.toLocal8Bit());
|
||||
const QString name = temporaryFile->fileName();
|
||||
temporaryFile->flush();
|
||||
temporaryFile->close();
|
||||
m_files->push_back(temporaryFile);
|
||||
return name;
|
||||
}
|
||||
|
||||
bool CustomWizardContext::replaceFields(const FieldReplacementMap &fm, QString *s,
|
||||
TemporaryFilePtrList *files)
|
||||
{
|
||||
return replaceFieldHelper(TemporaryFileTransform(files), fm, s);
|
||||
}
|
||||
|
||||
void CustomWizardContext::reset()
|
||||
|
||||
@@ -32,12 +32,14 @@
|
||||
|
||||
#include <coreplugin/basefilewizard.h>
|
||||
|
||||
#include <QtCore/QList>
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QMap>
|
||||
#include <QtCore/QSharedPointer>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QIODevice;
|
||||
class QDebug;
|
||||
class QTemporaryFile;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace ProjectExplorer {
|
||||
@@ -68,6 +70,23 @@ struct CustomWizardFile {
|
||||
bool openProject;
|
||||
};
|
||||
|
||||
// Argument to the generator script containing placeholders to
|
||||
// be replaced by field values or file names
|
||||
// as in '--class-name=%ClassName%' or '--description=%Description%'.
|
||||
struct GeneratorScriptArgument {
|
||||
enum Flags {
|
||||
// Omit this arguments if all field placeholders expanded to empty strings.
|
||||
OmitEmpty = 0x1,
|
||||
// Do use the actual field value, but write it to a temporary
|
||||
// text file and inserts its file name (suitable for multiline texts).
|
||||
WriteFile = 0x2 };
|
||||
|
||||
explicit GeneratorScriptArgument(const QString &value = QString());
|
||||
|
||||
QString value;
|
||||
unsigned flags;
|
||||
};
|
||||
|
||||
struct CustomWizardParameters
|
||||
{
|
||||
public:
|
||||
@@ -81,12 +100,12 @@ public:
|
||||
Core::BaseFileWizardParameters *bp, QString *errorMessage);
|
||||
QString toString() const;
|
||||
|
||||
QString filesGeneratorScriptFullPath() const;
|
||||
|
||||
QString directory;
|
||||
QString klass;
|
||||
QList<CustomWizardFile> files;
|
||||
QString filesGeneratorScript;
|
||||
QStringList filesGeneratorScript; // Complete binary, such as 'cmd /c myscript.pl'.
|
||||
QList<GeneratorScriptArgument> filesGeneratorScriptArguments;
|
||||
|
||||
QString fieldPageTitle;
|
||||
QList<CustomWizardField> fields;
|
||||
int firstPageId;
|
||||
@@ -102,14 +121,24 @@ public:
|
||||
|
||||
struct CustomWizardContext {
|
||||
typedef QMap<QString, QString> FieldReplacementMap;
|
||||
typedef QSharedPointer<QTemporaryFile> TemporaryFilePtr;
|
||||
typedef QList<TemporaryFilePtr> TemporaryFilePtrList;
|
||||
|
||||
void reset();
|
||||
|
||||
// Replace field values delimited by '%' with special modifiers:
|
||||
// %Field% -> simple replacement
|
||||
// %Field:l% -> lower case replacement, 'u' upper case,
|
||||
// 'c' capitalize first letter.
|
||||
static void replaceFields(const FieldReplacementMap &fm, QString *s);
|
||||
// 'c' capitalize first letter. Return value indicates whether non-empty
|
||||
// replacements where encountered
|
||||
static bool replaceFields(const FieldReplacementMap &fm, QString *s);
|
||||
|
||||
// Special replaceFields() overload used for the arguments of a generator
|
||||
// script: Write the expanded field values out to temporary files and
|
||||
// inserts file names instead of the expanded fields in string 's'.
|
||||
static bool replaceFields(const FieldReplacementMap &fm, QString *s,
|
||||
TemporaryFilePtrList *files);
|
||||
|
||||
static QString processFile(const FieldReplacementMap &fm, QString in);
|
||||
|
||||
FieldReplacementMap baseReplacements;
|
||||
|
||||
@@ -29,7 +29,9 @@
|
||||
|
||||
#include "customwizardscriptgenerator.h"
|
||||
#include "customwizard.h"
|
||||
#include "customwizardparameters.h" // XML attributes
|
||||
#include "customwizardparameters.h"
|
||||
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
#include <QtCore/QProcess>
|
||||
#include <QtCore/QDir>
|
||||
@@ -41,84 +43,74 @@
|
||||
namespace ProjectExplorer {
|
||||
namespace Internal {
|
||||
|
||||
typedef QSharedPointer<QTemporaryFile> TemporaryFilePtr;
|
||||
|
||||
// Format pattern for temporary files
|
||||
static inline QString tempFilePattern()
|
||||
// Parse helper: Determine the correct binary to run:
|
||||
// Expand to full wizard path if it is relative and located
|
||||
// in the wizard directory, else assume it can be found in path.
|
||||
// On Windows, run non-exe files with 'cmd /c'.
|
||||
QStringList fixGeneratorScript(const QString &configFile, QString binary)
|
||||
{
|
||||
QString tempPattern = QDir::tempPath();
|
||||
if (!tempPattern.endsWith(QLatin1Char('/')))
|
||||
tempPattern += QLatin1Char('/');
|
||||
tempPattern += QLatin1String("qtcreatorXXXXXX.txt");
|
||||
return tempPattern;
|
||||
}
|
||||
|
||||
// Create a temporary file with content
|
||||
static inline TemporaryFilePtr writeTemporaryFile(const QString &content)
|
||||
{
|
||||
TemporaryFilePtr temporaryFile(new QTemporaryFile(tempFilePattern()));
|
||||
if (!temporaryFile->open())
|
||||
return TemporaryFilePtr();
|
||||
temporaryFile->write(content.toLocal8Bit());
|
||||
temporaryFile->close();
|
||||
return temporaryFile;
|
||||
if (binary.isEmpty())
|
||||
return QStringList();
|
||||
// Expand to full path if it is relative and in the wizard
|
||||
// directory, else assume it can be found in path.
|
||||
QFileInfo binaryInfo(binary);
|
||||
if (!binaryInfo.isAbsolute()) {
|
||||
QString fullPath = QFileInfo(configFile).absolutePath();
|
||||
fullPath += QLatin1Char('/');
|
||||
fullPath += binary;
|
||||
const QFileInfo fullPathInfo(fullPath);
|
||||
if (fullPathInfo.isFile()) {
|
||||
binary = fullPathInfo.absoluteFilePath();
|
||||
binaryInfo = fullPathInfo;
|
||||
}
|
||||
} // not absolute
|
||||
QStringList rc(binary);
|
||||
#ifdef Q_OS_WIN // Windows: Cannot run scripts by QProcess, do 'cmd /c'
|
||||
const QString extension = binaryInfo.suffix();
|
||||
if (!extension.isEmpty() && extension.compare(QLatin1String("exe"), Qt::CaseInsensitive) != 0) {
|
||||
rc.push_front(QLatin1String("/C"));
|
||||
rc.push_front(QString::fromLocal8Bit(qgetenv("COMSPEC")));
|
||||
if (rc.front().isEmpty())
|
||||
rc.front() = QLatin1String("cmd.exe");
|
||||
}
|
||||
#endif
|
||||
return rc;
|
||||
}
|
||||
|
||||
// Helper for running the optional generation script.
|
||||
static bool
|
||||
runGenerationScriptHelper(const QString &workingDirectory,
|
||||
QString binary, bool dryRun,
|
||||
const QStringList &script,
|
||||
const QList<GeneratorScriptArgument> &argumentsIn,
|
||||
bool dryRun,
|
||||
const QMap<QString, QString> &fieldMap,
|
||||
QString *stdOut /* = 0 */, QString *errorMessage)
|
||||
{
|
||||
typedef QSharedPointer<QTemporaryFile> TemporaryFilePtr;
|
||||
typedef QList<TemporaryFilePtr> TemporaryFilePtrList;
|
||||
typedef QMap<QString, QString>::const_iterator FieldConstIterator;
|
||||
|
||||
QProcess process;
|
||||
const QString binary = script.front();
|
||||
QStringList arguments;
|
||||
// Check on the process
|
||||
const QFileInfo binaryInfo(binary);
|
||||
if (!binaryInfo.isFile()) {
|
||||
*errorMessage = QString::fromLatin1("The Generator script %1 does not exist").arg(binary);
|
||||
return false;
|
||||
}
|
||||
#ifdef Q_OS_WIN // Windows: Cannot run scripts by QProcess, do 'cmd /c'
|
||||
const QString extension = binaryInfo.suffix();
|
||||
if (!extension.isEmpty() && extension.compare(QLatin1String("exe"), Qt::CaseInsensitive) != 0) {
|
||||
arguments.push_back(QLatin1String("/C"));
|
||||
arguments.push_back(binary);
|
||||
binary = QString::fromLocal8Bit(qgetenv("COMSPEC"));
|
||||
if (binary.isEmpty())
|
||||
binary = QLatin1String("cmd.exe");
|
||||
}
|
||||
#endif // Q_OS_WIN
|
||||
// Arguments
|
||||
if (dryRun)
|
||||
arguments << QLatin1String("--dry-run");
|
||||
// Turn the field replacement map into a list of arguments "key=value".
|
||||
// Pass on free-format-texts as a temporary files indicated by a colon
|
||||
// separator "key:filename"
|
||||
TemporaryFilePtrList temporaryFiles;
|
||||
const int binarySize = script.size();
|
||||
for (int i = 1; i < binarySize; i++)
|
||||
arguments.push_back(script.at(i));
|
||||
|
||||
const FieldConstIterator cend = fieldMap.constEnd();
|
||||
for (FieldConstIterator it = fieldMap.constBegin(); it != cend; ++it) {
|
||||
const QString &value = it.value();
|
||||
// Is a temporary file required?
|
||||
const bool passAsTemporaryFile = value.contains(QLatin1Char('\n'));
|
||||
if (passAsTemporaryFile) {
|
||||
// Create a file and pass on as "key:filename"
|
||||
TemporaryFilePtr temporaryFile = writeTemporaryFile(value);
|
||||
if (temporaryFile.isNull()) {
|
||||
*errorMessage = QString::fromLatin1("Cannot create temporary file");
|
||||
return false;
|
||||
}
|
||||
temporaryFiles.push_back(temporaryFile);
|
||||
arguments << (it.key() + QLatin1Char(':') + QDir::toNativeSeparators(temporaryFile->fileName()));
|
||||
} else {
|
||||
// Normal value "key=value"
|
||||
arguments << (it.key() + QLatin1Char('=') + value);
|
||||
}
|
||||
// Arguments: Prepend 'dryrun' and do field replacement
|
||||
if (dryRun)
|
||||
arguments.push_back(QLatin1String("--dry-run"));
|
||||
|
||||
// Arguments: Prepend 'dryrun'. Do field replacement to actual
|
||||
// argument value to expand via temporary file if specified
|
||||
CustomWizardContext::TemporaryFilePtrList temporaryFiles;
|
||||
foreach (const GeneratorScriptArgument &argument, argumentsIn) {
|
||||
QString value = argument.value;
|
||||
const bool nonEmptyReplacements
|
||||
= argument.flags & GeneratorScriptArgument::WriteFile ?
|
||||
CustomWizardContext::replaceFields(fieldMap, &value, &temporaryFiles) :
|
||||
CustomWizardContext::replaceFields(fieldMap, &value);
|
||||
if (nonEmptyReplacements || !(argument.flags & GeneratorScriptArgument::OmitEmpty))
|
||||
arguments.push_back(value);
|
||||
}
|
||||
process.setWorkingDirectory(workingDirectory);
|
||||
if (CustomWizard::verbose())
|
||||
@@ -157,13 +149,14 @@ static bool
|
||||
// Do a dry run of the generation script to get a list of files
|
||||
Core::GeneratedFiles
|
||||
dryRunCustomWizardGeneratorScript(const QString &targetPath,
|
||||
const QString &script,
|
||||
const QStringList &script,
|
||||
const QList<GeneratorScriptArgument> &arguments,
|
||||
const QMap<QString, QString> &fieldMap,
|
||||
QString *errorMessage)
|
||||
{
|
||||
// Run in temporary directory as the target path may not exist yet.
|
||||
QString stdOut;
|
||||
if (!runGenerationScriptHelper(QDir::tempPath(), script, true,
|
||||
if (!runGenerationScriptHelper(QDir::tempPath(), script, arguments, true,
|
||||
fieldMap, &stdOut, errorMessage))
|
||||
return Core::GeneratedFiles();
|
||||
Core::GeneratedFiles files;
|
||||
@@ -199,10 +192,14 @@ Core::GeneratedFiles
|
||||
return files;
|
||||
}
|
||||
|
||||
bool runCustomWizardGeneratorScript(const QString &targetPath, const QString &script,
|
||||
const QMap<QString, QString> &fieldMap, QString *errorMessage)
|
||||
bool runCustomWizardGeneratorScript(const QString &targetPath,
|
||||
const QStringList &script,
|
||||
const QList<GeneratorScriptArgument> &arguments,
|
||||
const QMap<QString, QString> &fieldMap,
|
||||
QString *errorMessage)
|
||||
{
|
||||
return runGenerationScriptHelper(targetPath, script, false, fieldMap,
|
||||
return runGenerationScriptHelper(targetPath, script, arguments,
|
||||
false, fieldMap,
|
||||
0, errorMessage);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,8 +31,7 @@
|
||||
#define CUSTOMWIZARDSCRIPTGENERATOR_H
|
||||
|
||||
#include <QtCore/QMap>
|
||||
#include <QtCore/QList>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QStringList>
|
||||
|
||||
namespace Core {
|
||||
class GeneratedFile;
|
||||
@@ -41,6 +40,8 @@ class GeneratedFile;
|
||||
namespace ProjectExplorer {
|
||||
namespace Internal {
|
||||
|
||||
struct GeneratorScriptArgument;
|
||||
|
||||
/* Custom wizard script generator functions. In addition to the <file> elements
|
||||
* that define template files in which macros are replaced, it is possible to have
|
||||
* a custom wizard call a generation script (specified in the "generatorscript"
|
||||
@@ -64,14 +65,21 @@ namespace Internal {
|
||||
* should create those, too.
|
||||
*/
|
||||
|
||||
// Parse the script arguments apart and expand the binary.
|
||||
QStringList fixGeneratorScript(const QString &configFile, QString attributeIn);
|
||||
|
||||
// Step 1) Do a dry run of the generation script to get a list of files on stdout
|
||||
QList<Core::GeneratedFile>
|
||||
dryRunCustomWizardGeneratorScript(const QString &targetPath, const QString &script,
|
||||
dryRunCustomWizardGeneratorScript(const QString &targetPath,
|
||||
const QStringList &script,
|
||||
const QList<GeneratorScriptArgument> &arguments,
|
||||
const QMap<QString, QString> &fieldMap,
|
||||
QString *errorMessage);
|
||||
|
||||
// Step 2) Generate files
|
||||
bool runCustomWizardGeneratorScript(const QString &targetPath, const QString &script,
|
||||
bool runCustomWizardGeneratorScript(const QString &targetPath,
|
||||
const QStringList &script,
|
||||
const QList<GeneratorScriptArgument> &arguments,
|
||||
const QMap<QString, QString> &fieldMap,
|
||||
QString *errorMessage);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user