// 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:flutter_tools/src/globals.dart'; import 'package:json_schema/json_schema.dart'; import 'package:yaml/yaml.dart'; import 'base/file_system.dart'; import 'cache.dart'; /// A wrapper around the `flutter` section in the `pubspec.yaml` file. class FlutterManifest { FlutterManifest._(); /// Returns null on missing or invalid manifest static Future<FlutterManifest> createFromPath(String path) async { return _createFromYaml(await _loadFlutterManifest(path)); } /// Returns null on missing or invalid manifest static Future<FlutterManifest> createFromString(String manifest) async { return _createFromYaml(loadYaml(manifest)); } static Future<FlutterManifest> _createFromYaml(Object yamlDocument) async { final FlutterManifest pubspec = new FlutterManifest._(); if (yamlDocument != null && !await _validate(yamlDocument)) return null; pubspec._descriptor = yamlDocument ?? <String, dynamic>{}; 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; bool get isEmpty => _descriptor.isEmpty; String get appName => _descriptor['name'] ?? ''; bool get usesMaterialDesign { return _flutterDescriptor['uses-material-design'] ?? false; } List<Map<String, dynamic>> get fontsDescriptor { return _flutterDescriptor['fonts'] ?? const <Map<String, dynamic>>[]; } List<String> get assets { return _flutterDescriptor['assets'] ?? const <String>[]; } 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( asset, weight: fontFile['weight'], style: fontFile['style'], )); } if (fontAssets.isNotEmpty) fonts.add(new Font(fontFamily['family'], fontAssets)); } return fonts; } } class Font { Font(this.familyName, this.fontAssets) : assert(familyName != null), assert(fontAssets != null), assert(fontAssets.isNotEmpty); 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 { FontAsset(this.asset, {this.weight, this.style}) : assert(asset != null); final String asset; 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; descriptor['asset'] = asset; return descriptor; } @override String toString() => '$runtimeType(asset: $asset, weight; $weight, style: $style)'; } Future<dynamic> _loadFlutterManifest(String manifestPath) async { if (manifestPath == null || !fs.isFileSync(manifestPath)) return null; final String manifestDescriptor = await fs.file(manifestPath).readAsString(); return loadYaml(manifestDescriptor); } 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; } }