mirror of https://github.com/flutter/samples.git
267 lines
7.7 KiB
267 lines
7.7 KiB
// Copyright 2019 The Flutter team. 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:convert';
|
|
import 'dart:io';
|
|
import 'prehighlighter.dart';
|
|
|
|
const _globalPrologue =
|
|
'''// This file is automatically generated by codeviewer_cli.
|
|
// Do not edit this file.
|
|
|
|
// Copyright 2019 The Flutter team. 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/material.dart';
|
|
import 'package:gallery/codeviewer/code_style.dart';
|
|
|
|
class CodeSegments {
|
|
''';
|
|
|
|
const _globalEpilogue = '}\n';
|
|
|
|
final Pattern beginSubsegment = RegExp(r'//\s+BEGIN');
|
|
final Pattern endSubsegment = RegExp(r'//\s+END');
|
|
|
|
enum _FileReadStatus {
|
|
comments,
|
|
imports,
|
|
finished,
|
|
}
|
|
|
|
/// Returns the new status of the scanner whose previous status was
|
|
/// [oldStatus], after scanning the line [line].
|
|
_FileReadStatus _updatedStatus(_FileReadStatus oldStatus, String line) {
|
|
_FileReadStatus lineStatus;
|
|
if (line.trim().startsWith('//')) {
|
|
lineStatus = _FileReadStatus.comments;
|
|
} else if (line.trim().startsWith('import')) {
|
|
lineStatus = _FileReadStatus.imports;
|
|
} else {
|
|
lineStatus = _FileReadStatus.finished;
|
|
}
|
|
|
|
_FileReadStatus newStatus;
|
|
switch (oldStatus) {
|
|
case _FileReadStatus.comments:
|
|
newStatus =
|
|
(line.trim().isEmpty || lineStatus == _FileReadStatus.comments)
|
|
? _FileReadStatus.comments
|
|
: lineStatus;
|
|
break;
|
|
case _FileReadStatus.imports:
|
|
newStatus = (line.trim().isEmpty || lineStatus == _FileReadStatus.imports)
|
|
? _FileReadStatus.imports
|
|
: _FileReadStatus.finished;
|
|
break;
|
|
case _FileReadStatus.finished:
|
|
newStatus = oldStatus;
|
|
break;
|
|
}
|
|
return newStatus;
|
|
}
|
|
|
|
Map<String, String> _createSegments(String sourceDirectoryPath) {
|
|
List<File> files = Directory(sourceDirectoryPath)
|
|
.listSync(recursive: true)
|
|
.whereType<File>()
|
|
.toList();
|
|
|
|
Map<String, StringBuffer> subsegments = {};
|
|
Map<String, String> subsegmentPrologues = {};
|
|
|
|
Set<String> appearedSubsegments = Set();
|
|
|
|
for (final file in files) {
|
|
// Process file.
|
|
|
|
String content = file.readAsStringSync();
|
|
List<String> lines = LineSplitter().convert(content);
|
|
|
|
_FileReadStatus status = _FileReadStatus.comments;
|
|
|
|
StringBuffer prologue = StringBuffer();
|
|
|
|
Set<String> activeSubsegments = Set();
|
|
|
|
for (final line in lines) {
|
|
// Update status.
|
|
|
|
status = _updatedStatus(status, line);
|
|
|
|
if (status != _FileReadStatus.finished) {
|
|
prologue.writeln(line);
|
|
}
|
|
|
|
// Process run commands.
|
|
|
|
if (line.trim().startsWith(beginSubsegment)) {
|
|
String argumentString = line.replaceFirst(beginSubsegment, '').trim();
|
|
List<String> arguments =
|
|
argumentString.isEmpty ? [] : argumentString.split(RegExp(r'\s+'));
|
|
|
|
for (final argument in arguments) {
|
|
if (activeSubsegments.contains(argument)) {
|
|
throw PreformatterException(
|
|
'BEGIN $argument is used twice in file ${file.path}');
|
|
} else if (appearedSubsegments.contains(argument)) {
|
|
throw PreformatterException('BEGIN $argument is used twice');
|
|
} else {
|
|
activeSubsegments.add(argument);
|
|
appearedSubsegments.add(argument);
|
|
subsegments[argument] = StringBuffer();
|
|
subsegmentPrologues[argument] = prologue.toString();
|
|
}
|
|
}
|
|
} else if (line.trim().startsWith(endSubsegment)) {
|
|
String argumentString = line.replaceFirst(endSubsegment, '').trim();
|
|
List<String> arguments =
|
|
argumentString.isEmpty ? [] : argumentString.split(RegExp(r'\s+'));
|
|
|
|
if (arguments.isEmpty && activeSubsegments.length == 1) {
|
|
arguments.add(activeSubsegments.first);
|
|
}
|
|
|
|
for (final argument in arguments) {
|
|
if (activeSubsegments.contains(argument)) {
|
|
activeSubsegments.remove(argument);
|
|
} else {
|
|
throw PreformatterException(
|
|
'END $argument is used without a paired BEGIN in ${file.path}');
|
|
}
|
|
}
|
|
} else {
|
|
// Simple line.
|
|
|
|
for (final name in activeSubsegments) {
|
|
subsegments[name].writeln(line);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (activeSubsegments.isNotEmpty) {
|
|
throw PreformatterException('File ${file.path} has unpaired BEGIN');
|
|
}
|
|
}
|
|
|
|
Map<String, List<TaggedString>> segments = {};
|
|
Map<String, String> segmentPrologues = {};
|
|
|
|
// Sometimes a code segment is made up of subsegments. They are marked by
|
|
// names with a "#" symbol in it, such as "bottomSheetDemoModal#1" and
|
|
// "bottomSheetDemoModal#2".
|
|
// The following code groups the subsegments by order into segments.
|
|
subsegments.forEach((key, value) {
|
|
String name;
|
|
double order;
|
|
|
|
if (key.contains('#')) {
|
|
List<String> parts = key.split('#');
|
|
name = parts[0];
|
|
order = double.parse(parts[1]);
|
|
} else {
|
|
name = key;
|
|
order = 0;
|
|
}
|
|
|
|
if (!segments.containsKey(name)) {
|
|
segments[name] = [];
|
|
}
|
|
segments[name].add(
|
|
TaggedString(
|
|
text: value.toString(),
|
|
order: order,
|
|
),
|
|
);
|
|
|
|
segmentPrologues[name] = subsegmentPrologues[key];
|
|
});
|
|
|
|
segments.forEach((key, value) {
|
|
value.sort((ts1, ts2) => (ts1.order - ts2.order).sign.round());
|
|
});
|
|
|
|
Map<String, String> answer = {};
|
|
|
|
for (final name in segments.keys) {
|
|
StringBuffer buffer = StringBuffer();
|
|
|
|
buffer.write(segmentPrologues[name].trim());
|
|
buffer.write('\n\n');
|
|
|
|
for (final ts in segments[name]) {
|
|
buffer.write(ts.text.trim());
|
|
buffer.write('\n\n');
|
|
}
|
|
|
|
answer[name] = buffer.toString();
|
|
}
|
|
|
|
return answer;
|
|
}
|
|
|
|
/// A string [text] together with a number [order], for sorting purposes.
|
|
/// Used to store different subsegments of a code segment.
|
|
/// The [order] of each subsegment is tagged with the code in order to be
|
|
/// sorted in the desired order.
|
|
class TaggedString {
|
|
TaggedString({this.text, this.order});
|
|
|
|
final String text;
|
|
final double order;
|
|
}
|
|
|
|
void _formatSegments(Map<String, String> segments, String targetFilePath) {
|
|
File targetFile = File(targetFilePath);
|
|
IOSink output = targetFile.openWrite();
|
|
|
|
output.write(_globalPrologue);
|
|
|
|
for (final name in segments.keys) {
|
|
String code = segments[name];
|
|
|
|
output.writeln(' static TextSpan $name (BuildContext context) {');
|
|
output.writeln(' final CodeStyle codeStyle = CodeStyle.of(context);');
|
|
output.writeln(' return TextSpan(children: [');
|
|
|
|
List<CodeSpan> codeSpans = DartSyntaxPrehighlighter().format(code);
|
|
|
|
for (final span in codeSpans) {
|
|
output.write(' ');
|
|
output.write(span.toString());
|
|
output.write(',\n');
|
|
}
|
|
|
|
output.write(' ]); }\n');
|
|
}
|
|
|
|
output.write(_globalEpilogue);
|
|
|
|
output.close();
|
|
}
|
|
|
|
/// Collect code segments, highlight, and write to file.
|
|
///
|
|
/// [writeSegments] walks through the directory specified by
|
|
/// [sourceDirectoryPath] and reads every file in it,
|
|
/// collects code segments marked by "// BEGIN <segment_name>" and "// END",
|
|
/// highlights them, and writes to the file specified by
|
|
/// [targetFilePath].
|
|
///
|
|
/// The output file is a dart source file with a class "CodeSegments" and
|
|
/// static methods of type TextSpan(BuildContext context).
|
|
/// Each method generates a widget that displays a segment of code.
|
|
///
|
|
/// The target file is overwritten.
|
|
void writeSegments({String sourceDirectoryPath, String targetFilePath}) {
|
|
Map<String, String> segments = _createSegments(sourceDirectoryPath);
|
|
_formatSegments(segments, targetFilePath);
|
|
}
|
|
|
|
class PreformatterException implements Exception {
|
|
PreformatterException(this.cause);
|
|
String cause;
|
|
}
|