import 'dart:io';
import 'package:path/path.dart';
class SampleError extends Error {
SampleError(this.message);
final String message;
@override
String toString() => 'SampleError($message)';
}
const String sampleCatalogKeywords = r'^Title:|^Summary:|^Description:|^Classes:|^Sample:|^See also:';
Directory outputDirectory;
Directory sampleDirectory;
Directory testDirectory;
Directory driverDirectory;
void logMessage(String s) { print(s); }
void logError(String s) { print(s); }
File inputFile(String dir, String name) {
return File(dir + Platform.pathSeparator + name);
}
File outputFile(String name, [Directory directory]) {
return File((directory ?? outputDirectory).path + Platform.pathSeparator + name);
}
void initialize() {
outputDirectory = Directory('.generated');
sampleDirectory = Directory('lib');
testDirectory = Directory('test');
driverDirectory = Directory('test_driver');
outputDirectory.createSync();
}
String expandTemplate(String template, Map<String, String> values) {
final RegExp tokenRE = RegExp(r'@\(([\w ]+)\)', multiLine: true);
return template.replaceAllMapped(tokenRE, (Match match) {
if (match.groupCount != 1)
throw SampleError('bad template keyword $match[0]');
final String keyword = match[1];
return values[keyword] ?? '';
});
}
void writeExpandedTemplate(File output, String template, Map<String, String> values) {
output.writeAsStringSync(expandTemplate(template, values));
logMessage('wrote $output');
}
class SampleInfo {
SampleInfo(this.sourceFile, this.commit);
final File sourceFile;
final String commit;
String sourceCode;
Map<String, String> commentValues;
String get sourceName => basenameWithoutExtension(sourceFile.path);
String get link => sourceName.replaceAll('_', '-');
String get sampleClass => commentValues['sample'];
Iterable<String> get highlightedClasses {
final String classNames = commentValues['classes'];
if (classNames == null)
return const <String>[];
return classNames.split(',').map<String>((String s) => s.trim()).where((String s) => s.isNotEmpty);
}
String get importPath => '..' + Platform.pathSeparator + sourceFile.path;
bool initialize() {
final String contents = sourceFile.readAsStringSync();
final RegExp startRE = RegExp(r'^/\*\s+^Sample\s+Catalog', multiLine: true);
final RegExp endRE = RegExp(r'^\*/', multiLine: true);
final Match startMatch = startRE.firstMatch(contents);
if (startMatch == null)
return false;
final int startIndex = startMatch.end;
final Match endMatch = endRE.firstMatch(contents.substring(startIndex));
if (endMatch == null)
return false;
final String comment = contents.substring(startIndex, startIndex + endMatch.start);
sourceCode = contents.substring(0, startMatch.start) + contents.substring(startIndex + endMatch.end);
if (sourceCode.trim().isEmpty)
throw SampleError('did not find any source code in $sourceFile');
final RegExp keywordsRE = RegExp(sampleCatalogKeywords, multiLine: true);
final List<Match> keywordMatches = keywordsRE.allMatches(comment).toList();
if (keywordMatches.isEmpty)
throw SampleError('did not find any keywords in the Sample Catalog comment in $sourceFile');
commentValues = <String, String>{};
for (int i = 0; i < keywordMatches.length; i += 1) {
final String keyword = comment.substring(keywordMatches[i].start, keywordMatches[i].end - 1);
final String value = comment.substring(
keywordMatches[i].end,
i == keywordMatches.length - 1 ? null : keywordMatches[i + 1].start,
);
commentValues[keyword.toLowerCase()] = value.trim();
}
commentValues['name'] = sourceName;
commentValues['path'] = 'examples/catalog/${sourceFile.path}';
commentValues['source'] = sourceCode.trim();
commentValues['link'] = link;
commentValues['android screenshot'] = 'https://storage.googleapis.com/flutter-catalog/$commit/${sourceName}_small.png';
return true;
}
}
void generate(String commit) {
initialize();
final List<SampleInfo> samples = <SampleInfo>[];
for (FileSystemEntity entity in sampleDirectory.listSync()) {
if (entity is File && entity.path.endsWith('.dart')) {
final SampleInfo sample = SampleInfo(entity, commit);
if (sample.initialize())
samples.add(sample);
}
}
samples.sort((SampleInfo a, SampleInfo b) {
return a.sourceName.compareTo(b.sourceName);
});
final String entryTemplate = inputFile('bin', 'entry.md.template').readAsStringSync();
final Iterable<String> entries = samples.map<String>((SampleInfo sample) {
return expandTemplate(entryTemplate, sample.commentValues);
});
writeExpandedTemplate(
outputFile('index.md'),
inputFile('bin', 'index.md.template').readAsStringSync(),
<String, String>{
'entries': entries.join('\n'),
},
);
for (SampleInfo sample in samples) {
writeExpandedTemplate(
outputFile(sample.sourceName + '.md'),
inputFile('bin', 'sample_page.md.template').readAsStringSync(),
sample.commentValues,
);
}
final Map<String, List<SampleInfo>> classToSamples = <String, List<SampleInfo>>{};
for (SampleInfo sample in samples) {
for (String className in sample.highlightedClasses) {
classToSamples[className] ??= <SampleInfo>[];
classToSamples[className].add(sample);
}
}
for (String className in classToSamples.keys) {
final Iterable<String> entries = classToSamples[className].map<String>((SampleInfo sample) {
return expandTemplate(entryTemplate, sample.commentValues);
});
writeExpandedTemplate(
outputFile('${className}_index.md'),
inputFile('bin', 'class_index.md.template').readAsStringSync(),
<String, String>{
'class': '$className',
'entries': entries.join('\n'),
'link': '${className}_index',
},
);
}
writeExpandedTemplate(
outputFile('screenshot.dart', driverDirectory),
inputFile('bin', 'screenshot.dart.template').readAsStringSync(),
<String, String>{
'imports': samples.map<String>((SampleInfo page) {
return "import '${page.importPath}' show ${page.sampleClass};\n";
}).join(),
'widgets': samples.map<String>((SampleInfo sample) {
return 'new ${sample.sampleClass}(),\n';
}).join(),
},
);
writeExpandedTemplate(
outputFile('screenshot_test.dart', driverDirectory),
inputFile('bin', 'screenshot_test.dart.template').readAsStringSync(),
<String, String>{
'paths': samples.map<String>((SampleInfo sample) {
return "'${outputFile(sample.sourceName + '.png').path}'";
}).join(',\n'),
},
);
logMessage('The following entries must appear in _data/catalog/widgets.json');
for (String className in classToSamples.keys)
logMessage('"sample": "${className}_index"');
}
void main(List<String> args) {
if (args.length != 1) {
logError(
'Usage (cd examples/catalog/; dart bin/sample_page.dart commit)\n'
'The flutter commit hash locates screenshots on storage.googleapis.com/flutter-catalog/'
);
exit(255);
}
try {
generate(args[0]);
} catch (error) {
logError(
'Error: sample_page.dart failed: $error\n'
'This sample_page.dart app expects to be run from the examples/catalog directory. '
'More information can be found in examples/catalog/README.md.'
);
exit(255);
}
exit(0);
}