preface

Many people have developed dart command line tools and uploaded them to pub. I will develop a command line tool. I want to reconfigure a ROM processing tool with DART command line, so that it can achieve cross-platform faster. In addition to adapting simple UI changes to PC, it is extremely difficult to adapt the business logic of specific functions, including the invocation of Windows terminal commands and the implementation of Local PTY. And some users want to run my application on a Linux server.

To fit the development of

Create a DART project

Both Python and DART have an ARG-like package that provides parsing of command arguments.

mkdir dart_cli && cd dart_cli
touch pubspec.yaml
Copy the code

Pubspec yaml content

name: dart_cli

dependencies:
  args: ^ 1.6.0
Copy the code

Run pub get to create a new bin folder and dart_cli.dart.

Write the main function

An arG parameter parsing example

import 'package:args/args.dart';

void main(List<String> arguments) {
  // Create an instance of ArgParser and specify the parameters to be entered
  final ArgParser argParser = newArgParser() .. addOption('name',
        abbr: 'n', defaultsTo: 'World', help: "Who would you like to greet?")
    ..addFlag('help',
        abbr: 'h', negatable: false, help: "Displays this help information.");

  ArgResults argResults = argParser.parse(arguments);
  if (argResults['help']) {
    print("""
** HELP **
${argParser.usage}"" ");
  }
  final String name = argResults['name'];

  print("Hello, $name!");
}
Copy the code

AddOption is the rule for adding parameter resolution. Abbr is short for parameter switch, so you can use either –name Nightmare or -n Nightmare for name switch.

Run pub run dart_cli or right-click dart_cli.dart in VS Code.

Test parameters

There is a Linux terminal trick to switch history commands by pressing the up and down keys

Run pub run dart_cli -n Nightmare or pub run dart_cli –name Nightmare

Pub run dart_cli -h

These are the basics of dart command-line tool development.

The advanced

If we want our command-line tool to do more than simply accept arguments and execute commands, what if we want it to be interactive?

All the knowledge of this part comes down to the writing of terminal simulator

Turn off the DART switch

  stdin.echoMode = false;
  stdin.lineMode = false;
Copy the code

The first one will print out the user’s input, and we usually just need to get the user’s input.

After the second one is turned on, we don’t get the user’s input until the user presses the back button.

Get keyboard input

while (true) { final int input = stdin.readByteSync(); if (input ! = -1) { print('input -> $input'); } await Future<void>.delayed(const Duration(milliseconds: 10)); }Copy the code

Without delay code, loops can be a huge CPU hog.

The up, down and left keys will involve the recognition of terminal escape sequence.

How to analyze and share with everyone

  bool csiStart = false;
  bool escStart = false;
  while (true) {
    final int input = stdin.readByteSync();
    if(input ! =- 1) {
      if (csiStart) {
        csiStart = false;
        // print(input);
        switch (input) {
          case 65:
            // up
            print('press up');
            break;
          case 66:
            // down
            print('press down');
            break;
          case 68:
            // left
            print('press left');
            break;
          case 67:
            // right
            print('press right');
            break; }}if (escStart) {
        escStart = false;
        if (input == 91) {
          csiStart = true; }}if (input == 27) {
        escStart = true; }}await Future<void>.delayed(const Duration(milliseconds: 10));
  }
Copy the code

Windows platform can not get up and down left and right listener, if there is an implementation method please leave a message in the comments section.

Convert the print

In command-line tool development, we sometimes prefer the print function not to wrap automatically.

void print(Object object) {
  stdout.write(object);
}
Copy the code

Realize the interaction

First print text that looks something like this

1. File conversion 2. Dynamic module 3Copy the code

Introduces a print string with index and arrow

int chooseIndex = 1; Const String arrow = '\x1b[1;32m←\x1b[0m ';Copy the code

\x1b[1;32m is to change the current foreground color of the terminal to green, font bold, \x1b[0m is to restore the default.

Introduce a simple cursor control method

const String cursorUpChar = '\x1b[A';
const String cursorDownChar = '\x1b[B';
const String cursorLeftChar = '\x1b[D';
const String cursorRightChar = '\x1b[C';
// Delete from the current cursor position to the end of the line
const String deleteOneChar = '\x1b[0K';
void cursorUp() => print(cursorUpChar);
void cursorDown() => print(cursorDownChar);
void cursorLeft() => print(cursorLeftChar);
void cursorRight() => print(cursorRightChar);
void deleteOne() => print(deleteOneChar);
Copy the code

Arrow control method

void showArrow() {
  for (int i = 3 - chooseIndex; i > 0; i--) {
    cursorUp();
  }
  print(arrowChar);
}

void deleteArrow() {
  cursorLeft();
  cursorLeft();
  cursorLeft();
  deleteOne();
}
Copy the code

Executive control arrow

case 65:
    if (chooseIndex > 1) {
        chooseIndex--;
        deleteArrow();
        cursorUp();
        print(arrowChar);
    }
    break;
case 66:
    if (chooseIndex < 3) {
        chooseIndex++;
        deleteArrow();
        cursorDown();
        print(arrowChar);
    }
    break;
Copy the code

The effect

The complete code

import 'dart:io';

const String arrowChar = '\ x1b 32 m please \ [1; x1b [0 m';
const String cursorUpChar = '\x1b[A';
const String cursorDownChar = '\x1b[B';
const String cursorLeftChar = '\x1b[D';
const String cursorRightChar = '\x1b[C';
// Delete from the current cursor position to the end of the line
const String deleteOneChar = '\x1b[0K';
void cursorUp() => print(cursorUpChar);
void cursorDown() => print(cursorDownChar);
void cursorLeft() => print(cursorLeftChar);
void cursorRight() => print(cursorRightChar);
void deleteOne() => print(deleteOneChar);

int chooseIndex = 1;
void showArrow() {
  for (int i = 3 - chooseIndex; i > 0; i--) {
    cursorUp();
  }
  print(arrowChar);
}

void deleteArrow() {
  cursorLeft();
  cursorLeft();
  cursorLeft();
  deleteOne();
}

Future<void> main(List<String> arguments) async {
  stdin.echoMode = false;
  stdin.lineMode = false;
  bool csiStart = false;
  bool escStart = false;
  print('1. File conversion \n');
  print('2. Dynamic module \n');
  print('3. One-click Execution ');
  // cursorLeft();
  showArrow();
  // Hide cursor, no effect
  print('\x1b[?25l');
  while (true) {
    final int input = stdin.readByteSync();
    if(input ! =- 1) {
      if (csiStart) {
        csiStart = false;
        // print(input);
        switch (input) {
          case 65:
            if (chooseIndex > 1) {
              chooseIndex--;
              deleteArrow();
              cursorUp();
              print(arrowChar);
            }
            // up
            // print('press up');
            break;
          case 66:
            // down
            // print('press down');
            if (chooseIndex < 3) {
              chooseIndex++;
              deleteArrow();
              cursorDown();
              print(arrowChar);
            }
            break;
          case 68:
            // left
            // print('press left');
            break;
          case 67:
            // right
            // print('press right');
            break; }}if (escStart) {
        escStart = false;
        if (input == 91) {
          csiStart = true; }}if (input == 27) {
        escStart = true; }}await Future<void>.delayed(const Duration(milliseconds: 10)); }}void print(Object object) {
  stdout.write(object);
}

Copy the code

Ok, that’s it. I’m currently working on this Dart command line tool that can reuse the util of my current Flutter project, such as user login, etc.

You can leave any questions in the comments section.