探索演示应用程序¶
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 实例。
在解析了主程序参数之后,但在执行任何命令处理之前以及应用程序进入交互模式之前,将调用 DemoApp 的 initialize_app() 方法。此钩子用于使用传递给主应用程序的参数打开与远程 Web 服务、数据库等的连接。
在识别命令之后,但在将参数传递给命令并运行命令之前,将调用 DemoApp 的 prepare_to_run_command() 方法。此钩子用于预命令验证或必须重复且无法由 initialize_app() 处理的设置。
在命令运行后,将调用 DemoApp 的 clean_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¶
按降序对列进行排序