Skip to content

Commit

Permalink
pub deps --json (#2896)
Browse files Browse the repository at this point in the history
  • Loading branch information
sigurdm authored Mar 16, 2021
1 parent 53a69e2 commit 056a8c9
Show file tree
Hide file tree
Showing 12 changed files with 724 additions and 239 deletions.
140 changes: 97 additions & 43 deletions lib/src/command/deps.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
// BSD-style license that can be found in the LICENSE file.

import 'dart:collection';

import 'package:path/path.dart' as p;
import 'dart:convert';

import '../ascii_tree.dart' as tree;
import '../command.dart';
Expand Down Expand Up @@ -53,6 +52,10 @@ class DepsCommand extends PubCommand {
argParser.addFlag('executables',
negatable: false, help: 'List all available executables.');

argParser.addFlag('json',
negatable: false,
help: 'Output dependency information in a json format.');

argParser.addOption('directory',
abbr: 'C', help: 'Run this in the directory<dir>.', valueHelp: 'dir');
}
Expand All @@ -64,26 +67,98 @@ class DepsCommand extends PubCommand {

_buffer = StringBuffer();

if (argResults['executables']) {
_outputExecutables();
} else {
for (var sdk in sdks.values) {
if (!sdk.isAvailable) continue;
_buffer.writeln("${log.bold('${sdk.name} SDK')} ${sdk.version}");
if (argResults['json']) {
if (argResults.wasParsed('dev')) {
usageException(
'Cannot combine --json and --dev.\nThe json output contains the dependency type in the output.');
}

_buffer.writeln(_labelPackage(entrypoint.root));

switch (argResults['style']) {
case 'compact':
_outputCompact();
break;
case 'list':
_outputList();
break;
case 'tree':
_outputTree();
break;
if (argResults.wasParsed('executables')) {
usageException(
'Cannot combine --json and --executables.\nThe json output always lists available executables.');
}
if (argResults.wasParsed('style')) {
usageException('Cannot combine --json and --style.');
}
final visited = <String>[];
final toVisit = [entrypoint.root.name];
final packagesJson = <dynamic>[];
while (toVisit.isNotEmpty) {
final current = toVisit.removeLast();
if (visited.contains(current)) continue;
visited.add(current);
final currentPackage = entrypoint.packageGraph.packages[current];
final next = (current == entrypoint.root.name
? entrypoint.root.immediateDependencies
: currentPackage.dependencies)
.keys
.toList();
final dependencyType = entrypoint.root.dependencyType(current);
final kind = currentPackage == entrypoint.root
? 'root'
: (dependencyType == DependencyType.direct
? 'direct'
: (dependencyType == DependencyType.dev
? 'dev'
: 'transitive'));
final source =
entrypoint.packageGraph.lockFile.packages[current]?.source?.name ??
'root';
packagesJson.add({
'name': current,
'version': currentPackage.version.toString(),
'kind': kind,
'source': source,
'dependencies': next
});
toVisit.addAll(next);
}
var executables = [
for (final package in [
entrypoint.root,
...entrypoint.root.immediateDependencies.keys
.map((name) => entrypoint.packageGraph.packages[name])
])
...package.executableNames.map((name) => package == entrypoint.root
? ':$name'
: (package.name == name ? name : '${package.name}:$name'))
];

_buffer.writeln(
JsonEncoder.withIndent(' ').convert(
{
'root': entrypoint.root.name,
'packages': packagesJson,
'sdks': [
for (var sdk in sdks.values)
if (sdk.version != null)
{'name': sdk.name, 'version': sdk.version.toString()}
],
'executables': executables
},
),
);
} else {
if (argResults['executables']) {
_outputExecutables();
} else {
for (var sdk in sdks.values) {
if (!sdk.isAvailable) continue;
_buffer.writeln("${log.bold('${sdk.name} SDK')} ${sdk.version}");
}

_buffer.writeln(_labelPackage(entrypoint.root));

switch (argResults['style']) {
case 'compact':
_outputCompact();
break;
case 'list':
_outputList();
break;
case 'tree':
_outputTree();
break;
}
}
}

Expand Down Expand Up @@ -268,34 +343,13 @@ class DepsCommand extends PubCommand {
];

for (var package in packages) {
var executables = _getExecutablesFor(package);
var executables = package.executableNames;
if (executables.isNotEmpty) {
_buffer.writeln(_formatExecutables(package.name, executables.toList()));
}
}
}

/// Returns `true` if [path] looks like a Dart entrypoint.
bool _isDartExecutable(String path) {
try {
var unit = analysisContextManager.parse(path);
return isEntrypoint(unit);
} on AnalyzerErrorGroup {
return false;
}
}

/// Lists all Dart files in the `bin` directory of the [package].
///
/// Returns file names without extensions.
Iterable<String> _getExecutablesFor(Package package) {
var packagePath = p.normalize(p.absolute(package.dir));
analysisContextManager.createContextsForDirectory(packagePath);
return package.executablePaths
.where((e) => _isDartExecutable(p.absolute(package.dir, e)))
.map(p.basenameWithoutExtension);
}

/// Returns formatted string that lists [executables] for the [packageName].
/// Examples:
///
Expand Down
3 changes: 3 additions & 0 deletions lib/src/package.dart
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ class Package {
.toList();
}

List<String> get executableNames =>
executablePaths.map(p.basenameWithoutExtension).toList();

/// Returns the path to the README file at the root of the entrypoint, or null
/// if no README file is found.
///
Expand Down
Loading

0 comments on commit 056a8c9

Please sign in to comment.