Saltar al contenido principal

Pule tu aplicación de CLI

Mejora el HelpCommand para proporcionar información más detallada y agrega un argumento onOutput para un manejo de salida flexible.

En este capítulo, darás los toques finales al paquete command_runner. Refinarás el HelpCommand para proporcionar información de uso más detallada y agregarás un argumento onOutput para un manejo de salida más flexible. Esto finaliza el paquete CommandRunner y lo prepara para su uso en escenarios más complejos.

Qué lograrás

Aprende cómo construir cadenas de texto de forma iterativa
Mejora la información de uso de la ayuda
Haz que tu ejecutor de comandos sea más flexible

Prerrequisitos

#

Antes de comenzar este capítulo, asegúrate de:

  • Haber completado el Capítulo 7 y tener un entorno de desarrollo de Dart en funcionamiento con el proyecto dartpedia.
  • Estar familiarizado con conceptos básicos de programación como variables, funciones y flujo de control.
  • Entender los conceptos de paquetes y librerías en Dart.
  • Estar familiarizado con los principios de programación orientada a objetos como la herencia y las clases abstractas.

Tareas

#

Pulirás el paquete command_runner para hacerlo más robusto y fácil de usar.

Tarea 1 Mejorar la salida de HelpCommand

#

Mejora el HelpCommand para proporcionar información de uso más detallada, incluyendo opciones y sus descripciones. Esto facilitará a los usuarios entender cómo usar tu aplicación de CLI.

  1. Abre el archivo command_runner/lib/src/help_command.dart.

  2. Agrega imports para console.dart y exceptions.dart en la parte superior del archivo. Los necesitas para usar las extensiones de color y para lanzar una ArgumentException.

    dart
    import 'dart:async';
    
    import 'package:command_runner/command_runner.dart';
    
    import 'console.dart';
    import 'exceptions.dart';
    
  3. Reemplaza el método run existente con el siguiente. Esta nueva versión utiliza un StringBuffer para construir eficientemente la cadena de ayuda e incluye la lógica para manejar la salida detallada (verbose).

    dart
    @override
    FutureOr<String> run(ArgResults args) async {
      final buffer = StringBuffer();
      buffer.writeln(runner.usage.titleText);
    
      if (args.flag('verbose')) {
        for (var cmd in runner.commands) {
          buffer.write(_renderCommandVerbose(cmd));
        }
    
        return buffer.toString();
      }
    
      if (args.hasOption('command')) {
        var (:option, :input) = args.getOption('command');
    
        var cmd = runner.commands.firstWhere(
          (command) => command.name == input,
          orElse: () {
            throw ArgumentException(
              'Input $input is not a known command.',
              name,
            );
          },
        );
    
        return _renderCommandVerbose(cmd);
      }
    
      // Verbose is false and no arg was passed in, so print basic usage.
      for (var command in runner.commands) {
        buffer.writeln(command.usage);
      }
    
      return buffer.toString();
    }
    

    StringBuffer es una clase de Dart que te permite construir cadenas de texto de manera eficiente. Tiene un mejor rendimiento que usar el operador +, especialmente al realizar muchas concatenaciones dentro de un bucle.

  4. Agrega el método de ayuda privado _renderCommandVerbose a la clase HelpCommand. Este método da formato a la salida detallada para un solo comando.

    dart
    String _renderCommandVerbose(Command cmd) {
      final indent = ' ' * 10;
      final buffer = StringBuffer();
      buffer.writeln(cmd.usage.instructionText); //abbr, name: description
      buffer.writeln('$indent ${cmd.help}');
      if (cmd.valueHelp != null) {
        buffer.writeln(
          '$indent [Argument] Required? ${cmd.requiresArgument}, Type: ${cmd.valueHelp}, Default: ${cmd.defaultValue ?? 'none'}',
        );
      }
      buffer.writeln('$indent Options:');
      for (var option in cmd.options) {
        buffer.writeln('$indent ${option.usage}');
      }
      return buffer.toString();
    }
    

Tarea 2 Agregar un callback onOutput

#

A continuación, agrega un argumento onOutput a CommandRunner para permitir un manejo de salida flexible.

  1. Abre el archivo command_runner/lib/src/command_runner_base.dart.

  2. Agrega el argumento onOutput al constructor de CommandRunner, y agrega el miembro onOutput correspondiente a la clase.

    dart
    class CommandRunner {
      CommandRunner({this.onOutput, this.onError});
    
      /// If not null, this method is used to handle output. Useful if you want to
      /// execute code before the output is printed to the console, or if you
      /// want to do something other than print output the console.
      /// If null, the onInput method will [print] the output.
      FutureOr<void> Function(String)? onOutput;
    
      FutureOr<void> Function(Object)? onError;
    
      // ... rest of the class
    }
    
  3. Actualiza el método run para usar el argumento onOutput.

    dart
      Future<void> run(List<String> input) async {
        try {
          final ArgResults results = parse(input);
          if (results.command != null) {
            Object? output = await results.command!.run(results);
            if (onOutput != null) {
              await onOutput!(output.toString());
            } else {
              print(output.toString());
            }
          }
        } on Exception catch (exception) {
          if (onError != null) {
            onError!(exception);
          } else {
            rethrow;
          }
        }
      }
    

    Esto updates el método run para usar la función onOutput si se proporciona; de lo contrario, se imprime en la consola de forma predeterminada.

Tarea 3 Usar el callback onOutput

#

Finalmente, actualiza tu aplicación principal para usar la nueva característica onOutput.

  1. Abre el archivo cli/bin/cli.dart.

  2. Actualiza la función main para pasar la función onOutput a CommandRunner. También necesitarás agregar un import para console.dart para hacer disponible la función write.

    dart
    import 'package:command_runner/command_runner.dart';
    
    const version = '0.0.1';
    
    void main(List<String> arguments) {
      var commandRunner = CommandRunner(
        onOutput: (String output) async {
          await write(output);
        },
        onError: (Object error) {
          if (error is Error) {
            throw error;
          }
          if (error is Exception) {
            print(error);
          }
        },
      )..addCommand(HelpCommand());
      commandRunner.run(arguments);
    }
    

Tarea 4 Probar los cambios

#

Prueba el HelpCommand mejorado y el callback onOutput.

  1. Abre tu terminal y navega al directorio cli.

  2. Ejecuta el comando dart run bin/cli.dart help --verbose.

    Deberías ver información de uso detallada para el comando help, impresa usando la función personalizada write.

Revisión

#

Qué lograste

Aquí tienes un resumen de lo que construiste y aprendiste en esta lección.
Usaste StringBuffer para construir cadenas de forma eficiente

Reemplazaste la concatenación de cadenas por un StringBuffer, lo cual puede ser más eficiente para construir cadenas en bucles. Métodos como writeln() y write() añaden contenido, mientras que toString() produce la cadena final.

Mejoraste HelpCommand con salida detallada (verbose)

Mejoraste el sistema de ayuda para mostrar información de uso detallada que incluye opciones, valores predeterminados y descripciones. La bandera --verbose y la opción --command dan control a los usuarios sobre el nivel de detalle.

Agregaste un argumento callback onOutput para un manejo flexible

Agregaste onOutput a CommandRunner, permitiendo a los consumidores de la librería personalizar cómo se muestra la salida. Este patrón hace que tu paquete sea más flexible y adaptable, permitiendo la impresión diferida, el registro (logging) o la redirección de la salida.

Cuestionario

#

Pon a prueba tu comprensión

1 / 2
What is the purpose of the StringBuffer class in Dart?
  1. Para almacenar una cadena de tamaño fijo.

    No exactamente.

    Las cadenas en Dart son inmutables y no tienen el concepto de tamaño fijo. StringBuffer sirve para un propósito diferente relacionado con la creación de cadenas.

  2. Para construir cadenas de manera eficiente añadiendo múltiples partes.

    ¡Correcto!

    StringBuffer proporciona una concatenación de cadenas eficiente al evitar la creación de objetos de cadena intermedios durante múltiples adiciones sucesivas.

  3. Para cifrar cadenas.

    No exactamente.

    El cifrado requiere librerías de criptografía especializadas. StringBuffer es una utilidad de propósito general, no está relacionada con la seguridad.

  4. Para comparar la igualdad de dos cadenas.

    No exactamente.

    La comparación de cadenas se hace usando == o compareTo(). StringBuffer sirve para un propósito diferente.

What does the onOutput argument in the CommandRunner class allow you to do?
  1. Definir el nombre de un comando.

    No exactamente.

    Los nombres de los comandos se definen en el getter name de la clase Command. onOutput sirve para un propósito diferente.

  2. Especificar la entrada para un comando.

    No exactamente.

    La entrada proviene de los argumentos de línea de comandos pasados a run(). El nombre onOutput sugiere que se trata de otra cosa.

  3. Establecer el mensaje de error para un comando.

    No exactamente.

    El manejo de errores tiene su propio mecanismo (onError). onOutput es para un tipo diferente de resultado.

  4. Personalizar el manejo de salida de un comando.

    ¡Correcto!

    onOutput te permite definir un comportamiento personalizado para la salida del comando, como formato, registro (logging) o escritura en diferentes destinos.

Siguiente lección

#

En la siguiente lección, te prepararás para la parte de Wikipedia de la aplicación.