blob: 188af314b687737376b652245d076b370e16d999 [file] [log] [blame]
// 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()
class Pubspec {
// TODO: executables
final String name;
@JsonKey(fromJson: _versionFromString)
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 for specifying included screenshot files.
@JsonKey(fromJson: parseScreenshots)
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)
final Map<String, VersionConstraint?>? environment;
@JsonKey(fromJson: parseDeps)
final Map<String, Dependency> dependencies;
@JsonKey(fromJson: parseDeps)
final Map<String, Dependency> devDependencies;
@JsonKey(fromJson: parseDeps)
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;
/// 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.screenshots,
this.documentation,
this.description,
Map<String, Dependency>? dependencies,
Map<String, Dependency>? devDependencies,
Map<String, Dependency>? dependencyOverrides,
this.flutter,
}) :
// ignore: deprecated_member_use_from_same_package
authors = _normalizeAuthors(author, authors),
environment = environment ?? const {},
dependencies = dependencies ?? const {},
devDependencies = devDependencies ?? 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);
}
/// 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>{
if (author != null) 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);
});