vlambda博客
学习文章列表

Go语言实现的在终端演示幻灯片工具


最近比较喜欢去GitHub上猎奇,这不又看到一个有意思的工具,它可以让你在终端中,演示你的幻灯片,而且幻灯片只需要使用Markdown标记语言即可书写,这对我们来说太方便了。


想象一下,你不再需要繁琐的PowerPoint,只需要一个文本编辑工具,使用Markdown标记语言即可写一个幻灯片,然后在终端里演示,这是非常酷的一件事情,现在让我们开始行动吧。


——   ——


首先,你需要安装 https://github.com/maaslalani/slides 这个工具,它完全使用Go语言编写,安装也比较简单。


如果你是Go语言开发者,那么你只需要使用如下命令即可安装它:

go install github.com/maaslalani/slides


如果你是MacOS用户,使用brew即可安装它。

brew install slides

当然,你也可以从这 https://github.com/maaslalani/slides/releases 下载,Mac、Linux的都有,甚至你可以从源代码安装,都随你,都很简单。


——   ——


安装成功后,打开你的终端,输入slides -h就可以看到一些帮助提示了。

 slides -hSlides is a terminal based presentation tool
Usage: slides <file.md> [flags]
Flags: -h, --help help for slides

从帮助中,你可以看到,它的确非常简单,只需要一个md文档即可使用。


——   ——


现在,我们使用一个官方的例子,看下演示的效果。



比如我把它下载到我的桌面上,然后我运行如下命令,就可以看到幻灯片的效果了。

➜  Desktop slides slides.md


Go语言实现的在终端演示幻灯片工具

如上图所示,这就是一个被slides渲染出来的幻灯片的效果,你可以从右下角看到,它一共有7页,当前是第1页。


左下角显示的是我的计算机的用户名称和时间,上面中间这部分,就是当前页的内容。


这时候你可以按上下键翻页,也可以一直敲回车键下一页,进行演示,效果非常棒。

——   ——


slides不仅可以从本地读取md文件,也可以读取网上的md文件,比如刚刚这个md文件,我们可以使用curl读取,然后作为输入流给slides即可。

curl https://github.com/maaslalani/slides/blob/main/examples/slides.md | slides

以上这个命令的效果,和我们上面演示的从桌面读取的,是一样的。


这个总结就是说,slides可以从一个输入流中读取md内容,解析成幻灯片。


——   ——

md文件的内容就是我们常用的、普通的markdown标记语言,那么slides是如何解析成一页页内容的呢?


这个是slides中的约定,它使用---来分页,也就是markdown里面的分割线。遇到一个---,slides就认为要分页了。我们来看下它的分页代码实现:

const ( delimiter = "\n---\n" altDelimiter = "\n~~~\n")
content = strings.ReplaceAll(content, altDelimiter, delimiter)slides := strings.Split(content, delimiter)


其实就是基于分隔符,使用strings.Split分成一个切片,这个切片就是需要演示的一页页内容。


从源代码中,我们也可以看到,slides其实也支持~~~这个分隔符分页。


——   ——

现在已经分成了一页页内容,那么每一页的md内容,是如何显示到终端里的呢?


相信你自己也能想出来,思路就是要解析MD内容,然后显示到终端中。要自己做这个功能,会比较繁琐,还要考虑样式美观等。


索性有开源,有现成的库可以帮我们做这个事情,它就是 https://github.com/charmbracelet/glamour ,这个是一个可以把md内容渲染到终端的库,并且还自带样式。


Stylesheet-based markdown rendering for your CLI apps.

https://github.com/charmbracelet/glamour


我们看下,在slides中是如何使用它的。

func (m Model) View() string { r, _ := glamour.NewTermRenderer(m.Theme, glamour.WithWordWrap(0)) slide, err := r.Render(m.Slides[m.Page]) if err != nil { slide = fmt.Sprintf("Error: Could not render markdown! (%v)", err) } slide = styles.Slide.Render(slide)
left := styles.Author.Render(m.Author) + styles.Date.Render(m.Date) right := styles.Page.Render(fmt.Sprintf("Slide %d / %d", m.Page+1, len(m.Slides))) status := styles.Status.Render(styles.JoinHorizontal(left, right, m.viewport.Width)) return styles.JoinVertical(slide, status, m.viewport.Height)}

从以上代码可以看到,new一个Render,然后渲染就可以了。


同时这里也可以看到,渲染出的幻灯片,要和作者、时间以及幻灯片状态,合在一起,组成一个终端的界面,也就是这一页幻灯片。


这里可以看到很多styles.Slide,styles.Author等,这些其实就是用于控制终端布局的,它使用的是lipgloss这个库控制的。

Style definitions for nice terminal layouts. Built with TUIs in mind.

https://github.com/charmbracelet/lipgloss

这个库的使用这里暂不讲,有兴趣的朋友可以研究下,这是个很不错的东西,可以让你写出来更好看的CLI Apps。

——   ——


以上讲到的代码,都是一个Model接口的实现,通过它我们才能使用bubbletea这个终端UI框架,把幻灯片的内容、作者、时间等信息,显示在终端中,也就是我上面的截图看到的这些。

 p := tea.NewProgram(model.Model{ Slides: slides, Page: 0, Author: user.Name, Date: time.Now().Format("2006-01-02"), Theme: styles.SelectTheme(m.Theme), }, tea.WithAltScreen())
err = p.Start()

以上是部分代码,用于把幻灯片展示到终端。完整的Model接口实现代码如下所示。

type Model struct { Slides []string Page int Author string Date string Theme glamour.TermRendererOption viewport viewport.Model}
func (m Model) Init() tea.Cmd { return nil}
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: m.viewport.Width = msg.Width m.viewport.Height = msg.Height return m, nil
case tea.KeyMsg: switch msg.String() { case "ctrl+c", "q": return m, tea.Quit case " ", "down", "k", "right", "l", "enter", "n": if m.Page < len(m.Slides)-1 { m.Page++ } case "up", "j", "left", "h", "p": if m.Page > 0 { m.Page-- } } } return m, nil}
func (m Model) View() string { r, _ := glamour.NewTermRenderer(m.Theme, glamour.WithWordWrap(0)) slide, err := r.Render(m.Slides[m.Page]) if err != nil { slide = fmt.Sprintf("Error: Could not render markdown! (%v)", err) } slide = styles.Slide.Render(slide)
left := styles.Author.Render(m.Author) + styles.Date.Render(m.Date) right := styles.Page.Render(fmt.Sprintf("Slide %d / %d", m.Page+1, len(m.Slides))) status := styles.Status.Render(styles.JoinHorizontal(left, right, m.viewport.Width)) return styles.JoinVertical(slide, status, m.viewport.Height)}

——   ——


关于bubbletea这个精美的终端UI框架,我这里不做过多介绍,你可以在GitHub上搜索看下。


经常关注GitHub是一件非常有意思的事情,他可以让你知道哪些Repo正在流行,是否可以学习它,甚至引入到团队、公司,提升整体的实力和效率。


同时,通过研究这些Repo,你可以看到它的实现,引用了哪些第三方的库,然后通过这些Repo,又可以学到更多,更有趣的第三方库的使用,这是一件非常有意思的事情,驱动着你对Go语言越来越了解。


——  精彩推荐  ——



扫码关注


分享、点赞、在看就是最大的支持