PageRenderTime 52ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/uliweb/utils/pyini.py

http://uliweb.googlecode.com/
Python | 418 lines | 375 code | 23 blank | 20 comment | 63 complexity | 926db068433aedefdc463718215ced04 MD5 | raw file
Possible License(s): Apache-2.0, BSD-3-Clause
  1. #! /usr/bin/env python
  2. #coding=utf-8
  3. # This module is used to parse and create new style ini file. This
  4. # new style ini file format should like a very simple python program
  5. # but you can define section like normal ini file, for example:
  6. #
  7. # [default]
  8. # key = value
  9. #
  10. # Then the result should be ini.default.key1 = value1
  11. # Whole ini file will be parsed into a dict-like object, but you can access
  12. # each section and key with '.', just like ini.section.key
  13. # For value can be simple python data type, such as: tuple, list, dict, int
  14. # float, string, etc. So it's very like a normal python file, but it's has
  15. # some sections definition.
  16. import sys, os
  17. import re
  18. import codecs
  19. import StringIO
  20. import locale
  21. import copy
  22. import tokenize
  23. import token
  24. from sorteddict import SortedDict
  25. __all__ = ['SortedDict', 'Section', 'Ini']
  26. try:
  27. set
  28. except:
  29. from sets import Set as set
  30. try:
  31. defaultencoding = locale.getdefaultlocale()[1]
  32. except:
  33. defaultencoding = None
  34. if not defaultencoding:
  35. defaultencoding = 'UTF-8'
  36. try:
  37. codecs.lookup(defaultencoding)
  38. except:
  39. defaultencoding = 'UTF-8'
  40. r_encoding = re.compile(r'\s*coding\s*[=:]\s*([-\w.]+)')
  41. __default_env__ = {}
  42. def set_env(env=None):
  43. global __default_env__
  44. __default_env__.update(env or {})
  45. def _uni_prt(a, encoding, beautiful=False, indent=0):
  46. escapechars = [("\\", "\\\\"), ("'", r"\'"), ('\"', r'\"'), ('\b', r'\b'),
  47. ('\t', r"\t"), ('\r', r"\r"), ('\n', r"\n")]
  48. s = []
  49. indent_char = ' '*4
  50. if isinstance(a, (list, tuple)):
  51. if isinstance(a, list):
  52. s.append(indent_char*indent + '[')
  53. else:
  54. s.append(indent_char*indent + '(')
  55. if beautiful:
  56. s.append('\n')
  57. for i, k in enumerate(a):
  58. if beautiful:
  59. ind = indent + 1
  60. else:
  61. ind = indent
  62. s.append(indent_char*ind + _uni_prt(k, encoding, beautiful, indent+1))
  63. if i<len(a)-1:
  64. if beautiful:
  65. s.append(',\n')
  66. else:
  67. s.append(', ')
  68. if beautiful:
  69. s.append('\n')
  70. if isinstance(a, list):
  71. s.append(indent_char*indent + ']')
  72. else:
  73. if len(a) > 0:
  74. s.append(',')
  75. s.append(indent_char*indent + ')')
  76. elif isinstance(a, dict):
  77. s.append(indent_char*indent + '{')
  78. if beautiful:
  79. s.append('\n')
  80. for i, k in enumerate(a.items()):
  81. key, value = k
  82. if beautiful:
  83. ind = indent + 1
  84. else:
  85. ind = indent
  86. s.append('%s: %s' % (indent_char*ind + _uni_prt(key, encoding, beautiful, indent+1), _uni_prt(value, encoding, beautiful, indent+1)))
  87. if i<len(a.items())-1:
  88. if beautiful:
  89. s.append(',\n')
  90. else:
  91. s.append(', ')
  92. if beautiful:
  93. s.append('\n')
  94. s.append(indent_char*indent + '}')
  95. elif isinstance(a, str):
  96. t = a
  97. for i in escapechars:
  98. t = t.replace(i[0], i[1])
  99. s.append("'%s'" % t)
  100. elif isinstance(a, unicode):
  101. t = a
  102. for i in escapechars:
  103. t = t.replace(i[0], i[1])
  104. try:
  105. s.append("u'%s'" % t.encode(encoding))
  106. except:
  107. import traceback
  108. traceback.print_exc()
  109. else:
  110. s.append(str(a))
  111. return ''.join(s)
  112. class Section(SortedDict):
  113. def __init__(self, name, comments=None, encoding=None):
  114. super(Section, self).__init__()
  115. self._name = name
  116. self.add_comment(comments=comments)
  117. self._field_comments = {}
  118. self._field_flag = {}
  119. self._encoding = encoding
  120. def add(self, key, value, comments=None, replace=False):
  121. self.__setitem__(key, value, replace)
  122. self._field_flag[key] = replace
  123. self.add_comment(key, comments)
  124. def __setitem__(self, key, value, replace=False):
  125. if not replace:
  126. v = self.get(key)
  127. #for mutable object, will merge them but not replace
  128. if isinstance(v, (list, dict)):
  129. if isinstance(v, list):
  130. value = list(set(v + value))
  131. else:
  132. v.update(value)
  133. value = v
  134. super(Section, self).__setitem__(key, value)
  135. def add_comment(self, key=None, comments=None):
  136. comments = comments or []
  137. if not isinstance(comments, (tuple, list)):
  138. comments = [comments]
  139. if not key:
  140. self._comments = comments
  141. else:
  142. self._field_comments[key] = copy.copy(comments)
  143. def comment(self, key=None):
  144. if not key:
  145. return self._comments
  146. else:
  147. return self._field_comments.get(key, [])
  148. def del_comment(self, key=None):
  149. self.add_comment(key, None)
  150. def dumps(self, out):
  151. if self._comments:
  152. print >> out, '\n'.join(self._comments)
  153. print >> out, '[%s]' % self._name
  154. for f in self.keys():
  155. comments = self.comment(f)
  156. if comments:
  157. print >> out, '\n'.join(comments)
  158. if self._field_flag.get(f, False):
  159. op = ' <= '
  160. else:
  161. op = ' = '
  162. buf = f + op + _uni_prt(self[f], self._encoding)
  163. if len(buf) > 79:
  164. buf = f + op + _uni_prt(self[f], self._encoding, True)
  165. print >> out, buf
  166. def __delitem__(self, key):
  167. super(Section, self).__delitem__(key)
  168. self.del_comment(key)
  169. def __delattr__(self, key):
  170. try:
  171. del self[key]
  172. except KeyError, k:
  173. raise AttributeError, k
  174. def __str__(self):
  175. buf = StringIO.StringIO()
  176. self.dumps(buf)
  177. return buf.getvalue()
  178. class Ini(SortedDict):
  179. def __init__(self, inifile=None, commentchar='#', encoding='utf-8', env=None):
  180. super(Ini, self).__init__()
  181. self._inifile = inifile
  182. # self.value = value
  183. self._commentchar = commentchar
  184. self._encoding = 'utf-8'
  185. self._env = __default_env__.copy()
  186. self._env.update(env or {})
  187. if self._inifile:
  188. self.read(self._inifile)
  189. def set_filename(self, filename):
  190. self._inifile = filename
  191. def get_filename(self):
  192. return self._inifile
  193. filename = property(get_filename, set_filename)
  194. def read(self, fobj):
  195. encoding = None
  196. if isinstance(fobj, (str, unicode)):
  197. f = open(fobj, 'rb')
  198. text = f.read()
  199. f.close()
  200. else:
  201. text = fobj.read()
  202. text = text + '\n'
  203. begin = 0
  204. if text.startswith(codecs.BOM_UTF8):
  205. begin = 3
  206. encoding = 'UTF-8'
  207. elif text.startswith(codecs.BOM_UTF16):
  208. begin = 2
  209. encoding = 'UTF-16'
  210. if not encoding:
  211. try:
  212. unicode(text, 'UTF-8')
  213. encoding = 'UTF-8'
  214. except:
  215. encoding = defaultencoding
  216. self._encoding = encoding
  217. f = StringIO.StringIO(text)
  218. f.seek(begin)
  219. lineno = 0
  220. comments = []
  221. status = 'c'
  222. section = None
  223. while 1:
  224. lastpos = f.tell()
  225. line = f.readline()
  226. lineno += 1
  227. if not line:
  228. break
  229. line = line.strip()
  230. if line:
  231. if line.startswith(self._commentchar):
  232. if lineno == 1: #first comment line
  233. b = r_encoding.search(line[1:])
  234. if b:
  235. self._encoding = b.groups()[0]
  236. continue
  237. comments.append(line)
  238. elif line.startswith('[') and line.endswith(']'):
  239. sec_name = line[1:-1].strip()
  240. #process include notation
  241. if sec_name.startswith('include:'):
  242. _filename = sec_name[8:].strip()
  243. _filename = os.path.abspath(_filename)
  244. if os.path.exists(_filename):
  245. old_encoding = self._encoding
  246. self.read(_filename)
  247. self._encoding = old_encoding
  248. else:
  249. import warnings
  250. warnings.warn(Warning("Can't find the file [%s], so just skip it" % _filename), stacklevel=2)
  251. continue
  252. section = self.add(sec_name, comments)
  253. comments = []
  254. elif '=' in line:
  255. if section is None:
  256. raise Exception, "No section found, please define it first in %s file" % self.filename
  257. #if find <=, then it'll replace the old value for mutable variables
  258. #because the default behavior will merge list and dict
  259. pos = line.find('<=')
  260. if pos != -1:
  261. begin, end = pos, pos+2
  262. replace_flag = True
  263. else:
  264. pos = line.find('=')
  265. begin, end = pos, pos+1
  266. replace_flag = False
  267. keyname = line[:begin].strip()
  268. rest = line[end:].strip()
  269. #if key= then value will be set ''
  270. if rest == '':
  271. v = None
  272. else:
  273. f.seek(lastpos+end)
  274. try:
  275. value = self.__read_line(f)
  276. except Exception, e:
  277. import traceback
  278. traceback.print_exc()
  279. raise Exception, "Parsing ini file error in line(%d): %s" % (lineno, line)
  280. try:
  281. if ((value.startswith("u'") and value.endswith("'")) or
  282. (value.startswith('u"') and value.endswith('"'))):
  283. v = unicode(value[2:-1], self._encoding)
  284. else:
  285. v = eval(value, self._env, section)
  286. except Exception, e:
  287. raise Exception, "Converting value (%s) error in line %d:%s" % (value, lineno, line)
  288. section.add(keyname, v, comments, replace=replace_flag)
  289. comments = []
  290. else:
  291. comments.append(line)
  292. def save(self, filename=None):
  293. if not filename:
  294. filename = self.filename
  295. if not filename:
  296. filename = sys.stdout
  297. if isinstance(filename, (str, unicode)):
  298. f = open(filename, 'wb')
  299. need_close = True
  300. else:
  301. f = filename
  302. need_close = False
  303. print >> f, '#coding=%s' % self._encoding
  304. for s in self.keys():
  305. section = self[s]
  306. section.dumps(f)
  307. def __read_line(self, f):
  308. g = tokenize.generate_tokens(f.readline)
  309. buf = []
  310. time = 0
  311. while 1:
  312. v = g.next()
  313. tokentype, t, start, end, line = v
  314. if tokentype == 54:
  315. continue
  316. if tokentype in (token.INDENT, token.DEDENT, tokenize.COMMENT):
  317. continue
  318. if tokentype == token.NEWLINE:
  319. return ''.join(buf)
  320. else:
  321. if t == '=' and time == 0:
  322. time += 1
  323. continue
  324. buf.append(t)
  325. def __setitem__(self, key, value):
  326. if key not in self:
  327. super(Ini, self).__setitem__(key, value)
  328. def update(self, value):
  329. for k, v in value.items():
  330. self.set_var(k, v)
  331. def add(self, sec_name, comments=None):
  332. if sec_name in self:
  333. section = self[sec_name]
  334. else:
  335. section = Section(sec_name, comments, self._encoding)
  336. self[sec_name] = section
  337. return section
  338. def __str__(self):
  339. buf = StringIO.StringIO()
  340. self.save(buf)
  341. return buf.getvalue()
  342. def get_var(self, key, default=None):
  343. obj = self
  344. for i in key.split('/', 1):
  345. obj = obj.get(i)
  346. if obj is None:
  347. break
  348. if obj is None:
  349. return default
  350. return obj
  351. def set_var(self, key, value):
  352. s = key.split('/', 1)
  353. obj = self
  354. for i in s[:-1]:
  355. obj = obj.add(i)
  356. obj[s[-1]] = value
  357. return True
  358. def del_var(self, key):
  359. s = key.split('/', 1)
  360. obj = self
  361. for i in s[:-1]:
  362. obj = obj.get(i)
  363. if obj is None:
  364. return False
  365. if s[-1] in obj:
  366. del obj[s[-1]]
  367. flag = True
  368. else:
  369. flag = False
  370. return flag