1"""DictForArgs.py 2 3See the doc string for the DictForArgs() function. 4 5Also, there is a test suite in Tests/TestDictForArgs.py 6""" 7 8import re 9 10 11class DictForArgsError(Exception): 12 """Error when building dictionary from arguments.""" 13 14 15def _SyntaxError(s): 16 raise DictForArgsError('Syntax error: %r' % s) 17 18 19_nameRE = re.compile(r'\w+') 20_equalsRE = re.compile(r'\=') 21_stringRE = re.compile(r'''"[^"]+"|'[^']+'|\S+''') 22_whiteRE = re.compile(r'\s+') 23 24_REs = [_nameRE, _equalsRE, _stringRE, _whiteRE] 25 26 27def DictForArgs(s): 28 """Build dictionary from arguments. 29 30 Takes an input such as: 31 x=3 32 name="foo" 33 first='john' last='doe' 34 required border=3 35 36 And returns a dictionary representing the same. For keys that aren't 37 given an explicit value (such as 'required' above), the value is '1'. 38 39 All values are interpreted as strings. If you want ints and floats, 40 you'll have to convert them yourself. 41 42 This syntax is equivalent to what you find in HTML and close to other 43 ML languages such as XML. 44 45 Returns {} for an empty string. 46 47 The informal grammar is: 48 (NAME [=NAME|STRING])* 49 50 Will raise DictForArgsError if the string is invalid. 51 52 See also: PyDictForArgs() and ExpandDictWithExtras() in this module. 53 """ 54 55 s = s.strip() 56 57 # Tokenize 58 59 verbose = False 60 matches = [] 61 start = 0 62 sLen = len(s) 63 64 if verbose: 65 print '>> DictForArgs(%s)' % repr(s) 66 print '>> sLen:', sLen 67 while start < sLen: 68 for regEx in _REs: 69 if verbose: 70 print '>> try:', regEx 71 match = regEx.match(s, start) 72 if verbose: 73 print '>> match:', match 74 if match is not None: 75 if match.re is not _whiteRE: 76 matches.append(match) 77 start = match.end() 78 if verbose: 79 print '>> new start:', start 80 break 81 else: 82 _SyntaxError(s) 83 84 if verbose: 85 names = [] 86 for match in matches: 87 if match.re is _nameRE: 88 name = 'name' 89 elif match.re is _equalsRE: 90 name = 'equals' 91 elif match.re is _stringRE: 92 name = 'string' 93 elif match.re is _whiteRE: 94 name = 'white' 95 names.append(name) 96 #print '>> match =', name, match 97 print '>> names =', names 98 99 100 # Process tokens 101 102 # At this point we have a list of all the tokens (as re.Match objects) 103 # We need to process these into a dictionary. 104 105 d = {} 106 matchesLen = len(matches) 107 i = 0 108 while i < matchesLen: 109 match = matches[i] 110 if i + 1 < matchesLen: 111 peekMatch = matches[i+1] 112 else: 113 peekMatch = None 114 if match.re is _nameRE: 115 if peekMatch is not None: 116 if peekMatch.re is _nameRE: 117 # We have a name without an explicit value 118 d[match.group()] = '1' 119 i += 1 120 continue 121 if peekMatch.re is _equalsRE: 122 if i + 2 < matchesLen: 123 target = matches[i+2] 124 if target.re is _nameRE or target.re is _stringRE: 125 value = target.group() 126 if value[0] == "'" or value[0] == '"': 127 value = value[1:-1] 128 # value = "'''%s'''" % value[1:-1] 129 # value = eval(value) 130 d[match.group()] = value 131 i += 3 132 continue 133 else: 134 d[match.group()] = '1' 135 i += 1 136 continue 137 _SyntaxError(s) 138 139 if verbose: 140 print 141 142 return d 143 144 145def PyDictForArgs(s): 146 """Build dictionary from arguments. 147 148 Takes an input such as: 149 x=3 150 name="foo" 151 first='john'; last='doe' 152 list=[1, 2, 3]; name='foo' 153 154 And returns a dictionary representing the same. 155 156 All values are interpreted as Python expressions. Any error in these 157 expressions will raise the appropriate Python exception. This syntax 158 allows much more power than DictForArgs() since you can include 159 lists, dictionaries, actual ints and floats, etc. 160 161 This could also open the door to hacking your software if the input 162 comes from a tainted source such as an HTML form or an unprotected 163 configuration file. 164 165 Returns {} for an empty string. 166 167 See also: DictForArgs() and ExpandDictWithExtras() in this module. 168 """ 169 if s: 170 s = s.strip() 171 if not s: 172 return {} 173 174 # special case: just a name 175 # meaning: name=1 176 # example: isAbstract 177 if ' ' not in s and '=' not in s and s[0].isalpha(): 178 s += '=1' 179 180 results = {} 181 exec s in results 182 183 del results['__builtins__'] 184 return results 185 186 187def ExpandDictWithExtras(d, key='Extras', delKey=True, dictForArgs=DictForArgs): 188 """Return a dictionary with the 'Extras' column expanded by DictForArgs(). 189 190 For example, given: 191 {'Name': 'foo', 'Extras': 'x=1 y=2'} 192 The return value is: 193 {'Name': 'foo', 'x': '1', 'y': '2'} 194 The key argument controls what key in the dictionary is used to hold 195 the extra arguments. The delKey argument controls whether that key and 196 its corresponding value are retained. 197 The same dictionary may be returned if there is no extras key. 198 The most typical use of this function is to pass a row from a DataTable 199 that was initialized from a CSV file (e.g., a spreadsheet or tabular file). 200 FormKit and MiddleKit both use CSV files and allow for an Extras column 201 to specify attributes that occur infrequently. 202 """ 203 if key in d: 204 newDict = dict(d) 205 if delKey: 206 del newDict[key] 207 newDict.update(dictForArgs(d[key])) 208 return newDict 209 else: 210 return d 211