命令行工具(CLI)

  命令行工具(CLI)是在图形用户界面得到普及之前使用最为广泛的用户界面,它通常不支持鼠标,用户通过键盘输入指令,计算机接收到指令后,予以执行。

  通常认为,命令行工具(CLI)没有图形用户界面(GUI)那么方便用户操作。因为,命令行工具的软件通常需要用户记忆操作的命令,但是,由于其本身的特点,命令行工具要较图形用户界面节约计算机系统的资源。在熟记命令的前提下,使用命令行工具往往要较使用图形用户界面的操作速度要快。所以,图形用户界面的操作系统中,都保留着可选的命令行工具。

  另外,命令行工具(CLI)应该是一个开箱即用的工具,不需要安装任何依赖。

  一些熟悉的CLI工具如下:

  1. dotnet cli

  2. vue cli

  3. angular cli

  4. aws cli

  5. azure cli

指令设计

  本文将使用.Net Core(版本3.1.102)编写一个CLI工具,实现配置管理以及条目(item)管理(调用WebApi实现),详情如下:

  

框架说明

  编写CLI使用的主要框架是CommandLineUtils,它主要有以下优势:

  1. 良好的语法设计

  2. 支持依赖注入

  3. 支持generic host

WebApi

  提供api让cli调用,实现条目(item)的增删改查:

[Route("api/items")]
[ApiController]
public class ItemsController : ControllerBase
{
private readonly IMemoryCache _cache;
private readonly string _key = "items"; public ItemsController(IMemoryCache memoryCache)
{
_cache = memoryCache;
} [HttpGet]
public IActionResult List()
{
var items = _cache.Get<List<Item>>(_key);
return Ok(items);
} [HttpGet("{id}")]
public IActionResult Get(string id)
{
var item = _cache.Get<List<Item>>(_key).FirstOrDefault(n => n.Id == id);
return Ok(item);
} [HttpPost]
public IActionResult Create(ItemForm form)
{
var items = _cache.Get<List<Item>>(_key) ?? new List<Item>(); var item = new Item
{
Id = Guid.NewGuid().ToString("N"),
Name = form.Name,
Age = form.Age
}; items.Add(item); _cache.Set(_key, items); return Ok(item);
} [HttpDelete("{id}")]
public IActionResult Delete(string id)
{
var items = _cache.Get<List<Item>>(_key); var item = items?.SingleOrDefault(n => n.Id == id);
if (item == null)
{
return NotFound();
} items.Remove(item);
_cache.Set(_key, items); return Ok();
}
}

CLI

  1. Program - 函数入口

[HelpOption(Inherited = true)] //显示指令帮助,并且让子指令也继承此设置
[Command(Description = "A tool to communicate with web api"), //指令描述
Subcommand(typeof(ConfigCommand), typeof(ItemCommand))] //子指令
class Program
{
public static int Main(string[] args)
{
//配置依赖注入
var serviceCollection = new ServiceCollection(); serviceCollection.AddSingleton(PhysicalConsole.Singleton);
serviceCollection.AddSingleton<IConfigService, ConfigService>();
serviceCollection.AddHttpClient<IItemClient, ItemClient>(); var services = serviceCollection.BuildServiceProvider(); var app = new CommandLineApplication<Program>();
app.Conventions
.UseDefaultConventions()
.UseConstructorInjection(services); var console = (IConsole)services.GetService(typeof(IConsole)); try
{
return app.Execute(args);
}
catch (UnrecognizedCommandParsingException ex) //处理未定义指令
{
console.WriteLine(ex.Message);
return -;
}
}

//指令逻辑
private int OnExecute(CommandLineApplication app, IConsole console)
{
console.WriteLine("Please specify a command.");
app.ShowHelp();
return ;
}
}

  2. ConfigCommand和ItemCommand - 实现的功能比较简单,主要是指令描述以及指定对应的子指令

[Command("config", Description = "Manage config"),
Subcommand(typeof(GetCommand), typeof(SetCommand))]
public class ConfigCommand
{
private int OnExecute(CommandLineApplication app, IConsole console)
{
console.Error.WriteLine("Please submit a sub command.");
app.ShowHelp();
return ;
}
} [Command("item", Description = "Manage item"),
Subcommand(typeof(CreateCommand), typeof(GetCommand), typeof(ListCommand), typeof(DeleteCommand))]
public class ItemCommand
{
private int OnExecute(CommandLineApplication app, IConsole console)
{
console.Error.WriteLine("Please submit a sub command.");
app.ShowHelp();
return ;
}
}

  3. ConfigService - 配置管理的具体实现,主要是文件读写

public interface IConfigService
{
void Set(); Config Get();
} public class ConfigService: IConfigService
{
private readonly IConsole _console;
private readonly string _directoryName;
private readonly string _fileName; public ConfigService(IConsole console)
{
_console = console;
_directoryName = ".api-cli";
_fileName = "config.json";
} public void Set()
{
var directory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), _directoryName);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
} var config = new Config
{
//弹出交互框,让用户输入,设置默认值为http://localhost:5000/
Endpoint = Prompt.GetString("Specify the endpoint:", "http://localhost:5000/")
}; if (!config.Endpoint.EndsWith("/"))
{
config.Endpoint += "/";
} var filePath = Path.Combine(directory, _fileName); using (var outputFile = new StreamWriter(filePath, false, Encoding.UTF8))
{
outputFile.WriteLine(JsonConvert.SerializeObject(config, Formatting.Indented));
}
_console.WriteLine($"Config saved in {filePath}.");
} public Config Get()
{
var filePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), _directoryName, _fileName); if (File.Exists(filePath))
{
var content = File.ReadAllText(filePath);
try
{
var config = JsonConvert.DeserializeObject<Config>(content);
return config;
}
catch
{
_console.WriteLine("The config is invalid, please use 'config set' command to reset one.");
}
}
else
{
_console.WriteLine("Config is not existed, please use 'config set' command to set one.");
} return null;
}
}

  4. ItemClient - 调用Web Api的具体实现,使用HttpClientFactory的方式

public interface IItemClient
{
Task<string> Create(ItemForm form); Task<string> Get(string id); Task<string> List(); Task<string> Delete(string id);
} public class ItemClient : IItemClient
{
public HttpClient Client { get; } public ItemClient(HttpClient client, IConfigService configService)
{
var config = configService.Get();
if (config == null)
{
return;
} client.BaseAddress = new Uri(config.Endpoint); Client = client;
} public async Task<string> Create(ItemForm form)
{
var content = new StringContent(JsonConvert.SerializeObject(form), Encoding.UTF8, "application/json");
var result = await Client.PostAsync("/api/items", content); if (result.IsSuccessStatusCode)
{
var stream = await result.Content.ReadAsStreamAsync();
var item = Deserialize<Item>(stream);
return $"Item created, info:{item}";
} return "Error occur, please again later.";
} public async Task<string> Get(string id)
{
var result = await Client.GetAsync($"/api/items/{id}"); if (result.IsSuccessStatusCode)
{
var stream = await result.Content.ReadAsStreamAsync();
var item = Deserialize<Item>(stream); var response = new StringBuilder();
response.AppendLine($"{"Id".PadRight(40, ' ')}{"Name".PadRight(20, ' ')}Age");
response.AppendLine($"{item.Id.PadRight(40, ' ')}{item.Name.PadRight(20, ' ')}{item.Age}");
return response.ToString();
} return "Error occur, please again later.";
} public async Task<string> List()
{
var result = await Client.GetAsync($"/api/items"); if (result.IsSuccessStatusCode)
{
var stream = await result.Content.ReadAsStreamAsync();
var items = Deserialize<List<Item>>(stream); var response = new StringBuilder();
response.AppendLine($"{"Id".PadRight(40, ' ')}{"Name".PadRight(20, ' ')}Age"); if (items != null && items.Count > )
{
foreach (var item in items)
{
response.AppendLine($"{item.Id.PadRight(40, ' ')}{item.Name.PadRight(20, ' ')}{item.Age}");
}
} return response.ToString();
} return "Error occur, please again later.";
} public async Task<string> Delete(string id)
{
var result = await Client.DeleteAsync($"/api/items/{id}"); if (result.IsSuccessStatusCode)
{
return $"Item {id} deleted.";
} if (result.StatusCode == HttpStatusCode.NotFound)
{
return $"Item {id} not found.";
} return "Error occur, please again later.";
} private static T Deserialize<T>(Stream stream)
{
using var reader = new JsonTextReader(new StreamReader(stream));
var serializer = new JsonSerializer();
return (T)serializer.Deserialize(reader, typeof(T));
}
}

如何发布

  在项目文件中设置发布程序的名称(AssemblyName):

  <PropertyGroup>
   <OutputType>Exe</OutputType>
   <TargetFramework>netcoreapp3.</TargetFramework>
   <AssemblyName>api-cli</AssemblyName>
  </PropertyGroup>

  进入控制台程序目录:

  cd src/NetCoreCLI

  发布Linux使用版本:

  dotnet publish -c Release -r linux-x64 /p:PublishSingleFile=true

  发布Windows使用版本:

  dotnet publish -c Release -r win-x64 /p:PublishSingleFile=true

  发布MAC使用版本:

    dotnet publish -c Release -r osx-x64 /p:PublishSingleFile=true

使用示例

  这里使用Linux作为示例环境。

  1. 以docker的方式启动web api

  

  2. 虚拟机上没有安装.net core的环境

  

  3. 把编译好的CLI工具拷贝到虚拟机上,授权并移动到PATH中(如果不移动,可以通过./api-cli的方式调用)

    sudo chmod +x api-cli #授权
sudo mv ./api-cli /usr/local/bin/api-cli #移动到PATH

  4. 设置配置文件:api-cli config set

  

  5. 查看配置文件:api-cli config get

  

  6. 创建条目:api-cli item create

  

  7. 条目列表:api-cli item list

  

  8. 获取条目:api-cli item get

  

  9. 删除条目:api-cli item delete

  

  10. 指令帮助:api-cli -h, api-cli config -h, api-cli item -h

  

  

  

  11. 错误指令:api-cli xxx

  

源码地址

  https://github.com/ErikXu/NetCoreCLI

参考资料

  https://docs.microsoft.com/en-us/dotnet/core/rid-catalog#using-rids](https://docs.microsoft.com/en-us/dotnet/core/rid-catalog#using-rids

  https://medium.com/swlh/build-a-command-line-interface-cli-program-with-net-core-428c4c85221

使用.Net Core编写命令行工具(CLI)的更多相关文章

  1. commanderJs编写命令行工具(cli)

    前言: 最近需要做一个内部的node cli来独立构建流程,对整个命令行工具实现流程有了大致了解,下面来解释一下如何实现一个cli,和如何使用 commander 库.   新手误区: 在开始实现之前 ...

  2. 如何用Node编写命令行工具

    0. 命令行工具 当全局安装模块之后,我们可以在控制台下执行指定的命令来运行操作,如果npm一样.我把这样的模块称之为命令行工具模块(如理解有偏颇,欢迎指正) 1.用Node编写命令行工具 在Node ...

  3. vs for Mac中的启用Entity Framework Core .NET命令行工具

    在vs for Mac的工具菜单中已没有了Package Manager Console. 我们可以通过以下方法使用Entity Framework Core .NET命令行工具: 1.添加Nuget ...

  4. 一个小时学会用 Go 编写命令行工具

    前言 最近因为项目需要写了一段时间的 Go ,相对于 Java 来说语法简单同时又有着一些 Python 之类的语法糖,让人大呼"真香". 但现阶段相对来说还是 Python 写的 ...

  5. 如何用node编写命令行工具,附上一个ginit示例,并推荐好用的命令行工具

    原文 手把手教你写一个 Node.js CLI 强大的 Node.js 除了能写传统的 Web 应用,其实还有更广泛的用途.微服务.REST API.各种工具……甚至还能开发物联网和桌面应用.Java ...

  6. golang开发:类库篇(三)命令行工具cli的使用

    为什么要使用命令行 觉得这个问题不应该列出来,又觉得如果初次进行WEB开发的话,可能会觉得所有的东西都可以使用API去做,会觉得命令行没有必要. 其实,一个生产的项目命令行是绕不过去的.比如运营需要导 ...

  7. Flask内置命令行工具—CLI

    应用发现 flask命令在Flask库安装后可使用,使用前需要正确配置FLASK_APP环境变量以告知用户程序所在位置.不同平台设置方式有所不同. Unix Bash (Linux, Mac, etc ...

  8. nodejs 编写(添加时间戳)命令行工具 timestamp

    Nodejs除了编写服务器端程序还可以编写命令行工具,如gulp.js就是Nodejs编写的. 接下来我们来实现一个添加时间戳的命令: $ timestamp action https://www.n ...

  9. node命令行工具之实现项目工程自动初始化的标准流程

    一.目的 传统的前端项目初始流程一般是这样: 可以看出,传统的初始化步骤,花费的时间并不少.而且,人工操作的情况下,总有改漏的情况出现.这个缺点有时很致命. 甚至有马大哈,没有更新项目仓库地址,导致提 ...

随机推荐

  1. C++ this指针详解(精辟)

    this 是 C++ 中的一个关键字,也是一个 const 指针,它指向当前对象,通过它可以访问当前对象的所有成员. 所谓当前对象,是指正在使用的对象.例如对于stu.show();,stu 就是当前 ...

  2. 使用 FreeMarker模板 Springboot 发送邮件

    四.使用 FreeMarker模板 HTML 标签的字符串拼接是一件很棘手的事.因为在你的大脑中解析HTML标签并想象它在渲染时会是什么样子是挺困难的.而将HTML混合在Java代码中又会使得这个问题 ...

  3. [LC] 253. Meeting Rooms II

    Given an array of meeting time intervals consisting of start and end times [[s1,e1],[s2,e2],...] (si ...

  4. PP图|QQ图|正态性检验|K-S检验|S-W检验|

    应用统计学: 物理条件一致时,有理由认为方差是一致的.配对检验可排除物理影响,使方差变小,但是自由度降低了,即样本数变小.二项分布均值假设检验的模型要依据前面的假设条件: PP图统计图要看中间的贴近情 ...

  5. scarky test

  6. 吴裕雄--天生自然HTML学习笔记:HTML 速查列表

    HTML 基本文档 <!DOCTYPE html> <html> <head> <title>文档标题</title> </head& ...

  7. AI在自动化测试领域的应用

    阿里QA导读:最近一两年随着深入学习技术浪潮的诞生,智能化测试迎来了新的发展,而AI也会引领下一代测试的新航向.Testin云测CTO陈冠诚先生的分享让我们看到AI在移动自动化测试领域里面的创新机会点 ...

  8. 01Java代码是怎么运行的

    从虚拟机视角来看,执行 Java 代码首先需要将它编译而成的 class 文件加载到 Java 虚拟机中.加载后的 Java 类会被存放于方法区(Method Area)中.实际运行时,虚拟机会执行方 ...

  9. Redis list实现原理 - 双向循环链表

    双向链表 双向表示每个节点知道自己的直接前驱和直接后继,每个节点需要三个域 查找方向可以是从左往右也可以是从右往左,但是要实现从右往左还需要终端节点的地址,所以通常会设计成双向的循环链表; 双向的循环 ...

  10. Ueditor富文本编辑器--上传图片自定义上传操作

    最近负责将公司官网从静态网站改版成动态网站,方便公司推广营销人员修改增加文案,避免官网文案维护过于依赖技术人员.在做后台管理系统时用到了富文本编辑器Ueditor,因为公司有一个阿里云文件资源服务器, ...