| // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file | |
| // for details. 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:checked_yaml/checked_yaml.dart'; | |
| import 'package:json_annotation/json_annotation.dart'; | |
| import 'package:pub_semver/pub_semver.dart'; | |
| import 'dependency.dart'; | |
| import 'screenshot.dart'; | |
| part 'pubspec.g.dart'; | |
| @JsonSerializable(createToJson: true) | |
| class Pubspec { | |
| // TODO: executables | |
| final String name; | |
| @JsonKey(fromJson: _versionFromString, toJson: _versionToString) | |
| final Version? version; | |
| final String? description; | |
| /// This should be a URL pointing to the website for the package. | |
| final String? homepage; | |
| /// Specifies where to publish this package. | |
| /// | |
| /// Accepted values: `null`, `'none'` or an `http` or `https` URL. | |
| /// | |
| /// [More information](https://dart.dev/tools/pub/pubspec#publish_to). | |
| final String? publishTo; | |
| /// Optional field to specify the source code repository of the package. | |
| /// Useful when a package has both a home page and a repository. | |
| final Uri? repository; | |
| /// Optional field to a web page where developers can report new issues or | |
| /// view existing ones. | |
| final Uri? issueTracker; | |
| /// Optional field to list the URLs where the package authors accept | |
| /// support or funding. | |
| final List<Uri>? funding; | |
| /// Optional field to list the topics that this packages belongs to. | |
| final List<String>? topics; | |
| /// Optional field to list advisories to be ignored by the client. | |
| final List<String>? ignoredAdvisories; | |
| /// Optional field for specifying included screenshot files. | |
| @JsonKey(fromJson: parseScreenshots, toJson: serializeScreenshots) | |
| final List<Screenshot>? screenshots; | |
| /// If there is exactly 1 value in [authors], returns it. | |
| /// | |
| /// If there are 0 or more than 1, returns `null`. | |
| @Deprecated('See https://dart.dev/tools/pub/pubspec#authorauthors') | |
| String? get author { | |
| if (authors.length == 1) { | |
| return authors.single; | |
| } | |
| return null; | |
| } | |
| @Deprecated('See https://dart.dev/tools/pub/pubspec#authorauthors') | |
| final List<String> authors; | |
| final String? documentation; | |
| @JsonKey(fromJson: _environmentMap, toJson: _serializeEnvironment) | |
| final Map<String, VersionConstraint?> environment; | |
| @JsonKey(fromJson: parseDeps, toJson: _serializeDeps) | |
| final Map<String, Dependency> dependencies; | |
| @JsonKey(fromJson: parseDeps, toJson: _serializeDeps) | |
| final Map<String, Dependency> devDependencies; | |
| @JsonKey(fromJson: parseDeps, toJson: _serializeDeps) | |
| final Map<String, Dependency> dependencyOverrides; | |
| /// Optional configuration specific to [Flutter](https://flutter.io/) | |
| /// packages. | |
| /// | |
| /// May include | |
| /// [assets](https://flutter.io/docs/development/ui/assets-and-images) | |
| /// and other settings. | |
| final Map<String, dynamic>? flutter; | |
| /// Optional field to specify executables | |
| @JsonKey(fromJson: _executablesMap, toJson: _serializeExecutables) | |
| final Map<String, String?> executables; | |
| /// If this package is a Pub Workspace, this field lists the sub-packages. | |
| final List<String>? workspace; | |
| /// Specifies how to resolve dependencies with the surrounding Pub Workspace. | |
| final String? resolution; | |
| /// If [author] and [authors] are both provided, their values are combined | |
| /// with duplicates eliminated. | |
| Pubspec( | |
| this.name, { | |
| this.version, | |
| this.publishTo, | |
| @Deprecated('See https://dart.dev/tools/pub/pubspec#authorauthors') | |
| String? author, | |
| @Deprecated('See https://dart.dev/tools/pub/pubspec#authorauthors') | |
| List<String>? authors, | |
| Map<String, VersionConstraint?>? environment, | |
| this.homepage, | |
| this.repository, | |
| this.issueTracker, | |
| this.funding, | |
| this.topics, | |
| this.ignoredAdvisories, | |
| this.screenshots, | |
| this.documentation, | |
| this.description, | |
| this.workspace, | |
| this.resolution, | |
| Map<String, Dependency>? dependencies, | |
| Map<String, Dependency>? devDependencies, | |
| Map<String, Dependency>? dependencyOverrides, | |
| this.flutter, | |
| Map<String, String?>? executables, | |
| }) : authors // ignore: deprecated_member_use_from_same_package | |
| = _normalizeAuthors( | |
| author, | |
| authors, | |
| ), | |
| environment = environment ?? const {}, | |
| dependencies = dependencies ?? const {}, | |
| devDependencies = devDependencies ?? const {}, | |
| executables = executables ?? const {}, | |
| dependencyOverrides = dependencyOverrides ?? const {} { | |
| if (name.isEmpty) { | |
| throw ArgumentError.value(name, 'name', '"name" cannot be empty.'); | |
| } | |
| if (publishTo != null && publishTo != 'none') { | |
| try { | |
| final targetUri = Uri.parse(publishTo!); | |
| if (!(targetUri.isScheme('http') || targetUri.isScheme('https'))) { | |
| throw const FormatException('Must be an http or https URL.'); | |
| } | |
| } on FormatException catch (e) { | |
| throw ArgumentError.value(publishTo, 'publishTo', e.message); | |
| } | |
| } | |
| } | |
| factory Pubspec.fromJson(Map json, {bool lenient = false}) { | |
| if (lenient) { | |
| while (json.isNotEmpty) { | |
| // Attempting to remove top-level properties that cause parsing errors. | |
| try { | |
| return _$PubspecFromJson(json); | |
| } on CheckedFromJsonException catch (e) { | |
| if (e.map == json && json.containsKey(e.key)) { | |
| json = Map.from(json)..remove(e.key); | |
| continue; | |
| } | |
| rethrow; | |
| } | |
| } | |
| } | |
| return _$PubspecFromJson(json); | |
| } | |
| /// Creates a JSON-serializable map for this instance. | |
| Map<String, dynamic> toJson() => _$PubspecToJson(this); | |
| /// Parses source [yaml] into [Pubspec]. | |
| /// | |
| /// When [lenient] is set, top-level property-parsing or type cast errors are | |
| /// ignored and `null` values are returned. | |
| factory Pubspec.parse(String yaml, {Uri? sourceUrl, bool lenient = false}) => | |
| checkedYamlDecode( | |
| yaml, | |
| (map) => Pubspec.fromJson(map!, lenient: lenient), | |
| sourceUrl: sourceUrl, | |
| ); | |
| static List<String> _normalizeAuthors(String? author, List<String>? authors) { | |
| final value = <String>{?author, ...?authors}; | |
| return value.toList(); | |
| } | |
| } | |
| Version? _versionFromString(String? input) => | |
| input == null ? null : Version.parse(input); | |
| Map<String, VersionConstraint?> _environmentMap(Map? source) => | |
| source?.map((k, value) { | |
| final key = k as String; | |
| if (key == 'dart') { | |
| // github.com/dart-lang/pub/blob/d84173eeb03c3/lib/src/pubspec.dart#L342 | |
| // 'dart' is not allowed as a key! | |
| throw CheckedFromJsonException( | |
| source, | |
| 'dart', | |
| 'VersionConstraint', | |
| 'Use "sdk" to for Dart SDK constraints.', | |
| badKey: true, | |
| ); | |
| } | |
| VersionConstraint? constraint; | |
| if (value == null) { | |
| constraint = null; | |
| } else if (value is String) { | |
| try { | |
| constraint = VersionConstraint.parse(value); | |
| } on FormatException catch (e) { | |
| throw CheckedFromJsonException(source, key, 'Pubspec', e.message); | |
| } | |
| return MapEntry(key, constraint); | |
| } else { | |
| throw CheckedFromJsonException( | |
| source, | |
| key, | |
| 'VersionConstraint', | |
| '`$value` is not a String.', | |
| ); | |
| } | |
| return MapEntry(key, constraint); | |
| }) ?? | |
| {}; | |
| Map<String, String?> _executablesMap(Map? source) => | |
| source?.map((k, value) { | |
| final key = k as String; | |
| if (value == null) { | |
| return MapEntry(key, null); | |
| } else if (value is String) { | |
| return MapEntry(key, value); | |
| } else { | |
| throw CheckedFromJsonException( | |
| source, | |
| key, | |
| 'String', | |
| '`$value` is not a String.', | |
| ); | |
| } | |
| }) ?? | |
| {}; | |
| Map<String, String?> _serializeEnvironment( | |
| Map<String, VersionConstraint?> map, | |
| ) => map.map((key, value) => MapEntry(key, value?.toString())); | |
| String? _versionToString(Version? version) => version?.toString(); | |
| Map<String, String?> _serializeExecutables(Map<String, String?>? map) => { | |
| ...?map, | |
| }; | |
| Map<String, dynamic> _serializeDeps(Map<String, Dependency> input) => { | |
| for (final MapEntry(:key, :value) in input.entries) key: value.toJson(), | |
| }; |