mirror of
https://github.com/flutter/samples.git
synced 2025-11-10 23:08:59 +00:00
Merge in the flutter gallery (#176)
This commit is contained in:
266
gallery/codeviewer_cli/lib/segment_generator.dart
Normal file
266
gallery/codeviewer_cli/lib/segment_generator.dart
Normal file
@@ -0,0 +1,266 @@
|
||||
// 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;
|
||||
}
|
||||
Reference in New Issue
Block a user