jenkins-bot has submitted this change and it was merged.
Change subject: [FIX] Editor: Support spaces, delete always and warn on \n (etc) ......................................................................
[FIX] Editor: Support spaces, delete always and warn on \n (etc)
This support spaces in the editor file name and warns if it contains a string literal like \n with Windows because the backslash was used as a path delimiter without escaping them.
The pywikibot.editor.TextEditor.command method has been deprecated, because outside scripts don't require access anyway and this makes it possible to return a list of command segments.
This now deletes the temporary file on any case, also when there were no changes done. And it uses the more secure 'mkstemp' function.
Bug: T86481 Change-Id: If7e9feee49acd1147c5554cdd74a7ad0758397c5 --- M pywikibot/config2.py M pywikibot/editor.py 2 files changed, 58 insertions(+), 32 deletions(-)
Approvals: John Vandenberg: Looks good to me, approved jenkins-bot: Verified
diff --git a/pywikibot/config2.py b/pywikibot/config2.py index e7df241..6e1a27e 100644 --- a/pywikibot/config2.py +++ b/pywikibot/config2.py @@ -886,6 +886,17 @@ elif transliteration_target in ('None', 'none'): transliteration_target = None
+if sys.platform == 'win32' and editor: + # single character string literals from + # https://docs.python.org/2/reference/lexical_analysis.html#string-literals + # encode('unicode-escape') also changes Unicode characters + if set(editor) & set('\a\b\f\n\r\t\v'): + print('WARNING: The editor path contains probably invalid escaped ' + 'characters. Make sure to use a raw-string (r"..." or r'...'), ' + 'forward slashs as a path delimiter or to escape the normal ' + 'path delimiter.') + + # Fix up default site if family == 'wikipedia' and mylang == 'language': print("WARNING: family and mylang are not set.\n" diff --git a/pywikibot/editor.py b/pywikibot/editor.py index d3e268a..b928dc7 100644 --- a/pywikibot/editor.py +++ b/pywikibot/editor.py @@ -11,50 +11,64 @@ __version__ = '$Id$' #
-import os -import tempfile import codecs +import os +import subprocess +import tempfile + import pywikibot from pywikibot import config +from pywikibot.tools import deprecated
class TextEditor(object):
"""Text editor."""
- def command(self, tempFilename, text, jumpIndex=None): + def _command(self, file_name, text, jump_index=None): """Return editor selected in user-config.py.""" - command = config.editor - if jumpIndex: + if jump_index: # Some editors make it possible to mark occurrences of substrings, # or to jump to the line of the first occurrence. # TODO: Find a better solution than hardcoding these, e.g. a config # option. - line = text[:jumpIndex].count('\n') - column = jumpIndex - (text[:jumpIndex].rfind('\n') + 1) + line = text[:jump_index].count('\n') + column = jump_index - (text[:jump_index].rfind('\n') + 1) else: line = column = 0 # Linux editors. We use startswith() because some users might use # parameters. if config.editor.startswith('kate'): - command += " -l %i -c %i" % (line + 1, column + 1) + command = ['-l', '%i' % (line + 1), '-c', '%i' % (column + 1)] elif config.editor.startswith('gedit'): - command += " +%i" % (line + 1) # seems not to support columns + command = ['+%i' % (line + 1)] # seems not to support columns elif config.editor.startswith('emacs'): - command += " +%i" % (line + 1) # seems not to support columns + command = ['+%i' % (line + 1)] # seems not to support columns elif config.editor.startswith('jedit'): - command += " +line:%i" % (line + 1) # seems not to support columns + command = ['+line:%i' % (line + 1)] # seems not to support columns elif config.editor.startswith('vim'): - command += " +%i" % (line + 1) # seems not to support columns + command = ['+%i' % (line + 1)] # seems not to support columns elif config.editor.startswith('nano'): - command += " +%i,%i" % (line + 1, column + 1) + command = ['+%i,%i' % (line + 1, column + 1)] # Windows editors elif config.editor.lower().endswith('notepad++.exe'): - command += " -n%i" % (line + 1) # seems not to support columns + command = ['-n%i' % (line + 1)] # seems not to support columns + else: + command = []
- command += ' %s' % tempFilename - pywikibot.log(u'Running editor: %s' % command) + command = [config.editor] + command + [file_name] + pywikibot.log(u'Running editor: %s' % TextEditor._concat(command)) return command + + @staticmethod + def _concat(command): + return ' '.join("'{0}'".format(part) if ' ' in part else part + for part in command) + + @deprecated('_command (should not be used from the outside)') + def command(self, tempFilename, text, jumpIndex=None): + """Return editor selected in user-config.py.""" + return TextEditor._concat(self._command(tempFilename, text, jumpIndex))
def edit(self, text, jumpIndex=None, highlight=None): """ @@ -73,24 +87,25 @@ @rtype: unicode or None """ if config.editor: - tempFilename = '%s.%s' % (tempfile.mktemp(), + tempFilename = '%s.%s' % (tempfile.mkstemp()[1], config.editor_filename_extension) - with codecs.open(tempFilename, 'w', - encoding=config.editor_encoding) as tempFile: - tempFile.write(text) - creationDate = os.stat(tempFilename).st_mtime - command = self.command(tempFilename, text, jumpIndex) - os.system(command) - lastChangeDate = os.stat(tempFilename).st_mtime - if lastChangeDate == creationDate: - # Nothing changed - return None - else: - with codecs.open(tempFilename, 'r', - encoding=config.editor_encoding) as temp_file: - newcontent = temp_file.read() + try: + with codecs.open(tempFilename, 'w', + encoding=config.editor_encoding) as tempFile: + tempFile.write(text) + creationDate = os.stat(tempFilename).st_mtime + subprocess.call(self._command(tempFilename, text, jumpIndex)) + lastChangeDate = os.stat(tempFilename).st_mtime + if lastChangeDate == creationDate: + # Nothing changed + return None + else: + with codecs.open(tempFilename, 'r', + encoding=config.editor_encoding) as temp_file: + newcontent = temp_file.read() + return newcontent + finally: os.unlink(tempFilename) - return newcontent
try: import gui # noqa