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

时间:2012-10-31 22:00:56

标签: python

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

理想的工作流程如下:

  1. 您的python脚本正在运行,并调用my_raw_input()。
  2. Vim(或emacs,或gedit或任何其他文本编辑器)打开一个空白文档
  3. 您在文档中键入一些文本,然后保存并退出
  4. python脚本恢复运行,文件内容为my_raw_input()的返回值。
  5. 如果您熟悉git,这是使用git commit时的体验,其中编辑器是通过core.editor配置的。其他工具如crontab -e也可以这样做。

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

    到目前为止的研究

    • os.exec使用编辑器命令替换当前进程,但不返回。即,当vim启动时你的python脚本退出。
    • popen不会以交互方式启动子进程,也不会显示用户界面。
    • vim有一个-命令行参数可以从stdin读取,但没有任何内容可以用:w写入stdout。
    • 我看了code for git,我根本无法关注。

    这可能吗?

    修改

    到目前为止答案很好。我还发现mecurial code正在做同样的事情。我还提出了一个通过查看crontab code得到的示例,但与某些响应相比,它看起来不必要地复杂。

    #!/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')
    

2 个答案:

答案 0 :(得分:9)

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

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

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

这个问题意味着我们无法使用TemporaryFileNamedTemporaryFile,我们必须使用较低级别的工具,以便我们可以关闭文件描述符并重新打开文件而不删除它。

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)

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

答案 1 :(得分:1)

您必须创建一个临时文件名,以便编辑器存储其内容。您可以使用tempfile.mkstemp()。如果你想在该文件中放入一些内容,你可以这样做。

为了运行该命令,subprocess.check_call()似乎是该作业的正确工具,因为python会等到此命令返回,并在子进程失败时引发异常。大致是:

import os
import tempfile
import subprocess

def my_raw_input(default=''):
    tf, tn = tempfile.mkstemp()
    os.close(tf)
    with open(tn) as tf:
        tf.write(default)
    rv = subprocess.check_call(['emacs', tn])
    with open(tn) as f:
        data = f.read()
    os.unlink(tn)
    return data

您当然可以自定义要使用的编辑器等等。