flutter_manifest.dart 4.94 KB
Newer Older
1 2 3 4 5 6 7
// Copyright 2017 The Chromium 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:async';

import 'package:json_schema/json_schema.dart';
8
import 'package:meta/meta.dart';
9 10 11 12
import 'package:yaml/yaml.dart';

import 'base/file_system.dart';
import 'cache.dart';
13
import 'globals.dart';
14

15
/// A wrapper around the `flutter` section in the `pubspec.yaml` file.
16 17 18
class FlutterManifest {
  FlutterManifest._();

19
  /// Returns null on invalid manifest. Returns empty manifest on missing file.
20
  static Future<FlutterManifest> createFromPath(String path) async {
21 22 23 24
    if (path == null || !fs.isFileSync(path))
      return _createFromYaml(null);
    final String manifest = await fs.file(path).readAsString();
    return createFromString(manifest);
25
  }
26

27
  /// Returns null on missing or invalid manifest
28
  @visibleForTesting
29 30 31 32 33 34
  static Future<FlutterManifest> createFromString(String manifest) async {
    return _createFromYaml(loadYaml(manifest));
  }

  static Future<FlutterManifest> _createFromYaml(Object yamlDocument) async {
    final FlutterManifest pubspec = new FlutterManifest._();
35
    if (yamlDocument != null && !await _validate(yamlDocument))
36 37
      return null;

38
    pubspec._descriptor = yamlDocument ?? <String, dynamic>{};
39 40 41 42 43 44 45 46 47 48
    pubspec._flutterDescriptor = pubspec._descriptor['flutter'] ?? <String, dynamic>{};
    return pubspec;
  }

  /// A map representation of the entire `pubspec.yaml` file.
  Map<String, dynamic> _descriptor;

  /// A map representation of the `flutter` section in the `pubspec.yaml` file.
  Map<String, dynamic> _flutterDescriptor;

49 50 51
  bool get isEmpty => _descriptor.isEmpty;

  String get appName => _descriptor['name'] ?? '';
52 53 54 55 56 57 58 59 60

  bool get usesMaterialDesign {
    return _flutterDescriptor['uses-material-design'] ?? false;
  }

  List<Map<String, dynamic>> get fontsDescriptor {
   return _flutterDescriptor['fonts'] ?? const <Map<String, dynamic>>[];
  }

61 62
  List<Uri> get assets {
    return _flutterDescriptor['assets']?.map(Uri.parse)?.toList() ?? const <Uri>[];
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
  }

  List<Font> _fonts;

  List<Font> get fonts {
    _fonts ??= _extractFonts();
    return _fonts;
  }

  List<Font> _extractFonts() {
    if (!_flutterDescriptor.containsKey('fonts'))
      return <Font>[];

    final List<Font> fonts = <Font>[];
    for (Map<String, dynamic> fontFamily in _flutterDescriptor['fonts']) {
      final List<Map<String, dynamic>> fontFiles = fontFamily['fonts'];
      final String familyName = fontFamily['family'];
      if (familyName == null) {
        printError('Warning: Missing family name for font.', emphasis: true);
        continue;
      }
      if (fontFiles == null) {
        printError('Warning: No fonts specified for font $familyName', emphasis: true);
        continue;
      }

      final List<FontAsset> fontAssets = <FontAsset>[];
      for (Map<String, dynamic> fontFile in fontFiles) {
        final String asset = fontFile['asset'];
        if (asset == null) {
          printError('Warning: Missing asset in fonts for $familyName', emphasis: true);
          continue;
        }

        fontAssets.add(new FontAsset(
98
          Uri.parse(asset),
99 100 101 102 103 104 105 106 107 108 109 110
          weight: fontFile['weight'],
          style: fontFile['style'],
        ));
      }
      if (fontAssets.isNotEmpty)
        fonts.add(new Font(fontFamily['family'], fontAssets));
    }
    return fonts;
  }
}

class Font {
111 112 113 114
  Font(this.familyName, this.fontAssets)
    : assert(familyName != null),
      assert(fontAssets != null),
      assert(fontAssets.isNotEmpty);
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130

  final String familyName;
  final List<FontAsset> fontAssets;

  Map<String, dynamic> get descriptor {
    return <String, dynamic>{
      'family': familyName,
      'fonts': fontAssets.map((FontAsset a) => a.descriptor).toList(),
    };
  }

  @override
  String toString() => '$runtimeType(family: $familyName, assets: $fontAssets)';
}

class FontAsset {
131 132
  FontAsset(this.assetUri, {this.weight, this.style})
    : assert(assetUri != null);
133

134
  final Uri assetUri;
135 136 137 138 139 140 141 142 143 144 145
  final int weight;
  final String style;

  Map<String, dynamic> get descriptor {
    final Map<String, dynamic> descriptor = <String, dynamic>{};
    if (weight != null)
      descriptor['weight'] = weight;

    if (style != null)
      descriptor['style'] = style;

146
    descriptor['asset'] = assetUri.path;
147 148 149 150
    return descriptor;
  }

  @override
151
  String toString() => '$runtimeType(asset: ${assetUri.path}, weight; $weight, style: $style)';
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
}

Future<bool> _validate(Object manifest) async {
  final String schemaPath = fs.path.join(
    fs.path.absolute(Cache.flutterRoot), 'packages', 'flutter_tools', 'schema',
    'pubspec_yaml.json',
  );
  final Schema schema = await Schema.createSchemaFromUrl(fs.path.toUri(schemaPath).toString());

  final Validator validator = new Validator(schema);
  if (validator.validate(manifest)) {
    return true;
  } else {
    printStatus('Error detected in pubspec.yaml:', emphasis: true);
    printError(validator.errors.join('\n'));
    return false;
  }
169
}