Python-Snack-最佳实践

46次阅读

共计 5952 个字符,预计需要花费 15 分钟才能阅读完成。

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

前言

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

具体场景

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

项目地址: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 完全是不一样的,工作中运用会不断有新需求,不断精益求精,不断深入。所以以工作作为平台,实现自己的技术价值,会有很大的成就感,与大家共勉咯。(#^.^#)

正文完
 0