diff --git a/appveyor.yml b/appveyor.yml index 591dc6c35..3aad0dd26 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -12,6 +12,8 @@ install: - '%PYTHON_HOME%\python.exe -V' - '%PYTHON_HOME%\Scripts\pip.exe --version' - '%PYTHON_HOME%\Scripts\pip.exe install pypiwin32' + - '%PYTHON_HOME%\Scripts\pip.exe install winshell' + - '%PYTHON_HOME%\Scripts\pip.exe install psutil' - ps: Start-FileDownload "http://ftp.gnome.org/pub/GNOME/binaries/win32/pygtk/2.24/pygtk-all-in-one-2.24.2.win32-py2.7.msi" - msiexec /i pygtk-all-in-one-2.24.2.win32-py2.7.msi /qn /norestart /log pygtk-install.log TARGETDIR=C:\Python27 ALLUSERS=1 # download Winapp.ini for TestWinapp.py diff --git a/bleachbit/Action.py b/bleachbit/Action.py index 4edb28395..e0de6f3ea 100644 --- a/bleachbit/Action.py +++ b/bleachbit/Action.py @@ -34,7 +34,7 @@ import General import Special -from Common import _ +from Common import _, FSE, expanduser, expandvars if 'posix' == os.name: re_flags = 0 @@ -96,8 +96,7 @@ def __init__(self, action_element): assert(isinstance(self.nwholeregex, (str, unicode, types.NoneType))) self.search = action_element.getAttribute('search') self.object_type = action_element.getAttribute('type') - self.path = os.path.expanduser(FileUtilities.expandvars( - action_element.getAttribute('path'))) + self.path = expanduser(expandvars(action_element.getAttribute('path'))) if 'nt' == os.name and self.path: # convert forward slash to backslash for compatibility with getsize() # and for display. Do not convert an empty path, or it will become @@ -171,8 +170,7 @@ def get_walk_all(top): def get_walk_files(top): for expanded in glob.iglob(top): - for path in FileUtilities.children_in_directory( - expanded, False): + for path in FileUtilities.children_in_directory(expanded, False): yield path if 'deep' == self.search: @@ -379,7 +377,7 @@ class Process(ActionProvider): action_key = 'process' def __init__(self, action_element): - self.cmd = FileUtilities.expandvars(action_element.getAttribute('cmd')) + self.cmd = expandvars(action_element.getAttribute('cmd')) # by default, wait self.wait = True wait = action_element.getAttribute('wait') diff --git a/bleachbit/Cleaner.py b/bleachbit/Cleaner.py index 6a73f53ff..fb1f15318 100644 --- a/bleachbit/Cleaner.py +++ b/bleachbit/Cleaner.py @@ -41,8 +41,8 @@ elif 'nt' == os.name: import Windows -from Common import _ -from FileUtilities import children_in_directory, expandvars +from Common import _, expanduser, expandvars +from FileUtilities import children_in_directory from Options import options # Suppress GTK warning messages while running in CLI #34 @@ -164,7 +164,7 @@ def is_running(self): print "debug: process '%s' is running" % pathname return True elif 'pathname' == test: - expanded = os.path.expanduser(expandvars(pathname)) + expanded = expanduser(expandvars(pathname)) for globbed in glob.iglob(expanded): if os.path.exists(globbed): print "debug: file '%s' exists indicating '%s' is running" % (globbed, self.name) @@ -252,7 +252,7 @@ def get_commands(self, option_id): if 'posix' == os.name: # This path is whitelisted under the System - Cache cleaner, # so it can be cleaned here. - dirs += [os.path.expanduser('~/.cache/mozilla')] + dirs += [expanduser('~/.cache/mozilla')] for dirname in dirs: for filename in children_in_directory(dirname, False): yield Command.Delete(filename) @@ -268,7 +268,7 @@ def get_commands(self, option_id): self.profile_dir, "cookies.sqlite") # crash reports if 'posix' == os.name: - crashdir = os.path.expanduser("~/.mozilla/firefox/Crash Reports") + crashdir = expanduser("~/.mozilla/firefox/Crash Reports") if 'nt' == os.name: crashdir = expandvars( "$USERPROFILE\\Application Data\\Mozilla\\Firefox\\Crash Reports") @@ -533,7 +533,7 @@ def get_commands(self, option_id): # cache if 'posix' == os.name and 'cache' == option_id: - dirname = os.path.expanduser("~/.cache/") + dirname = expanduser("~/.cache/") for filename in children_in_directory(dirname, True): if self.whitelisted(filename): continue @@ -640,7 +640,7 @@ def get_commands(self, option_id): # most recently used documents list if 'posix' == os.name and 'recent_documents' == option_id: - files += [os.path.expanduser("~/.recently-used")] + files += [expanduser("~/.recently-used")] # GNOME 2.26 (as seen on Ubuntu 9.04) will retain the list # in memory if it is simply deleted, so it must be shredded # (or at least truncated). @@ -651,7 +651,7 @@ def get_commands(self, option_id): # # https://bugzilla.gnome.org/show_bug.cgi?id=591404 for pathname in ["~/.recently-used.xbel", "~/.local/share/recently-used.xbel"]: - pathname = os.path.expanduser(pathname) + pathname = expanduser(pathname) if os.path.lexists(pathname): yield Command.Shred(pathname) if HAVE_GTK: @@ -690,7 +690,7 @@ def get_commands(self, option_id): # trash if 'posix' == os.name and 'trash' == option_id: - dirname = os.path.expanduser("~/.Trash") + dirname = expanduser("~/.Trash") for filename in children_in_directory(dirname, False): yield Command.Delete(filename) # fixme http://www.ramendik.ru/docs/trashspec.html @@ -698,13 +698,13 @@ def get_commands(self, option_id): # ~/.local/share/Trash # * GNOME 2.22, Fedora 9 # * KDE 4.1.3, Ubuntu 8.10 - dirname = os.path.expanduser("~/.local/share/Trash/files") + dirname = expanduser("~/.local/share/Trash/files") for filename in children_in_directory(dirname, True): yield Command.Delete(filename) - dirname = os.path.expanduser("~/.local/share/Trash/info") + dirname = expanduser("~/.local/share/Trash/info") for filename in children_in_directory(dirname, True): yield Command.Delete(filename) - dirname = os.path.expanduser("~/.local/share/Trash/expunged") + dirname = expanduser("~/.local/share/Trash/expunged") # desrt@irc.gimpnet.org tells me that the trash # backend puts files in here temporary, but in some situations # the files are stuck. @@ -800,16 +800,15 @@ def whitelisted(self, pathname): '^/tmp/orbit-[^/]+/bonobo-activation-server-[a-z0-9-]*ior$', '^/tmp/pulse-[^/]+/pid$', '^/var/tmp/kdecache-'] - regexes.append('^' + os.path.expanduser('~/.cache/wallpaper/')) + regexes.append('^' + expanduser('~/.cache/wallpaper/')) # Clean Firefox cache from Firefox cleaner (LP#1295826) - regexes.append('^' + os.path.expanduser('~/.cache/mozilla')) + regexes.append('^' + expanduser('~/.cache/mozilla')) # Clean Google Chrome cache from Google Chrome cleaner (LP#656104) - regexes.append('^' + os.path.expanduser('~/.cache/google-chrome')) - regexes.append( - '^' + os.path.expanduser('~/.cache/gnome-control-center/')) + regexes.append('^' + expanduser('~/.cache/google-chrome')) + regexes.append('^' + expanduser('~/.cache/gnome-control-center/')) # iBus Pinyin # https://bugs.launchpad.net/bleachbit/+bug/1538919 - regexes.append('^' + os.path.expanduser('~/.cache/ibus/')) + regexes.append('^' + expanduser('~/.cache/ibus/')) for regex in regexes: if None != re.match(regex, pathname): return True diff --git a/bleachbit/Common.py b/bleachbit/Common.py index fd0faec63..7c7e45736 100644 --- a/bleachbit/Common.py +++ b/bleachbit/Common.py @@ -27,6 +27,7 @@ import locale import logging import os +import re import sys APP_VERSION = "1.13" @@ -86,11 +87,80 @@ license_filename = lf break +# os.path.expandvars does not work well with non-ascii Windows paths. +# This is a unicode-compatible reimplementation of that function. +def expandvars(var): + """Expand environment variables. + + Return the argument with environment variables expanded. Substrings of the + form $name or ${name} or %name% are replaced by the value of environment + variable name.""" + if isinstance(var, str): + final = var.decode('utf-8') + else: + final = var + + if 'posix' == os.name: + final = os.path.expandvars(final) + elif 'nt' == os.name: + if (2, 5) == sys.version_info[0:2]: + import backport + final = backport.expandvars(final) + else: + import _winreg + if final.startswith('${'): + final = re.sub(r'\$\{(.*?)\}(?=$|\\)', + lambda x: '%%%s%%' % x.group(1), + final) + elif final.startswith('$'): + final = re.sub(r'\$(.*?)(?=$|\\)', + lambda x: '%%%s%%' % x.group(1), + final) + final = _winreg.ExpandEnvironmentStrings(final) + return final + +# Windows paths have to be unicode, but os.path.expanduser does not support it. +# This is a unicode-compatible reimplementation of that function. +def expanduser(path): + """Expand the path with the home directory. + + Return the argument with an initial component of "~" or "~user" replaced by + that user's home directory. + """ + if isinstance(path, str): + final = path.decode('utf-8') + else: + final = path + + if 'posix' == os.name: + final = os.path.expanduser(final) + elif 'nt' == os.name: + if (2, 5) == sys.version_info[0:2]: + import backport + final = backport.expandvars(final) + elif final == '~' or final.startswith('~/') or final.startswith('~user/'): + found = False + for env in [u'%USERPROFILE%', u'%HOME%']: + if env in os.environ: + home = expandvars(env) + found = True + break + if not found: + h_drive = expandvars(u'%HOMEDRIVE%') + h_path = expandvars(u'%HOMEPATH%') + home = os.path.join(h_drive, h_path) + final = final.replace('~user/', '') + final = final.replace('~/', '') + final = final.replace('~', '') + final = os.path.join(home, final) + return final + + # configuration portable_mode = False options_dir = None if 'posix' == os.name: - options_dir = os.path.expanduser("~/.config/bleachbit") + options_dir = expanduser("~/.config/bleachbit") elif 'nt' == os.name: if os.path.exists(os.path.join(bleachbit_exe_path, 'bleachbit.ini')): # portable mode @@ -98,7 +168,7 @@ options_dir = bleachbit_exe_path else: # installed mode - options_dir = os.path.expandvars("${APPDATA}\\BleachBit") + options_dir = expandvars("${APPDATA}") options_file = os.path.join(options_dir, "bleachbit.ini") # personal cleaners @@ -148,13 +218,13 @@ # launcher launcher_path = '/usr/share/applications/bleachbit.desktop' if 'posix' == os.name: - autostart_path = os.path.expanduser( - '~/.config/autostart/bleachbit.desktop') + autostart_path = expanduser('~/.config/autostart/bleachbit.desktop') # # gettext # +FSE = sys.getfilesystemencoding() try: (user_locale, encoding) = locale.getdefaultlocale() except: @@ -188,6 +258,7 @@ def ungettext(singular, plural, n): return singular return plural + # # string decoding # @@ -195,8 +266,6 @@ def ungettext(singular, plural, n): # and byte encoded. This decodes them into Unicode. # See . # - - def decode_str(s): """Decode a string into Unicode using the default encoding""" if isinstance(s, Exception): diff --git a/bleachbit/Diagnostic.py b/bleachbit/Diagnostic.py index 00600e745..0f9064cea 100644 --- a/bleachbit/Diagnostic.py +++ b/bleachbit/Diagnostic.py @@ -44,8 +44,8 @@ def diagnostic_info(): pass s += "\nlocal_cleaners_dir = %s" % Common.local_cleaners_dir s += "\nlocale_dir = %s" % Common.locale_dir - s += "\noptions_dir = %s" % Common.options_dir - s += "\npersonal_cleaners_dir = %s" % Common.personal_cleaners_dir + s += "\noptions_dir = %s" % Common.options_dir.decode(Common.FSE) + s += "\npersonal_cleaners_dir = %s" % Common.personal_cleaners_dir.decode(Common.FSE) s += "\nsystem_cleaners_dir = %s" % Common.system_cleaners_dir s += "\nlocale.getdefaultlocale = %s" % str(locale.getdefaultlocale()) if 'posix' == os.name: @@ -54,8 +54,11 @@ def diagnostic_info(): envs = ('APPDATA', 'LocalAppData', 'LocalAppDataLow', 'Music', 'USERPROFILE', 'ProgramFiles', 'ProgramW6432', 'TMP') for env in envs: - s += "\nos.getenv('%s') = %s" % (env, os.getenv(env)) - s += "\nos.path.expanduser('~') = %s" % os.path.expanduser('~') + if os.getenv(env): + s += "\nos.getenv('%s') = %s" % (env, os.getenv(env).decode(Common.FSE)) + else: + s += "\nos.getenv('%s') = %s" % (env, os.getenv(env)) + s += "\nos.path.expanduser('~') = %s" % Common.expanduser('~').decode(Common.FSE) if sys.platform.startswith('linux'): if hasattr(platform, 'linux_distribution'): s += "\nplatform.linux_distribution() = %s" % str( diff --git a/bleachbit/FileUtilities.py b/bleachbit/FileUtilities.py index e6470bc59..175701a49 100644 --- a/bleachbit/FileUtilities.py +++ b/bleachbit/FileUtilities.py @@ -41,6 +41,7 @@ import time import ConfigParser import Common +from Common import expanduser if 'nt' == os.name: import pywintypes @@ -354,23 +355,14 @@ def execute_sqlite3(path, cmds): def expand_glob_join(pathname1, pathname2): """Join pathname1 and pathname1, expand pathname, glob, and return as list""" ret = [] - pathname3 = os.path.expanduser( - expandvars(os.path.join(pathname1, pathname2))) + pathname3 = expanduser(Common.expandvars(os.path.join(pathname1, pathname2))) for pathname4 in glob.iglob(pathname3): ret.append(pathname4) return ret def expandvars(path): - if (2, 5) == sys.version_info[0:2] and 'nt' == os.name: - # Python 2.5 should not change $foo when foo is unknown, but - # it actually strips it out. - import backport - expandvars = backport.expandvars - return backport.expandvars(path) - else: - expandvars = os.path.expandvars - return os.path.expandvars(path) + return Common.expandvars(path) def extended_path(path): @@ -387,8 +379,8 @@ def extended_path(path): def free_space(pathname): """Return free space in bytes""" if 'nt' == os.name: - _, _, free_bytes = win32file.GetDiskFreeSpaceEx(pathname) - return free_bytes + import psutil + return psutil.disk_usage(pathname).free mystat = os.statvfs(pathname) return mystat.f_bfree * mystat.f_bsize @@ -449,21 +441,18 @@ def guess_overwrite_paths(): # ~/.config makes it easy to find them and clean them. ret = [] if 'posix' == os.name: - home = os.path.expanduser('~/.cache') + home = expanduser('~/.cache') if not os.path.exists(home): - home = os.path.expanduser("~") + home = expanduser("~") ret.append(home) if not same_partition(home, '/tmp/'): ret.append('/tmp') elif 'nt' == os.name: - localtmp = expandvars('$TMP') + localtmp = Common.expandvars('$TMP') logger = logging.getLogger(__name__) if not os.path.exists(localtmp): logger.warning('%TMP% does not exist: %s', localtmp) localtmp = None - else: - from win32file import GetLongPathName - localtmp = GetLongPathName(localtmp) from Windows import get_fixed_drives for drive in get_fixed_drives(): if localtmp and same_partition(localtmp, drive): @@ -505,7 +494,7 @@ def listdir(directory): for pathname in listdir(dirname): yield pathname return - dirname = os.path.expanduser(directory) + dirname = expanduser(directory) if not os.path.lexists(dirname): return for filename in os.listdir(dirname): diff --git a/bleachbit/GUI.py b/bleachbit/GUI.py index 7a188a092..5bcb11d2f 100755 --- a/bleachbit/GUI.py +++ b/bleachbit/GUI.py @@ -36,7 +36,8 @@ from Common import _, _p, APP_NAME, APP_VERSION, APP_URL, appicon_path, \ help_contents_url, license_filename, options_file, options_dir, \ - online_update_notification_enabled, release_notes_url, portable_mode + online_update_notification_enabled, release_notes_url, portable_mode, \ + expanduser from Cleaner import backends, register_cleaners from GuiPreferences import PreferencesDialog from Options import options @@ -930,7 +931,7 @@ def __init__(self, uac=True, shred_paths=None): logger = logging.getLogger(__name__) logger.exception( _("Error loading the SQLite module: the antivirus software may be blocking it.")) - if 'posix' == os.name and os.path.expanduser('~') == '/root': + if 'posix' == os.name and expanduser('~') == '/root': self.append_text( _('You are running BleachBit with administrative privileges for cleaning shared parts of the system, and references to the user profile folder will clean only the root account.')) diff --git a/bleachbit/General.py b/bleachbit/General.py index c3269c18d..8ed5c47ae 100644 --- a/bleachbit/General.py +++ b/bleachbit/General.py @@ -26,7 +26,7 @@ import os import sys import traceback - +import Common # # XML @@ -107,7 +107,7 @@ def getrealuid(): def makedirs(path): """Make directory recursively considering sudo permissions. 'Path' should not end in a delimiter.""" - print "debug: makedirs(%s)" % path + print "debug: makedirs(%s)" % path.encode(Common.FSE) if os.path.lexists(path): return parentdir = os.path.split(path)[0] @@ -127,10 +127,9 @@ def run_external(args, stdout=False, env=None): kwargs = {} if subprocess.mswindows: # hide the 'DOS box' window + import win32process, win32con stui = subprocess.STARTUPINFO() - import win32process stui.dwFlags = win32process.STARTF_USESHOWWINDOW - import win32con stui.wShowWindow = win32con.SW_HIDE kwargs['startupinfo'] = stui p = subprocess.Popen(args, stdout=stdout, diff --git a/bleachbit/GuiBasic.py b/bleachbit/GuiBasic.py index 69d6ca562..821cc90e9 100644 --- a/bleachbit/GuiBasic.py +++ b/bleachbit/GuiBasic.py @@ -29,7 +29,7 @@ if 'nt' == os.name: import Windows -from Common import _ +from Common import _, expanduser def browse_folder(parent, title, multiple, stock_button): @@ -48,7 +48,7 @@ def browse_folder(parent, title, multiple, stock_button): stock_button, gtk.RESPONSE_OK), action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER) chooser.set_select_multiple(multiple) - chooser.set_current_folder(os.path.expanduser('~')) + chooser.set_current_folder(expanduser('~')) resp = chooser.run() if multiple: ret = chooser.get_filenames() @@ -72,7 +72,7 @@ def browse_file(parent, title): parent=parent, action=gtk.FILE_CHOOSER_ACTION_OPEN, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)) - chooser.set_current_folder(os.path.expanduser('~')) + chooser.set_current_folder(expanduser('~')) resp = chooser.run() path = chooser.get_filename() chooser.destroy() @@ -95,7 +95,7 @@ def browse_files(parent, title): action=gtk.FILE_CHOOSER_ACTION_OPEN, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_DELETE, gtk.RESPONSE_OK)) chooser.set_select_multiple(True) - chooser.set_current_folder(os.path.expanduser('~')) + chooser.set_current_folder(expanduser('~')) resp = chooser.run() paths = chooser.get_filenames() chooser.destroy() diff --git a/bleachbit/Options.py b/bleachbit/Options.py index c4304a46b..aeb1ce7c7 100644 --- a/bleachbit/Options.py +++ b/bleachbit/Options.py @@ -25,6 +25,7 @@ import os import re +import sys import traceback import ConfigParser @@ -53,7 +54,6 @@ def path_to_option(pathname): pathname = pathname[0] + pathname[2:] return pathname - class Options: """Store and retrieve user preferences""" @@ -97,6 +97,7 @@ def __purge(self): # restore colon lost because ConfigParser treats colon special # in keys pathname = pathname[0] + ':' + pathname[1:] + pathname = pathname.decode('utf-8') exists = False try: exists = os.path.lexists(pathname) @@ -121,7 +122,7 @@ def get(self, option, section='bleachbit'): option = option[0] + option[2:] if option in boolean_keys: return self.config.getboolean(section, option) - return self.config.get(section, option) + return self.config.get(section, option.encode('utf-8')) def get_hashpath(self, pathname): """Recall the hash for a file""" @@ -238,7 +239,7 @@ def restore(self): def set(self, key, value, section='bleachbit', commit=True): """Set a general option""" - self.config.set(section, key, str(value)) + self.config.set(section, key.encode('utf-8'), str(value)) if commit: self.__flush() diff --git a/bleachbit/Winapp.py b/bleachbit/Winapp.py index 86e6dc5da..e33526522 100644 --- a/bleachbit/Winapp.py +++ b/bleachbit/Winapp.py @@ -34,7 +34,7 @@ import traceback from Action import Delete, Winreg -from Common import _ +from Common import _, FSE, expandvars from xml.dom.minidom import parseString @@ -103,7 +103,7 @@ def winapp_expand_vars(pathname): # Change %foo% to ${foo} as required by Python 2.5.4 (but not 2.7.8) pathname = re.sub(r'%([a-zA-Z0-9]+)%', r'${\1}', pathname) # This is the regular expansion - expand1 = os.path.expandvars(pathname) + expand1 = expandvars(pathname) # Winapp2.ini expands %ProgramFiles% to %ProgramW6432%, etc. subs = (('ProgramFiles', 'ProgramW6432'), ('CommonProgramFiles', 'CommonProgramW6432')) @@ -111,7 +111,7 @@ def winapp_expand_vars(pathname): pattern = re.compile(r'\${%s}' % sub_orig, flags=re.IGNORECASE) if pattern.match(pathname): expand2 = pattern.sub('${%s}' % sub_repl, pathname) - return (expand1, os.path.expandvars(expand2)) + return (expand1, expandvars(expand2)) return (expand1,) @@ -232,14 +232,14 @@ def handle_section(self, section): """Parse a section""" # if simple detection fails then discard the section if self.parser.has_option(section, 'detect'): - key = self.parser.get(section, 'detect') + key = self.parser.get(section, 'detect').decode(FSE) if not Windows.detect_registry_key(key): return if self.parser.has_option(section, 'detectfile'): - if not detect_file(self.parser.get(section, 'detectfile')): + if not detect_file(self.parser.get(section, 'detectfile').decode(FSE)): return if self.parser.has_option(section, 'detectos'): - required_ver = self.parser.get(section, 'detectos') + required_ver = self.parser.get(section, 'detectos').decode(FSE) if not detectos(required_ver): return # in case of multiple detection, discard if none match @@ -248,7 +248,7 @@ def handle_section(self, section): for n in range(1, MAX_DETECT): option_id = 'detectfile%d' % n if self.parser.has_option(section, option_id): - if detect_file(self.parser.get(section, option_id)): + if detect_file(self.parser.get(section, option_id).decode(FSE)): matches = matches + 1 if 0 == matches: return @@ -257,7 +257,7 @@ def handle_section(self, section): for n in range(1, MAX_DETECT): option_id = 'detect%d' % n if self.parser.has_option(section, option_id): - if Windows.detect_registry_key(self.parser.get(section, option_id)): + if Windows.detect_registry_key(self.parser.get(section, option_id).decode(FSE)): matches = matches + 1 if 0 == matches: return @@ -268,14 +268,14 @@ def handle_section(self, section): option_id = 'excludekey%d' % n if self.parser.has_option(section, option_id): excludekeys.append( - self.excludekey_to_nwholeregex(self.parser.get(section, option_id))) + self.excludekey_to_nwholeregex(self.parser.get(section, option_id).decode(FSE))) # there are two ways to specify sections: langsecref= and section= if self.parser.has_option(section, 'langsecref'): # verify the langsecref number is known # langsecref_num is 3021, games, etc. - langsecref_num = self.parser.get(section, 'langsecref') + langsecref_num = self.parser.get(section, 'langsecref').decode(FSE) elif self.parser.has_option(section, 'section'): - langsecref_num = self.parser.get(section, 'section') + langsecref_num = self.parser.get(section, 'section').decode(FSE) else: print 'ERROR: neither option LangSecRef nor Section found in section %s' % (section) return @@ -290,7 +290,7 @@ def handle_section(self, section): self.handle_regkey(lid, section, option) elif option == 'warning': self.cleaners[lid].set_warning( - section2option(section), self.parser.get(section, 'warning')) + section2option(section), self.parser.get(section, 'warning').decode(FSE)) elif option in ('default', 'detectfile', 'detect', 'langsecref', 'section') \ or ['detect%d' % x for x in range(1, MAX_DETECT)] \ or ['detectfile%d' % x for x in range(1, MAX_DETECT)]: @@ -327,19 +327,19 @@ def __make_file_provider(self, dirname, filename, recurse, removeself, excludeke # just one exclude_str = excludekeys[0] excludekeysxml = 'nwholeregex="%s"' % exclude_str - action_str = '