blob: fe87c10a60cd797f7aa0b9de9d2400e98e492f37 [file] [log] [blame]
// Copyright (c) 2014, 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.
library googleapis_generator.package_configuration;
import 'dart:async';
import 'dart:io';
import 'package:discoveryapis_generator/discoveryapis_generator.dart';
import 'package:pool/pool.dart';
import 'package:yaml/yaml.dart';
import '../googleapis_generator.dart';
class Package {
final String name;
final List<String> apis;
final Pubspec pubspec;
final String readme;
final String license;
final String changelog;
final String example;
Package(this.name, this.apis, this.pubspec, this.readme, this.license,
this.changelog, this.example);
}
/// Configuration of a set of packages generated from a set of APIs exposed by
/// a Discovery Service.
class DiscoveryPackagesConfiguration {
String configFile;
Map yaml;
Map<String, Package> packages;
Iterable<String> excessApis;
Iterable<String> missingApis;
/// Create a new discovery packages configuration.
///
/// [config] is the path to the YAML configuration file.
///
/// [allApis] is the list of all supported APIs returned by the Discovery
/// Service.
///
/// The format of a YAML document describing a number of packages looks
/// like this:
///
/// packages:
/// - googleapis:
/// version: 0.1.0
/// author: Dart Team <misc@dartlang.org>
/// homepage: http://www.dartlang.org
/// readme: resources/README.md
/// license: resources/LICENSE
/// apis:
/// - analytics:v3
/// - bigquery:v2
/// - googleapis_beta:
/// version: 0.1.0
/// author: Dart Team <misc@dartlang.org>
/// homepage: http://www.dartlang.org
/// readme: resources/README.md
/// license: resources/LICENSE
/// apis:
/// - datastore:v1beta2
/// - dns:v1beta1
/// skipped_apis:
/// - adexchangebuyer:v1
///
/// Each package to build is listed under the key `packages:`.
///
/// The key `skipped_apis` is used to list APIs returned buy the Discovery
/// Service but is not part of any generated packages.
///
/// The file names for the content of readme and license files are resolved
/// relative to the configuration file.
DiscoveryPackagesConfiguration(this.configFile) {
yaml = loadYaml(File(configFile).readAsStringSync());
}
/// Downloads discovery documents from the configuration.
///
/// [discoveryDocsDir] is the directory where all the downloaded discovery
/// documents are stored.
Future download(
String discoveryDocsDir, List<DirectoryListItems> items) async {
// Delete all downloaded discovery documents.
final dir = Directory(discoveryDocsDir);
if (dir.existsSync()) dir.deleteSync(recursive: true);
// Get all rest discovery documents & initialize this object.
final allApis = await fetchDiscoveryDocuments();
_initialize(allApis);
// Download the discovery documents for the packages to build
// (only the APIs we're interested in).
final pool = Pool(10);
var count = 0;
try {
await pool.forEach(packages.entries, (e) async {
print(' ${++count} of ${packages.length} - ${e.key}');
await downloadDiscoveryDocuments('$discoveryDocsDir/${e.key}',
ids: e.value.apis);
}).drain();
} finally {
await pool.close();
}
}
/// Generate packages from the configuration.
///
/// [discoveryDocsDir] is the directory where all the downloaded discovery
/// documents are stored.
///
/// [generatedApisDir] is the directory where the packages are generated.
/// Each package is generated in a sub-directory.
void generate(String discoveryDocsDir, String generatedApisDir) {
// Delete all downloaded discovery documents.
final dir = Directory(discoveryDocsDir);
if (!dir.existsSync()) {
throw Exception(
'Error: The given `$discoveryDocsDir` directory does not exist.');
}
// Load discovery documents from disc & initialize this object.
final allApis = <RestDescription>[];
(yaml['packages'] as List).forEach((package) {
(package as Map).forEach((name, _) {
allApis.addAll(loadDiscoveryDocuments('$discoveryDocsDir/$name'));
});
});
_initialize(allApis);
// Generate packages.
packages.forEach((name, package) {
final results = generateAllLibraries('$discoveryDocsDir/$name',
'$generatedApisDir/$name', package.pubspec);
for (final result in results) {
if (!result.success) {
print(result.toString());
}
}
File('$generatedApisDir/$name/README.md')
.writeAsStringSync(package.readme);
if (package.license != null) {
File('$generatedApisDir/$name/LICENSE')
.writeAsStringSync(package.license);
}
if (package.changelog != null) {
File('$generatedApisDir/$name/CHANGELOG.md')
.writeAsStringSync(package.changelog);
}
if (package.example != null) {
Directory('$generatedApisDir/$name/example').createSync();
File('$generatedApisDir/$name/example/main.dart')
.writeAsStringSync(package.example);
}
});
}
/// Initializes the missingApis/excessApis/packages properties from a list
/// of [RestDescription]s.
void _initialize(List<RestDescription> allApis) {
packages = _packagesFromYaml(yaml['packages'], configFile, allApis);
final knownApis =
_calculateKnownApis(packages, _listFromYaml(yaml['skipped_apis']));
missingApis = _calculateMissingApis(knownApis, allApis);
excessApis = _calculateExcessApis(knownApis, allApis);
}
// Return empty list for YAML null value.
static List _listFromYaml(value) => value ?? [];
static String _generateReadme(
String readmeFile, List<RestDescription> items) {
final sb = StringBuffer();
if (readmeFile != null) {
sb.write(File(readmeFile).readAsStringSync());
}
sb.writeln('''
## Available Google APIs
The following is a list of APIs that are currently available inside this
package.
''');
for (var item in items) {
sb.write('#### ');
if (item.icons != null &&
item.icons.x16 != null &&
item.icons.x16.startsWith('https://')) {
sb.write('![Logo](${item.icons.x16}) ');
}
sb
..writeln('${item.title} - ${item.name} ${item.version}')
..writeln()
..writeln('${item.description}')
..writeln();
if (item.documentationLink != null) {
sb.writeln('Official API documentation: ${item.documentationLink}');
sb.writeln();
}
}
return sb.toString();
}
static Map<String, Package> _packagesFromYaml(YamlList configPackages,
String configFile, List<RestDescription> allApis) {
final packages = <String, Package>{};
configPackages.forEach((package) {
package.forEach((name, values) {
packages[name] = _packageFromYaml(name, values, configFile, allApis);
});
});
return packages;
}
static Package _packageFromYaml(String name, YamlMap values,
String configFile, List<RestDescription> allApis) {
final apis = _listFromYaml(values['apis']).cast<String>();
final version = values['version'] ?? '0.1.0-dev';
final author = values['author'];
final homepage = values['homepage'];
final configUri = Uri.file(configFile);
allApis.sort((RestDescription a, RestDescription b) {
final result = a.name.compareTo(b.name);
if (result != 0) return result;
return a.version.compareTo(b.version);
});
var readmeFile;
if (values['readme'] != null) {
readmeFile = configUri.resolve(values['readme']).path;
}
var licenseFile;
if (values['license'] != null) {
licenseFile = configUri.resolve(values['license']).path;
}
var changelogFile;
if (values['changelog'] != null) {
changelogFile = configUri.resolve(values['changelog']).path;
}
var example;
if (values['example'] != null) {
final exampleFile = configUri.resolve(values['example']).path;
example = File(exampleFile).readAsStringSync();
}
// Generate package description.
final apiDescriptions = <RestDescription>[];
const description = 'Auto-generated client libraries for accessing Google '
'APIs described through the API discovery service.';
allApis.forEach((RestDescription apiDescription) {
if (apis.contains(apiDescription.id)) {
apiDescriptions.add(apiDescription);
}
});
// Generate the README.md file content.
final readme = _generateReadme(readmeFile, apiDescriptions);
// Read the LICENSE
final license = File(licenseFile).readAsStringSync();
// Read CHANGELOG.md
final changelog = File(changelogFile).readAsStringSync();
// Create package description with pubspec.yaml information.
final pubspec =
Pubspec(name, version, description, author: author, homepage: homepage);
return Package(
name,
List<String>.from(apis),
pubspec,
readme,
license,
changelog,
example,
);
}
/// The known APIs are the APis mentioned in each package together with
/// the APIs explicitly skipped.
static Set<String> _calculateKnownApis(
Map<String, Package> packages, YamlList skippedApis) {
final knownApis = <String>{};
knownApis.addAll(skippedApis.cast<String>());
packages.forEach((_, package) => knownApis.addAll(package.apis));
return knownApis;
}
/// The missing APIs are the APIs returned from the Discovery Service
/// but not mentioned in the configuration.
static Iterable<String> _calculateMissingApis(
Iterable<String> knownApis, List<RestDescription> allApis) =>
allApis
.where((item) => !knownApis.contains(item.id))
.map((item) => item.id);
/// The excess APIs are the APIs mentioned in the configuration but not
/// returned from the Discovery Service.
static Iterable<String> _calculateExcessApis(
Iterable<String> knownApis, List<RestDescription> allApis) {
final excessApis = Set<String>.from(knownApis);
allApis.forEach((item) => excessApis.remove(item.id));
return excessApis;
}
}