简体   繁体   English

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

[英]Python raw_input() replacement that uses a configurable text editor

I'm trying to implement a replacement for raw_input() that would use a configurable text editor like vim as the interface to to the user. 我正在尝试实现raw_input()的替代品,该替代品将使用诸如vim之类的可配置文本编辑器作为用户界面。

The ideal workflow would be like this: 理想的工作流程如下:

  1. Your python script is running, and makes a call to my_raw_input(). 您的python脚本正在运行,并调用my_raw_input()。
  2. Vim (or emacs, or gedit, or any other text editor) opens w/ a blank document Vim(或emacs,gedit或任何其他文本编辑器)使用空白文档打开
  3. You type some text into the document, then save and exit 您在文档中键入一些文本,然后保存并退出
  4. The python script resumes running, with the contents of the file as the return value of my_raw_input(). python脚本将继续运行,并将文件内容作为my_raw_input()的返回值。

If you're familiar with git, this is the experience when using git commit , where the editor is configured via core.editor . 如果您熟悉git,这就是使用git commit时的经验,该编辑器是通过core.editor配置的。 Other utilities like crontab -e also do this. 其他实用程序,例如crontab -e也可以这样做。

Ultimately, I would like this my_raw_input() function to also take an optional string w/ the default input contents, which the user could then edit. 最终,我希望my_raw_input()函数也可以使用带有默认输入内容的可选字符串,然后用户可以对其进行编辑。

Research so far 到目前为止的研究

  • os.exec replaces the current process with the editor command, but does not return. os.exec用editor命令替换当前进程,但不返回。 Ie, your python script exits when vim starts. 即,当vim启动时,您的python脚本退出。
  • popen does not start the child process interactively, there is no user interface displayed. popen不会以交互方式启动子进程,没有显示用户界面。
  • vim has a - command-line parameter to read from stdin, but nothing to write to stdout with :w . Vim有一个-命令行参数,从标准输入读取,但是没有写入到标准输出与:w
  • I took a look at the code for git , which I can't follow at all. 我看了一下git代码 ,我根本无法理解。

Is this possible? 这可能吗?

Edit 编辑

Good answers so far. 到目前为止,很好的答案。 I also found the mecurial code that's doing the same thing. 我还发现了做相同事情的商业代码 I also came up with an example that works from looking at the crontab code , but it looks like it's needlessly complicated compared to some of the responses. 我还想出了一个可以通过查看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')

You write the data to a temporary file, and then read it when the editor returns. 您将数据写入一个临时文件,然后在编辑器返回时读取它。 If you run git commit you'll notice that git is doing the same thing. 如果您运行git commit ,则会注意到git在做同样的事情。

There is no extra step to starting a program interactively, as long as the child process has stdin and stdout wired to a terminal it will be interactive. 只要子进程已将stdinstdout连接到终端,就无需交互启动程序,无需交互操作。

There is a gotcha with working with editors -- many of them will save files by writing a temporary file in the same directory and moving it over the old file. 与编辑器打交道有一个陷阱-他们中的许多人将通过在同一目录中写入临时文件并将其移到旧文件上来保存文件。 This makes the save operation completely atomic (ignoring that the power might go out) but means that we have to re-open the temporary file after the editor runs, since our old file handle will point to a file that is no longer part of the file system (but it's still on disk). 这使保存操作完全原子化(忽略电源可能会耗尽),但意味着我们必须在编辑器运行后重新打开临时文件,因为旧文件句柄将指向不再属于该文件的文件。文件系统(但仍在磁盘上)。

This gotcha means that we can't use TemporaryFile or NamedTemporaryFile , we have to use a lower-level facility so we can close the file descriptor and re-open the file without deleting it. 这个陷阱意味着我们不能使用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)

The Git sample code is so complicated because it's not using a nice high-level library like Python's subprocess module. Git示例代码非常复杂,因为它没有使用像Python的subprocess模块这样的高级库。 If you read the subprocess module source code, big chunks of it will look like the linked Git source code (except written in Python instead of C). 如果您阅读了subprocess模块的源代码,则其中的大块块看起来就像链接的Git源代码(用Python代替C编写的除外)。

You would have to create a temporary file name, for the editor to store its stuff in. You could use tempfile.mkstemp() for that. 您必须创建一个临时文件名,以便编辑器将其内容存储在其中。您可以使用tempfile.mkstemp() If you want to put some contents in that file, you can do that. 如果要在该文件中放入一些内容,则可以这样做。

For running the command, subprocess.check_call() seems like the correct tool for the job, since python waits until this command returns, and raises an exception when the subprocess fails. 对于运行命令, subprocess.check_call()似乎是该作业的正确工具,因为python等待该命令返回,并在子进程失败时引发异常。 Roughly: 大致:

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

You can of course customize which editor to use, et cetera. 当然,您可以自定义要使用的编辑器等等。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM