config.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. # config.py - deals with loading configuration information.
  2. # Loads config data from a .cfg file. Also caches the compiled
  3. # data back into a .cfc file.
  4. # If you are wondering how to avoid needing .cfg files (eg,
  5. # if you are freezing Pythonwin etc) I suggest you create a
  6. # .py file, and put the config info in a docstring. Then
  7. # pass a CStringIO file (rather than a filename) to the
  8. # config manager.
  9. import sys
  10. import string
  11. from . import keycodes
  12. import marshal
  13. import stat
  14. import os
  15. import types
  16. import traceback
  17. import pywin
  18. import glob
  19. import importlib.util
  20. import win32api
  21. debugging = 0
  22. if debugging:
  23. import win32traceutil # Some trace statements fire before the interactive window is open.
  24. def trace(*args):
  25. sys.stderr.write(" ".join(map(str, args)) + "\n")
  26. else:
  27. trace = lambda *args: None
  28. compiled_config_version = 3
  29. def split_line(line, lineno):
  30. comment_pos = line.find("#")
  31. if comment_pos>=0: line = line[:comment_pos]
  32. sep_pos = line.rfind("=")
  33. if sep_pos == -1:
  34. if line.strip():
  35. print("Warning: Line %d: %s is an invalid entry" % (lineno, repr(line)))
  36. return None, None
  37. return "", ""
  38. return line[:sep_pos].strip(), line[sep_pos+1:].strip()
  39. def get_section_header(line):
  40. # Returns the section if the line is a section header, else None
  41. if line[0] == "[":
  42. end = line.find("]")
  43. if end==-1: end=len(line)
  44. rc = line[1:end].lower()
  45. try:
  46. i = rc.index(":")
  47. return rc[:i], rc[i+1:]
  48. except ValueError:
  49. return rc, ""
  50. return None, None
  51. def find_config_file(f):
  52. return os.path.join(pywin.__path__[0], f + ".cfg")
  53. def find_config_files():
  54. return [os.path.split(x)[1]
  55. for x in [os.path.splitext(x)[0] for x in glob.glob(os.path.join(pywin.__path__[0], "*.cfg"))]
  56. ]
  57. class ConfigManager:
  58. def __init__(self, f):
  59. self.filename = "unknown"
  60. self.last_error = None
  61. self.key_to_events = {}
  62. if hasattr(f, "readline"):
  63. fp = f
  64. self.filename = "<config string>"
  65. compiled_name = None
  66. else:
  67. try:
  68. f = find_config_file(f)
  69. src_stat = os.stat(f)
  70. except os.error:
  71. self.report_error("Config file '%s' not found" % f)
  72. return
  73. self.filename = f
  74. self.basename = os.path.basename(f)
  75. trace("Loading configuration", self.basename)
  76. compiled_name = os.path.splitext(f)[0] + ".cfc"
  77. try:
  78. cf = open(compiled_name, "rb")
  79. try:
  80. ver = marshal.load(cf)
  81. ok = compiled_config_version == ver
  82. if ok:
  83. kblayoutname = marshal.load(cf)
  84. magic = marshal.load(cf)
  85. size = marshal.load(cf)
  86. mtime = marshal.load(cf)
  87. if magic == importlib.util.MAGIC_NUMBER and \
  88. win32api.GetKeyboardLayoutName() == kblayoutname and \
  89. src_stat[stat.ST_MTIME] == mtime and \
  90. src_stat[stat.ST_SIZE] == size:
  91. self.cache = marshal.load(cf)
  92. trace("Configuration loaded cached", compiled_name)
  93. return # We are ready to roll!
  94. finally:
  95. cf.close()
  96. except (os.error, IOError, EOFError):
  97. pass
  98. fp = open(f)
  99. self.cache = {}
  100. lineno = 1
  101. line = fp.readline()
  102. while line:
  103. # Skip to the next section (maybe already there!)
  104. section, subsection = get_section_header(line)
  105. while line and section is None:
  106. line = fp.readline()
  107. if not line: break
  108. lineno = lineno + 1
  109. section, subsection = get_section_header(line)
  110. if not line: break
  111. if section=="keys":
  112. line, lineno = self._load_keys(subsection, fp, lineno)
  113. elif section == "extensions":
  114. line, lineno = self._load_extensions(subsection, fp, lineno)
  115. elif section == "idle extensions":
  116. line, lineno = self._load_idle_extensions(subsection, fp, lineno)
  117. elif section == "general":
  118. line, lineno = self._load_general(subsection, fp, lineno)
  119. else:
  120. self.report_error("Unrecognised section header '%s:%s'" % (section,subsection))
  121. line = fp.readline()
  122. lineno = lineno + 1
  123. # Check critical data.
  124. if not self.cache.get("keys"):
  125. self.report_error("No keyboard definitions were loaded")
  126. if not self.last_error and compiled_name:
  127. try:
  128. cf = open(compiled_name, "wb")
  129. marshal.dump(compiled_config_version, cf)
  130. marshal.dump(win32api.GetKeyboardLayoutName(), cf)
  131. marshal.dump(importlib.util.MAGIC_NUMBER, cf)
  132. marshal.dump(src_stat[stat.ST_SIZE], cf)
  133. marshal.dump(src_stat[stat.ST_MTIME], cf)
  134. marshal.dump(self.cache, cf)
  135. cf.close()
  136. except (IOError, EOFError):
  137. pass # Ignore errors - may be read only.
  138. def configure(self, editor, subsections = None):
  139. # Execute the extension code, and find any events.
  140. # First, we "recursively" connect any we are based on.
  141. if subsections is None: subsections = []
  142. subsections = [''] + subsections
  143. general = self.get_data("general")
  144. if general:
  145. parents = general.get("based on", [])
  146. for parent in parents:
  147. trace("Configuration based on", parent, "- loading.")
  148. parent = self.__class__(parent)
  149. parent.configure(editor, subsections)
  150. if parent.last_error:
  151. self.report_error(parent.last_error)
  152. bindings = editor.bindings
  153. codeob = self.get_data("extension code")
  154. if codeob is not None:
  155. ns = {}
  156. try:
  157. exec(codeob, ns)
  158. except:
  159. traceback.print_exc()
  160. self.report_error("Executing extension code failed")
  161. ns = None
  162. if ns:
  163. num = 0
  164. for name, func in list(ns.items()):
  165. if type(func)==types.FunctionType and name[:1] != '_':
  166. bindings.bind(name, func)
  167. num = num + 1
  168. trace("Configuration Extension code loaded", num, "events")
  169. # Load the idle extensions
  170. for subsection in subsections:
  171. for ext in self.get_data("idle extensions", {}).get(subsection, []):
  172. try:
  173. editor.idle.IDLEExtension(ext)
  174. trace("Loaded IDLE extension", ext)
  175. except:
  176. self.report_error("Can not load the IDLE extension '%s'" % ext)
  177. # Now bind up the key-map (remembering a reverse map
  178. subsection_keymap = self.get_data("keys")
  179. num_bound = 0
  180. for subsection in subsections:
  181. keymap = subsection_keymap.get(subsection, {})
  182. bindings.update_keymap(keymap)
  183. num_bound = num_bound + len(keymap)
  184. trace("Configuration bound", num_bound, "keys")
  185. def get_key_binding(self, event, subsections = None):
  186. if subsections is None: subsections = []
  187. subsections = [''] + subsections
  188. subsection_keymap = self.get_data("keys")
  189. for subsection in subsections:
  190. map = self.key_to_events.get(subsection)
  191. if map is None: # Build it
  192. map = {}
  193. keymap = subsection_keymap.get(subsection, {})
  194. for key_info, map_event in list(keymap.items()):
  195. map[map_event] = key_info
  196. self.key_to_events[subsection] = map
  197. info = map.get(event)
  198. if info is not None:
  199. return keycodes.make_key_name( info[0], info[1] )
  200. return None
  201. def report_error(self, msg):
  202. self.last_error = msg
  203. print("Error in %s: %s" % (self.filename, msg))
  204. def report_warning(self, msg):
  205. print("Warning in %s: %s" % (self.filename, msg))
  206. def _readline(self, fp, lineno, bStripComments = 1):
  207. line = fp.readline()
  208. lineno = lineno + 1
  209. if line:
  210. bBreak = get_section_header(line)[0] is not None # A new section is starting
  211. if bStripComments and not bBreak:
  212. pos = line.find("#")
  213. if pos>=0: line=line[:pos]+"\n"
  214. else:
  215. bBreak=1
  216. return line, lineno, bBreak
  217. def get_data(self, name, default=None):
  218. return self.cache.get(name, default)
  219. def _save_data(self, name, data):
  220. self.cache[name] = data
  221. return data
  222. def _load_general(self, sub_section, fp, lineno):
  223. map = {}
  224. while 1:
  225. line, lineno, bBreak = self._readline(fp, lineno)
  226. if bBreak: break
  227. key, val = split_line(line, lineno)
  228. if not key: continue
  229. key = key.lower()
  230. l = map.get(key, [])
  231. l.append(val)
  232. map[key]=l
  233. self._save_data("general", map)
  234. return line, lineno
  235. def _load_keys(self, sub_section, fp, lineno):
  236. # Builds a nested dictionary of
  237. # (scancode, flags) = event_name
  238. main_map = self.get_data("keys", {})
  239. map = main_map.get(sub_section, {})
  240. while 1:
  241. line, lineno, bBreak = self._readline(fp, lineno)
  242. if bBreak: break
  243. key, event = split_line(line, lineno)
  244. if not event: continue
  245. sc, flag = keycodes.parse_key_name(key)
  246. if sc is None:
  247. self.report_warning("Line %d: Invalid key name '%s'" % (lineno, key))
  248. else:
  249. map[sc, flag] = event
  250. main_map[sub_section] = map
  251. self._save_data("keys", main_map)
  252. return line, lineno
  253. def _load_extensions(self, sub_section, fp, lineno):
  254. start_lineno = lineno
  255. lines = []
  256. while 1:
  257. line, lineno, bBreak = self._readline(fp, lineno, 0)
  258. if bBreak: break
  259. lines.append(line)
  260. try:
  261. c = compile(
  262. "\n" * start_lineno + # produces correct tracebacks
  263. "".join(lines), self.filename, "exec")
  264. self._save_data("extension code", c)
  265. except SyntaxError as details:
  266. errlineno = details.lineno + start_lineno
  267. # Should handle syntax errors better here, and offset the lineno.
  268. self.report_error("Compiling extension code failed:\r\nFile: %s\r\nLine %d\r\n%s" \
  269. % (details.filename, errlineno, details.msg))
  270. return line, lineno
  271. def _load_idle_extensions(self, sub_section, fp, lineno):
  272. extension_map = self.get_data("idle extensions")
  273. if extension_map is None: extension_map = {}
  274. extensions = []
  275. while 1:
  276. line, lineno, bBreak = self._readline(fp, lineno)
  277. if bBreak: break
  278. line = line.strip()
  279. if line:
  280. extensions.append(line)
  281. extension_map[sub_section] = extensions
  282. self._save_data("idle extensions", extension_map)
  283. return line, lineno
  284. def test():
  285. import time
  286. start = time.clock()
  287. f="default"
  288. cm = ConfigManager(f)
  289. map = cm.get_data("keys")
  290. took = time.clock()-start
  291. print("Loaded %s items in %.4f secs" % (len(map), took))
  292. if __name__=='__main__':
  293. test()