探索演示应用程序

cliff 源代码包包含一个 demoapp 目录,其中包含一个示例主程序和几个命令插件。

设置

要安装和试验演示应用程序,您应该创建一个虚拟环境并激活它。这将使以后删除该应用程序变得容易,因为它没有任何实际用途,并且您可能不希望在理解其工作原理后保留它

$ pip install virtualenv
$ virtualenv .venv
$ . .venv/bin/activate
(.venv)$

接下来,在相同的环境中安装 cliff

(.venv)$ python setup.py install

最后,将演示应用程序安装到虚拟环境中

(.venv)$ cd demoapp
(.venv)$ python setup.py install

用法

cliff 和演示应用程序都已安装,您现在可以运行命令 cliffdemo

要获取基本命令用法说明以及插件中可用命令的列表,请运行

(.venv)$ cliffdemo -h

或者

(.venv)$ cliffdemo --help

通过将命令名作为参数传递给 cliffdemo 来运行 simple 命令

(.venv)$ cliffdemo simple

simple 命令会将以下输出打印到控制台

sending greeting
hi!

要查看单个命令的帮助信息,请使用 help 命令

(.venv)$ cliffdemo help files

--help 选项

(.venv)$ cliffdemo files --help

有关更多信息,请参阅自动生成的文档 如下

源代码

cliffdemo 应用程序定义在一个 cliffdemo 包中,其中包含几个模块。

main.py

主应用程序定义在 main.py

 1import sys
 2
 3from cliff.app import App
 4from cliff.commandmanager import CommandManager
 5
 6
 7class DemoApp(App):
 8    def __init__(self):
 9        super().__init__(
10            description='cliff demo app',
11            version='0.1',
12            command_manager=CommandManager('cliff.demo'),
13            deferred_help=True,
14        )
15
16    def initialize_app(self, argv):
17        self.LOG.debug('initialize_app')
18
19    def prepare_to_run_command(self, cmd):
20        self.LOG.debug('prepare_to_run_command %s', cmd.__class__.__name__)
21
22    def clean_up(self, cmd, result, err):
23        self.LOG.debug('clean_up %s', cmd.__class__.__name__)
24        if err:
25            self.LOG.debug('got an error: %s', err)
26
27
28def main(argv=sys.argv[1:]):
29    myapp = DemoApp()
30    return myapp.run(argv)
31
32
33if __name__ == '__main__':
34    sys.exit(main(sys.argv[1:]))

DemoApp 类继承自 App 并重写 __init__() 以设置程序描述和版本号。它还传递一个配置为在 cliff.demo 命名空间中查找插件的 CommandManager 实例。

在解析了主程序参数之后,但在执行任何命令处理之前以及应用程序进入交互模式之前,将调用 DemoAppinitialize_app() 方法。此钩子用于使用传递给主应用程序的参数打开与远程 Web 服务、数据库等的连接。

在识别命令之后,但在将参数传递给命令并运行命令之前,将调用 DemoAppprepare_to_run_command() 方法。此钩子用于预命令验证或必须重复且无法由 initialize_app() 处理的设置。

在命令运行后,将调用 DemoAppclean_up() 方法。如果命令引发了异常,则将异常对象传递给 clean_up()。否则,err 参数为 None

main.py 中定义的 main() 函数注册为控制台脚本入口点,以便可以从命令行运行 DemoApp(请参阅下面的 setup.py 的讨论)。

simple.py

simple.py 中定义了两个命令

 1import logging
 2
 3from cliff.command import Command
 4
 5
 6class Simple(Command):
 7    "A simple command that prints a message."
 8
 9    log = logging.getLogger(__name__)
10
11    def take_action(self, parsed_args):
12        self.log.info('sending greeting')
13        self.log.debug('debugging')
14        self.app.stdout.write('hi!\n')
15
16
17class Error(Command):
18    "Always raises an error"
19
20    log = logging.getLogger(__name__)
21
22    def take_action(self, parsed_args):
23        self.log.info('causing error')
24        raise RuntimeError('this is the expected exception')

Simple 演示了使用日志记录在不同详细程度下向控制台发出消息

(.venv)$ cliffdemo simple
sending greeting
hi!

(.venv)$ cliffdemo -v simple
prepare_to_run_command Simple
sending greeting
debugging
hi!
clean_up Simple

(.venv)$ cliffdemo -q simple
hi!

Error 在被调用时始终引发 RuntimeError 异常,可用于试验 cliff 的错误处理功能

(.venv)$ cliffdemo error
causing error
ERROR: this is the expected exception

(.venv)$ cliffdemo -v error
prepare_to_run_command Error
causing error
ERROR: this is the expected exception
clean_up Error
got an error: this is the expected exception

(.venv)$ cliffdemo --debug error
causing error
this is the expected exception
Traceback (most recent call last):
  File ".../cliff/app.py", line 218, in run_subcommand
    result = cmd.run(parsed_args)
  File ".../cliff/command.py", line 43, in run
    self.take_action(parsed_args)
  File ".../demoapp/cliffdemo/simple.py", line 24, in take_action
    raise RuntimeError('this is the expected exception')
RuntimeError: this is the expected exception
Traceback (most recent call last):
  File "/Users/dhellmann/Envs/cliff/bin/cliffdemo", line 9, in <module>
    load_entry_point('cliffdemo==0.1', 'console_scripts', 'cliffdemo')()
  File ".../demoapp/cliffdemo/main.py", line 33, in main
    return myapp.run(argv)
  File ".../cliff/app.py", line 160, in run
    result = self.run_subcommand(remainder)
  File ".../cliff/app.py", line 218, in run_subcommand
    result = cmd.run(parsed_args)
  File ".../cliff/command.py", line 43, in run
    self.take_action(parsed_args)
  File ".../demoapp/cliffdemo/simple.py", line 24, in take_action
    raise RuntimeError('this is the expected exception')
RuntimeError: this is the expected exception

list.py

list.py 包含一个从 cliff.lister.Lister 派生的单个命令,该命令打印当前目录中文件的列表。

 1import logging
 2import os
 3
 4from cliff.lister import Lister
 5
 6
 7class Files(Lister):
 8    """Show a list of files in the current directory.
 9
10    The file name and size are printed by default.
11    """
12
13    log = logging.getLogger(__name__)
14
15    def take_action(self, parsed_args):
16        return (
17            ('Name', 'Size'),
18            ((n, os.stat(n).st_size) for n in os.listdir('.')),
19        )

Files 准备数据,Lister 管理输出格式化程序并将数据打印到控制台

(.venv)$ cliffdemo files
+---------------+------+
|      Name     | Size |
+---------------+------+
| build         |  136 |
| cliffdemo.log | 2546 |
| Makefile      | 5569 |
| source        |  408 |
+---------------+------+

(.venv)$ cliffdemo files -f csv
"Name","Size"
"build",136
"cliffdemo.log",2690
"Makefile",5569
"source",408

show.py

show.py 包含一个从 cliff.show.ShowOne 派生的单个命令,该命令打印指定文件的属性。

 1import logging
 2import os
 3
 4from cliff.show import ShowOne
 5
 6
 7class File(ShowOne):
 8    "Show details about a file"
 9
10    log = logging.getLogger(__name__)
11
12    def get_parser(self, prog_name):
13        parser = super().get_parser(prog_name)
14        parser.add_argument('filename', nargs='?', default='.')
15        return parser
16
17    def take_action(self, parsed_args):
18        stat_data = os.stat(parsed_args.filename)
19        columns = (
20            'Name',
21            'Size',
22            'UID',
23            'GID',
24            'Modified Time',
25        )
26        data = (
27            parsed_args.filename,
28            stat_data.st_size,
29            stat_data.st_uid,
30            stat_data.st_gid,
31            stat_data.st_mtime,
32        )
33        return (columns, data)

File 准备数据,ShowOne 管理输出格式化程序并将数据打印到控制台

(.venv)$ cliffdemo file setup.py
+---------------+--------------+
|     Field     |    Value     |
+---------------+--------------+
| Name          | setup.py     |
| Size          | 5825         |
| UID           | 502          |
| GID           | 20           |
| Modified Time | 1335569964.0 |
+---------------+--------------+

setup.py

演示应用程序使用 setuptools 封装。

 1#!/usr/bin/env python
 2
 3from setuptools import find_packages
 4from setuptools import setup
 5
 6PROJECT = 'cliffdemo'
 7
 8# Change docs/sphinx/conf.py too!
 9VERSION = '0.1'
10
11try:
12    long_description = open('README.rst').read()
13except OSError:
14    long_description = ''
15
16setup(
17    name=PROJECT,
18    version=VERSION,
19    description='Demo app for cliff',
20    long_description=long_description,
21    author='Doug Hellmann',
22    author_email='doug.hellmann@gmail.com',
23    url='https://github.com/openstack/cliff',
24    download_url='https://github.com/openstack/cliff/tarball/master',
25    classifiers=[
26        'Development Status :: 3 - Alpha',
27        'License :: OSI Approved :: Apache Software License',
28        'Programming Language :: Python',
29        'Programming Language :: Python :: 3',
30        'Programming Language :: Python :: 3 :: Only',
31        'Intended Audience :: Developers',
32        'Environment :: Console',
33    ],
34    platforms=['Any'],
35    scripts=[],
36    provides=[],
37    install_requires=['cliff'],
38    namespace_packages=[],
39    packages=find_packages(),
40    include_package_data=True,
41    entry_points={
42        'console_scripts': ['cliffdemo = cliffdemo.main:main'],
43        'cliff.demo': [
44            'simple = cliffdemo.simple:Simple',
45            'two_part = cliffdemo.simple:Simple',
46            'error = cliffdemo.simple:Error',
47            'list files = cliffdemo.list:Files',
48            'files = cliffdemo.list:Files',
49            'file = cliffdemo.show:File',
50            'show file = cliffdemo.show:File',
51            'unicode = cliffdemo.encoding:Encoding',
52            'hooked = cliffdemo.hook:Hooked',
53        ],
54        'cliff.demo.hooked': [
55            'sample-hook = cliffdemo.hook:Hook',
56        ],
57    },
58    zip_safe=False,
59)

封装说明的重要部分是 entry_points 设置。所有命令都在 cliff.demo 命名空间中注册。每个主程序应定义自己的命令命名空间,以便它仅加载应管理的命令插件。

命令扩展钩子

应用程序的单个子命令可以通过作为单独插件注册的钩子进行扩展。在演示应用程序中,hooked 命令注册了一个扩展。

钩子的命名空间是应用程序命名空间和命令名称的组合。在这种情况下,应用程序命名空间是 cliff.demo,命令是 hooked,因此扩展命名空间是 cliff.demo.hooked。如果子命令名称包含空格,则用下划线(”_”)替换它们以构建命名空间。

 1# All Rights Reserved.
 2#
 3#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 4#    not use this file except in compliance with the License. You may obtain
 5#    a copy of the License at
 6#
 7#         https://apache.org/licenses/LICENSE-2.0
 8#
 9#    Unless required by applicable law or agreed to in writing, software
10#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12#    License for the specific language governing permissions and limitations
13#    under the License.
14
15import logging
16
17from cliff.command import Command
18from cliff.hooks import CommandHook
19
20
21class Hooked(Command):
22    "A command to demonstrate how the hooks work"
23
24    log = logging.getLogger(__name__)
25
26    def take_action(self, parsed_args):
27        self.app.stdout.write('this command has an extension\n')
28
29
30class Hook(CommandHook):
31    """Hook sample for the 'hooked' command.
32
33    This would normally be provided by a separate package from the
34    main application, but is included in the demo app for simplicity.
35
36    """
37
38    def get_parser(self, parser):
39        print('sample hook get_parser()')
40        parser.add_argument('--added-by-hook')
41        return parser
42
43    def get_epilog(self):
44        return 'extension epilog text'
45
46    def before(self, parsed_args):
47        self.cmd.app.stdout.write('before\n')
48
49    def after(self, parsed_args, return_code):
50        self.cmd.app.stdout.write('after\n')

虽然 hooked 命令不会向其创建的解析器添加任何参数,但帮助输出显示扩展添加了一个 --added-by-hook 选项。

(.venv)$ cliffdemo hooked -h
sample hook get_parser()
usage: cliffdemo hooked [-h] [--added-by-hook ADDED_BY_HOOK]

A command to demonstrate how the hooks work

optional arguments:
  -h, --help            show this help message and exit
  --added-by-hook ADDED_BY_HOOK

extension epilog text

(.venv)$ cliffdemo hooked
sample hook get_parser()
before
this command has an extension
after

参见

cliff.hooks.CommandHook – 命令钩子的 API。

自动生成的文档

以下文档使用以下指令生成,该指令由 cliff Sphinx 扩展 提供。

.. autoprogram-cliff:: cliffdemo.main.DemoApp
   :application: cliffdemo

.. autoprogram-cliff:: cliff.demo
   :application: cliffdemo

输出

全局选项

cliff 演示应用程序

cliffdemo [--version] [-v | -q] [--log-file LOG_FILE] [--debug]
--version

显示程序版本号并退出

-v, --verbose

增加输出的详细程度。可以重复使用。

-q, --quiet

抑制输出,只显示警告和错误。

--log-file <LOG_FILE>

指定一个用于记录输出的文件。默认禁用。

--debug

在发生错误时显示回溯信息。

命令选项

error

始终引发错误

cliffdemo error
file

显示有关文件的详细信息

cliffdemo file
    [-f {json,shell,table,value,yaml}]
    [-c COLUMN]
    [--noindent]
    [--prefix PREFIX]
    [--max-width <integer>]
    [--fit-width]
    [--print-empty]
    [filename]
-f <FORMATTER>, --format <FORMATTER>

输出格式,默认为表格

-c COLUMN, --column COLUMN

指定要包含的列,可以重复以显示多个列

--noindent

是否禁用 JSON 的缩进

--prefix <PREFIX>

为所有变量名添加前缀

--max-width <integer>

最大显示宽度,小于 1 则禁用。您也可以使用 CLIFF_MAX_TERM_WIDTH 环境变量,但参数优先。

--fit-width

使表格适应显示宽度。如果 –max-width 大于 0,则隐式启用。设置环境变量 CLIFF_FIT_WIDTH=1 以始终启用

--print-empty

如果没有要显示的数据,则打印空表格。

filename
files

显示当前目录中的文件列表。

默认情况下,打印文件名和大小。

cliffdemo files
    [-f {csv,json,table,value,yaml}]
    [-c COLUMN]
    [--quote {all,minimal,none,nonnumeric}]
    [--noindent]
    [--max-width <integer>]
    [--fit-width]
    [--print-empty]
    [--sort-column SORT_COLUMN]
    [--sort-ascending | --sort-descending]
-f <FORMATTER>, --format <FORMATTER>

输出格式,默认为表格

-c COLUMN, --column COLUMN

指定要包含的列,可以重复以显示多个列

--quote <QUOTE_MODE>

何时包含引号,默认为非数字

--noindent

是否禁用 JSON 的缩进

--max-width <integer>

最大显示宽度,小于 1 则禁用。您也可以使用 CLIFF_MAX_TERM_WIDTH 环境变量,但参数优先。

--fit-width

使表格适应显示宽度。如果 –max-width 大于 0,则隐式启用。设置环境变量 CLIFF_FIT_WIDTH=1 以始终启用

--print-empty

如果没有要显示的数据,则打印空表格。

--sort-column SORT_COLUMN

指定要对数据进行排序的列(首先指定的列具有优先级,不存在的列将被忽略),可以重复

--sort-ascending

按升序对列进行排序

--sort-descending

按降序对列进行排序

hooked

一个演示钩子如何工作的命令

cliffdemo hooked
列出文件

显示当前目录中的文件列表。

默认情况下,打印文件名和大小。

cliffdemo list files
    [-f {csv,json,table,value,yaml}]
    [-c COLUMN]
    [--quote {all,minimal,none,nonnumeric}]
    [--noindent]
    [--max-width <integer>]
    [--fit-width]
    [--print-empty]
    [--sort-column SORT_COLUMN]
    [--sort-ascending | --sort-descending]
-f <FORMATTER>, --format <FORMATTER>

输出格式,默认为表格

-c COLUMN, --column COLUMN

指定要包含的列,可以重复以显示多个列

--quote <QUOTE_MODE>

何时包含引号,默认为非数字

--noindent

是否禁用 JSON 的缩进

--max-width <整数>

最大显示宽度,小于 1 则禁用。您也可以使用 CLIFF_MAX_TERM_WIDTH 环境变量,但参数优先。

--fit-width

使表格适应显示宽度。如果 –max-width 大于 0,则隐式启用。设置环境变量 CLIFF_FIT_WIDTH=1 以始终启用

--print-empty

如果没有要显示的数据,则打印空表格。

--sort-column SORT_COLUMN

指定要对数据进行排序的列(首先指定的列具有优先级,不存在的列将被忽略),可以重复

--sort-ascending

按升序对列进行排序

--sort-descending

按降序对列进行排序

显示文件

显示有关文件的详细信息

cliffdemo show file
    [-f {json,shell,table,value,yaml}]
    [-c COLUMN]
    [--noindent]
    [--prefix PREFIX]
    [--max-width <integer>]
    [--fit-width]
    [--print-empty]
    [filename]
-f <格式化器>, --format <格式化器>

输出格式,默认为表格

-c COLUMN, --column COLUMN

指定要包含的列,可以重复以显示多个列

--noindent

是否禁用 JSON 的缩进

--prefix <前缀>

为所有变量名添加前缀

--max-width <整数>

最大显示宽度,小于 1 则禁用。您也可以使用 CLIFF_MAX_TERM_WIDTH 环境变量,但参数优先。

--fit-width

使表格适应显示宽度。如果 –max-width 大于 0,则隐式启用。设置环境变量 CLIFF_FIT_WIDTH=1 以始终启用

--print-empty

如果没有要显示的数据,则打印空表格。

文件名
simple

一个简单的命令,用于打印消息。

cliffdemo simple
两部分

一个简单的命令,用于打印消息。

cliffdemo two part
Unicode

显示一些 Unicode 文本

cliffdemo unicode
    [-f {csv,json,table,value,yaml}]
    [-c COLUMN]
    [--quote {all,minimal,none,nonnumeric}]
    [--noindent]
    [--max-width <integer>]
    [--fit-width]
    [--print-empty]
    [--sort-column SORT_COLUMN]
    [--sort-ascending | --sort-descending]
-f <格式化器>, --format <格式化器>

输出格式,默认为表格

-c COLUMN, --column COLUMN

指定要包含的列,可以重复以显示多个列

--quote <引用模式>

何时包含引号,默认为非数字

--noindent

是否禁用 JSON 的缩进

--max-width <整数>

最大显示宽度,小于 1 则禁用。您也可以使用 CLIFF_MAX_TERM_WIDTH 环境变量,但参数优先。

--fit-width

使表格适应显示宽度。如果 –max-width 大于 0,则隐式启用。设置环境变量 CLIFF_FIT_WIDTH=1 以始终启用

--print-empty

如果没有要显示的数据,则打印空表格。

--sort-column SORT_COLUMN

指定要对数据进行排序的列(首先指定的列具有优先级,不存在的列将被忽略),可以重复

--sort-ascending

按升序对列进行排序

--sort-descending

按降序对列进行排序