本文不会涉及太多技术细节和源码,请放心食用

大家好,我是 HelloGitHub 的老荀,好久不见啊!

我在完成 HelloZooKeeper 系列之后,就很少“露面了”。但是我对开源和 HelloGitHub 的热情并没有丝毫的减少。这不,逮着个机会就来输出一波,防止被大家遗忘。

这次带来的是我写的一款在终端浏览 HelloGitHub 的工具:hg-tui,让你双手不离开键盘就能畅游在 HG 的开源世界。功能如下:

  • 色彩丰富、平铺展示
  • 关键字搜索月刊往期的项目
  • 类 Vim 的快捷键操作方式
  • 一键直达开源项目首页
  • 支持 Linux、macOS、Windows

地址:https://github.com/kaixinbaba/hg-tui

下面我将分享自己发起这个开源项目的缘起、构思、再到开发的全部过程,最后分享一下,我通过做这个项目对开源的一些感悟。

一、起因

我本职是做 Java 开发,但架不住 Rust 太有意思了!所以最近在学 Rust 恰好前段时间看到 HG 讲解 tui.rs 的文章

看完后手痒得厉害,就写了一篇 tui.rs 入门文章,但感觉还不过瘾就想写一个项目练手。

因为我平时经常上 HelloGitHub 找开源项目,所以就决定用 tui.rs 做一个终端浏览 HelloGitHub 官网的工具。

官网:https://hellogithub.com/

二、构思

首先我希望这个应用能有以下功能:

  • 有搜索框,可以按关键词搜索 HelloGitHub 中的任意项目
  • 通过表格按列展示搜索结果
  • 既然是终端应用,那操作方式肯定是使用键盘方式,快捷键我采用了一些大家熟知的 Vim 快捷键
  • 浏览项目的途中,可以随时在浏览器中打开当前浏览的项目

有了这些主要功能点的思路,下面就要想想怎么设计一个界面了,我本职工作后端一碰到画界面就头疼,几经周折大概把界面设计成了这样:

又因为是 TUI 界面层级不能太深,所以再多弄个详情页面(用来浏览文字明细)或者弹窗页面(提示消息)就差不多了。

我又想到了 GitHub 为每一种编程语言都设计了一种颜色,我也可以把这些颜色应用在我的项目里,让整个终端界面看起来没那么单调,色彩更丰富。效果如下:

主界面:

详情页:

弹窗提示:

最后为了向 TUI 妥协,按期数或类别搜索,我是通过使用搜索前缀来和普通关键词搜索作出区别。

上面展示的这些差不多已经是这个项目的全部了

三、开发

3.1 技术选型

要实现上述的那些功能,就要从 Rust 的生态中选择合适的库了

下面这些是我在这个项目中使用到的:

  • 基础设施:anyhowthiserrorlazy_staticbetter-panic
  • 绘制 UI:tuicrossterm
  • HTTP client:reqwest
  • 缓存:cached
  • HTML 解析:nipper
  • 工具:regexcrossbeam-channel
  • 命令行:clap

虽然 Rust 还是编程界的小学生(2011 年启动),但是经过了这些年的发展,生态已经逐渐完善,工具库已经很丰富了。再加上 Rust 是系统级的语言,值得投入时间学习!

3.2 项目结构

项目结构规划(非全部)

src
├── app.rs // 统一管理整个应用的状态
├── cli.rs // 命令行解析
├── draw.rs // 绘制 UI
├── events.rs // UI 事件、输入事件、通知
├── fetch.rs // HTTP 请求
├── main.rs // 入口
├── parse.rs // HTML 解析
├── utils.rs // 工具
└── widget // 自定义组件
├── ...

合理的分文件(目录)开发,可以让每个功能模块 高内聚、低耦合,并且可以很容易地分开进行单元测试。

当然这些文件也不是在项目之初就已经一股脑地建立好的,都是在完善功能的路上一点点添加进来的~

3.3 主要代码

因为是基于 tui.rs 开发的应用,所以主流程肯定是遵循该库的设计的,首先需要定义一个 App 用来保存整个项目的状态信息。

pub struct App {
/// 用户输入框
pub input: InputState,
/// 内容展示
pub content: ContentState,
/// 弹窗提示
pub popup: PopupState,
/// 状态栏
pub statusline: StatusLineState,
/// 模式
pub mode: AppMode,
/// 项目明细子页面
pub project_detail: ProjectDetailState,
...
}

每一个状态字段,其实就是对应一个自定义组件.要在 tui.rs 中实现自定义组件(实现方式也是我自己的理解)也很简单只要三步,我以 Input 组件为例。

/// 用户输入框组件,组件本身没有字段,是一个无状态的对象
/// 无状态对象只关心 UI 怎么绘制,不存储数据
pub struct Input {} /// 组件的状态,每一个字段就是组件需要存储的数据
#[derive(Debug)]
pub struct InputState {
input: String,
active: bool,
pub mode: SearchMode,
} /// 最后为 Input 组件实现 StatefulWidget trait
impl StatefulWidget for Input {
type State = InputState; // 指定关联类型为 InputState /// area 绘制的区域
/// buf 缓冲区(可以直接写入字符串,如果要高度定制的话,可以理解为画笔)
/// state 从这个变量中直接取绘制过程中需要的数据
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
// 具体绘制的逻辑
...
}
}

只要是面向用户的应用,都会处理各种各样的用户输入(事件)。Rust 中一般都使用 channel 来解耦处理各种各样的事件,再利用 Rust 强大的枚举支持,定义各种各样的事件(用户输入和非用户输入)即可。

/// 定义事件枚举
#[derive(Debug, Clone)]
pub enum HGEvent {
/// 用户事件(键盘事件)
UserEvent(KeyEvent),
/// 应用内部组件的通知事件
NotifyEvent(Notify),
} #[derive(Debug, Clone, PartialEq)]
pub enum Notify {
/// 重绘界面
Redraw,
/// 退出应用
Quit,
/// 弹出窗口展示消息
Message(Message),
/// tick,比如一些数据需要每隔一段时间自动更新的(比如:显示的时间)
Tick,
} /// 弹窗的消息,分为 错误、警告、提示
#[derive(Debug, Clone, PartialEq)]
pub enum Message {
Error(String),
Warn(String),
Tips(String),
}

为了区分用户事件和通知,我使用了两个不同的 channel 分别处理这两类:

lazy_static! {
/// 因为通知队列希望被应用内部共享,所以使用了 lazy_static 方便使用
pub static ref NOTIFY: (Sender<HGEvent>, Receiver<HGEvent>) = bounded(1024);
}

又因为不同的事件处理,并不应该互相阻塞,所以整个应用采用了最基础的多线程模型来提高性能,这里使用的也是标准库的多线程。

pub fn handle_key_event(event_app: Arc<Mutex<App>>) {
let (sender, receiver) = unbounded();
...
std::thread::spawn(move || loop {
// 单独一个线程接收用户事件
if let Ok(Event::Key(event)) = crossterm::event::read() {
sender.send(HGEvent::UserEvent(event)).unwrap();
}
});
std::thread::spawn(move || loop {
// 单独一个线程处理用户事件
if let Ok(HGEvent::UserEvent(key_event)) = receiver.recv() {
...
}
});
}

其他剩下的就是业务逻辑,完整的代码可以直接看仓库 https://github.com/kaixinbaba/hg-tui

四、心路历程

一开始我做 hg-tui 项目的时候,仅仅是为了做个实际的项目把玩一下 tui.rs 这个框架,做好之后问题层出不穷,但我深知没有与生俱来的完美,只有不断的迭代才能让它越来越好,经过 100 多次的提交后,现在用着感觉顺手多了。毕竟作者是项目的第一个用户,自己用着不舒服其他人就更不喜欢了!

我想着既然要让别人用,一定要容易安装。接着我做了基于 GitHub Action 自动编译和发布,支持 Windows、Linux、macOS 直接下载就能用。

我还做了对 homebrew 安装的支持,但因为 Star 数不够没有收录到 homecore 要求:30 forks、30 watchers、75 stars

希望大家看到这里的话能给个 star

地址:https://github.com/kaixinbaba/hg-tui

五、最后

hg-tui 它从出生那一刻起,体内流淌的就是开源的血。

它很小甚至是微不足道,我本不想开源,但蛋蛋的一段话让我改变了主意:开源不是完结,仅仅只是开始

一个开源项目可能只是作者的一个灵光乍现,也可能只是为了解决自己实际工作生活中的小小痛点,没准用完就丢到角落里了。但开源出来或许就能找到有相同需求的人,从而延续这个项目的生命,或许这就是开源的本意吧。

以上就是我做这个项目的全部心得和收获,如果你们对 hg-tui 有什么建议和问题,欢迎给我提 issue

最后,如果你喜欢本文和项目的话,欢迎点赞和 Star 爱你们哟~

解锁!玩转 HelloGitHub 的新姿势的更多相关文章

  1. 【转+自己研究】新姿势之Docker Remote API未授权访问漏洞分析和利用

    0x00 概述 最近提交了一些关于 docker remote api 未授权访问导致代码泄露.获取服务器root权限的漏洞,造成的影响都比较严重,比如 新姿势之获取果壳全站代码和多台机器root权限 ...

  2. FJNU 1159 Fat Brother’s new way(胖哥的新姿势)

    FJNU 1159 Fat Brother’s new way(胖哥的新姿势) Time Limit: 1000MS   Memory Limit: 257792K [Description] [题目 ...

  3. 玩转 Ceph 的正确姿势

    玩转 Ceph 的正确姿势 本文先介绍 Ceph, 然后会聊到一些正确使用 Ceph 的姿势:在集群规模小的时候,Ceph 怎么玩都没问题:但集群大了(到PB级别),这些准则可是保证集群健康运行的不二 ...

  4. Broadcom BCM94352z/DW1560驱动新姿势

    转自:https://blog.daliansky.net/Broadcom-BCM94352z-DW1560-drive-new-posture.html Broadcom WiFi/BlueToo ...

  5. vue组件通信新姿势

    在vue项目实际开发中我们经常会使用props和emit来进行子父组件的传值通信,父组件向子组件传递数据是通过prop传递的, 子组件传递数据给父组件是通过$emit触发事件来做到的.例如: Vue. ...

  6. JSON CSRF新姿势

    以前做渗透测试,遇到过很多次POST数据为JSON数据的CSRF,一直没有搞定,最近发现一个新姿势, ​​​本文作者:Mannix@安全文库 微信公众号:安全文库 测试的时候,当应用程序验证了Cont ...

  7. bzoj1656: [Usaco2006 Jan] The Grove 树木 (bfs+新姿势)

      题目大意:一个n*m的图中,“.”可走,“X”不可走,“*”为起点,问从起点开始绕所有X一圈回到起点最少需要走多少步. 一开始看到这题,自己脑洞了下怎么写,应该是可过,然后跑去看了题解,又学会了一 ...

  8. Java计时新姿势

    为获得更好的阅读体验,请访问原文:传送门 前言: 最近公司来了个大佬,从他那里学到不少东西,其中一个就是计时 的新姿势「StopWatch」,赶紧来一起了解了解吧! 一.最简单的计时 在我们的程序中不 ...

  9. NET Core 3.0 AutoFac替换内置DI的新姿势

    原文:NET Core 3.0 AutoFac替换内置DI的新姿势 .NET Core 3.0 和 以往版本不同,替换AutoFac服务的方式有了一定的变化,在尝试着升级项目的时候出现了一些问题. 原 ...

随机推荐

  1. 面试题:给你个id,去拿到name,多叉树遍历

    前天面试遇到一个多叉树面试的题目,在这里分享记录一下. 题目:一个树形的数据(如下数据),面试官给你一个id,然后拿到对应的name? 数据结构大概是这个样子 var cityData = [ { i ...

  2. ES6-11学习笔记--正则表达式的扩展

    y修饰符 u修饰符   复习以前的修饰符: i(忽略大小写) m(多行匹配) g(全局匹配)   y修饰符:粘连修饰符 const str = 'aaa_aa_a' const reg1 = /a+/ ...

  3. Android Studio连接SQLite数据库与SQLite Studio实时同步的实现

    最近学习用到了android开发连接数据库这一块,发现连接成功后,都要先访问安卓项目的数据库路径data/data/项目/databases,然后把对应的db文件拷出来,再在SQLite的可视化工具中 ...

  4. java中throws子句是怎么用的?工作原理是什么

    7.throws子句 马克-to-win:当你的方法里抛出了checked异常,如你不catch,代表你当时不处理(不想处理或没条件处理),但你必须得通过"throws那个异常"告 ...

  5. java的内存泄露是如何发生的,如何避免和发现

    java的垃圾回收与内存泄露的关系:[新手可忽略不影响继续学习] 马克-to-win:上一节讲了,(i)对象被置成null.(ii)局部对象(无需置成null)当程序运行到右大括号.(iii)匿名对象 ...

  6. Taro时间选择器+地址选择器

    时间地址选择器 https://www.manzj.net/topic/5c3c2123cd01b8af5bce4373

  7. ubuntu连接不到WiFi

    ubuntu连接不到WiFi 在软件与更新中,进入附加驱动. 搜到对应的无线网卡驱动,安装后在重启电脑.

  8. Java实现单链表的逆序打印

    思路1:可以将链表进行反转,然后进行数据的输出即可,单链表反转地址如下https://blog.csdn.net/Kevinnsm/article/details/113763272 这个思路1肯定有 ...

  9. 帝国cms修改成https后后台登陆空白的解决办法

    以下方法适用帝国cms7.5版本: 7.5版本已经有了http和https自动识别,但是因为一些疑难杂症的原因,自动识别判断的不准,后台登录也是空白, 我们可以打开e/config.php查找'htt ...

  10. uniapp-scroll-view纵向(竖向)滑动当scrollTop为0时卡顿问题

    这个问题目前遇到的人少,所以找到答案不容易,我也是各种细节亲测才发现的解决方案.记录下来 当uniapp用scroll-view竖向滚动时,在scrollTop为0时,下拉会卡顿. 解决方法(只需要在 ...