| // Copyright 2015 The Chromium 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:logging/logging.dart'; |
| import 'package:mustache4dart/mustache4dart.dart' as mustache; |
| import 'package:path/path.dart' as p; |
| |
| import '../artifacts.dart'; |
| import '../process.dart'; |
| |
| final Logger _logging = new Logger('sky_tools.init'); |
| |
| class InitCommand extends Command { |
| final String name = 'init'; |
| final String description = 'Create a new Flutter project.'; |
| |
| InitCommand() { |
| argParser.addOption('out', abbr: 'o', help: 'The output directory.'); |
| argParser.addFlag('pub', |
| defaultsTo: true, |
| help: 'Whether to run "pub get" after the project has been created.'); |
| } |
| |
| @override |
| Future<int> run() async { |
| if (!argResults.wasParsed('out')) { |
| print('No option specified for the output directory.'); |
| print(argParser.usage); |
| return 2; |
| } |
| |
| if (ArtifactStore.flutterRoot == null) { |
| stderr.writeln('Neither the --flutter-root command line flag nor the FLUTTER_ROOT environment'); |
| stderr.writeln('variable was specified. Unable to find package:flutter.'); |
| return 2; |
| } |
| String flutterRoot = p.absolute(ArtifactStore.flutterRoot); |
| |
| String flutterPackagePath = p.join(flutterRoot, 'packages', 'flutter'); |
| if (!FileSystemEntity.isFileSync(p.join(flutterPackagePath, 'pubspec.yaml'))) { |
| print('Unable to find package:flutter in $flutterPackagePath'); |
| return 2; |
| } |
| |
| // TODO: Confirm overwrite of an existing directory with the user. |
| Directory out = new Directory(argResults['out']); |
| |
| new FlutterSimpleTemplate().generateInto(out, flutterPackagePath); |
| |
| print(''); |
| |
| String message = '''All done! To run your application: |
| |
| \$ cd ${out.path} |
| \$ flutter start |
| '''; |
| |
| if (argResults['pub']) { |
| int code = await pubGet(directory: out.path); |
| if (code != 0) |
| return code; |
| } |
| |
| print(message); |
| return 0; |
| } |
| |
| Future<int> pubGet({ |
| String directory: '', |
| bool skipIfAbsent: false, |
| bool verbose: true |
| }) async { |
| File pubSpecYaml = new File(p.join(directory, 'pubspec.yaml')); |
| File pubSpecLock = new File(p.join(directory, 'pubspec.lock')); |
| File dotPackages = new File(p.join(directory, '.packages')); |
| |
| if (!pubSpecYaml.existsSync()) { |
| if (skipIfAbsent) |
| return 0; |
| _logging.severe('$directory: no pubspec.yaml found'); |
| return 1; |
| } |
| |
| if (!pubSpecLock.existsSync() || pubSpecYaml.lastModifiedSync().isAfter(pubSpecLock.lastModifiedSync())) { |
| if (verbose) |
| print("Running pub get in $directory..."); |
| int code = await runCommandAndStreamOutput( |
| [sdkBinaryName('pub'), 'get'], |
| workingDirectory: directory |
| ); |
| if (code != 0) |
| return code; |
| } |
| |
| if ((pubSpecLock.existsSync() && pubSpecLock.lastModifiedSync().isAfter(pubSpecYaml.lastModifiedSync())) && |
| (dotPackages.existsSync() && dotPackages.lastModifiedSync().isAfter(pubSpecYaml.lastModifiedSync()))) |
| return 0; |
| |
| _logging.severe('$directory: pubspec.yaml, pubspec.lock, and .packages are in an inconsistent state'); |
| return 1; |
| } |
| } |
| |
| abstract class Template { |
| final String name; |
| final String description; |
| |
| Map<String, String> files = {}; |
| |
| Template(this.name, this.description); |
| |
| void generateInto(Directory dir, String flutterPackagePath) { |
| String dirPath = p.normalize(dir.absolute.path); |
| String projectName = _normalizeProjectName(p.basename(dirPath)); |
| print('Creating ${p.basename(projectName)}...'); |
| dir.createSync(recursive: true); |
| |
| String relativeFlutterPackagePath = p.relative(flutterPackagePath, from: dirPath); |
| |
| files.forEach((String path, String contents) { |
| Map m = {'projectName': projectName, 'description': description, 'flutterPackagePath': relativeFlutterPackagePath}; |
| contents = mustache.render(contents, m); |
| path = path.replaceAll('/', Platform.pathSeparator); |
| File file = new File(p.join(dir.path, path)); |
| file.parent.createSync(); |
| file.writeAsStringSync(contents); |
| print(file.path); |
| }); |
| } |
| |
| String toString() => name; |
| } |
| |
| class FlutterSimpleTemplate extends Template { |
| FlutterSimpleTemplate() : super('flutter-simple', 'A minimal Flutter project.') { |
| files['.gitignore'] = _gitignore; |
| files['flutter.yaml'] = _flutterYaml; |
| files['pubspec.yaml'] = _pubspec; |
| files['README.md'] = _readme; |
| files['lib/main.dart'] = _libMain; |
| } |
| } |
| |
| String _normalizeProjectName(String name) { |
| name = name.replaceAll('-', '_').replaceAll(' ', '_'); |
| // Strip any extension (like .dart). |
| if (name.contains('.')) |
| name = name.substring(0, name.indexOf('.')); |
| return name; |
| } |
| |
| const String _gitignore = r''' |
| .DS_Store |
| .idea |
| .packages |
| .pub/ |
| build/ |
| packages |
| pubspec.lock |
| '''; |
| |
| const String _readme = r''' |
| # {{projectName}} |
| |
| {{description}} |
| |
| ## Getting Started |
| |
| For help getting started with Flutter, view our online |
| [documentation](http://flutter.io/). |
| '''; |
| |
| const String _pubspec = r''' |
| name: {{projectName}} |
| description: {{description}} |
| dependencies: |
| flutter: |
| path: {{flutterPackagePath}} |
| '''; |
| |
| const String _flutterYaml = r''' |
| name: {{projectName}} |
| material-design-icons: |
| - name: content/add |
| '''; |
| |
| const String _libMain = r''' |
| import 'package:flutter/material.dart'; |
| |
| void main() { |
| runApp( |
| new MaterialApp( |
| title: "Flutter Demo", |
| routes: { |
| '/': (RouteArguments args) => new FlutterDemo() |
| } |
| ) |
| ); |
| } |
| |
| class FlutterDemo extends StatelessComponent { |
| Widget build(BuildContext context) { |
| return new Scaffold( |
| toolBar: new ToolBar( |
| center: new Text("Flutter Demo") |
| ), |
| body: new Material( |
| child: new Center( |
| child: new Text("Hello world!") |
| ) |
| ), |
| floatingActionButton: new FloatingActionButton( |
| child: new Icon( |
| icon: 'content/add' |
| ) |
| ) |
| ); |
| } |
| } |
| '''; |