[Dart翻译]用dart创建一个命令行应用程序

110 阅读5分钟

原文地址:medium.com/@m_knabe/cr…

原文作者:medium.com/@m_knabe

发布时间:2019年5月15日-5分钟阅读

Dart是一种伟大的 "新 "编程语言。我真的很喜欢用它和Flutter一起使用,我从来没有尝试过和Angular一起使用,但我认识的开发者都这样做过,他们也这样说我。但是我们也可以使用Dart🎯来构建命令行应用程序。所以我们试试吧 🤤。

我们要建立什么?

一个小的命令行工具,调用OpenWeatherAPI,我们设置一些参数,如邮政编码和国家,这是我们的输出。

简单又好理解的基本原理。所以,让我们开始。 首先我们要安装一些东西,是的,好的,我们需要Dart 😃,这里是安装说明的链接 -> dart.dev/get-dart。

下一步我们要用这个命令安装stagehand

pub global activate stagehand

Stagehand是一个小小的Dart项目生成器,适用于命令行应用程序、Web服务器等。

所以之后,让我们在某个地方创建一个文件夹 📁,并生成一个小的启动项目。

mkdir dartweather && cd dartweather && stagehand console-full

之后你可以看到,我们有一些新的文件夹,一个main.dartdartweather.dart文件,pubspec等等。很好👍,现在让我们运行pub get 现在让我们运行pub get,我们就可以开始了。

好吧,在这里,你看到mediumtest.dart... ... 羞愧在我身上。

我更喜欢从头开始,所以我删除了main.dartdartweather.dart里面的所有内容。

在你的main.dart里面添加这段代码,我会解释的。

import 'dart:io';
import 'package:args/args.dart';
import 'package:dartweather/dartweather.dart';

main(List<String> arguments) {
  exitCode = 0;
  final parser = ArgParser()
    ..addOption('zip', abbr: 'z')
    ..addOption('country', abbr: 'c', defaultsTo: 'de');

  final argResults = parser.parse(arguments);

  weatherCli(argResults['zip'], argResults['country']);
}

我想导入的内容应该很清楚。Args为参数,dart io为io的东西,我们的app逻辑从dartweather文件中导入。没有什么特别的地方。

所以主方法是我们的切入点,就像在其他的dart应用程序一样。

在第一行中,我们将exitCode设置为0,它表示一切正常,我们可以继续。有3个退出代码选项。

  • 0 = 成功
  • 1 = 警告
  • 2 = 错误

之后我们创建一个Argumentparser。我们可以在这里定义出选项,输入等等。

addOption方法需要一个名字,比如zip 。当我们要运行我们的应用程序时,我们可以像这样调用dartweather --zip 1234 。为了制作一个简短的选项,我们设置abbr属性。还有很多其他的选项,比如帮助文本之类的。看一下就知道了。

对于国家选项,我们设置一个默认值。所以我住在德国,这就是为什么我取了de 。选择你想要的。

然后我们用final argResults = parser.parse(arguments);来解析我们的输入。

这将创建一个带有String-Keys和我们选择的值的地图。这就是为什么我们在选项中添加了名字。

现在我们可以运行我们的逻辑了。

那么我们到底要做什么呢?我们调用Api (去openweathermap.org/api 并为你的Api密钥创建一个账户)并显示一些天气信息。

所以让我们从最基本的东西开始。我们需要一些模型。

我把例子Json和转换为Dart与这个网站:javiercbk.github.io/json_to_dar…

但是这里要注意一些事情,有时候这个转换器不能正确识别双值。我把最多的int值重构为double值,还有一些重命名等。

所以这就是结果。

class ForeCast {
  Coord coord;
  List<Weather> weather;
  String base;
  Main main;
  double visibility;
  Wind wind;
  Clouds clouds;
  double dt;
  Sys sys;
  int id;
  String name;
  double cod;

  ForeCast(
      {this.coord,
      this.weather,
      this.base,
      this.main,
      this.visibility,
      this.wind,
      this.clouds,
      this.dt,
      this.sys,
      this.id,
      this.name,
      this.cod});

  ForeCast.fromJson(Map<String, dynamic> json) {
    coord = json['coord'] != null ? new Coord.fromJson(json['coord']) : null;
    if (json['weather'] != null) {
      weather = new List<Weather>();
      json['weather'].forEach((v) {
        weather.add(new Weather.fromJson(v));
      });
    }
    base = json['base'];
    main = json['main'] != null ? new Main.fromJson(json['main']) : null;
    visibility = json['visibility'].toDouble();
    wind = json['wind'] != null ? new Wind.fromJson(json['wind']) : null;
    clouds = json['clouds'] != null ? new Clouds.fromJson(json['clouds']) : null;
    dt = json['dt'].toDouble();
    sys = json['sys'] != null ? new Sys.fromJson(json['sys']) : null;
    id = json['id'];
    name = json['name'];
    cod = json['cod'].toDouble();
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    if (this.coord != null) {
      data['coord'] = this.coord.toJson();
    }
    if (this.weather != null) {
      data['weather'] = this.weather.map((v) => v.toJson()).toList();
    }
    data['base'] = this.base;
    if (this.main != null) {
      data['main'] = this.main.toJson();
    }
    data['visibility'] = this.visibility;
    if (this.wind != null) {
      data['wind'] = this.wind.toJson();
    }
    if (this.clouds != null) {
      data['clouds'] = this.clouds.toJson();
    }
    data['dt'] = this.dt;
    if (this.sys != null) {
      data['sys'] = this.sys.toJson();
    }
    data['id'] = this.id;
    data['name'] = this.name;
    data['cod'] = this.cod;
    return data;
  }
}

class Coord {
  double lon;
  double lat;

  Coord({this.lon, this.lat});

  Coord.fromJson(Map<String, dynamic> json) {
    lon = json['lon'].toDouble();
    lat = json['lat'].toDouble();
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['lon'] = this.lon;
    data['lat'] = this.lat;
    return data;
  }
}

class Weather {
  int id;
  String main;
  String description;
  String icon;

  Weather({this.id, this.main, this.description, this.icon});

  Weather.fromJson(Map<String, dynamic> json) {
    id = json['id'];
    main = json['main'];
    description = json['description'];
    icon = json['icon'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['id'] = this.id;
    data['main'] = this.main;
    data['description'] = this.description;
    data['icon'] = this.icon;
    return data;
  }
}

class Main {
  double temp;
  double pressure;
  double humidity;
  double tempMin;
  double tempMax;

  Main({this.temp, this.pressure, this.humidity, this.tempMin, this.tempMax});

  Main.fromJson(Map<String, dynamic> json) {
    temp = json['temp'].toDouble();
    pressure = json['pressure'].toDouble();
    humidity = json['humidity'].toDouble();
    tempMin = json['temp_min'].toDouble();
    tempMax = json['temp_max'].toDouble();
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['temp'] = this.temp;
    data['pressure'] = this.pressure;
    data['humidity'] = this.humidity;
    data['temp_min'] = this.tempMin;
    data['temp_max'] = this.tempMax;
    return data;
  }
}

class Wind {
  double speed;
  double deg;

  Wind({this.speed, this.deg});

  Wind.fromJson(Map<String, dynamic> json) {
    speed = json['speed'].toDouble();
    deg = json['deg'].toDouble();
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['speed'] = this.speed;
    data['deg'] = this.deg;
    return data;
  }
}

class Clouds {
  double all;

  Clouds({this.all});

  Clouds.fromJson(Map<String, dynamic> json) {
    all = json['all'].toDouble();
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['all'] = this.all;
    return data;
  }
}

class Sys {
  int type;
  int id;
  double message;
  String country;
  int sunrise;
  int sunset;

  Sys({this.type, this.id, this.message, this.country, this.sunrise, this.sunset});

  Sys.fromJson(Map<String, dynamic> json) {
    type = json['type'];
    id = json['id'];
    message = json['message'].toDouble();
    country = json['country'];
    sunrise = json['sunrise'];
    sunset = json['sunset'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['type'] = this.type;
    data['id'] = this.id;
    data['message'] = this.message;
    data['country'] = this.country;
    data['sunrise'] = this.sunrise;
    data['sunset'] = this.sunset;
    return data;
  }
}

好吧,好吧,也许我们应该把它分割成更小的文件... ... 随意这样做吧 😃。

现在,让我们添加一个常量文件,为我们的API密钥🔑 ,基本网址等。

const API_KEY = '<YOUAPIKEY>';
const BASE_URL = 'https://api.openweathermap.org/data/2.5/weather?';
const API_KEY_QUERY = '&appid=$API_KEY';

为了好玩,我创建了一个符文文件,这样我们就可以在我们的控制台输出中添加Emojis! EMOJIIIS! 每个人都喜欢,对不对?

final rainBow = Runes('\u{1F308}');

在所有这些东西之后,创建一个文件,叫做api.dart。在这里我们要创建一个方法,返回一个带有两个参数的Future<ForeCast>

import 'package:cli/constants.dart';
import 'package:cli/models/models.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:meta/meta.dart';

Future<ForeCast> getWather({@required String zip, String country = 'de'}) async {
  var url = '${BASE_URL}zip=$zip,$country&units=metric${API_KEY_QUERY}';
  var response = await http.get(url);
  var jsonResponse = json.decode(response.body);
  return ForeCast.fromJson(jsonResponse);
}

所以这是非常基本的,我们创建一个url,获取响应,将数据解码为json,并将其转换为我们的模型。

所以现在我们已经做了大部分的事情,最后一部分是创建一个正确的控制台输出和调用我们的方法。

import 'dart:io';
import 'package:cli/runes.dart';
import 'package:cli/api.dart';

Future weatherCli(String zip, String country) async {
  if (zip.isEmpty || country.isEmpty) {
    stderr.writeln('error: arguments missing');
  } else {
    try {
      final apiResult = await getWather(zip: zip, country: country);
      stdout.writeln('');
      stdout.writeln('Weather in ${apiResult.name} ${String.fromCharCodes(rainBow)}');
      stdout.writeln('---------------------------------------');

      final date = DateTime.fromMillisecondsSinceEpoch(apiResult.dt.toInt() * 1000);
      final dateTimeString = '${date.day}.${date.month}.${date.year} :';
      final tempString =
          'Now: ${apiResult.main.temp} °C, Min: ${apiResult.main.tempMin} °C, Max: ${apiResult.main.tempMax} °C';
      stdout.writeln(dateTimeString);
      stdout.writeln(tempString);
      stdout.writeln('');
    } catch (e) {
      stderr.writeln('error: networking error');
      stderr.writeln(e.toString());
    }
  }
}

首先我们检查我们的参数,如果一切正常,我们就调用我们的api方法,并用try & catch把它包起来。用stdout.writeln()写一行新的内容,stderr.writeln()告诉控制台这是一个错误,之后applicaton将被关闭。

现在我们可以测试一下我们的命令行工具了。

所以打开你的终端,在你的开发文件夹里,输入

dart bin/main.dart -z #YOURZIPCODE#。

你应该有以下输出。

也许加载的时间有点长,那是因为我们没有真正将它编译成超快的机器代码。所以我们现在就来做这个。

dart2aot bin/main.dart bin/main.dart.aot
dartaotruntime bin/main.dart.aot

所以现在你看,它的速度超快。💨 你可以用这个命令来测量它。

time dartaotruntime bin/main.dart.aot

但是少了一个☝️东西。我不想用路径和dartaotruntime来调用它。我想要一个快速的小命令,整体在我的控制台里面。所以我们来做这最后一部分。

进入你的pubspec.yaml,在最后添加这个属性。

#Give you CLI a name. Call it  'dartweather -z 33189'
executables:
  dartweather: main

在那里我们可以定义我们的命令行工具的名称和入口点。

之后,我们把它做成全局的。

pub global activate --source path <path to project in my case ~/develop/dartweather>

太好了!现在我们可以调用我们的应用程序,在我们的终端内的任何地方。

谢谢你的阅读!

如果你不能调用全局值,你必须把PATH添加到你的.bash_profile | .bashrc中。

export PATH="$PATH": "HOME/.pub-cache/bin"