// Copyright 2016 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. /// Make `n` copies of flutter_gallery. import 'dart:io'; import 'package:args/args.dart'; import 'package:path/path.dart' as path; /// If no `copies` param is passed in, we scale the generated app up to 60k lines. const int kTargetLineCount = 60 * 1024; void main(List args) { // If we're run from the `tools` dir, set the cwd to the repo root. if (path.basename(Directory.current.path) == 'tools') Directory.current = Directory.current.parent.parent; final ArgParser argParser = ArgParser(); argParser.addOption('out'); argParser.addOption('copies'); argParser.addFlag('delete', negatable: false); argParser.addFlag('help', abbr: 'h', negatable: false); final ArgResults results = argParser.parse(args); if (results['help']) { print('Generate n copies of flutter_gallery.\n'); print('usage: dart mega_gallery.dart '); print(argParser.usage); exit(0); } final Directory source = Directory(_normalize('examples/flutter_gallery')); final Directory out = Directory(_normalize(results['out'])); if (results['delete']) { if (out.existsSync()) { print('Deleting ${out.path}'); out.deleteSync(recursive: true); } exit(0); } if (!results.wasParsed('out')) { print('The --out parameter is required.'); print(argParser.usage); exit(1); } int copies; if (!results.wasParsed('copies')) { final SourceStats stats = getStatsFor(_dir(source, 'lib')); copies = (kTargetLineCount / stats.lines).round(); } else { copies = int.parse(results['copies']); } print('Making $copies copies of flutter_gallery.'); print(''); print('Stats:'); print(' packages/flutter : ${getStatsFor(Directory("packages/flutter"))}'); print(' examples/flutter_gallery : ${getStatsFor(Directory("examples/flutter_gallery"))}'); final Directory lib = _dir(out, 'lib'); if (lib.existsSync()) lib.deleteSync(recursive: true); // Copy everything that's not a symlink, dot directory, or build/. _copy(source, out); // Make n - 1 copies. for (int i = 1; i < copies; i++) _copyGallery(out, i); // Create a new entry-point. _createEntry(_file(out, 'lib/main.dart'), copies); // Update the pubspec. String pubspec = _file(out, 'pubspec.yaml').readAsStringSync(); pubspec = pubspec.replaceAll('../../packages/flutter', '../../../packages/flutter'); _file(out, 'pubspec.yaml').writeAsStringSync(pubspec); // Remove the (flutter_gallery specific) analysis_options.yaml file. _file(out, 'analysis_options.yaml').deleteSync(); _file(out, '.dartignore').writeAsStringSync(''); // Count source lines and number of files; tell how to run it. print(' ${path.relative(results["out"])} : ${getStatsFor(out)}'); } // TODO(devoncarew): Create an entry-point that builds a UI with all `n` copies. void _createEntry(File mainFile, int copies) { final StringBuffer imports = StringBuffer(); for (int i = 1; i < copies; i++) { imports.writeln('// ignore: unused_import'); imports.writeln("import 'gallery_$i/main.dart' as main_$i;"); } final String contents = ''' // Copyright 2016 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 'package:flutter/widgets.dart'; import 'gallery/app.dart'; ${imports.toString().trim()} void main() { runApp(const GalleryApp()); } '''; mainFile.writeAsStringSync(contents); } void _copyGallery(Directory galleryDir, int index) { final Directory lib = _dir(galleryDir, 'lib'); final Directory dest = _dir(lib, 'gallery_$index'); dest.createSync(); // Copy demo/, gallery/, and main.dart. _copy(_dir(lib, 'demo'), _dir(dest, 'demo')); _copy(_dir(lib, 'gallery'), _dir(dest, 'gallery')); _file(dest, 'main.dart').writeAsBytesSync(_file(lib, 'main.dart').readAsBytesSync()); } void _copy(Directory source, Directory target) { if (!target.existsSync()) target.createSync(recursive: true); for (FileSystemEntity entity in source.listSync(followLinks: false)) { final String name = path.basename(entity.path); if (entity is Directory) { if (name == 'build' || name.startsWith('.')) continue; _copy(entity, Directory(path.join(target.path, name))); } else if (entity is File) { if (name == '.packages' || name == 'pubspec.lock') continue; final File dest = File(path.join(target.path, name)); dest.writeAsBytesSync(entity.readAsBytesSync()); } } } Directory _dir(Directory parent, String name) => Directory(path.join(parent.path, name)); File _file(Directory parent, String name) => File(path.join(parent.path, name)); String _normalize(String filePath) => path.normalize(path.absolute(filePath)); class SourceStats { int files = 0; int lines = 0; @override String toString() => '${_comma(files).padLeft(3)} files, ${_comma(lines).padLeft(6)} lines'; } SourceStats getStatsFor(Directory dir, [SourceStats stats]) { stats ??= SourceStats(); for (FileSystemEntity entity in dir.listSync(recursive: false, followLinks: false)) { final String name = path.basename(entity.path); if (entity is File && name.endsWith('.dart')) { stats.files += 1; stats.lines += _lineCount(entity); } else if (entity is Directory && !name.startsWith('.')) { getStatsFor(entity, stats); } } return stats; } int _lineCount(File file) { return file.readAsLinesSync().where((String line) { line = line.trim(); if (line.isEmpty || line.startsWith('//')) return false; return true; }).length; } String _comma(int count) { final String str = count.toString(); if (str.length > 3) return str.substring(0, str.length - 3) + ',' + str.substring(str.length - 3); return str; }