使用可配置文本编辑器的Python raw_input()替换

Cha*_*ert 3 python

我正在尝试实现raw_input()的替换,它将使用像vim这样的可配置文本编辑器作为用户的接口.

理想的工作流程如下:

  1. 你的python脚本正在运行,并调用my_raw_input().
  2. Vim(或emacs,或gedit,或任何其他文本编辑器)打开一个空白文档
  3. 您在文档中键入一些文本,然后保存并退出
  4. python脚本恢复运行,文件内容作为my_raw_input()的返回值.

如果您熟悉git,这是使用时的体验git commit,其中编辑器是通过core.editor配置的.其他公用事业crontab -e也这样做.

最后,我希望my_raw_input()函数也可以使用一个可选字符串w /默认输入内容,然后用户可以编辑它.

到目前为止研究

  • os.exec用编辑器命令替换当前进程,但不返回.即,你的python脚本在vim启动时退出.
  • popen不以交互方式启动子进程,没有显示用户界面.
  • vim有一个-命令行参数可以从stdin中读取,但是没有要写入stdout的内容:w.
  • 我看了一下git代码,我根本无法理解.

这可能吗?

编辑

到目前为止答案很好.我还发现了做同样事情的mecurial代码.我还提出了一个从查看crontab代码开始工作的示例,但与某些响应相比,它看起来像是不必要的复杂.

#!/usr/bin/python
import os
import tempfile


def raw_input_editor(default=None, editor=None):
    ''' like the built-in raw_input(), except that it uses a visual
    text editor for ease of editing. Unline raw_input() it can also
    take a default value. '''

    editor = editor or get_editor()

    with tempfile.NamedTemporaryFile(mode='r+') as tmpfile:

        if default:
            tmpfile.write(default)
            tmpfile.flush()

        child_pid = os.fork()
        is_child = child_pid == 0

        if is_child:
            os.execvp(editor, [editor, tmpfile.name])
        else:
            os.waitpid(child_pid, 0)
            tmpfile.seek(0)
            return tmpfile.read().strip()


def get_editor():
    return (os.environ.get('VISUAL')
        or os.environ.get('EDITOR')
        or 'vi')


if __name__ == "__main__":
    print raw_input_editor('this is a test')
Run Code Online (Sandbox Code Playgroud)

Die*_*Epp 9

您将数据写入临时文件,然后在编辑器返回时读取它.如果你运行git commit你会注意到git正在做同样的事情.

没有额外的步骤以交互方式启动程序,只要子进程具有stdinstdout连接到终端,它将是交互式的.

有一个与编辑器合作的问题 - 他们中的许多人将通过在同一目录中写入临时文件并将其移动到旧文件来保存文件.这使得保存操作完全原子化(忽略电源可能会消失),但意味着我们必须在编辑器运行后重新打开临时文件,因为我们的旧文件句柄将指向一个不再属于该文件的文件文件系统(但它仍在磁盘上).

这个问题意味着我们不能使用TemporaryFile或者NamedTemporaryFile我们必须使用较低级别的工具,因此我们可以关闭文件描述符并重新打开文件而不删除它.

import tempfile
import subprocess
import os

def edit(data):
    fdes = -1
    path = None
    fp = None
    try:
        fdes, path = tempfile.mkstemp(suffix='.txt', text=True)
        fp = os.fdopen(fdes, 'w+')
        fdes = -1
        fp.write(data)
        fp.close()
        fp = None

        editor = (os.environ.get('VISUAL') or
                  os.environ.get('EDITOR') or
                  'nano')
        subprocess.check_call([editor, path])

        fp = open(path, 'r')
        return fp.read()
    finally:
        if fp is not None:
            fp.close()
        elif fdes >= 0:
            os.close(fdes)
        if path is not None:
            try:
                os.unlink(path)
            except OSError:
                pass

text = edit('Hello, World!')
print(text)
Run Code Online (Sandbox Code Playgroud)

Git示例代码非常复杂,因为它没有像Python的subprocess模块那样使用漂亮的高级库.如果您阅读subprocess模块源代码,它的大块将看起来像链接的Git源代码(除了用Python而不是C编写).