1

python snack

了解linux的人应该听说过NewtNewt是一个为RedHat安装程序而设计的基于文本的窗口开发工具,它是由c语言编写并不依赖X包,linux下的dialogwhiptail都是基于它。而我们今天讨论的snack则是Newt提供的python接口,redhat的系统都自带这个模块,本文就如何使用snack制作伪终端页面展开讲解,并配合代码展示实现效果。

前言

为啥说是最佳实践呢?因为我使用snack的过程中,上网查阅相关资料,发现有关信息甚少。偶尔几篇文章都是处于API或者Demo的级别,并且讲的都不全,更别说高级扩展功能了。我正好工作需要给我们的一个系统做一个终端部署控制台UI,所以我就使用了python snack来实现,期间不断新需求,不断迭代,从基本页面到增删改查,再到校验、再到配置导入、再到进度条等等,不断的迭代开发让我对snack不断地加深认知,它支持的或不支持的我都想办法一一解决,所以在这把我这段时间的收货进行总结并分享给需要的人。

具体场景

本文实践的需求是做一个部署控制台工具,该工具主要分为三个阶段:基础配置、高级配置和部署进度。基础配置页面需要我们创建一些主机,填写一些主机的信息,比如IPHostnamePassword,然后高级配置我们也需要创建一些主机,不过我们可以复用基础设置的主机,所以我们的工具要支持在高级配置中导入基础配置的功能,在高级配置中我们还有一个全局配置,也就是不限于单个主机的配置(其中具体部署原理和是非,我就不多展开赘述,这不是本文的重点)。最后就是进度条页面,我们可以展示部署的过程阶段和相关时间信息。

deploy console

项目地址:https://github.com/tony-yin/B...

开胃凉菜

远古时代

先上几道凉菜,给大家开开胃。所谓的凉菜就是介绍一下python snack的基础组件,基础组件很多,类似html,主要有:

  • Textbox
  • TextboxReflowed
  • Button
  • Compactbutton
  • Checkbox
  • Listbox
  • SingleRadioButton
  • Scale
  • Entry

然后就是一些组合组件,也就是基于上述基础组件封装而得到,主要有:

  • RadioBar
  • ButtonBar
  • CheckboxTree

上面这些组件就是所有的基础组件(组合组件也算基础组件),这些组件最终呈现还需要gridform这两个组件,grid表示“网格”的意思,跟html中的table类似,由行和列组成,我们的基础组件需要放在网格中来实现页面布局;而form也类似“表单”,我们需要把grid填充到form中,运行后,就可以看到图形化页面了。

工业革命

经过上面基础组件的介绍,想必你对snack的组件有了充分的了解,这时候你可以参考文末的refer做几个小demo,做了之后你会发现页面是出来了,emmm... 可是感觉好繁琐哦,很多重复性的代码,而且页面布局也怪怪的,如果要把布局搞好,又需要加很多代码。

我们把用基础组件的阶段称之为“远古时代”,每做一个window,都得一瓦一砖地慢慢堆砌,这样效率太低了,所以我们急需一波“工业革命”来提高生产力。

python snack似乎考虑到了这个问题,它在上述基础组件之外还提供了dialog相关组件,dialog组件即集大成者,一个dialog组件就是一个window,也就是我们上面所说的form,并且该form中填充了必需的各种基础组件,dialog组件主要有:

  • ListboxChoiceWindow
  • ButtonChoiceWindow
  • EntryWindow

返璞归真

当今社会,大家吃惯了大鱼大肉,反而更是想念农村的野味。同理,我们用惯了“工业革命”的产物,发现虽然可用,但是仅仅停留在基础可用级别上,想换换样式,加加自己的定制化需求,都是有限的,完全达不到新需求的技术实现要求。所以,我们不能只知道用别人实现的现成的产物,我们可以尝试着“返璞归真”一下,回归最初的“远古时代”,自己实现一把“工业革命”。所谓的“dialog”组件无非也就是基础组件的封装而已,我们也可以自己实现一套自己的组件库,这个在前端是非常流行的,例如font-awesomeiviewant-design等等。这里我们自己实现了以下dialog

  • ExtButtonChoiceWindow
  • ExtAlert
  • ExtCheckboxWindow
  • ExtListboxChoiceWindow
  • ExtEntryWindow
  • ExtPwdEntryWindow
  • ExtProgressWindow
  • ExtTextWindow

扩展的功能主要有:

  • 热键支持扩展
  • 按钮样式扩展
  • 布局大小自动化扩展
  • 暗文输入框扩展
  • 弹出窗口扩展
  • 进度条窗口信息展示扩展
  • 动态展示扩展
扩展组件库地址:widget extend library

管饱正菜

凉菜不够,正菜来凑。上面就是把python snackAPI罗列了一下,做个小Demo还行,但是距离产品化还很远,接下来我结合我做部署控制台工具的实践经历分享一下几个“正菜”,必须够硬,不接受反驳,不接受批评, O(∩_∩)O ~

热键

python snack提供了两种帮助用户使用的途径,一种是窗口下方的操作提示栏,另一个就是热键了。热键就是快捷键,比如我们可以敲击键盘上面的ESC键实现页面的返回。我们可以通过调用gridrunOnce接口获取热键的输入,例如hotkey = g.runOnce(),然后我们根据hotkey的值进行判断并执行对应的操作。

页面切换

当我们存在多个页面的时候,我们需要页面切换的功能,翻阅文档,并没有发现提供类似的功能。在我们的工具中,页面切换主要有两种方式,一种是点击button,一种是热键,既然没有原生的页面切换接口,我们就根据触发方式手动切换页面。比如我们想实现页面1点击next按钮想跳转页面2,那我们只需要获取button的返回值,判断是否为next,如果是next,直接调用页面2的方法即可,热键同理,即判断热键内容是否为对应热键。

ret, button, lb = ExtListboxChoiceWindow(                                  screen, 
    'Distribute Storage Config',
    'Distribute Storage Config',
    ips,
    buttons=("prev", "next", "exit"),
    width=50,
    height=5,
)                                                                                                                                        
if button == "exit" or ret == "ESC":
    screen.finish()
elif button == "prev":
    Welcome_Deploy_Window()
elif button == "next":
    Additional_Config_Window()
elif lb is not None:
    Basic_Host_Window(lb)

增删改查

增删改查永远是一个软件系统绕不开的基础功能。

“查”:

首先是整体查看,我们可以通过一个列表展示所有信息,这时候我们可以用ExtListboxChoiceWindow组件来实现;然后就是单个查看了,我们可能有多条信息,我们想查看单个信息的详细内容时,我们可以通过点击具体的item进入详细信息的dialog,如何实现呢?listbox中有一个current的概念,也就是listbox中每个li的唯一标识,我们可以用列表的index来填充,因为往往列表页面的信息也无非是数组或者是列表的方式,我们获取到当前的current,即获取到数组的索引,然后就是根据索引查值了,我们再调用新增页面,将查到的值赋值到Textbox即可,Textbox有一个setText就是做这个事情的。当然我们的ExtEntryWindow组件也可以做到赋值填充。请参考上述代码中的lb,其实就是listboxli.current()接口。

“增”:

我们可以通过一个新增按钮或者listbox中的一个li作为新增按钮来触发新增操作,点击后出现一个dialogdialog中有一些TextboxRadioCheckbox等。

def Basic_Host_Window(current, data=None):
    buttons = [ 'save', 'cancel', 'exit']
    if not data:
        data = ['IP Address:', 'Hostname:', 'Password:']
        if current != 'add':
            data = get_format_data(Basic_Config[current], BASIC_TYPE)
            buttons.insert(1, 'Delete')

    host = ExtEntryWindow(
        screen,
        '{} host'.format('Add' if current == 'add' else 'Edit'),
        'Please fill storage host info.',
        data,
        width = 40, 
        entryWidth = 40, 
        buttons = buttons
    )                            

“改”:

修改操作的方法是在list页面选中需要修改的项,然后进入详情页面,可以查看之前创建时填写的信息,也就是我们在“查”中查看单个信息提到的方式,我们所要做的就是在用户点击save按钮的时候,获取用户编辑后的数据,再进行一次修改即可,在我们工具中,此操作就是根据索引修改数组中对应索引的数据而已。

“删”:

有增就有删,这边我暂时还没实现批量删除的功能,一方面python snack的支持功能有限,一方面时间有限,所以我只实现了单个删除的功能,在新增和编辑的页面添加一个delete按钮即可,为了提醒用户错删,我们还要加上一个确认提示框。

if host[1] == "delete":
    button = ExtButtonChoiceWindow(                                            screen,
        'Delete host',
        'Are you sure to delete current host?'
    )
    if button == "ok":
        del(Basic_Config[current])
    else:
        Basic_Host_Window(current)

组件扩展

构建自己的组件库真的很有必要,对于默认的button样式,我真是吐槽到不想再吐槽,它居然还认为自己的bordernice?!所以最终构建自己的组件库的初衷就是想把各个dialog中的button改为compactbutton,没办法,默认的dialog组件不给改呀,所以我们得自己返璞归真一下。

当然我们做扩展组件库,也不是仅仅因为一个button样式,还有很多新需求都要依赖自己扩展的组件。比如热键,原生dialog无法支持热键;还有进度条的进度时间和任务信息展示;还有Gridform的动态布局等等。具体就不一一介绍了,想深入了解的直接看代码,做个小Demo,一目了然。

爽口甜菜

充实的正菜吃饱了,是时候来一波甜菜漱漱口,解解渴了。

在做进度条页面的时候,想除了显示进度任务完成信息之外,还想显示一下开始时间和花费时间。发现pythontime模块比较坑爹,对于时间差的转换支持不行,查阅资料只发现datetime可以将时间差转换为微秒、秒和小时三个单位,但是我想实现时间差的自动转换,也就是60s自动转换为1min60min转为1h24h转为1d,超越天为单位的我就不进行转换了,逻辑不难,只是拿出来分享给有需要的人,不必重复造轮子罢了。

def get_time_interval(start_time):
    start_time = datetime.fromtimestamp(start_time)
    now_time = datetime.fromtimestamp(time.time())
    interval = (now_time - start_time).seconds
    format_interval = get_format_interval(interval)                                                                                          
    return format_interval


def get_format_interval(interval):
    if interval < 60:
        format_interval = "{}s".format(str(interval))
    elif 60 <= interval < 60*60:
        format_interval = "{}min {}s".format(
            str(interval/60), str(interval%60))
    elif 60*60 <= interval < 60*60*24:
        format_interval = "{}h {}min {}s".format(
            str(interval/(60*60)),
            str(interval%(60*60)/60),
            str(interval%(60*60)%60)
        )
    elif 60*60*24 <= interval:
        format_interval = "{}d {}h {}min {}s".format( 
            str(interval/(60*60*24)),
            str(interval%(60*60*24)/60*60),
            str(interval%(60*60)/60),
            str(interval%(60*60)%60)
        )
        
    return format_interval

用餐总结

原本只是想做一个终端图形化的进度条页面,但是后续需求越来越多,导致做成了一个部署控制台工具,整个工程开发和优化花了大约两个星期的时间,项目中遇到的很多难点和问题很多都与python snack无关,所以没有做详细解释,就比如上述的甜菜,大家有兴趣的自行翻阅代码即可。

python snack还有很多未知的我没有使用,比如checkbox tree等,但我相信万变不离其宗,有了这次实践,其他组件的使用和扩展应该不会花很多时间,其实做这个东西,我最深的感触就是前端发展的迅速,python snack2000年初的产物了,很多页面逻辑跟jQuery比起来要弱的多,更别说现在的angularvue等等了,但是领域不同,毕竟是伪终端页面,能做成这样已经不错了。如果是真正的桌面图形化界面(GUI),有pyqt这种神器,功能貌似很强大。

我在之前的一个项目中,就使用过python snack做的控制台,当然当时不知道是用这个技术做的,当时觉得蛮牛的,尝试过修改终端文字成汉子,后来没有成功,便不了了之。这次机缘巧合,工作需要做这么一个控制台,在工作中学习和使用自己感兴趣的技术的感觉真是爽呀。工作中运用技术和自己业余时间学习新技术并做个小Demo完全是不一样的,工作中运用会不断有新需求,不断精益求精,不断深入。所以以工作作为平台,实现自己的技术价值,会有很大的成就感,与大家共勉咯。(#^.^#)


Tony_Zby
7.1k 声望154 粉丝

世界太大,没事瞄一瞄