diff -r b0935ef48186 Lib/lib-tk/Tkinter.py --- a/Lib/lib-tk/Tkinter.py Sun Dec 16 13:55:47 2012 +0100 +++ b/Lib/lib-tk/Tkinter.py Sun Dec 16 20:53:46 2012 +0200 @@ -41,6 +41,7 @@ TclError = _tkinter.TclError from types import * from Tkconstants import * +import re wantobjects = 1 @@ -58,6 +59,37 @@ except AttributeError: _tkinter.deletefilehandler = None +_magic_re = re.compile(r'([\\{}])') +_space_re = re.compile(r'([\s])') + +def _join(value): + """Internal function.""" + return ' '.join(map(_stringify, value)) + +def _stringify(value): + """Internal function.""" + if isinstance(value, (list, tuple)): + if len(value) == 1: + value = _stringify(value[0]) + if value[0] == '{': + value = '{%s}' % value + else: + value = '{%s}' % _join(value) + else: + if isinstance(value, basestring): + value = unicode(value) + else: + value = str(value) + if not value: + value = '{}' + elif _magic_re.search(value): + # add '\' before special characters and spaces + value = _magic_re.sub(r'\\\1', value) + value = _space_re.sub(r'\\\1', value) + elif value[0] == '"' or _space_re.search(value): + value = '{%s}' % value + return value + def _flatten(tuple): """Internal function.""" res = () @@ -1086,7 +1118,7 @@ nv.append('%d' % item) else: # format it to proper Tcl code if it contains space - nv.append(('{%s}' if ' ' in item else '%s') % item) + nv.append(_stringify(item)) else: v = ' '.join(nv) res = res + ('-'+k, v) diff -r b0935ef48186 Lib/lib-tk/test/test_ttk/test_functions.py --- a/Lib/lib-tk/test/test_ttk/test_functions.py Sun Dec 16 13:55:47 2012 +0100 +++ b/Lib/lib-tk/test/test_ttk/test_functions.py Sun Dec 16 20:53:46 2012 +0200 @@ -50,13 +50,17 @@ ttk._format_optdict({'test': {'left': 'as is'}}), {'-test': {'left': 'as is'}}) - # check script formatting and untouched value(s) + # check script formatting check_against( ttk._format_optdict( - {'test': [1, -1, '', '2m', 0], 'nochange1': 3, - 'nochange2': 'abc def'}, script=True), - {'-test': '{1 -1 {} 2m 0}', '-nochange1': 3, - '-nochange2': 'abc def' }) + {'test': [1, -1, '', '2m', 0], 'test2': 3, + 'test3': '', 'test4': 'abc def', + 'test5': '"abc"', 'test6': '{}', + 'test7': '} -spam {'}, script=True), + {'-test': '{1 -1 {} 2m 0}', '-test2': '3', + '-test3': '{}', '-test4': '{abc def}', + '-test5': '{"abc"}', '-test6': r'\{\}', + '-test7': r'\}\ -spam\ \{'}) opts = {u'αβγ': True, u'á': False} orig_opts = opts.copy() @@ -70,6 +74,32 @@ ttk._format_optdict( {'option': ('one two', 'three')}), {'-option': '{one two} three'}) + check_against( + ttk._format_optdict( + {'option': ('one\ttwo', 'three')}), + {'-option': '{one\ttwo} three'}) + + # passing empty strings inside a tuple/list + check_against( + ttk._format_optdict( + {'option': ('', 'one')}), + {'-option': '{} one'}) + + # passing values with braces inside a tuple/list + check_against( + ttk._format_optdict( + {'option': ('one} {two', 'three')}), + {'-option': r'one\}\ \{two three'}) + + # passing quoted strings inside a tuple/list + check_against( + ttk._format_optdict( + {'option': ('"one"', 'two')}), + {'-option': '{"one"} two'}) + check_against( + ttk._format_optdict( + {'option': ('{one}', 'two')}), + {'-option': r'\{one\} two'}) # ignore an option amount_opts = len(ttk._format_optdict(opts, ignore=(u'á'))) // 2 diff -r b0935ef48186 Lib/lib-tk/test/test_ttk/test_widgets.py --- a/Lib/lib-tk/test/test_ttk/test_widgets.py Sun Dec 16 13:55:47 2012 +0100 +++ b/Lib/lib-tk/test/test_ttk/test_widgets.py Sun Dec 16 20:53:46 2012 +0200 @@ -188,6 +188,14 @@ self.combo.configure(values=[1, '', 2]) self.assertEqual(self.combo['values'], ('1', '', '2')) + # testing values with spaces + self.combo['values'] = ['a b', 'a\tb', 'a\nb'] + self.assertEqual(self.combo['values'], ('a b', 'a\tb', 'a\nb')) + + # testing values with special characters + self.combo['values'] = [r'a\tb', '"a"', '} {'] + self.assertEqual(self.combo['values'], (r'a\tb', '"a"', '} {')) + # out of range self.assertRaises(Tkinter.TclError, self.combo.current, len(self.combo['values'])) diff -r b0935ef48186 Lib/lib-tk/ttk.py --- a/Lib/lib-tk/ttk.py Sun Dec 16 13:55:47 2012 +0100 +++ b/Lib/lib-tk/ttk.py Sun Dec 16 20:53:46 2012 +0200 @@ -26,8 +26,7 @@ "tclobjs_to_py", "setup_master"] import Tkinter - -_flatten = Tkinter._flatten +from Tkinter import _flatten, _join, _stringify # Verify if Tk is new enough to not need the Tile package _REQUIRE_TILE = True if Tkinter.TkVersion < 8.5 else False @@ -47,39 +46,56 @@ master.tk.eval('package require tile') # TclError may be raised here master._tile_loaded = True +def _format_optvalue(value, script=False): + """Internal function.""" + if script: + # if caller passes a Tcl script to tk.call, all the values need to + # be grouped into words (arguments to a command in Tcl dialect) + value = _stringify(value) + elif isinstance(value, (list, tuple)): + value = _join(value) + return value + def _format_optdict(optdict, script=False, ignore=None): """Formats optdict to a tuple to pass it to tk.call. E.g. (script=False): {'foreground': 'blue', 'padding': [1, 2, 3, 4]} returns: ('-foreground', 'blue', '-padding', '1 2 3 4')""" - format = "%s" if not script else "{%s}" opts = [] for opt, value in optdict.iteritems(): - if ignore and opt in ignore: - continue + if not ignore or opt not in ignore: + opts.append("-%s" % opt) + if value is not None: + opts.append(_format_optvalue(value, script)) - if isinstance(value, (list, tuple)): - v = [] - for val in value: - if isinstance(val, basestring): - v.append(unicode(val) if val else '{}') - else: - v.append(str(val)) + return _flatten(opts) - # format v according to the script option, but also check for - # space in any value in v in order to group them correctly - value = format % ' '.join( - ('{%s}' if ' ' in val else '%s') % val for val in v) - - if script and value == '': - value = '{}' # empty string in Python is equivalent to {} in Tcl - - opts.append(("-%s" % opt, value)) - - # Remember: _flatten skips over None - return _flatten(opts) +def _mapdict_values(items): + # each value in mapdict is expected to be a sequence, where each item + # is another sequence containing a state (or several) and a value + # E.g. (script=False): + # [('active', 'selected', 'grey'), ('focus', [1, 2, 3, 4])] + # returns: + # ['active selected', 'grey', 'focus', [1, 2, 3, 4]] + opt_val = [] + for item in items: + state = item[:-1] + val = item[-1] + # hacks for bakward compatibility + state[0] # raise IndexError if empty + if len(state) == 1: + # if it is empty (something that evaluates to False), then + # format it to Tcl code to denote the "normal" state + state = state[0] or '' + else: + # group multiple states + state = ' '.join(state) # raise TypeError if not str + opt_val.append(state) + if val is not None: + opt_val.append(val) + return opt_val def _format_mapdict(mapdict, script=False): """Formats mapdict to pass it to tk.call. @@ -90,32 +106,11 @@ returns: ('-expand', '{active selected} grey focus {1, 2, 3, 4}')""" - # if caller passes a Tcl script to tk.call, all the values need to - # be grouped into words (arguments to a command in Tcl dialect) - format = "%s" if not script else "{%s}" opts = [] for opt, value in mapdict.iteritems(): - - opt_val = [] - # each value in mapdict is expected to be a sequence, where each item - # is another sequence containing a state (or several) and a value - for statespec in value: - state, val = statespec[:-1], statespec[-1] - - if len(state) > 1: # group multiple states - state = "{%s}" % ' '.join(state) - else: # single state - # if it is empty (something that evaluates to False), then - # format it to Tcl code to denote the "normal" state - state = state[0] or '{}' - - if isinstance(val, (list, tuple)): # val needs to be grouped - val = "{%s}" % ' '.join(map(str, val)) - - opt_val.append("%s %s" % (state, val)) - - opts.append(("-%s" % opt, format % ' '.join(opt_val))) + opts.extend(("-%s" % opt, + _format_optvalue(_mapdict_values(value), script))) return _flatten(opts) @@ -129,7 +124,7 @@ iname = args[0] # next args, if any, are statespec/value pairs which is almost # a mapdict, but we just need the value - imagespec = _format_mapdict({None: args[1:]})[1] + imagespec = _join(_mapdict_values(args[1:])) spec = "%s %s" % (iname, imagespec) else: @@ -138,7 +133,7 @@ # themed styles on Windows XP and Vista. # Availability: Tk 8.6, Windows XP and Vista. class_name, part_id = args[:2] - statemap = _format_mapdict({None: args[2:]})[1] + statemap = _join(_mapdict_values(args[2:])) spec = "%s %s %s" % (class_name, part_id, statemap) opts = _format_optdict(kw, script) @@ -148,11 +143,11 @@ # otherwise it will clone {} (empty element) spec = args[0] # theme name if len(args) > 1: # elementfrom specified - opts = (args[1], ) + opts = (_format_optvalue(args[1], script),) if script: spec = '{%s}' % spec - opts = ' '.join(map(str, opts)) + opts = ' '.join(opts) return spec, opts @@ -189,7 +184,7 @@ for layout_elem in layout: elem, opts = layout_elem opts = opts or {} - fopts = ' '.join(map(str, _format_optdict(opts, True, "children"))) + fopts = ' '.join(_format_optdict(opts, True, ("children",))) head = "%s%s%s" % (' ' * indent, elem, (" %s" % fopts) if fopts else '') if "children" in opts: @@ -215,11 +210,11 @@ for name, opts in settings.iteritems(): # will format specific keys according to Tcl code if opts.get('configure'): # format 'configure' - s = ' '.join(map(unicode, _format_optdict(opts['configure'], True))) + s = ' '.join(_format_optdict(opts['configure'], True)) script.append("ttk::style configure %s %s;" % (name, s)) if opts.get('map'): # format 'map' - s = ' '.join(map(unicode, _format_mapdict(opts['map'], True))) + s = ' '.join(_format_mapdict(opts['map'], True)) script.append("ttk::style map %s %s;" % (name, s)) if 'layout' in opts: # format 'layout' which may be empty @@ -706,30 +701,9 @@ exportselection, justify, height, postcommand, state, textvariable, values, width """ - # The "values" option may need special formatting, so leave to - # _format_optdict the responsibility to format it - if "values" in kw: - kw["values"] = _format_optdict({'v': kw["values"]})[1] - Entry.__init__(self, master, "ttk::combobox", **kw) - def __setitem__(self, item, value): - if item == "values": - value = _format_optdict({item: value})[1] - - Entry.__setitem__(self, item, value) - - - def configure(self, cnf=None, **kw): - """Custom Combobox configure, created to properly format the values - option.""" - if "values" in kw: - kw["values"] = _format_optdict({'v': kw["values"]})[1] - - return Entry.configure(self, cnf, **kw) - - def current(self, newindex=None): """If newindex is supplied, sets the combobox value to the element at position newindex in the list of values. Otherwise, diff -r b0935ef48186 Lib/test/test_glob.py --- a/Lib/test/test_glob.py Sun Dec 16 13:55:47 2012 +0100 +++ b/Lib/test/test_glob.py Sun Dec 16 20:53:46 2012 +0200 @@ -5,6 +5,8 @@ import shutil import sys +def fsdecode(s): + return unicode(s, sys.getfilesystemencoding()) class GlobTests(unittest.TestCase): @@ -31,7 +33,8 @@ self.mktemp('a', 'bcd', 'efg', 'ha') if hasattr(os, 'symlink'): os.symlink(self.norm('broken'), self.norm('sym1')) - os.symlink(self.norm('broken'), self.norm('sym2')) + os.symlink('broken', self.norm('sym2')) + os.symlink(os.path.join('a', 'bcd'), self.norm('sym3')) def tearDown(self): shutil.rmtree(self.tempdir) @@ -44,10 +47,16 @@ p = os.path.join(self.tempdir, pattern) res = glob.glob(p) self.assertEqual(list(glob.iglob(p)), res) + ures = [fsdecode(x) for x in res] + self.assertEqual(glob.glob(fsdecode(p)), ures) + self.assertEqual(list(glob.iglob(fsdecode(p))), ures) return res def assertSequencesEqual_noorder(self, l1, l2): + l1 = list(l1) + l2 = list(l2) self.assertEqual(set(l1), set(l2)) + self.assertEqual(sorted(l1), sorted(l2)) def test_glob_literal(self): eq = self.assertSequencesEqual_noorder @@ -58,13 +67,23 @@ # test return types are unicode, but only if os.listdir # returns unicode filenames - uniset = set([unicode]) - tmp = os.listdir(u'.') - if set(type(x) for x in tmp) == uniset: + uniset = {unicode} + tmp = os.listdir(fsdecode(os.curdir)) + if {type(x) for x in tmp} == uniset: u1 = glob.glob(u'*') - u2 = glob.glob(u'./*') - self.assertEqual(set(type(r) for r in u1), uniset) - self.assertEqual(set(type(r) for r in u2), uniset) + u2 = glob.glob(os.path.join(fsdecode(os.curdir), u'*')) + self.assertEqual({type(r) for r in u1}, uniset) + self.assertEqual({type(r) for r in u2}, uniset) + + # test return types are string, but only if os.listdir + # returns string filenames + strset = {str} + tmp = os.listdir(os.curdir) + if {type(x) for x in tmp} == strset: + u1 = glob.glob('*') + u2 = glob.glob(os.path.join(os.curdir, '*')) + self.assertEqual({type(r) for r in u1}, strset) + self.assertEqual({type(r) for r in u2}, strset) def test_glob_one_directory(self): eq = self.assertSequencesEqual_noorder @@ -103,11 +122,24 @@ self.assertEqual(len(res), 1) # either of these results are reasonable self.assertIn(res[0], [self.tempdir, self.tempdir + os.sep]) + eq = self.assertSequencesEqual_noorder + eq(self.glob('a*', ''), [os.path.join(self.tempdir, x, '') + for x in ['a', 'aaa', 'aab']]) + + def test_glob_symlinks(self): + if hasattr(os, 'symlink'): + eq = self.assertSequencesEqual_noorder + eq(self.glob('sym3'), [self.norm('sym3')]) + eq(self.glob('sym3', '*'), [self.norm('sym3', 'EF'), + self.norm('sym3', 'efg')]) + eq(self.glob('*', '*F'), [self.norm('aaa', 'zzzF'), + self.norm('aab', 'F'), self.norm('sym3', 'EF')]) def test_glob_broken_symlinks(self): if hasattr(os, 'symlink'): eq = self.assertSequencesEqual_noorder - eq(self.glob('sym*'), [self.norm('sym1'), self.norm('sym2')]) + eq(self.glob('sym*'), [self.norm('sym1'), self.norm('sym2'), + self.norm('sym3')]) eq(self.glob('sym1'), [self.norm('sym1')]) eq(self.glob('sym2'), [self.norm('sym2')])