"cmd" --- 对面向行的命令解释器的支持
************************************

**源代码:** Lib/cmd.py

======================================================================

"Cmd" 类提供简单框架用于编写面向行的命令解释器。  这些通常对测试工具，
管理工具和原型有用，这些工具随后将被包含在更复杂的接口中。

class cmd.Cmd(completekey='tab', stdin=None, stdout=None)

   一个 "Cmd" 实例或子类实例是面向行的解释器框架结构。 实例化 "Cmd" 本
   身是没有充分理由的， 它作为自定义解释器类的超类是非常有用的为了继承
   "Cmd" 的方法并且封装动作方法。

   可选参数 *completekey* 是完成键的 "readline" 名称；默认是 "Tab" 。
   如果 *completekey* 不是 "None" 并且 "readline" 是可用的， 命令完成
   会自动完成。

   默认值 "'tab'" 会被特殊对待，以使其在任何 "readline.backend" 上都指
   向 "Tab" 键。 根据特殊规则，如果 "readline.backend" 是 "editline"，
   "Cmd" 将使用 "'^I'" 而不是 "'tab'"。 请注意其它的值不会以这种方式来
   处理，并可能仅适用于特定的后端。

   可选参数 *stdin* 和 *stdout* 指定了 Cmd 实例或子类实例将用于输入和
   输出的文件对象。如果没有指定，它们将默认为 "sys.stdin" 和
   "sys.stdout" 。

   如果你想要使用一个给定的 *stdin* ，确保将实例的 "use_rawinput" 属性
   设置为 "False" ，否则 *stdin* 将被忽略。

   在 3.13 版本发生变更: 对于 "editline" 来说 "completekey='tab'" 将被
   替换为 "'^I'"。


Cmd 对象
========

"Cmd" 实例有下列方法：

Cmd.cmdloop(intro=None)

   反复发出提示，接受输入，从收到的输入中解析出一个初始前缀，并分派给
   操作方法，将其余的行作为参数传递给它们。

   可选参数是在第一个提示之前发布的横幅或介绍字符串（这将覆盖 "intro"
   类属性）。

   如果 "readline" 模块已被加载，输入将自动继承类似 **bash**的历史列表
   编辑功能（例如， "Control"-"P" 滚动到上一条命令，"Control"-"N" 前进
   到下一条命令，"Control"-"F" 非破坏性地将光标向右移动，"Control"-"B"
   非破坏性地将光标向左移动，等等）。

   输入的文件结束符将作为字符串 "'EOF'" 被传回。

   当且仅当命令名称 "foo" 具有 "do_foo()" 方法时解释器实例才会识别它。
   存在一种特殊情况，以字符 "'?'" 开头的行将被分派给 "do_help()" 方法
   。 还存在另一种特殊情况，以字符 "'!'" 开头的行将被分派给
   "do_shell()" 方法（如果定义了该方法的话）。

   当 "postcmd()" 方法返回真值时此方法将返回。  "postcmd()" 的 *stop*
   参数是命令对应的 "do_*()" 方法的返回值。

   如果启用了补全，则会自动完成命令的补全，命令参数的补全则是通过调用
   "complete_foo()" 并附带参数 *text*, *line*, *begidx* 和 *endidx* 来
   完成的。 *text* 是我们要尝试匹配的字符串前缀：所有被返回的匹配必须
   以它为开头。 *line* 是去除了开头空白符的当前输入行，*begidx* 和
   *endidx* 是前缀文本的开始和结束索引号，它们可被用来根据参数所在的位
   置提供不同的补全。

Cmd.do_help(arg)

   所有 "Cmd" 的子类都继承了一个预定义的 "do_help()"。 调用该方法时传
   入一个参数 "'bar'"，将唤起对应的方法 "help_bar()"，如果该方法不存在
   ，则将打印 "do_bar()" 的文档字符串，如果有文档字符串的话。 在没有参
   数的情况下，"do_help()" 将列出所有可用的帮助主题（即任何具有对应的
   "help_*()" 方法的命令或具有文档字符串的命令），还会列出任何未写入文
   档的命令。

Cmd.onecmd(str)

   解释该参数就好像它是为响应提示而键入的一样。 此方法可以被重写，但通
   常不需要这样做；请参阅针对有用的执行钩子的 "precmd()" 和
   "postcmd()" 方法。 返回值是一个指明解释器对命令的解释是否应停止的旗
   标。 如果对于命令 *str* 存在 "do_*()" 方法，则将返回该方法的返回值
   ，否则将返回 "default()" 方法的返回值。

Cmd.emptyline()

   在响应提示输入空行时调用的方法。如果此方法未被覆盖，则重复输入的最
   后一个非空命令。

Cmd.default(line)

   当命令前缀不能被识别的时候在输入行调用的方法。如果此方法未被覆盖，
   它将输出一个错误信息并返回。

Cmd.completedefault(text, line, begidx, endidx)

   当没有命令专属的 "complete_*()" 方法可供使用时将被调用以完成输入行
   的方法。 在默认情况下，它将返回一个空列表。

Cmd.columnize(list, displaywidth=80)

   调用以将一个字符串列表显示为紧凑的列集的方法。 每列的宽度仅够显示其
   内容。 各列之间以两个空格分隔以保证可读性。

Cmd.precmd(line)

   钩方法在命令行 *line* 被解释之前执行，但是在输入提示被生成和发出后
   。这个方法是一个在 "Cmd" 中的存根；它的存在是为了被子类覆盖。返回值
   被用作  "onecmd()" 方法执行的命令； "precmd()" 的实现或许会重写命令
   或者简单地返回 *line* 不变。

Cmd.postcmd(stop, line)

   钩方法只在命令调度完成后执行。这个方法是一个在 "Cmd" 中的存根；它的
   存在是为了被子类覆盖。 *line* 是被执行的命令行， *stop* 是一个表示
   在调用 "postcmd()" 之后是否终止执行的标志；这将作为 "onecmd()" 方法
   的返回值。这个方法的返回值被用作与 *stop* 相关联的内部标志的新值；
   返回 false 将导致解释继续。

Cmd.preloop()

   钩方法当 "cmdloop()" 被调用时执行一次。此方法是一个在 "Cmd" 中的存
   根；它的存在是为了被子类覆盖。

Cmd.postloop()

   钩方法在 "cmdloop()" 即将返回时执行一次。这个方法是一个在 "Cmd" 中
   的存根；它的存在是为了被子类覆盖。

"Cmd" 的子类的实例有一些公共实例变量：

Cmd.prompt

   用于请求输入的提示符。

Cmd.identchars

   接受命令前缀的字符串。

Cmd.lastcmd

   最近的非空命令前缀。

Cmd.cmdqueue

   排队的输入行列表。当需要新的输入时，在 "cmdloop()" 中检查 cmdqueue
   列表；如果它不是空的，它的元素将被按顺序处理，就像在提示符处输入一
   样。

Cmd.intro

   要作为简介或横幅发出的字符串。 可以通过给 "cmdloop()" 方法一个参数
   来覆盖它。

Cmd.doc_header

   如果帮助输出包含一个已记录命令的小节时要发出的标题。

Cmd.misc_header

   如果帮助输出包含一个杂项帮助主题小节时（也就是说，存在没有对应
   "do_*()" 方法的 "help_*()" 方法）要发出的标题。

Cmd.undoc_header

   如果帮助输出包含一个未写入文档的命令小节时（也就是说，存在没有对应
   "help_*()" 方法的 "do_*()" 方法）要发出的标题。

Cmd.ruler

   用于在帮助信息标题的下方绘制分隔符的字符，如果为空，则不绘制标尺线
   。这个字符默认是 "'='" 。

Cmd.use_rawinput

   一个旗标，默认为真值。 如为真值，"cmdloop()" 将使用 "input()" 显示
   一条提示并读取下一个命令；如为假值，则将使用 "sys.stdout.write()"
   和 "sys.stdin.readline()"。 （这意味着通过在受支持的系统上导入
   "readline"，解释器将自动支持类似 **Emacs** 的行编辑和命令历史按键操
   作。）


Cmd 例子
========

"cmd" 模块主要被用于构建自定义 shell 让用户以交互的方式使用一个程序。
模块主要被用于构建自定义 shell 让用户以交互的方式使用一个程序。

这部分提供了一个简单的例子来介绍如何使用一部分在 "turtle" 模块中的命令
构建一个 shell 。

基本 turtle 命令比如 "forward()" 将被添加到一个具有名为 "do_forward()"
的方法的 "Cmd" 子类。 参数将被转换为数字并发送给 turtle 模块。 文档字
符串将被用于 shell 所提供的帮助工具。

该示例还包括一个通过 "precmd()" 方法实现的基本录制和回放工具，这个方法
负责将输入转换为小写形式并将命令写入到文件。 "do_playback()" 方法将读
取文件并将被录制的命令添加到 "cmdqueue" 用于立即回放:

   import cmd, sys
   from turtle import *

   class TurtleShell(cmd.Cmd):
       intro = 'Welcome to the turtle shell.   Type help or ? to list commands.\n'
       prompt = '(turtle) '
       file = None

       # ----- 基本 turtle 命令 -----
       def do_forward(self, arg):
           'Move the turtle forward by the specified distance:  FORWARD 10'
           forward(*parse(arg))
       def do_right(self, arg):
           'Turn turtle right by given number of degrees:  RIGHT 20'
           right(*parse(arg))
       def do_left(self, arg):
           'Turn turtle left by given number of degrees:  LEFT 90'
           left(*parse(arg))
       def do_goto(self, arg):
           'Move turtle to an absolute position with changing orientation.  GOTO 100 200'
           goto(*parse(arg))
       def do_home(self, arg):
           'Return turtle to the home position:  HOME'
           home()
       def do_circle(self, arg):
           'Draw circle with given radius an options extent and steps:  CIRCLE 50'
           circle(*parse(arg))
       def do_position(self, arg):
           'Print the current turtle position:  POSITION'
           print('Current position is %d %d\n' % position())
       def do_heading(self, arg):
           'Print the current turtle heading in degrees:  HEADING'
           print('Current heading is %d\n' % (heading(),))
       def do_color(self, arg):
           'Set the color:  COLOR BLUE'
           color(arg.lower())
       def do_undo(self, arg):
           'Undo (repeatedly) the last turtle action(s):  UNDO'
       def do_reset(self, arg):
           'Clear the screen and return turtle to center:  RESET'
           reset()
       def do_bye(self, arg):
           'Stop recording, close the turtle window, and exit:  BYE'
           print('Thank you for using Turtle')
           self.close()
           bye()
           return True

       # ----- 记录和回放 -----
       def do_record(self, arg):
           'Save future commands to filename:  RECORD rose.cmd'
           self.file = open(arg, 'w')
       def do_playback(self, arg):
           'Playback commands from a file:  PLAYBACK rose.cmd'
           self.close()
           with open(arg) as f:
               self.cmdqueue.extend(f.read().splitlines())
       def precmd(self, line):
           line = line.lower()
           if self.file and 'playback' not in line:
               print(line, file=self.file)
           return line
       def close(self):
           if self.file:
               self.file.close()
               self.file = None

   def parse(arg):
       'Convert a series of zero or more numbers to an argument tuple'
       return tuple(map(int, arg.split()))

   if __name__ == '__main__':
       TurtleShell().cmdloop()

这是一个示例会话，其中 turtle shell 显示帮助功能，使用空行重复命令，以
及简单的记录和回放功能：

   Welcome to the turtle shell.   Type help or ? to list commands.

   (turtle) ?

   Documented commands (type help <topic>):
   ========================================
   bye     color    goto     home  playback  record  right
   circle  forward  heading  left  position  reset   undo

   (turtle) help forward
   Move the turtle forward by the specified distance:  FORWARD 10
   (turtle) record spiral.cmd
   (turtle) position
   Current position is 0 0

   (turtle) heading
   Current heading is 0

   (turtle) reset
   (turtle) circle 20
   (turtle) right 30
   (turtle) circle 40
   (turtle) right 30
   (turtle) circle 60
   (turtle) right 30
   (turtle) circle 80
   (turtle) right 30
   (turtle) circle 100
   (turtle) right 30
   (turtle) circle 120
   (turtle) right 30
   (turtle) circle 120
   (turtle) heading
   Current heading is 180

   (turtle) forward 100
   (turtle)
   (turtle) right 90
   (turtle) forward 100
   (turtle)
   (turtle) right 90
   (turtle) forward 400
   (turtle) right 90
   (turtle) forward 500
   (turtle) right 90
   (turtle) forward 400
   (turtle) right 90
   (turtle) forward 300
   (turtle) playback spiral.cmd
   Current position is 0 0

   Current heading is 0

   Current heading is 180

   (turtle) bye
   Thank you for using Turtle
