123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322 |
- # config.py - deals with loading configuration information.
- # Loads config data from a .cfg file. Also caches the compiled
- # data back into a .cfc file.
- # If you are wondering how to avoid needing .cfg files (eg,
- # if you are freezing Pythonwin etc) I suggest you create a
- # .py file, and put the config info in a docstring. Then
- # pass a CStringIO file (rather than a filename) to the
- # config manager.
- import sys
- import string
- from . import keycodes
- import marshal
- import stat
- import os
- import types
- import traceback
- import pywin
- import glob
- import importlib.util
- import win32api
- debugging = 0
- if debugging:
- import win32traceutil # Some trace statements fire before the interactive window is open.
- def trace(*args):
- sys.stderr.write(" ".join(map(str, args)) + "\n")
- else:
- trace = lambda *args: None
- compiled_config_version = 3
- def split_line(line, lineno):
- comment_pos = line.find("#")
- if comment_pos>=0: line = line[:comment_pos]
- sep_pos = line.rfind("=")
- if sep_pos == -1:
- if line.strip():
- print("Warning: Line %d: %s is an invalid entry" % (lineno, repr(line)))
- return None, None
- return "", ""
- return line[:sep_pos].strip(), line[sep_pos+1:].strip()
- def get_section_header(line):
- # Returns the section if the line is a section header, else None
- if line[0] == "[":
- end = line.find("]")
- if end==-1: end=len(line)
- rc = line[1:end].lower()
- try:
- i = rc.index(":")
- return rc[:i], rc[i+1:]
- except ValueError:
- return rc, ""
- return None, None
- def find_config_file(f):
- return os.path.join(pywin.__path__[0], f + ".cfg")
- def find_config_files():
- return [os.path.split(x)[1]
- for x in [os.path.splitext(x)[0] for x in glob.glob(os.path.join(pywin.__path__[0], "*.cfg"))]
- ]
- class ConfigManager:
- def __init__(self, f):
- self.filename = "unknown"
- self.last_error = None
- self.key_to_events = {}
- if hasattr(f, "readline"):
- fp = f
- self.filename = "<config string>"
- compiled_name = None
- else:
- try:
- f = find_config_file(f)
- src_stat = os.stat(f)
- except os.error:
- self.report_error("Config file '%s' not found" % f)
- return
- self.filename = f
- self.basename = os.path.basename(f)
- trace("Loading configuration", self.basename)
- compiled_name = os.path.splitext(f)[0] + ".cfc"
- try:
- cf = open(compiled_name, "rb")
- try:
- ver = marshal.load(cf)
- ok = compiled_config_version == ver
- if ok:
- kblayoutname = marshal.load(cf)
- magic = marshal.load(cf)
- size = marshal.load(cf)
- mtime = marshal.load(cf)
- if magic == importlib.util.MAGIC_NUMBER and \
- win32api.GetKeyboardLayoutName() == kblayoutname and \
- src_stat[stat.ST_MTIME] == mtime and \
- src_stat[stat.ST_SIZE] == size:
- self.cache = marshal.load(cf)
- trace("Configuration loaded cached", compiled_name)
- return # We are ready to roll!
- finally:
- cf.close()
- except (os.error, IOError, EOFError):
- pass
- fp = open(f)
- self.cache = {}
- lineno = 1
- line = fp.readline()
- while line:
- # Skip to the next section (maybe already there!)
- section, subsection = get_section_header(line)
- while line and section is None:
- line = fp.readline()
- if not line: break
- lineno = lineno + 1
- section, subsection = get_section_header(line)
- if not line: break
- if section=="keys":
- line, lineno = self._load_keys(subsection, fp, lineno)
- elif section == "extensions":
- line, lineno = self._load_extensions(subsection, fp, lineno)
- elif section == "idle extensions":
- line, lineno = self._load_idle_extensions(subsection, fp, lineno)
- elif section == "general":
- line, lineno = self._load_general(subsection, fp, lineno)
- else:
- self.report_error("Unrecognised section header '%s:%s'" % (section,subsection))
- line = fp.readline()
- lineno = lineno + 1
- # Check critical data.
- if not self.cache.get("keys"):
- self.report_error("No keyboard definitions were loaded")
- if not self.last_error and compiled_name:
- try:
- cf = open(compiled_name, "wb")
- marshal.dump(compiled_config_version, cf)
- marshal.dump(win32api.GetKeyboardLayoutName(), cf)
- marshal.dump(importlib.util.MAGIC_NUMBER, cf)
- marshal.dump(src_stat[stat.ST_SIZE], cf)
- marshal.dump(src_stat[stat.ST_MTIME], cf)
- marshal.dump(self.cache, cf)
- cf.close()
- except (IOError, EOFError):
- pass # Ignore errors - may be read only.
- def configure(self, editor, subsections = None):
- # Execute the extension code, and find any events.
- # First, we "recursively" connect any we are based on.
- if subsections is None: subsections = []
- subsections = [''] + subsections
- general = self.get_data("general")
- if general:
- parents = general.get("based on", [])
- for parent in parents:
- trace("Configuration based on", parent, "- loading.")
- parent = self.__class__(parent)
- parent.configure(editor, subsections)
- if parent.last_error:
- self.report_error(parent.last_error)
- bindings = editor.bindings
- codeob = self.get_data("extension code")
- if codeob is not None:
- ns = {}
- try:
- exec(codeob, ns)
- except:
- traceback.print_exc()
- self.report_error("Executing extension code failed")
- ns = None
- if ns:
- num = 0
- for name, func in list(ns.items()):
- if type(func)==types.FunctionType and name[:1] != '_':
- bindings.bind(name, func)
- num = num + 1
- trace("Configuration Extension code loaded", num, "events")
- # Load the idle extensions
- for subsection in subsections:
- for ext in self.get_data("idle extensions", {}).get(subsection, []):
- try:
- editor.idle.IDLEExtension(ext)
- trace("Loaded IDLE extension", ext)
- except:
- self.report_error("Can not load the IDLE extension '%s'" % ext)
- # Now bind up the key-map (remembering a reverse map
- subsection_keymap = self.get_data("keys")
- num_bound = 0
- for subsection in subsections:
- keymap = subsection_keymap.get(subsection, {})
- bindings.update_keymap(keymap)
- num_bound = num_bound + len(keymap)
- trace("Configuration bound", num_bound, "keys")
- def get_key_binding(self, event, subsections = None):
- if subsections is None: subsections = []
- subsections = [''] + subsections
- subsection_keymap = self.get_data("keys")
- for subsection in subsections:
- map = self.key_to_events.get(subsection)
- if map is None: # Build it
- map = {}
- keymap = subsection_keymap.get(subsection, {})
- for key_info, map_event in list(keymap.items()):
- map[map_event] = key_info
- self.key_to_events[subsection] = map
- info = map.get(event)
- if info is not None:
- return keycodes.make_key_name( info[0], info[1] )
- return None
- def report_error(self, msg):
- self.last_error = msg
- print("Error in %s: %s" % (self.filename, msg))
- def report_warning(self, msg):
- print("Warning in %s: %s" % (self.filename, msg))
- def _readline(self, fp, lineno, bStripComments = 1):
- line = fp.readline()
- lineno = lineno + 1
- if line:
- bBreak = get_section_header(line)[0] is not None # A new section is starting
- if bStripComments and not bBreak:
- pos = line.find("#")
- if pos>=0: line=line[:pos]+"\n"
- else:
- bBreak=1
- return line, lineno, bBreak
- def get_data(self, name, default=None):
- return self.cache.get(name, default)
- def _save_data(self, name, data):
- self.cache[name] = data
- return data
- def _load_general(self, sub_section, fp, lineno):
- map = {}
- while 1:
- line, lineno, bBreak = self._readline(fp, lineno)
- if bBreak: break
- key, val = split_line(line, lineno)
- if not key: continue
- key = key.lower()
- l = map.get(key, [])
- l.append(val)
- map[key]=l
- self._save_data("general", map)
- return line, lineno
- def _load_keys(self, sub_section, fp, lineno):
- # Builds a nested dictionary of
- # (scancode, flags) = event_name
- main_map = self.get_data("keys", {})
- map = main_map.get(sub_section, {})
- while 1:
- line, lineno, bBreak = self._readline(fp, lineno)
- if bBreak: break
- key, event = split_line(line, lineno)
- if not event: continue
- sc, flag = keycodes.parse_key_name(key)
- if sc is None:
- self.report_warning("Line %d: Invalid key name '%s'" % (lineno, key))
- else:
- map[sc, flag] = event
- main_map[sub_section] = map
- self._save_data("keys", main_map)
- return line, lineno
- def _load_extensions(self, sub_section, fp, lineno):
- start_lineno = lineno
- lines = []
- while 1:
- line, lineno, bBreak = self._readline(fp, lineno, 0)
- if bBreak: break
- lines.append(line)
- try:
- c = compile(
- "\n" * start_lineno + # produces correct tracebacks
- "".join(lines), self.filename, "exec")
- self._save_data("extension code", c)
- except SyntaxError as details:
- errlineno = details.lineno + start_lineno
- # Should handle syntax errors better here, and offset the lineno.
- self.report_error("Compiling extension code failed:\r\nFile: %s\r\nLine %d\r\n%s" \
- % (details.filename, errlineno, details.msg))
- return line, lineno
- def _load_idle_extensions(self, sub_section, fp, lineno):
- extension_map = self.get_data("idle extensions")
- if extension_map is None: extension_map = {}
- extensions = []
- while 1:
- line, lineno, bBreak = self._readline(fp, lineno)
- if bBreak: break
- line = line.strip()
- if line:
- extensions.append(line)
- extension_map[sub_section] = extensions
- self._save_data("idle extensions", extension_map)
- return line, lineno
- def test():
- import time
- start = time.clock()
- f="default"
- cm = ConfigManager(f)
- map = cm.get_data("keys")
- took = time.clock()-start
- print("Loaded %s items in %.4f secs" % (len(map), took))
- if __name__=='__main__':
- test()
|