def _construct_aspell(lang, envs, encoding, variety, extopts, suponly): # Get Pology's internal personal dictonary for this language. dictpath, temporary = _compose_personal_dict(lang, envs) if not suponly: # Prepare Aspell options. aopts = {} aopts["lang"] = lang aopts["encoding"] = encoding if variety: aopts["variety"] = variety if dictpath: aopts["personal-path"] = dictpath if extopts: aopts.update(extopts) aopts = dict([(x, y.encode(encoding)) for x, y in aopts.items()]) # Create Aspell object. import pology.external.pyaspell as A try: checker = A.Aspell(aopts.items()) except A.AspellConfigError, e: raise PologyError( _("@info", "Aspell configuration error:\n%(msg)s", msg=e)) except A.AspellError, e: raise PologyError( _("@info", "Cannot initialize Aspell:\n%(msg)s", msg=e))
def __init__ (self, msg): """ Constructor. All the parameters are made available as instance variables. @param msg: a description of what went wrong @type msg: string """ self.msg = msg PologyError.__init__(self, msg)
def _construct_enchant(provider, lang, envs, encoding, variety, suponly): # Get Pology's internal personal dictonary for this language. dictpath, temporary = _compose_personal_dict(lang, envs) if not suponly: try: import enchant except ImportError: pkgs = ["python-enchant"] raise PologyError( _("@info", "Python wrapper for Enchant not found, " "please install it (possible package names: " "%(pkglist)s).", pkglist=format_item_list(pkgs))) # Create Enchant broker. try: broker = enchant.Broker() except Exception, e: raise PologyError( _("@info", "Cannot initialize Enchant:\n%(msg)s", msg=e)) # Find Enchant language. e_langs = filter(broker.dict_exists, [variety, lang]) if e_langs: e_lang = e_langs[0] else: if variety is not None: raise PologyError( _("@info", "Language '%(lang)s' and variety '%(var)s' " "not known to Enchant.", lang=lang, var=variety)) else: raise PologyError( _("@info", "Language '%(lang)s' not known to Enchant.", lang=lang)) # Choose the provider for the selected language. try: broker.set_ordering((e_lang or "*"), provider) except Exception, e: raise PologyError( _("@info", "Cannot configure Enchant for provider '%(pvd)s':\n%(msg)s", pvd=provider, msg=e))
def parse_entities (defstr, src=None): """ Parse XML entity definitions from given string. The string should contain only entity definitions in DTD form, without any prolog or epilogue:: ... <!ENTITY foo 'Foo-fum'> <!ENTITY bar 'Bar-boo'> ... If the same entity is defined several times, the last read definition is taken as final. @param defstr: entity-defining string @type defstr: string @param src: name of the source, for problem reporting @param src: C{None} or string @returns: name-value pairs of parsed entities @rtype: dict """ # Equip with prolog and epilogue. defstr = "<?xml version='1.0' encoding='UTF-8'?>\n" \ "<!DOCTYPE entityLoader [" + defstr + "]><done/>" # Parse entities. entities = {} def handler (name, is_parameter_entity, value, base, systemId, publicId, notationName): entities[name] = value p = xml.parsers.expat.ParserCreate() p.EntityDeclHandler = handler try: p.Parse(defstr, True) except xml.parsers.expat.ExpatError, inst: if src: raise PologyError( _("@info error report for a named source", "%(src)s: %(msg)s", src=src, msg=inst)) else: raise PologyError( _("@info error report for a string", "<string>: %(msg)s", msg=inst))
def log (self, path, rev1=None, rev2=None): """ Get revision log of the path. Revision log entry consists of revision ID, commiter name, date string, and commit message. Except the revision ID, any of these may be empty strings, depending on the particular VCS. The log is ordered from earliest to newest revision. A section of entries between revisions C{rev1} (inclusive) and C{rev2} (exclusive) can be returned instead of the whole log. If C{rev1} is C{None}, selected IDs start from the first in the log. If C{rev2} is C{None}, selected IDs end with the last in the log. If either C{rev1} or C{rev2} is not C{None} and does not exist in the path's log, or the path is not versioned, empty log is returned. @param path: path to query for revisions @type path: string @param rev1: entries starting from this revision (inclusive) @type rev1: string @param rev2: entries up to this revision (exclusive) @type rev2: string @return: revision ID, committer name, date string, commit message @rtype: [(string*4)*] """ raise PologyError( _("@info", "Selected version control system does not define " "revision history query."))
def diff (self, path, rev1=None, rev2=None): """ Get diff between revisions of the given path. Unified diff is computed and reported as list of 2-tuples, where the first element is a tag, and the second the payload. For tags C{" "}, C{"+"}, and C{"-"}, the payload is the line (without newline) which was equal, added or removed, respectively. Payload for tag C{":"} is the path of the diffed file, and for C{"@"} the 4-tuple of old start line, old number of lines, new start line, and new number of lines, which are represented by the following difference segment. Diffs can be requested between specific revisions. If both C{rev1} and C{rev2} are C{None}, diff is taken from last known commit to working copy. If only C{rev2} is C{None} diff is taken from C{rev1} to working copy. @param path: path to query for modified lines @type path: string @param rev1: diff from this revision @type rev1: string @param rev2: diff to this revision @type rev2: string @return: tagged unified diff @rtype: [(string, string or (int, int, int, int))*] """ raise PologyError( _("@info", "Selected version control system does not define diffing."))
def _match_text (text, tests, unmatched_tests=None): match = False for test in tests: if isinstance(test, basestring): if test == text: match = True break elif isinstance(test, _Wre): if test.regex.search(text): match = True break elif callable(test): if test(text): match = True break else: raise PologyError( _("@info", "Unknown matcher type '%(type)s'.", type=type(test))) if unmatched_tests is not None: if match and test in unmatched_tests: unmatched_tests.remove(test) return match
def add (self, paths, repadd=False): """ Add paths to version control. It depends on the particular VCS what adding means, but in general it should be the point where the subsequent L{commit()} on the same path will record addition in the repository history. Also a single path can be given instead of sequence of paths. Actually added paths may be different from input paths, e.g. if an input path is already version controlled, or input path's parent directory was added as well. List of added paths can be requested with C{repadd} parameter, and it will become the second element of return value. @param paths: paths to add @type paths: <string*> or string @param repadd: whether to report which paths were actually added @type repadd: bool @return: C{True} if addition successful, possibly list of added paths @rtype: bool or (bool, [string*]) """ raise PologyError( _("@info", "Selected version control system does not define adding."))
def _read_dict_file(filepath): # Parse the header for encoding. enc_def = "UTF-8" file = codecs.open(filepath, "r", enc_def) header = file.readline() m = re.search(r"^(\S+)\s+(\S+)\s+(\d+)\s+(\S+)\s*", header) if not m: raise PologyError( _("@info", "Malformed header in dictionary file '%(file)s'.", file=filepath)) enc = m.group(4) # Reopen in correct encoding if not the default. if enc.lower() != enc_def.lower(): file.close() file = codecs.open(filepath, "r", enc) # Read words. words = [] for line in file: word = line.strip() if word: words.append(word) return words
def export (self, path, rev, dstpath, rewrite=None): """ Export a versioned file or directory. Makes a copy of versioned file or directory pointed to by local path C{path}, in the revision C{rev}, to destination C{dstpath}. If C{rev} is C{None}, the clean version of C{path} according to current local repository state is copied to C{dstpath}. Final repository path, as determined from C{path}, can be filtered through an external function C{rewrite} before being used. The function takes as arguments the path and revision strings. This can be useful, for example, to reroute remote repository URL. @param path: path of the versioned file or directory in local repository @type path: string @param rev: revision to export @type rev: string or C{None} @param dstpath: file path to export to @type dstpath: string @param rewrite: function to filter resolved repository path @type rewrite: (string, string)->string or None @return: C{True} if fetching succeeded, C{False} otherwise @rtype: bool """ raise PologyError( _("@info", "Selected version control system does not define " "fetching of a versioned path."))
def _assert_spec_single (att, obj, spec): if "type" in spec: if not isinstance(obj, spec["type"]): if att != "*": raise PologyError( _("@info", "Expected %(type1)s for attribute '%(attr)s', " "got %(type2)s.", type1=spec["type"], attr=att, type2=type(obj))) else: raise PologyError( _("@info", "Expected %(type1)s for sequence element, " "got %(type2)s.", type1=spec["type"], type2=type(obj))) if "spec" in spec: _assert_spec_init(obj, spec["spec"])
def assert_spec_getitem (self): if not hasattr(self, "_spec"): return if "*" not in self._spec: raise PologyError( _("@info", "Object '%(obj)s' is not specified to be a sequence.", obj=self))
def assert_spec_getattr (self, att): if not hasattr(self, "_spec"): return if att not in self._spec: raise PologyError( _("@info", "Attribute '%(attr)s' is not among specified.", attr=att))
def assert_spec_setitem (self, itemobj): if not hasattr(self, "_spec"): return if "*" in self._spec: _assert_spec_single("*", itemobj, self._spec["*"]) else: raise PologyError( _("@info", "Object '%(obj)s' is not specified to be a sequence.", obj=self))
def tofunc(sel): if hasattr(sel, "search"): return lambda x: bool(sel.search(x)) elif isinstance(sel, basestring): sel_rx = re.compile(sel, re.U) return lambda x: bool(sel_rx.search(x)) elif callable(sel): return sel else: raise PologyError( _("@info", "Cannot convert object '%(obj)s' into a string matcher.", obj=sel))
def unescape_c(s): """ Unescape text for C-style quoted strings. Octal and hex sequences (C{\\0OO}, C{\\xHH}) are converted into the corresponding ASCII characters if less than 128, or else thrown out (with a warning). Invalid escape sequences raise exception. @param s: text to unescape (without wrapping quotes) @type s: string @returns: unescaped text @rtype: string @see: L{escape_c} """ segs = [] p = 0 while True: pp = p p = s.find("\\", p) if p < 0: segs.append(s[pp:]) break segs.append(s[pp:p]) p += 1 c = s[p:p + 1] ec = None if c in ("x", "0"): dd = s[p + 1:p + 3] if len(dd) == 2: try: ec = chr(int(dd, c == "x" and 16 or 8)) p += 3 except: pass else: ec = _unescapes_c.get(c) if ec is not None: p += 1 if ec is None: raise PologyError( _("@info \"C\" is the C programming language", "Invalid C escape sequence after '%(snippet)s'.", snippet=s[:p])) segs.append(ec) return type(s)().join(segs)
def assert_spec_setattr (self, att, subobj): if not hasattr(self, "_spec"): return if att in self._spec: spec = self._spec[att] if spec.get("derived", False): raise PologyError( _("@info", "Derived attribute '%(attr)s' is read-only.", attr=att)) _assert_spec_single(att, subobj, spec) elif att.endswith("_modcount"): if not isinstance(subobj, int): raise PologyError( _("@info", "Expected %(type1)s for attribute '%(attr)s', " "got %(type2)s.", type1=int, attr=att, type2=type(subobj))) else: raise PologyError( _("@info", "Attribute '%(attr)s' is not among specified.", attr=att))
def is_versioned (self, path): """ Check if path is under version control. @param path: path to check @type path: string @return: C{True} if versioned @rtype: bool """ raise PologyError( _("@info", "Selected version control system does not define " "checking whether a path is version controlled."))
def revision (self, path): """ Get current revision ID of the path. @param path: path to query for revision @type path: string @return: revision ID @rtype: string """ raise PologyError( _("@info", "Selected version control system does not define " "revision query."))
def diff (self, path, rev1=None, rev2=None): # Base override. root, path = self._gitroot(path) if rev1 is not None and rev2 is not None: rspec = "%s..%s" % (rev1, rev2) elif rev1 is not None: rspec = "%s" % rev1 elif rev2 is not None: raise PologyError( _("@info" "Git cannot diff from non-staged paths to a commit.")) else: rspec = "" res = collect_system(["git", "diff", rspec, path], wdir=root, env=self._env) if res[-1] != 0: warning(_("@info" "Git reports it cannot diff path '%(path)s':\n" "%(msg)s", path=path, msg=res[1])) return [] udiff = [] nskip = 0 for line in res[0].split("\n"): if nskip > 0: nskip -= 1 continue if line.startswith("diff"): m = re.search(r"a/(.*?) *b/", line) udiff.append((":", m.group(1) if m else "")) nskip = 3 elif line.startswith("@@"): m = re.search(r"-(\d+),(\d+) *\+(\d+),(\d+)", line) spans = tuple(map(int, m.groups())) if m else (0, 0, 0, 0) udiff.append(("@", spans)) elif line.startswith(" "): udiff.append((" ", line[1:])) elif line.startswith("-"): udiff.append(("-", line[1:])) elif line.startswith("+"): udiff.append(("+", line[1:])) return udiff
def is_clear (self, path): """ Check if the path is in clear state. Clear state means none of: not version-controlled, modified, added... @param path: path to check the state of @type path: string @return: C{True} if clear @rtype: bool """ raise PologyError( _("@info", "Selected version control system does not define state query."))
def diff (self, path, rev1=None, rev2=None): # Base override. if rev1 is not None and rev2 is not None: rspec = "-r %s:%s" % (rev1, rev2) elif rev1 is not None: rspec = "-r %s" % rev1 elif rev2 is not None: raise PologyError( _("@info \"Subversion\" is a version control system", "Subversion cannot diff from working copy " "to a named revision.")) else: rspec = "" res = collect_system(["svn", "diff", path, rspec], env=self._env) if res[-1] != 0: warning(_("@info", "Subversion reports it cannot diff path '%(path)s':\n" "%(msg)s", path=path, msg=res[1])) return [] udiff = [] nskip = 0 for line in res[0].split("\n"): if nskip > 0: nskip -= 1 continue if line.startswith("Index:"): udiff.append((":", line[line.find(":") + 1:].strip())) nskip = 3 elif line.startswith("@@"): m = re.search(r"-(\d+),(\d+) *\+(\d+),(\d+)", line) spans = tuple(map(int, m.groups())) if m else (0, 0, 0, 0) udiff.append(("@", spans)) elif line.startswith(" "): udiff.append((" ", line[1:])) elif line.startswith("-"): udiff.append(("-", line[1:])) elif line.startswith("+"): udiff.append(("+", line[1:])) return udiff
def revert (self, path): """ Revert a versioned file or directory. The path is reverted to the clean version of itself according to current local repository state. @param path: path of the versioned file or directory in local repository @type path: string @return: C{True} if reverting succeeded, C{False} otherwise @rtype: bool """ raise PologyError( _("@info", "Selected version control system does not define " "reverting a versioned path."))
def remove (self, path): """ Remove path from version control and from disk. It depends on the particular VCS what removing means, but in general it should be the point where the subsequent L{commit()} on the same path will record removal in the repository history. @param path: path to remove @type path: string @return: C{True} if removal successful @rtype: bool """ raise PologyError( _("@info", "Selected version control system does not define removing."))
def commit (self, paths, message=None, msgfile=None, incparents=True): """ Commit paths to the repository. Paths can include any number of files and directories. Also a single path string can be given instead of a sequence. It depends on the particular VCS what committing means, but in general it should be the earliest level at which modifications are recorded in the repository history. Commit message can be given either directly, through C{message} parameter, or read from a file with path given by C{msgfile}. If both C{message} and C{msgfile} are given, C{message} takes precedence and C{msgfile} is ignored. If the commit message is not given, VCS should ask for one as usual (pop an editor window, or whatever the user has configured). Some VCS require that the parent directory of a path to be committed has been committed itself or included in the commit list if not. If that is the case, C{incparents} parameter determines if this function should assure that non-committed parents are included into the commit list too. This may be expensive to check, so it is good to disable it if all parents are known to be committed or included in the input paths. @param paths: paths to commit @type paths: <string*> or string @param message: commit message @type message: string @param msgfile: path to file with the commit message @type msgfile: string @param incparents: whether to automatically include non-committed parents in the commit list @type incparents: bool @return: C{True} if committing succeeded, C{False} otherwise @rtype: bool """ raise PologyError( _("@info", "Selected version control system does not define " "committing of paths."))
def to_commit (self, path): """ Get paths which need to be committed within the given path. Input path can be either a file or directory. If it is a directory, it depends on VCS whether it will only report files within it that need to be committed, or subdirectories too (including the given directory). @param path: path to query for non-committed paths @type path: string @return: non-committed paths @rtype: [string*] """ raise PologyError( _("@info", "Selected version control system does not define " "listing of non-committed paths."))
def _gitroot (self, paths): single = False if isinstance(paths, basestring): paths = [paths] single = True # Take first path as referent. path = os.path.abspath(paths[0]) root = None if os.path.isfile(path): pdir = os.path.dirname(path) else: pdir = path while True: gitpath = os.path.join(pdir, ".git") if os.path.isdir(gitpath): root = pdir break pdir_p = pdir pdir = os.path.dirname(pdir) if pdir == pdir_p: break if root is None: raise PologyError( _("@info \"Git\" is a version control system", "Cannot find Git repository for '%(path)s'.", path=path)) rpaths = [] for path in paths: path = os.path.abspath(path) path = path[len(root) + len(os.path.sep):] rpaths.append(path) if single: return root, rpaths[0] else: return root, rpaths
def _compose_personal_dict(lang, envs): # Collect all applicable dictionary files # (for a given environment, in its subdirectiory and all above). dictpaths = set() spell_root = os.path.join(datadir(), "lang", lang, "spell") for env in (envs or [""]): spell_sub = os.path.join(".", env) while spell_sub: spell_dir = os.path.join(spell_root, spell_sub) if os.path.isdir(spell_dir): for item in os.listdir(spell_dir): if item.endswith(".aspell"): dictpaths.add(os.path.join(spell_dir, item)) spell_sub = os.path.dirname(spell_sub) dictpaths = list(dictpaths) dictpaths.sort() if not dictpaths: return None, False # If only one dictionary found, Aspell can use it as-is. if len(dictpaths) == 1: return dictpaths[0], False # Composit all dictionary files into one temporary. words = [] for dictpath in dictpaths: words.extend(_read_dict_file(dictpath)) tmpf = tempfile.NamedTemporaryFile() tmpf.close() try: tmpf = codecs.open(tmpf.name, "w", "UTF-8") tmpf.write("personal_ws-1.1 %s %d UTF-8\n" % (lang, len(words))) tmpf.writelines([x + "\n" for x in words]) tmpf.close() except Exception, e: raise PologyError( _("@info", "Cannot create composited spelling dictionary " "in current working directory:\n%(msg)s", msg=e))
def move (self, spath, dpath): """ Move versioned file or directory within the repository. It depends on the particular VCS what moving means, but in general it should be the point where the subsequent L{commit()} on source and destination path (or their common parent directory) will record the move in the repository history. @param spath: source path @type spath: string @param dpath: destination path @type dpath: string @return: C{True} if moving successful @rtype: bool """ raise PologyError( _("@info", "Selected version control system does not define moving."))
def _delimit(alts, delims): good = False for delim in delims: good = True for alt in alts: if delim in alt: good = False break if good: break if not good: fmtalts = format_item_list(["{%s}" % x for x in alts]) raise PologyError( _("@info", "No delimiter from '%(delimstr)s' can be used for " "alternatives directive containing: %(snippetlist)s.", delimstr=delims, snippetlist=fmtalts)) return delim + delim.join(alts) + delim