Skip to content

Commit

Permalink
Remove the old CLI options and executables.
Browse files Browse the repository at this point in the history
Before the `dart format` command was added to the core Dart SDK, users accessed the formatter by running a separate `dartfmt` executable that was included with the Dart SDK. That executable had a different CLI interface. For example, you had to pass `-w` to get it to overwrite files and if you passed no arguments at all, it silently sat there waiting for input on stdin. When we added `dart format`, we took that opportunity to revamp the CLI options.

However, the dart_style package still exposed an executable with the old CLI. If you ran `dart pub global activate dart_style`, this would give you a `dartfmt` (and `dartformat`) executable with the old CLI options. Now that almost everyone is using `dart format`, we have removed the old CLI and the old package executables.

You can still run the formatter on the CLI through the package (for example, if you want to use a particular version of dart_style instead of the one bundled with your Dart SDK). But it now uses the exact same CLI options and arguments as the `dart format` command. You can invoke it with `dart run dart_style:format <args...>`.
  • Loading branch information
munificent committed Sep 11, 2024
1 parent 5d35f4d commit 3075b76
Show file tree
Hide file tree
Showing 12 changed files with 257 additions and 1,000 deletions.
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
## 3.0.0-wip

* **Remove the old formatter executables and CLI options.**

Before the `dart format` command was added to the core Dart SDK, users
accessed the formatter by running a separate `dartfmt` executable that was
included with the Dart SDK. That executable had a different CLI interface.
For example, you had to pass `-w` to get it to overwrite files and if you
passed no arguments at all, it silently sat there waiting for input on stdin.
When we added `dart format`, we took that opportunity to revamp the CLI
options.

However, the dart_style package still exposed an executable with the old CLI.
If you ran `dart pub global activate dart_style`, this would give you a
`dartfmt` (and `dartformat`) executable with the old CLI options. Now that
almost everyone is using `dart format`, we have removed the old CLI and the
old package executables.

You can still run the formatter on the CLI through the package (for example,
if you want to use a particular version of dart_style instead of the one
bundled with your Dart SDK). But it now uses the exact same CLI options and
arguments as the `dart format` command. You can invoke it with
`dart run dart_style:format <args...>`.

## 2.3.7

* Allow passing a language version to `DartFomatter()`. Formatted code will be
Expand Down
174 changes: 13 additions & 161 deletions bin/format.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,169 +3,21 @@
// BSD-style license that can be found in the LICENSE file.
import 'dart:io';

import 'package:args/args.dart';
import 'package:dart_style/src/cli/formatter_options.dart';
import 'package:dart_style/src/cli/options.dart';
import 'package:dart_style/src/cli/output.dart';
import 'package:dart_style/src/cli/show.dart';
import 'package:dart_style/src/cli/summary.dart';
import 'package:dart_style/src/io.dart';
import 'package:dart_style/src/short/style_fix.dart';
import 'package:args/command_runner.dart';
import 'package:dart_style/src/cli/format_command.dart';

void main(List<String> args) async {
var parser = ArgParser(allowTrailingOptions: true);
void main(List<String> arguments) async {
var runner =
CommandRunner<int>('format', 'Idiomatically format Dart source code.');
runner.argParser.addFlag('verbose',
abbr: 'v', negatable: false, help: 'Show verbose help.');
runner.addCommand(FormatCommand(
verbose: arguments.contains('-v') || arguments.contains('--verbose')));

defineOptions(parser,
oldCli: true, verbose: args.contains('--verbose') || args.contains('-v'));

ArgResults argResults;
try {
argResults = parser.parse(args);
} on FormatException catch (err) {
usageError(parser, err.message);
}

if (argResults['help'] as bool) {
printUsage(parser);
return;
}

if (argResults['version'] as bool) {
print(dartStyleVersion);
return;
}

if (argResults['verbose'] as bool && !(argResults['help'] as bool)) {
usageError(parser, 'Can only use --verbose with --help.');
}

List<int>? selection;
try {
selection = parseSelection(argResults, 'preserve');
} on FormatException catch (exception) {
usageError(parser, exception.message);
}

if (argResults['dry-run'] as bool && argResults['overwrite'] as bool) {
usageError(
parser, 'Cannot use --dry-run and --overwrite at the same time.');
}

void checkForReporterCollision(String chosen, String other) {
if (!(argResults[other] as bool)) return;

usageError(parser, 'Cannot use --$chosen and --$other at the same time.');
}

var show = Show.legacy;
var summary = Summary.none;
var output = Output.show;
var setExitIfChanged = false;
if (argResults['dry-run'] as bool) {
checkForReporterCollision('dry-run', 'overwrite');
checkForReporterCollision('dry-run', 'machine');

show = Show.dryRun;
output = Output.none;
} else if (argResults['overwrite'] as bool) {
checkForReporterCollision('overwrite', 'machine');

if (argResults.rest.isEmpty) {
usageError(parser,
'Cannot use --overwrite without providing any paths to format.');
}

show = Show.overwrite;
output = Output.write;
} else if (argResults['machine'] as bool) {
output = Output.json;
}

if (argResults['profile'] as bool) summary = Summary.profile();

setExitIfChanged = argResults['set-exit-if-changed'] as bool;

int pageWidth;
try {
pageWidth = int.parse(argResults['line-length'] as String);
} on FormatException catch (_) {
usageError(
parser,
'--line-length must be an integer, was '
'"${argResults['line-length']}".');
}

int indent;
try {
indent = int.parse(argResults['indent'] as String);
if (indent < 0 || indent.toInt() != indent) throw const FormatException();
} on FormatException catch (_) {
usageError(
parser,
'--indent must be a non-negative integer, was '
'"${argResults['indent']}".');
}

var followLinks = argResults['follow-links'] as bool;

var fixes = <StyleFix>[];
if (argResults['fix'] as bool) fixes.addAll(StyleFix.all);
for (var fix in StyleFix.all) {
if (argResults['fix-${fix.name}'] as bool) {
if (argResults['fix'] as bool) {
usageError(parser, '--fix-${fix.name} is redundant with --fix.');
}

fixes.add(fix);
}
}

if (argResults.wasParsed('stdin-name') && argResults.rest.isNotEmpty) {
usageError(parser, 'Cannot pass --stdin-name when not reading from stdin.');
}

var options = FormatterOptions(
indent: indent,
pageWidth: pageWidth,
followLinks: followLinks,
fixes: fixes,
show: show,
output: output,
summary: summary,
setExitIfChanged: setExitIfChanged,
experimentFlags: argResults['enable-experiment'] as List<String>);

if (argResults.rest.isEmpty) {
await formatStdin(options, selection, argResults['stdin-name'] as String);
} else {
await formatPaths(options, argResults.rest);
}

options.summary.show();
}

/// Prints [error] and usage help then exits with exit code 64.
Never usageError(ArgParser parser, String error) {
printUsage(parser, error);
exit(64);
}

void printUsage(ArgParser parser, [String? error]) {
var output = stdout;

var message = 'Idiomatically format Dart source code.';
if (error != null) {
message = error;
output = stdout;
await runner.runCommand(runner.parse(['format', ...arguments]));
} on UsageException catch (exception) {
stderr.writeln(exception);
exit(64);
}

output.write('''$message
Usage: dartfmt [options...] [files or directories...]
Example: dartfmt -w .
Reformats every Dart file in the current directory tree.
${parser.usage}
''');
}
127 changes: 124 additions & 3 deletions lib/src/cli/format_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
// BSD-style license that can be found in the LICENSE file.
import 'dart:io';

import 'package:args/args.dart';
import 'package:args/command_runner.dart';
import 'package:pub_semver/pub_semver.dart';

import '../dart_formatter.dart';
import '../io.dart';
import '../short/style_fix.dart';
import 'formatter_options.dart';
import 'options.dart';
import 'output.dart';
import 'show.dart';
import 'summary.dart';
Expand All @@ -27,7 +27,102 @@ class FormatCommand extends Command<int> {
'${runner!.executableName} $name [options...] <files or directories...>';

FormatCommand({bool verbose = false}) {
defineOptions(argParser, oldCli: false, verbose: verbose);
argParser.addFlag('verbose',
abbr: 'v',
negatable: false,
help: 'Show all options and flags with --help.');

if (verbose) argParser.addSeparator('Output options:');

argParser.addOption('output',
abbr: 'o',
help: 'Set where to write formatted output.',
allowed: ['write', 'show', 'json', 'none'],
allowedHelp: {
'write': 'Overwrite formatted files on disk.',
'show': 'Print code to terminal.',
'json': 'Print code and selection as JSON.',
'none': 'Discard output.'
},
defaultsTo: 'write');
argParser.addOption('show',
help: 'Set which filenames to print.',
allowed: ['all', 'changed', 'none'],
allowedHelp: {
'all': 'All visited files and directories.',
'changed': 'Only the names of files whose formatting is changed.',
'none': 'No file names or directories.',
},
defaultsTo: 'changed',
hide: !verbose);
argParser.addOption('summary',
help: 'Show the specified summary after formatting.',
allowed: ['line', 'profile', 'none'],
allowedHelp: {
'line': 'Single-line summary.',
'profile': 'How long it took for format each file.',
'none': 'No summary.'
},
defaultsTo: 'line',
hide: !verbose);

argParser.addOption('language-version',
help: 'Language version of formatted code.\n'
'Use "latest" to parse as the latest supported version.\n'
'Omit to look for a surrounding package config.',
// TODO(rnystrom): Show this when the tall-style experiment ships.
hide: true);

argParser.addFlag('set-exit-if-changed',
negatable: false,
help: 'Return exit code 1 if there are any formatting changes.');

if (verbose) {
argParser.addSeparator('Non-whitespace fixes (off by default):');
}

argParser.addFlag('fix',
negatable: false, help: 'Apply all style fixes.', hide: !verbose);

for (var fix in StyleFix.all) {
argParser.addFlag('fix-${fix.name}',
negatable: false, help: fix.description, hide: !verbose);
}

if (verbose) argParser.addSeparator('Other options:');

argParser.addOption('line-length',
abbr: 'l',
help: 'Wrap lines longer than this.',
defaultsTo: '80',
hide: true);
argParser.addOption('indent',
abbr: 'i',
help: 'Add this many spaces of leading indentation.',
defaultsTo: '0',
hide: !verbose);

argParser.addFlag('follow-links',
negatable: false,
help: 'Follow links to files and directories.\n'
'If unset, links will be ignored.',
hide: !verbose);
argParser.addFlag('version',
negatable: false, help: 'Show dart_style version.', hide: !verbose);
argParser.addMultiOption('enable-experiment',
help: 'Enable one or more experimental features.\n'
'See dart.dev/go/experiments.',
hide: !verbose);

if (verbose) argParser.addSeparator('Options when formatting from stdin:');

argParser.addOption('selection',
help: 'Track selection (given as "start:length") through formatting.',
hide: !verbose);
argParser.addOption('stdin-name',
help: 'Use this path in error messages when input is read from stdin.',
defaultsTo: 'stdin',
hide: !verbose);
}

@override
Expand Down Expand Up @@ -130,7 +225,7 @@ class FormatCommand extends Command<int> {

List<int>? selection;
try {
selection = parseSelection(argResults, 'selection');
selection = _parseSelection(argResults, 'selection');
} on FormatException catch (exception) {
usageException(exception.message);
}
Expand Down Expand Up @@ -178,4 +273,30 @@ class FormatCommand extends Command<int> {
// and set their own exitCode.
return exitCode;
}

List<int>? _parseSelection(ArgResults argResults, String optionName) {
var option = argResults[optionName] as String?;
if (option == null) return null;

// Can only preserve a selection when parsing from stdin.
if (argResults.rest.isNotEmpty) {
throw FormatException(
'Can only use --$optionName when reading from stdin.');
}

try {
var coordinates = option.split(':');
if (coordinates.length != 2) {
throw const FormatException(
'Selection should be a colon-separated pair of integers, '
'"123:45".');
}

return coordinates.map<int>((coord) => int.parse(coord.trim())).toList();
} on FormatException catch (_) {
throw FormatException(
'--$optionName must be a colon-separated pair of integers, was '
'"${argResults[optionName]}".');
}
}
}
17 changes: 0 additions & 17 deletions lib/src/cli/formatter_options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -95,21 +95,4 @@ class FormatterOptions {
// Set the exit code.
if (setExitIfChanged && changed) exitCode = 1;
}

/// Describes the directory whose contents are about to be processed.
void showDirectory(String path) {
if (output != Output.json) {
show.directory(path);
}
}

/// Describes the symlink at [path] that wasn't followed.
void showSkippedLink(String path) {
show.skippedLink(path);
}

/// Describes the hidden [path] that wasn't processed.
void showHiddenPath(String path) {
show.hiddenPath(path);
}
}
Loading

0 comments on commit 3075b76

Please sign in to comment.