Skip to content

Commit

Permalink
New command workspace list (#4378)
Browse files Browse the repository at this point in the history
  • Loading branch information
sigurdm authored Sep 12, 2024
1 parent 2a1f61c commit be207ac
Show file tree
Hide file tree
Showing 10 changed files with 282 additions and 79 deletions.
95 changes: 16 additions & 79 deletions lib/src/command/outdated.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import 'dart:async';
import 'dart:convert';
import 'dart:math';

import 'package:collection/collection.dart' show IterableExtension;
import 'package:path/path.dart' as p;
Expand All @@ -15,6 +14,7 @@ import '../entrypoint.dart';
import '../io.dart';
import '../lock_file.dart';
import '../log.dart' as log;
import '../log.dart';
import '../package.dart';
import '../package_name.dart';
import '../pubspec.dart';
Expand Down Expand Up @@ -552,8 +552,8 @@ Future<void> _outputHuman(
final markedRows =
Map.fromIterables(rows, await mode.markVersionDetails(rows));

List<_FormattedString> formatted(_PackageDetails package) => [
_FormattedString(package.name),
List<FormattedString> formatted(_PackageDetails package) => [
FormattedString(package.name),
...markedRows[package]!.map((m) => m.toHuman()),
];

Expand All @@ -575,64 +575,42 @@ Future<void> _outputHuman(
final devTransitiveRows =
rows.where(hasKind(_DependencyKind.devTransitive)).map(formatted);

final formattedRows = <List<_FormattedString>>[
final formattedRows = <List<FormattedString>>[
['Package Name', 'Current', 'Upgradable', 'Resolvable', 'Latest']
.map((s) => _format(s, log.bold))
.map((s) => format(s, log.bold))
.toList(),
if (hasDirectDependencies) ...[
[
if (directRows.isEmpty)
_format('\ndirect dependencies: ${mode.allGood}', log.bold)
format('\ndirect dependencies: ${mode.allGood}', log.bold)
else
_format('\ndirect dependencies:', log.bold),
format('\ndirect dependencies:', log.bold),
],
...directRows,
],
if (includeDevDependencies && hasDevDependencies) ...[
[
if (devRows.isEmpty)
_format('\ndev_dependencies: ${mode.allGood}', log.bold)
format('\ndev_dependencies: ${mode.allGood}', log.bold)
else
_format('\ndev_dependencies:', log.bold),
format('\ndev_dependencies:', log.bold),
],
...devRows,
],
if (showTransitiveDependencies) ...[
if (transitiveRows.isNotEmpty)
[_format('\ntransitive dependencies:', log.bold)],
[format('\ntransitive dependencies:', log.bold)],
...transitiveRows,
if (includeDevDependencies) ...[
if (devTransitiveRows.isNotEmpty)
[_format('\ntransitive dev_dependencies:', log.bold)],
[format('\ntransitive dev_dependencies:', log.bold)],
...devTransitiveRows,
],
],
];

final columnWidths = <int, int>{};
for (var i = 0; i < formattedRows.length; i++) {
if (formattedRows[i].length > 1) {
for (var j = 0; j < formattedRows[i].length; j++) {
final currentMaxWidth = columnWidths[j] ?? 0;
columnWidths[j] = max(
formattedRows[i][j].computeLength(useColors: useColors),
currentMaxWidth,
);
}
}
}

for (final row in formattedRows) {
final b = StringBuffer();
for (var j = 0; j < row.length; j++) {
b.write(row[j].formatted(useColors: useColors));
b.write(
' ' *
((columnWidths[j]! + 2) -
row[j].computeLength(useColors: useColors)),
);
}
log.message(b.toString());
for (final line in log.renderTable(formattedRows, useColors)) {
log.message(line);
}

final upgradable = rows.where(
Expand Down Expand Up @@ -1016,16 +994,8 @@ enum _DependencyKind {
devTransitive,
}

_FormattedString _format(
String value,
String Function(String) format, {
String? prefix = '',
}) {
return _FormattedString(value, format: format, prefix: prefix);
}

abstract class _Details {
_FormattedString toHuman();
FormattedString toHuman();
Object? toJson();
}

Expand All @@ -1035,7 +1005,7 @@ class _SimpleDetails implements _Details {
_SimpleDetails(this.details);

@override
_FormattedString toHuman() => _FormattedString(details);
FormattedString toHuman() => FormattedString(details);

@override
Object? toJson() => null;
Expand All @@ -1060,7 +1030,7 @@ class _MarkedVersionDetails implements _Details {
_jsonExplanation = jsonExplanation;

@override
_FormattedString toHuman() => _FormattedString(
FormattedString toHuman() => FormattedString(
_versionDetails?.describe ?? '-',
format: _format,
prefix: _prefix,
Expand All @@ -1078,39 +1048,6 @@ class _MarkedVersionDetails implements _Details {
}
}

class _FormattedString {
final String value;

/// Should apply the ansi codes to present this string.
final String Function(String) _format;

/// A prefix for marking this string if colors are not used.
final String _prefix;

final String _suffix;

_FormattedString(
this.value, {
String Function(String)? format,
String? prefix,
String? suffix,
}) : _format = format ?? _noFormat,
_prefix = prefix ?? '',
_suffix = suffix ?? '';

String formatted({required bool useColors}) {
return useColors
? _format(_prefix + value + _suffix)
: _prefix + value + _suffix;
}

int computeLength({required bool? useColors}) {
return _prefix.length + value.length + _suffix.length;
}

static String _noFormat(String x) => x;
}

/// Whether the package [name] is overridden anywhere in the workspace rooted at
/// [workspaceRoot].
bool hasOverride(Package workspaceRoot, String name) {
Expand Down
18 changes: 18 additions & 0 deletions lib/src/command/workspace.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) 2024, 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 '../command.dart';
import 'workspace_list.dart';

class WorkspaceCommand extends PubCommand {
@override
String get description => 'Work with the pub workspace.';

@override
String get name => 'workspace';

WorkspaceCommand() {
addSubcommand(WorkspaceListCommand());
}
}
63 changes: 63 additions & 0 deletions lib/src/command/workspace_list.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright (c) 2024, 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 'dart:convert';

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

import '../command.dart';
import '../log.dart';
import '../utils.dart';

class WorkspaceListCommand extends PubCommand {
@override
String get description =>
'List all packages in the workspace, and their directory';

@override
String get name => 'list';

WorkspaceListCommand() {
argParser.addFlag(
'json',
negatable: false,
help: 'output information in a json format',
);
}

@override
void runProtected() {
if (argResults.flag('json')) {
message(
const JsonEncoder.withIndent(' ').convert({
'packages': [
...entrypoint.workspaceRoot.transitiveWorkspace.map(
(package) => {
'name': package.name,
'path': p.canonicalize(package.dir),
},
),
],
}),
);
} else {
for (final line in renderTable(
[
[format('Package', bold), format('Path', bold)],
for (final package in entrypoint.workspaceRoot.transitiveWorkspace)
[
format(package.name, (x) => x),
format(
'${p.relative(p.absolute(package.dir))}${p.separator}',
(x) => x,
),
],
],
canUseAnsiCodes,
)) {
message(line);
}
}
}
}
2 changes: 2 additions & 0 deletions lib/src/command_runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import 'command/unpack.dart';
import 'command/upgrade.dart';
import 'command/uploader.dart';
import 'command/version.dart';
import 'command/workspace.dart';
import 'exit_codes.dart' as exit_codes;
import 'git.dart' as git;
import 'io.dart';
Expand Down Expand Up @@ -156,6 +157,7 @@ class PubCommandRunner extends CommandRunner<int> implements PubTopLevel {
addCommand(LoginCommand());
addCommand(LogoutCommand());
addCommand(VersionCommand());
addCommand(WorkspaceCommand());
addCommand(TokenCommand());
}

Expand Down
78 changes: 78 additions & 0 deletions lib/src/log.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ library;
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';

import 'package:args/command_runner.dart';
import 'package:path/path.dart' as p;
Expand Down Expand Up @@ -635,3 +636,80 @@ class _JsonLogger {
stdout.writeln(jsonEncode(message));
}
}

/// Represents a string and its highlighting separately, such that we can
/// compute the displayed length.
class FormattedString {
final String value;

/// Should apply the ansi codes to present this string.
final String Function(String) _format;

/// A prefix for marking this string if colors are not used.
final String _prefix;

final String _suffix;

FormattedString(
this.value, {
String Function(String)? format,
String? prefix,
String? suffix,
}) : _format = format ?? _noFormat,
_prefix = prefix ?? '',
_suffix = suffix ?? '';

String formatted({required bool useColors}) {
return useColors
? _format(_prefix + value + _suffix)
: _prefix + value + _suffix;
}

int computeLength({required bool? useColors}) {
return _prefix.length + value.length + _suffix.length;
}

static String _noFormat(String x) => x;
}

FormattedString format(
String value,
String Function(String) format, {
String? prefix = '',
}) =>
FormattedString(value, format: format, prefix: prefix);

/// Formats a table of [rows], inserting enough spaces to make columns line up.
List<String> renderTable(
List<List<FormattedString>> rows,
bool useColors,
) {
// Compute the width of each column by taking the max across all rows.
final columnWidths = <int, int>{};
for (var i = 0; i < rows.length; i++) {
if (rows[i].length > 1) {
for (var j = 0; j < rows[i].length; j++) {
final currentMaxWidth = columnWidths[j] ?? 0;
columnWidths[j] = max(
rows[i][j].computeLength(useColors: useColors),
currentMaxWidth,
);
}
}
}

final result = <String>[];
for (final row in rows) {
final b = StringBuffer();
for (var j = 0; j < row.length; j++) {
b.write(row[j].formatted(useColors: useColors));
b.write(
' ' *
((columnWidths[j]! + 2) -
row[j].computeLength(useColors: useColors)),
);
}
result.add(b.toString());
}
return result;
}
2 changes: 2 additions & 0 deletions lib/src/pub_embeddable_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import 'command/token.dart';
import 'command/unpack.dart';
import 'command/upgrade.dart';
import 'command/uploader.dart';
import 'command/workspace.dart';
import 'log.dart' as log;
import 'log.dart';
import 'utils.dart';
Expand Down Expand Up @@ -86,6 +87,7 @@ class PubEmbeddableCommand extends PubCommand implements PubTopLevel {
addSubcommand(LoginCommand());
addSubcommand(LogoutCommand());
addSubcommand(TokenCommand());
addSubcommand(WorkspaceCommand());
}

@override
Expand Down
1 change: 1 addition & 0 deletions test/testdata/goldens/embedding/embedding_test/--help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Available subcommands:
token Manage authentication tokens for hosted pub repositories.
unpack Downloads a package and unpacks it in place.
upgrade Upgrade the current package's dependencies to latest versions.
workspace Work with the pub workspace.

Run "pub_command_runner help" to see global options.
See https://dart.dev/tools/pub/cmd/pub-global for detailed documentation.
Expand Down
Loading

0 comments on commit be207ac

Please sign in to comment.