Dart命令行工具(一)- 尝试上手开发

Dart命令行工具(一)- 尝试上手开发

Android小彩虹2021-08-14 15:57:37220A+A-

前言

已经有很多人开发了一些 dart 命令行工具,并上传到了 pub ,而我会来开发一个命令行工具是我想将一款 ROM 处理工具用 dart 命令行重构,这样能更快的实现跨平台,即使原工具是使用 Flutter 开发,但在适配桌面端的过程中,除了能将 UI 简单的改动能适配到 PC 端后,其中具体功能的业务逻辑适配起来异常的困难,其中包括 windows 终端命令的调用,local pty 的实现等。并且一部分的用户希望能在 Linux 服务器运行我的程序。

上手开发

创建一个 dart 工程

无论是 python 还是 dart,都会有一个 arg 类似的包来提供对命令参数的解析。

mkdir dart_cli && cd dart_cli
touch pubspec.yaml

pubspec.yaml 内容

name: dart_cli

dependencies:
  args: ^1.6.0

随后执行pub get 我们再新建一个 bin 文件夹,再在文件夹里面新建 dart_cli.dart。

编写 main 函数

一个 arg 参数解析例子

import 'package:args/args.dart';

void main(List<String> arguments) {
  // 创建ArgParser的实例,同时指定需要输入的参数
  final ArgParser argParser = new ArgParser()
    ..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!");
}

addOption 是添加参数解析的规则,abbr 是参数开关的缩写,也就是说 name 这个参数开关,你可以使用 --name Nightmare,也可以使用 -n Nightmare。

执行 pub run dart_cli 或者在 vs code 中右键运行这个 dart_cli.dart。

测试参数

有个 linux 终端小技巧,按上下键可以切换历史命令

执行pub run dart_cli -n Nightmare``````pub run dart_cli --name Nightmare

同理pub run dart_cli -h

以上是 dart 开发命令行工具最基础的部分。

进阶

如果我们希望我们的命令行工具不只是简单的接收参数执行命令,如果想让它能够具有交互功能,应该怎么来做呢?

这部分的知识全部归根于对终端模拟器的编写

关闭 dart 开关

  stdin.echoMode = false;
  stdin.lineMode = false;

第一个开启会将用户的输入打印出来,我们通常只需要获得用户的输入值。

第二个开启后,在用户按下回车前之前,我们拿不到用户的输入。

获取键盘输入

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

不加延时代码的话,循环会极大占用 CPU。

其中上下左右的按键会涉及到终端转义序列的识别。

如何解析,分享给大家

  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));
  }

在 windows 平台拿不到上下左右的监听,如果有实现方法请在评论区留言。

转换 print

在命令行工具的开发中,我们有时候更希望 print 函数不自动换行。

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

实现交互

先打印类似于这样的文本

1.文件转换
2.动态模块
3.一键执行

引入一个 index 与箭头的打印字符串

int chooseIndex = 1;
const String arrow = ' \x1b[1;32m←\x1b[0m ';

\x1b[1;32m是将终端当前前景色改为绿色,字体加粗,\x1b[0m是恢复默认。

引入简单的光标控制方法

const String cursorUpChar = '\x1b[A';
const String cursorDownChar = '\x1b[B';
const String cursorLeftChar = '\x1b[D';
const String cursorRightChar = '\x1b[C';
// 从当前光标的位置删至行的末尾
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);

箭头控制方法

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

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

执行控制箭头

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

效果

完整代码

import 'dart:io';

const String arrowChar = ' \x1b[1;32m←\x1b[0m ';
const String cursorUpChar = '\x1b[A';
const String cursorDownChar = '\x1b[B';
const String cursorLeftChar = '\x1b[D';
const String cursorRightChar = '\x1b[C';
// 从当前光标的位置删至行的末尾
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.文件转换\n');
  print('2.动态模块\n');
  print('3.一键执行');
  // cursorLeft();
  showArrow();
  // 隐藏光标,没有生效
  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);
}

好了就这样了,目前正在着手这个 dart 命令行工具能复用我当前的 Flutter 项目的 util,比如用户登录等。

任何疑问可以评论区留言。

点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1
本网站由 提供CDN/云存储服务

联系我们