Unverified Commit 78f53bed authored by Darren Austin's avatar Darren Austin Committed by GitHub

Script to generate widget theme data defaults from the Material token database. (#95370)

parent a9c43bf7
...@@ -874,6 +874,7 @@ Future<void> _runFrameworkTests() async { ...@@ -874,6 +874,7 @@ Future<void> _runFrameworkTests() async {
await _runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'android_semantics_testing'), fatalWarnings: false); await _runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'android_semantics_testing'), fatalWarnings: false);
await _runFlutterTest(path.join(flutterRoot, 'dev', 'manual_tests')); await _runFlutterTest(path.join(flutterRoot, 'dev', 'manual_tests'));
await _runFlutterTest(path.join(flutterRoot, 'dev', 'tools', 'vitool')); await _runFlutterTest(path.join(flutterRoot, 'dev', 'tools', 'vitool'));
await _runFlutterTest(path.join(flutterRoot, 'dev', 'tools', 'gen_defaults'));
await _runFlutterTest(path.join(flutterRoot, 'dev', 'tools', 'gen_keycodes')); await _runFlutterTest(path.join(flutterRoot, 'dev', 'tools', 'gen_keycodes'));
await _runFlutterTest(path.join(flutterRoot, 'dev', 'benchmarks', 'test_apps', 'stocks')); await _runFlutterTest(path.join(flutterRoot, 'dev', 'benchmarks', 'test_apps', 'stocks'));
await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_driver'), tests: <String>[path.join('test', 'src', 'real_tests')], options: soundNullSafetyOptions); await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_driver'), tests: <String>[path.join('test', 'src', 'real_tests')], options: soundNullSafetyOptions);
......
## Token Defaults Generator
Script that generates widget component theme data defaults
based on the Material Token database. These tokens were
extracted into a JSON file from an internal Google database.
## Usage
Run this program from the root of the git repository:
```
dart dev/tools/gen_defaults/bin/gen_defaults.dart
```
## Templates
There is a template file for every component that needs defaults from
the token database. These templates are implemented as subclasses of
`TokenTemplate`. This base class provides some utilities and a structure
for adding a new chunk of generated code to the bottom of a given file.
Templates need to override the `generate` method to provide the generated
code chunk as a string. The tokens are represented as a `Map<String, dynamic>`
that is loaded from `data/material-tokens.json`. Templates can look up
whatever properties are needed in this structure to provide the properties
needed for the component.
See `lib/fab_template.dart` for an example that generates defaults for the
Floating Action Button.
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Generate component theme data defaults based on the Material
// Design Token database. These tokens were extracted into a
// JSON file from the internal Google database.
//
// ## Usage
//
// Run this program from the root of the git repository.
//
// ```
// dart dev/tools/gen_defaults/bin/gen_defaults.dart
// ```
import 'dart:convert';
import 'dart:io';
import 'package:gen_defaults/fab_template.dart';
Future<void> main(List<String> args) async {
const String tokensDB = 'dev/tools/gen_defaults/data/material-tokens.json';
final Map<String, dynamic> tokens = jsonDecode(File(tokensDB).readAsStringSync()) as Map<String, dynamic>;
const String materialLib = 'packages/flutter/lib/src/material';
FABTemplate('$materialLib/floating_action_button.dart', tokens).updateFile();
}
This diff is collapsed.
// Copyright 2014 The Flutter 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 'template.dart';
class FABTemplate extends TokenTemplate {
const FABTemplate(String fileName, Map<String, dynamic> tokens) : super(fileName, tokens);
@override
String generate() => '''
// Generated version ${tokens["version"]}, ${tokens["date"]}
class _M3Defaults extends FloatingActionButtonThemeData {
_M3Defaults(this.context, this.type, this.hasChild)
: _colors = Theme.of(context).colorScheme,
_textTheme = Theme.of(context).textTheme;
final BuildContext context;
final _FloatingActionButtonType type;
final bool hasChild;
final ColorScheme _colors;
final TextTheme _textTheme;
bool get _isExtended => type == _FloatingActionButtonType.extended;
@override Color? get foregroundColor => _colors.${color("md.comp.fab.primary.icon")};
@override Color? get backgroundColor => _colors.${color("md.comp.fab.primary.container")};
@override Color? get splashColor => _colors.${color("md.comp.fab.primary.pressed.state-layer")};
@override double get elevation => ${elevation("md.comp.fab.primary.container")};
@override Color? get focusColor => _colors.${color("md.comp.fab.primary.focus.state-layer")};
@override double get focusElevation => ${elevation("md.comp.fab.primary.focus.container")};
@override Color? get hoverColor => _colors.${color("md.comp.fab.primary.hover.state-layer")};
@override double get hoverElevation => ${elevation("md.comp.fab.primary.hover.container")};
@override double get highlightElevation => ${elevation("md.comp.fab.primary.pressed.container")};
@override
ShapeBorder? get shape {
switch (type) {
case _FloatingActionButtonType.regular:
return ${shape("md.comp.fab.primary.container.shape")};
case _FloatingActionButtonType.small:
return ${shape("md.comp.fab.primary.small.container.shape")};
case _FloatingActionButtonType.large:
return ${shape("md.comp.fab.primary.large.container.shape")};
case _FloatingActionButtonType.extended:
return ${shape("md.comp.extended-fab.primary.container.shape")};
}
}
@override bool? get enableFeedback => true;
@override
double? get iconSize {
switch (type) {
case _FloatingActionButtonType.regular: return ${value("md.comp.fab.primary.icon.size")};
case _FloatingActionButtonType.small: return ${value("md.comp.fab.primary.small.icon.size")};
case _FloatingActionButtonType.large: return ${value("md.comp.fab.primary.large.icon.size")};
case _FloatingActionButtonType.extended: return ${value("md.comp.extended-fab.primary.icon.size")};
}
}
@override
BoxConstraints? get sizeConstraints => const BoxConstraints.tightFor(
width: ${value("md.comp.fab.primary.container.width")},
height: ${value("md.comp.fab.primary.container.height")},
);
@override
BoxConstraints? get smallSizeConstraints => const BoxConstraints.tightFor(
width: ${value("md.comp.fab.primary.small.container.width")},
height: ${value("md.comp.fab.primary.small.container.height")},
);
@override
BoxConstraints? get largeSizeConstraints => const BoxConstraints.tightFor(
width: ${value("md.comp.fab.primary.large.container.width")},
height: ${value("md.comp.fab.primary.large.container.height")},
);
@override
BoxConstraints? get extendedSizeConstraints => const BoxConstraints.tightFor(
height: ${value("md.comp.extended-fab.primary.container.height")},
);
@override double? get extendedIconLabelSpacing => 8.0;
@override EdgeInsetsGeometry? get extendedPadding => EdgeInsetsDirectional.only(start: hasChild && _isExtended ? 16.0 : 20.0, end: 20.0);
@override TextStyle? get extendedTextStyle => _textTheme.${textStyle("md.comp.extended-fab.primary.label-text")};
}
''';
}
// Copyright 2014 The Flutter 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:io';
abstract class TokenTemplate {
const TokenTemplate(this.fileName, this.tokens);
static const String beginGeneratedComment = '''
// BEGIN GENERATED TOKEN PROPERTIES
''';
static const String headerComment = '''
// Generated code to the end of this file. Do not edit by hand.
// These defaults are generated from the Material Design Token
// database by the script dev/tools/gen_defaults/bin/gen_defaults.dart.
''';
static const String endGeneratedComment = '''
// END GENERATED TOKEN PROPERTIES
''';
final String fileName;
final Map<String, dynamic> tokens;
/// Replace or append the contents of the file with the text from [generate].
///
/// If the file already contains generated block at the end, it will
/// be replaced by the [generate] output. Otherwise the content will
/// just be appended to the end of the file.
Future<void> updateFile() async {
String contents = File(fileName).readAsStringSync();
final int previousGeneratedIndex = contents.indexOf(beginGeneratedComment);
if (previousGeneratedIndex != -1) {
contents = contents.substring(0, previousGeneratedIndex);
}
final StringBuffer buffer = StringBuffer(contents);
buffer.write(beginGeneratedComment);
buffer.write(headerComment);
buffer.write(generate());
buffer.write(endGeneratedComment);
File(fileName).writeAsStringSync(buffer.toString());
}
/// Provide the generated content for the template.
///
/// This abstract method needs to be implemented by subclasses
/// to provide the content that [updateFile] will append to the
/// bottom of the file.
String generate();
String color(String tokenName) {
final String tokenColor = '$tokenName.color';
final String tokenOpacity = '$tokenName.opacity';
String value = '${tokens[tokenColor]!}';
if (tokens.containsKey(tokenOpacity)) {
final String opacity = tokens[tokens[tokenOpacity]!]!.toString();
value += '.withOpacity($opacity)';
}
return value;
}
String elevation(String tokenName) {
final String elevationName = '$tokenName.elevation';
final Map<String, dynamic> elevationValue = tokens[tokens[elevationName]!]! as Map<String, dynamic>;
return elevationValue['value']!.toString();
}
String shape(String tokenName) {
// TODO(darrenaustin): handle more than just rounded rectangle shapes
final String shapeToken = tokens[tokenName]! as String;
final Map<String, dynamic> shape = tokens[shapeToken]! as Map<String, dynamic>;
final Map<String, dynamic> shapeValue = shape['value']! as Map<String, dynamic>;
return 'const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(${shapeValue['value']!})))';
}
String value(String tokenName) {
final Map<String, dynamic> value = tokens[tokenName]! as Map<String, dynamic>;
return value['value'].toString();
}
String textStyle(String tokenName) {
final String fontName = '$tokenName.font';
return tokens[fontName]!.toString();
}
}
name: gen_defaults
description: A command line script to generate Material component defaults from the token database.
version: 1.0.0
environment:
sdk: ">=2.12.0-0 <3.0.0"
dependencies:
async: 2.8.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
charcode: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
collection: 1.15.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
http_parser: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
source_span: 1.8.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
string_scanner: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
term_glyph: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
typed_data: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
dev_dependencies:
path: 1.8.0
test: 1.19.5
_fe_analyzer_shared: 31.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
analyzer: 2.8.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
boolean_selector: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
cli_util: 0.3.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
convert: 3.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
coverage: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
crypto: 3.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
file: 6.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
frontend_server_client: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
glob: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
http_multi_server: 3.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
io: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
js: 0.6.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
logging: 1.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
matcher: 0.12.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
mime: 1.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
node_preamble: 2.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
package_config: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
pool: 1.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
pub_semver: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
shelf: 1.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
shelf_packages_handler: 3.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
shelf_static: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
shelf_web_socket: 1.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
source_map_stack_trace: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
source_maps: 0.10.10 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
stack_trace: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
stream_channel: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
test_core: 0.4.9 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
vm_service: 7.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
watcher: 1.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
web_socket_channel: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
webkit_inspection_protocol: 1.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
yaml: 3.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
# PUBSPEC CHECKSUM: aa20
// Copyright 2014 The Flutter 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:io';
import 'package:gen_defaults/template.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';
void main() {
test('Templates will append to the end of a file', () {
final Directory tempDir = Directory.systemTemp.createTempSync('gen_defaults');
try {
// Create a temporary file with some content.
final File tempFile = File(path.join(tempDir.path, 'test_template.txt'));
tempFile.createSync();
tempFile.writeAsStringSync('''
// This is a file with stuff in it.
// This part shouldn't be changed by
// the template.
''');
// Have a test template append new parameterized content to the end of
// the file.
final Map<String, dynamic> tokens = <String, dynamic>{'foo': 'Foobar', 'bar': 'Barfoo'};
TestTemplate(tempFile.path, tokens).updateFile();
expect(tempFile.readAsStringSync(), '''
// This is a file with stuff in it.
// This part shouldn't be changed by
// the template.
// BEGIN GENERATED TOKEN PROPERTIES
// Generated code to the end of this file. Do not edit by hand.
// These defaults are generated from the Material Design Token
// database by the script dev/tools/gen_defaults/bin/gen_defaults.dart.
static final String tokenFoo = 'Foobar';
static final String tokenBar = 'Barfoo';
// END GENERATED TOKEN PROPERTIES
''');
} finally {
tempDir.deleteSync(recursive: true);
}
});
test('Templates will update over previously generated code at the end of a file', () {
final Directory tempDir = Directory.systemTemp.createTempSync('gen_defaults');
try {
// Create a temporary file with some content.
final File tempFile = File(path.join(tempDir.path, 'test_template.txt'));
tempFile.createSync();
tempFile.writeAsStringSync('''
// This is a file with stuff in it.
// This part shouldn't be changed by
// the template.
// BEGIN GENERATED TOKEN PROPERTIES
// Generated code to the end of this file. Do not edit by hand.
// These defaults are generated from the Material Design Token
// database by the script dev/tools/gen_defaults/bin/gen_defaults.dart.
static final String tokenFoo = 'Foobar';
static final String tokenBar = 'Barfoo';
// END GENERATED TOKEN PROPERTIES
''');
// Have a test template append new parameterized content to the end of
// the file.
final Map<String, dynamic> tokens = <String, dynamic>{'foo': 'foo', 'bar': 'bar'};
TestTemplate(tempFile.path, tokens).updateFile();
expect(tempFile.readAsStringSync(), '''
// This is a file with stuff in it.
// This part shouldn't be changed by
// the template.
// BEGIN GENERATED TOKEN PROPERTIES
// Generated code to the end of this file. Do not edit by hand.
// These defaults are generated from the Material Design Token
// database by the script dev/tools/gen_defaults/bin/gen_defaults.dart.
static final String tokenFoo = 'foo';
static final String tokenBar = 'bar';
// END GENERATED TOKEN PROPERTIES
''');
} finally {
tempDir.deleteSync(recursive: true);
}
});
}
class TestTemplate extends TokenTemplate {
TestTemplate(String fileName, Map<String, dynamic> tokens) : super(fileName, tokens);
@override
String generate() => '''
static final String tokenFoo = '${tokens['foo']}';
static final String tokenBar = '${tokens['bar']}';
''';
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment