blob: 9bed1dd291fe87d4a218e93e5850dd877265750e [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:path/path.dart' as path;
import 'package:strings/strings.dart' as strings;
import 'command_runner.dart';
/// Create command.
class CreateCommand extends ModCommand {
final Directory _templatesDir;
/// Creates a new instance of [CreateCommand].
CreateCommand({Directory templatesDir}) : _templatesDir = templatesDir {
argParser.addOption(
'template',
abbr: 't',
allowed: const <String>['mod'],
help: 'Specify the type of project to create',
valueHelp: 'type',
allowedHelp: const <String, String>{
'mod': '(default) Generate a Fuchsia mod.',
},
defaultsTo: 'mod',
);
}
@override
String get description => '''Create a new Fuchsia mod project.
The project name must be lowercase_with_underscores.
''';
@override
String get name => 'create';
@override
String get invocation => '${runner.executableName} $name <project_name>';
/// The templates directory.
Directory get templatesDir =>
_templatesDir ??
new Directory(path.join(modPackageRoot.path, 'templates'));
static final RegExp _projectNamePattern = new RegExp(r'^[a-z][a-z0-9_]*$');
@override
Future<Null> run() async {
// Check if the argument is correct.
if (argResults.rest.length != 1) {
throw new UsageException('The project name must be provided.', usage);
}
// Check if the project name is valid.
String projectName = argResults.rest[0];
if (!isValidProjectName(projectName)) {
throw new UsageException(
'The project name must be lowercase_with_underscores',
usage,
);
}
Map<String, String> templateValues = new Map<String, String>.unmodifiable(
<String, String>{
'project_name': strings.underscore(projectName),
'projectName': strings.camelize(projectName, true),
'ProjectName': strings.camelize(projectName, false),
},
);
String projectPath = path.join(
runner.workingDir.path,
templateValues['project_name'],
);
if (FileSystemEntity.typeSync(projectPath) !=
FileSystemEntityType.notFound) {
throw new UsageException('"$projectName" already exists.', usage);
}
// Copy the files over, and replace the template variables.
await copyTemplateDir(
new Directory(path.join(templatesDir.path, argResults['template'])),
new Directory(projectPath),
templateValues,
);
printStatus('''Project "$projectName" created successfully!
Be sure to add this project to your BUILD.gn package definition so that it
appears on the target device.
''');
}
/// Copies the src template directory into the dest directory recursively,
/// while replacing all the template parameters with actual values.
static Future<Null> copyTemplateDir(
Directory src,
Directory dest,
Map<String, String> templateValues,
) async {
await dest.create(recursive: true);
await for (FileSystemEntity entity in src.list()) {
String destName = path.join(
dest.path,
renderTemplateString(path.basename(entity.path), templateValues),
);
if (entity is File) {
await copyTemplateFile(entity, new File(destName), templateValues);
} else if (entity is Directory) {
await copyTemplateDir(entity, new Directory(destName), templateValues);
} else {
throw new Exception('Unrecognized type: ${entity.runtimeType}');
}
}
}
/// Copies the src template file into the dest file, while replacing all the
/// template parameters with actual values.
static Future<Null> copyTemplateFile(
File src,
File dest,
Map<String, String> templateValues,
) async {
await dest.writeAsString(
renderTemplateString(
await src.readAsString(),
templateValues,
),
);
}
/// Replaces all the template parameters in the given template with the actual
/// values provided in the map.
static String renderTemplateString(
String template,
Map<String, String> templateValues,
) {
String result = template;
for (String key in templateValues.keys) {
result = result.replaceAll('__${key}__', templateValues[key]);
}
return result;
}
/// Determines whether the given project name is valid.
static bool isValidProjectName(String projectName) =>
_projectNamePattern.hasMatch(projectName);
}