code:
#!/usr/bin/python
# -*- coding: utf-8 -*-

import shutil
import sys
import re
import os

from optparse import OptionParser

def read_file(path):
    with open(path) as f:
        return f.read().strip()

def write_file(path, content):
    open(path, "w").write(content)
    open(path, "a").write("\n")

class Components():
    COMPONENT_XML = """<PISI>
    <Name>%s</Name>
</PISI>
"""
    EMPTY_COMPONENT = """        <Component>
            <Name>%s</Name>
            <LocalName xml:lang="en">FIXME</LocalName>
            FIXME
            <Description xml:lang="en">FIXME</Description>
            <Group>FIXME</Group>
            <Maintainer>
                <Name>PisiLinux Community</Name>
                <Email>admins@pisilinux.org</Email>
            </Maintainer>
        </Component>"""
    CBGN = """<PISI>
    <Components>"""
    CEND = """
    </Components>
</PISI>"""
    LOCALNAME = '            <LocalName xml:lang="%s">%s</LocalName>\n'
    SUMMARY = '            %s\n'
    DESCRIPTION = '            <Description xml:lang="%s">%s</Description>\n'
    COMPONENT = """
        <Component>
            <Name>%s</Name>
%s%s%s            <Group>%s</Group>
            <Maintainer>
                <Name>%s</Name>
                <Email>%s</Email>
            </Maintainer>
        </Component>"""

    def __init__(self, path):
        self.path = path
        self.components = []
        self.components_xml = "%s/components.xml" % self.path
        self.name_ptrn = re.compile("\s*<Name>(.+?)<\/Name>\s*")
        self.lnamech_ptrn = re.compile("\s*<LocalName.*?>(.+?)<\/LocalName>\s*")
        self.lname_ptrn = re.compile("\s*<LocalName\s+xml:lang\s?=\s?[\'\"](\w\w.*?)[\'\"]\s?>(.+?)<\/LocalName>\s*")
        self.summarych_ptrn = re.compile("\s*(.+?)<\/Summary>\s*")
        self.summary_ptrn = re.compile("\s*(.+?)<\/Summary>\s*")
        self.descch_ptrn = re.compile("\s*<Description.*?>(.+?)<\/Description>\s*")
        self.desc_ptrn = re.compile("\s*<Description\s+xml:lang\s?=\s?[\'\"](\w\w.*?)[\'\"]\s?>(.+?)<\/Description>\s*")
        self.group_ptrn = re.compile("\s*<Group>(.+?)<\/Group>\s*")
        self.email_ptrn = re.compile("\s*<Email>(.+?)<\/Email>\s*")
        self.cbgn_ptrn = re.compile("\s*<Component>\s*")
        self.cend_ptrn = re.compile("\s*<\/Component>\s*")
        self.mbgn_ptrn = re.compile("\s*<Maintainer>\s*")
        self.mend_ptrn = re.compile("\s*<\/Maintainer>\s*")
        self.csend_ptrn = re.compile("\s*<\/Components>\s*")

    def read_components(self):
        csfile = read_file(self.components_xml)
        cread = False
        mread = False
        component = {}
        for line in csfile.split("\n"):
            if re.search(self.cbgn_ptrn, line):
                cread = True
                component["err"] = []
                component["unknown"] = []
                component["clname"] = []
                component["csummary"] = []
                component["cdesc"] = []
                continue
            elif re.search(self.cend_ptrn, line):
                cread = False
                self.components.append(component)
                component = {}
                continue
            if cread:
                if re.search(self.mbgn_ptrn, line):
                    mread = True
                    continue
                elif re.search(self.mend_ptrn, line):
                    mread = False
                    continue
                elif re.search(self.name_ptrn, line):
                    if mread: component["mname"] = re.sub(self.name_ptrn, r"\1", line)
                    else: component["cname"] = re.sub(self.name_ptrn, r"\1", line)
                elif re.search(self.lnamech_ptrn, line):
                    if re.search(self.lname_ptrn, line):
                        component["clname"].append(re.sub(self.lname_ptrn, r"\1:\2", line))
                    else:
                        component["err"].append("LocalName")
                elif re.search(self.summarych_ptrn, line):
                    if re.search(self.summary_ptrn, line):
                        component["csummary"].append(re.sub(self.summary_ptrn, r"\1:\2", line))
                    else:
                        component["err"].append("Summary")
                elif re.search(self.descch_ptrn, line):
                    if re.search(self.desc_ptrn, line):
                        component["cdesc"].append(re.sub(self.desc_ptrn, r"\1:\2", line))
                    else:
                        component["err"].append("Description")
                elif re.search(self.group_ptrn, line):
                    component["cgroup"] = re.sub(self.group_ptrn, r"\1", line)
                elif re.search(self.email_ptrn, line):
                    component["memail"] = re.sub(self.email_ptrn, r"\1", line)
                else: component["unknown"].append(line)

    def sort_components(self):
        self.components = sorted(self.components, key=lambda k: k['cname'])

    def backup_components(self):
        files = os.walk(self.path).next()[2]
        last = 0
        for f in files:
            if f.startswith("components.xml.") and int(f[-3:]) > last: last = int(f[-3:])
        last = str(last + 1)
        while len(last) < 3: last = "0" + last
        shutil.copyfile(self.components_xml, ".".join((self.components_xml, last)))

    def write_components(self):
        self.sort_components()
        cs = ''
        for c in self.components:
            ln = ""
            for i in c["clname"]:
                ln += self.LOCALNAME % tuple(i.split(":"))
            s = ""
            for i in c["csummary"]:
                s += self.SUMMARY % tuple(i.split(":"))
            d = ""
            for i in c["cdesc"]:
                d += self.DESCRIPTION % tuple(i.split(":"))
            cs += self.COMPONENT % (c["cname"], ln, s, d, c["cgroup"], c["mname"], c["memail"])
        self.backup_components()
        open(self.components_xml, "w").write(self.CBGN)
        open(self.components_xml, "a").write(cs)
        open(self.components_xml, "a").write(self.CEND)
        open(self.components_xml, "a").write("\n")

    def edit(self):
        l = []
        mcommands = {"q": "quit",
                    "l": "list components",
                    "c": "choose component",
                    "h": "help"}
        ecommands = {"mn": "set maintainer name",
                     "me": "set maintainer email",
                     "ln": "set local name (lang:name)",
                     "s": "set summary (lang:summary)",
                     "d": "set description (lang:description)",
                     "m": "main menu",
                     "w": "write",
                     "h": "help"}

        def read_command(m):
            opts = mcommands.keys() if m == "m" else ecommands.keys()
            return (opts, raw_input(":".join(sorted(opts)) + " > ").split())

        def list_components(param, regexp = True):
            res = []
            num = 0
            if not param: param = ".*"
            for c in self.components:
                if regexp and re.search(param, c["cname"]):
                    res.append(c["cname"])
                    print num, c["cname"]
                    num += 1
                elif not regexp and param == c["cname"]:
                    res.append(c["cname"])
                    break
            return res

        def get_component_data(name):
            #print self.components
            for c in self.components:
                if name == c["cname"]: return c
            print "Component %s not found!" % name
            return False

        def print_help(hdict):
            for i in iter(sorted(hdict.iteritems())):
                print i[0] + "\t" + i[1]

        def edit_loop(component):
            def update(key, val, data):
                d = val.split(":")
                if not len(d) == 2: print "Wrong param format! (ln lang:text)"
                else:
                    lexists = -1
                    for i in data[key]:
                        if i.split(":")[0] == d[0]:
                            lexists = data[key].index(i)
                            break
                    if lexists > -1: data[key][lexists] = val
                    else: data[key].append(val)
                return data

            data = get_component_data(component)
            while True:
                print "-" * (len(component) + 8)
                print "Editing " + component
                print "-" * (len(component) + 8)
                print "LocalName:"
                for i in data["clname"]: print "  %s" %i
                print "Summary:"
                for i in data["csummary"]: print "  %s" %i
                print "Description:"
                for i in data["cdesc"]: print "  %s" %i
                print "Group:\n  %s" % data["cgroup"]
                print "Maintainer name:\n  %s" % data["mname"]
                print "Email:\n  %s" % data["memail"]
                o, c = read_command("c")
                if not c[0] in o: continue
                if len(c) == 1: c.append("")
                if c[0] == "m": break
                elif c[0] == "h": print_help(ecommands)
                elif c[0] == "ln": data = update("clname", c[1], data)
                elif c[0] == "s": data = update("csummary", c[1], data)
                elif c[0] == "d": data = update("cdesc", c[1], data)
                elif c[0] == "mn": data["mname"] = c[1]
                elif c[0] == "me": data["memail"] = c[1]
                elif c[0] == "w":  self.write_components()
                else: print"%s is not implemented yet" % c[0]

        def edit_component(param, l):
            try:
                int(param)
                edit_loop(l[int(param)])
            except ValueError:
                l = list_components(param, regexp = False)
            if not len(l) == 1: print "Component %s not found" % param
            else: edit_loop(l[0])

        while True:
            o, c = read_command("m")
            if len(c) == 1: c.append("")
            if not c[0] in o: continue
            elif c[0] == "q": break
            elif c[0] == "l": l = list_components(c[1])
            elif c[0] == "c": edit_component(c[1], l)
            elif c[0] == "h": print_help(mcommands)
            else: print"%s is not implemented yet" % c[0]

    def check(self):
        cs = []
        for root, dirs, files in os.walk(self.path):
            c = root.split(self.path)[1][1:].split("/")
            if "files" in c or "comar" in c: continue
            c = ".".join(c)
            component_xml = "%s/component.xml" % root
            pspec_xml = "%s/pspec.xml" % root
            actions_py = "%s/actions.py" % root
            if not os.path.isfile(component_xml) and not os.path.isfile(pspec_xml):
                if os.path.isfile(actions_py): print "WARNING: %s not exists" % pspec_xml
                else:
                    is_src_repo = False
                    for r, d, f in os.walk(root):
                        if "pspec.xml" in f:
                            is_src_repo = True
                            break
                    if is_src_repo and c:
                        print "%s not exists. creating..." % component_xml
                        write_file(component_xml, self.COMPONENT_XML % c)
            if os.path.isfile(component_xml) and c: cs.append(c)

        mcs = cs[:]
        csfile = read_file(self.components_xml)
        maintainer = False
        new_file = []
        for line in csfile.split("\n"):
            new_file.append(line)
            if re.search(self.mbgn_ptrn, line): maintainer = True
            elif re.search(self.mend_ptrn, line): maintainer = False
            elif re.search(self.csend_ptrn, line):
                for m in mcs:
                    new_file.insert(-1, self.EMPTY_COMPONENT % m)
            if not re.search(self.name_ptrn, line) or maintainer: continue
            cn = re.sub(self.name_ptrn, r"\1", line)
            if cn in mcs: mcs.pop(mcs.index(cn))

        new_file = "\n".join(new_file)
        write_file(self.components_xml, new_file)

if __name__ == "__main__":
    usage = "Usage: %prog [PATH]"
    parser = OptionParser(usage)
    parser.add_option("-c", "--check", action="store_true", dest="check", help="fix missing component.xml files and entries in components.xml")
    parser.add_option("-e", "--edit", action="store_true", dest="edit", help="edit components.xml")
    (options,args) = parser.parse_args()
    try:
        root = args[0]
    except IndexError:
        print "Using ./ as PATH"
        root = "./"

    if root.endswith("/"): root = root[:-1]

    cs = Components(root)
    if not os.path.isfile(cs.components_xml):
        print "%s not exists!" % cs.components_xml
        sys.exit(1)
    if not os.access(cs.components_xml, os.W_OK):
        print "Cannot write to %s" % cs.components_xml
        sys.exit(1)
    cs.read_components()

    if options.check: cs.check()
    if options.edit:
        cs.edit()