原文作者: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.dart和dartweather.dart文件,pubspec等等。很好👍,现在让我们运行pub get 现在让我们运行pub get,我们就可以开始了。
好吧,在这里,你看到mediumtest.dart... ... 羞愧在我身上。
我更喜欢从头开始,所以我删除了main.dart和dartweather.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"