• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

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

武飞扬头像
梦魇兽
帮助3

前言

已经有很多人开发了一些 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,比如用户登录等。

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

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanheggfbh
系列文章
更多 icon
同类精品
更多 icon
继续加载