Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/material_ui/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ environment:

workspace:
- example
- tool/gen_defaults

dependencies:
cupertino_ui:
Expand Down
24 changes: 24 additions & 0 deletions packages/material_ui/tool/gen_defaults/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
## Token Defaults Generator

Script that generates component theme data defaults based on token data.

## Usage
Run this program from the root of the git repository:
```sh
dart packages/material_ui/tool/gen_defaults/bin/gen_defaults.dart [-v]
```

This updates generated component theming files under
`packages/material_ui/lib/src/generated`.

## Templates

There is a template file for every component that needs defaults from the token
database. These templates are implemented as subclasses of either `M3TokenTemplate` or `M3ETokenTemplate`.

Templates need to override the `generateContents` method to provide the
generated code block as a string.

## Tokens

Tokens are stored in `data/`, and are sourced from an internal Google database.
4 changes: 4 additions & 0 deletions packages/material_ui/tool/gen_defaults/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
include: ../../../../analysis_options.yaml

formatter:
page_width: 100
28 changes: 28 additions & 0 deletions packages/material_ui/tool/gen_defaults/bin/gen_defaults.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright 2013 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// ## Usage
//
// Run from the root of flutter/packages:
//
// ```
// dart packages/material_ui/tool/gen_defaults/bin/gen_defaults.dart [-v]
// ```

import 'package:args/args.dart';

// TODO(elliette): Import template files.
// import '../templates/x_template.dart';

Future<void> main(List<String> args) async {
// Parse arguments
final parser = ArgParser();
parser.addFlag('verbose', abbr: 'v', help: 'Enable verbose output', negatable: false);
final ArgResults argResults = parser.parse(args);
// TODO(elliette): Add token logger when verbose flag is used.
// ignore: unused_local_variable
final verbose = argResults['verbose'] as bool;
// TODO(elliette): Invoke template generators.
// const XTemplate().generateFile(verbose: verbose);
}
17 changes: 17 additions & 0 deletions packages/material_ui/tool/gen_defaults/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: gen_defaults
description: A command line script to generate Material component defaults from the token database.
Comment thread
elliette marked this conversation as resolved.
publish_to: none
version: 1.0.0

environment:
sdk: ^3.10.0-0
Comment thread
elliette marked this conversation as resolved.

resolution: workspace
Comment thread
elliette marked this conversation as resolved.

dependencies:
args: any
meta: any

dev_dependencies:
path: any
test: any
111 changes: 111 additions & 0 deletions packages/material_ui/tool/gen_defaults/templates/template.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright 2013 The Flutter Authors
// 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:meta/meta.dart';

enum _MaterialVersion { material3, material3Expressive }

abstract class M3TokenTemplate extends _TokenTemplate {
const M3TokenTemplate();

@override
_MaterialVersion get _version => _MaterialVersion.material3;
}

abstract class M3ETokenTemplate extends _TokenTemplate {
const M3ETokenTemplate();

@override
_MaterialVersion get _version => _MaterialVersion.material3Expressive;
}

abstract class _TokenTemplate {
const _TokenTemplate();

static const String copyrightHeader = '''
// Copyright 2013 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
''';

static const String headerComment = '''

// Do not edit by hand. The code is generated from data in the Material
// Design token database by the script:
// packages/material_ui/tool/gen_defaults/bin/gen_defaults.dart.
''';

/// The Material version this template is for.
_MaterialVersion get _version;

/// The name of the template, which corresponds to the target file name.
/// E.g., 'typography' for generating 'typography_defaults.g.dart'.
String get name;

@visibleForTesting
String get materialLib {
const packagePath = 'packages/material_ui';
const generatedDirectory = 'lib/src/generated';
final String relativeOutputPath = switch (_version) {
_MaterialVersion.material3 => generatedDirectory,
_MaterialVersion.material3Expressive => '$generatedDirectory/material_3_expressive',
};
if (Directory(packagePath).existsSync()) {
return '$packagePath/$relativeOutputPath';
}
return relativeOutputPath;
}

String generateContents();

void generateFile({bool verbose = false}) {
final fileName = '$materialLib/${name}_defaults.g.dart';
if (verbose) {
stdout.writeln('Generating file: $fileName');
stdout.writeln('Target parent file name: $name.dart');
}
final file = File(fileName);
if (!file.existsSync()) {
if (verbose) {
stdout.writeln('File does not exist, creating it.');
}
file.createSync(recursive: true);
}

final parentName = '$name.dart';

if (verbose) {
stdout.writeln('Generating contents...');
}
final buffer = StringBuffer();
buffer.write(copyrightHeader);
buffer.write(headerComment);
final String partOfPath = switch (_version) {
_MaterialVersion.material3 => '../$parentName',
_MaterialVersion.material3Expressive => '../../material_3_expressive/$parentName',
};
buffer.write("part of '$partOfPath';\n\n");
buffer.write(generateContents());

if (verbose) {
stdout.writeln('Writing generated contents to $fileName...');
}
file.writeAsStringSync(buffer.toString());
if (verbose) {
stdout.writeln('Formatting $fileName...');
}
final ProcessResult result = Process.runSync(Platform.resolvedExecutable, <String>[
'format',
fileName,
]);
if (result.exitCode != 0) {
stderr.writeln('Failed to format $fileName: ${result.stderr}');
}
if (verbose) {
stdout.writeln('Done generating $fileName.');
}
}
}
112 changes: 112 additions & 0 deletions packages/material_ui/tool/gen_defaults/test/gen_defaults_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright 2013 The Flutter Authors
// 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:test/test.dart';
import 'test_fixtures/test_templates.dart';

void main() {
Directory? tempDir;
String testPath() => tempDir!.path;

group('gen_defaults templates', () {
setUp(() {
tempDir = Directory.systemTemp.createTempSync('gen_defaults');
});

tearDown(() {
tempDir!.deleteSync(recursive: true);
});

test('will generate a part file ending in _defaults.g.dart', () {
final template = ButtonTemplate(testPath());
template.generateFile(verbose: true);

final file = File('${testPath()}/button_defaults.g.dart');
expect(file.existsSync(), isTrue);
});

test('will generate a file with the correct header text', () {
final template = ButtonTemplate(testPath());
template.generateFile();

final file = File('${testPath()}/button_defaults.g.dart');
final String fileContents = file.readAsStringSync();
expect(fileContents, contains(_fileHeader));
});

test('will generate a file with the expected contents', () {
final template = ButtonTemplate(testPath());
template.generateFile();

final file = File('${testPath()}/button_defaults.g.dart');
final String fileContents = file.readAsStringSync();
expect(fileContents, contains(_buttonDefaultsClass));
});

test('will completely overwrite any previous code', () {
final file = File('${testPath()}/button_defaults.g.dart');
const randomText = 'Pre-existing random text.';
file.writeAsStringSync(randomText);

final template = ButtonTemplate(testPath());
template.generateFile();
final String fileContents = file.readAsStringSync();
expect(fileContents, isNot(contains(randomText)));
expect(fileContents, contains(_buttonDefaultsClass));
});

test('will run dart format over the generated file', () {
final template = UnformattedTemplate(testPath());
template.generateFile();

final file = File('${testPath()}/unformatted_defaults.g.dart');
expect(file.readAsStringSync(), contains(formattedClass));
});

test('materialLib path resolves correctly based on MaterialVersion', () {
final m3Template = TestM3Template();
final m3ExpressiveTemplate = TestM3ExpressiveTemplate();
const materialUiDir = 'packages/material_ui';
const generatedDir = 'lib/src/generated';

final bool hasPackageDir = Directory(materialUiDir).existsSync();
if (hasPackageDir) {
expect(m3Template.materialLib, '$materialUiDir/$generatedDir');
expect(
m3ExpressiveTemplate.materialLib,
'$materialUiDir/$generatedDir/material_3_expressive',
);
} else {
expect(m3Template.materialLib, generatedDir);
expect(m3ExpressiveTemplate.materialLib, '$generatedDir/material_3_expressive');
}
});
});
}

const _fileHeader = '''
// Copyright 2013 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Do not edit by hand. The code is generated from data in the Material
// Design token database by the script:
// packages/material_ui/tool/gen_defaults/bin/gen_defaults.dart.
''';

const _buttonDefaultsClass = '''
class _ButtonDefaults {
static const double height = 40.0;
static const double borderRadius = 8.0;
}
''';

const formattedClass = '''
class UnformattedClass {
final int x = 1;
final String y = 'hello';
}
''';
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright 2013 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

class TokenButton {
static const double height = 40.0;
static const double borderRadius = 8.0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright 2013 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import '../../templates/template.dart';
import 'button_token_data.dart';

class ButtonTemplate extends M3ETokenTemplate {
ButtonTemplate(this.customMaterialLib);

final String customMaterialLib;

@override
String get name => 'button';

@override
String get materialLib => customMaterialLib;

@override
String generateContents() {
return '''
class _ButtonDefaults {
static const double height = ${TokenButton.height};
static const double borderRadius = ${TokenButton.borderRadius};
}
''';
}
}

class UnformattedTemplate extends M3TokenTemplate {
UnformattedTemplate(this.customMaterialLib);

final String customMaterialLib;

@override
String get name => 'unformatted';

@override
String get materialLib => customMaterialLib;

@override
String generateContents() {
return '''
class UnformattedClass {
final int x = 1 ;
final String y = 'hello' ;
}
''';
}
}

class TestM3Template extends M3TokenTemplate {
@override
String get name => 'm3';

@override
String generateContents() => '';
}

class TestM3ExpressiveTemplate extends M3ETokenTemplate {
@override
String get name => 'm3e';

@override
String generateContents() => '';
}