Python raw_input () reemplaza que usa un editor de texto configurable

Estoy intentando implementar un reemplazo para raw_input () que usaría un editor de texto configurable como vim como la interfaz para el usuario.

El flujo de trabajo ideal sería así:

  1. La secuencia de comandos de Python se está ejecutando y realiza una llamada a my_raw_input ().
  2. Vim (o emacs, o gedit, o cualquier otro editor de texto) se abre con un documento en blanco
  3. Usted escribe algo de texto en el documento, luego guarda y sale
  4. La secuencia de comandos de Python continúa ejecutándose, con el contenido del archivo como el valor de retorno de my_raw_input ().

Si está familiarizado con git, esta es la experiencia al usar git commit , donde el editor se configura a través de core.editor . Otras utilidades como crontab -e también hacen esto.

En última instancia, me gustaría que esta función my_raw_input () también tome una cadena opcional con los contenidos de entrada predeterminados, que el usuario podría editar.

Investigar hasta ahora

  • os.exec reemplaza el proceso actual con el comando del editor, pero no regresa. Es decir, su secuencia de comandos de Python se cierra cuando se inicia vim.
  • Popen no inicia el proceso secundario de forma interactiva, no se muestra ninguna interfaz de usuario.
  • vim tiene un - parámetro de línea de comando para leer desde stdin, pero nada que escribir en stdout con :w .
  • Eché un vistazo al código de git , que no puedo seguir en absoluto.

es posible?

Editar

Buenas respuestas hasta ahora. También encontré el código mecánico que está haciendo lo mismo. También se me ocurrió un ejemplo que funciona al mirar el código crontab , pero parece que es innecesariamente complicado en comparación con algunas de las respuestas.

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

Escribe los datos en un archivo temporal y luego lo lees cuando vuelve el editor. Si ejecuta git commit , notará que git está haciendo lo mismo.

No hay ningún paso adicional para iniciar un progtwig de forma interactiva, siempre que el proceso hijo tenga una stdin y una salida stdin conectada a un terminal, será interactivo.

Hay un problema con el trabajo con los editores, muchos de ellos guardarán archivos al escribir un archivo temporal en el mismo directorio y moverlo sobre el archivo anterior. Esto hace que la operación de guardado sea completamente atómica (ignorando que la energía podría apagarse), pero significa que tenemos que volver a abrir el archivo temporal después de que se ejecute el editor, ya que nuestro antiguo identificador de archivo apuntará a un archivo que ya no forma parte del archivo. sistema de archivos (pero todavía está en el disco).

Esto significa que no podemos usar TemporaryFile o NamedTemporaryFile , tenemos que usar una instalación de nivel inferior para poder cerrar el descriptor de archivo y volver a abrir el archivo sin eliminarlo.

 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) 

El código de muestra de Git es muy complicado porque no utiliza una biblioteca de alto nivel como el módulo de subprocess de Python. Si lees el código fuente del módulo de subprocess , grandes partes de él se verán como el código fuente de Git vinculado (excepto escrito en Python en lugar de C).

Tendrías que crear un nombre de archivo temporal para que el editor almacene sus cosas. Podrías usar tempfile.mkstemp() para eso. Si quieres poner algo de contenido en ese archivo, puedes hacerlo.

Para ejecutar el comando, subprocess.check_call() parece ser la herramienta correcta para el trabajo, ya que Python espera hasta que este comando regrese, y genera una excepción cuando falla el subproceso. Aproximadamente:

 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 

Por supuesto, puede personalizar qué editor usar, etcétera.