Commit e71f802 add comps_filter

2 files Authored and Committed by Adam Miller 2 years ago
add comps_filter

    
  1 @@ -0,0 +1,209 @@
  2 + #!/usr/bin/python
  3 + # -*- coding: utf-8 -*-
  4 + 
  5 + 
  6 + import sys
  7 + import fnmatch
  8 + import optparse
  9 + import lxml.etree
 10 + import re
 11 + from io import StringIO
 12 + 
 13 + 
 14 + class CompsFilter(object):
 15 +     def __init__(self, file_obj, reindent=False):
 16 +         self.reindent = reindent
 17 +         parser = None
 18 +         if self.reindent:
 19 +             parser = lxml.etree.XMLParser(remove_blank_text=True)
 20 +         self.tree = lxml.etree.parse(file_obj, parser=parser)
 21 +         self.encoding = "utf-8"
 22 + 
 23 +     def _filter_elements_by_arch(self, xpath, arch, only_arch=False):
 24 +         if only_arch:
 25 +             # remove all elements without the 'arch' attribute
 26 +             for i in self.tree.xpath(xpath + "[not(@arch)]"):
 27 +                 i.getparent().remove(i)
 28 + 
 29 +         for i in self.tree.xpath(xpath + "[@arch]"):
 30 +             arches = i.attrib.get("arch")
 31 +             arches = re.split(r"[, ]+", arches)
 32 +             arches = [j for j in arches if j]
 33 +             if arch not in arches:
 34 +                 # remove elements not matching the arch
 35 +                 i.getparent().remove(i)
 36 +             else:
 37 +                 # remove the 'arch' attribute
 38 +                 del i.attrib["arch"]
 39 + 
 40 +     def filter_packages(self, arch, only_arch=False):
 41 +         """
 42 +         Filter packages according to arch.
 43 +         If only_arch is set, then only packages for the specified arch are preserved.
 44 +         Multiple arches separated by comma can be specified in the XML.
 45 +         """
 46 +         self._filter_elements_by_arch("/comps/group/packagelist/packagereq", arch, only_arch)
 47 + 
 48 +     def filter_groups(self, arch, only_arch=False):
 49 +         """
 50 +         Filter groups according to arch.
 51 +         If only_arch is set, then only groups for the specified arch are preserved.
 52 +         Multiple arches separated by comma can be specified in the XML.
 53 +         """
 54 +         self._filter_elements_by_arch("/comps/group", arch, only_arch)
 55 + 
 56 +     def filter_category_groups(self):
 57 +         """
 58 +         Remove undefined groups from categories.
 59 +         """
 60 +         all_groups = self.tree.xpath("/comps/group/id/text()")
 61 +         for category in self.tree.xpath("/comps/category"):
 62 +             for group in category.xpath("grouplist/groupid"):
 63 +                 if group.text not in all_groups:
 64 +                     group.getparent().remove(group)
 65 + 
 66 +     def remove_empty_groups(self, keep_empty=None):
 67 +         """
 68 +         Remove all groups without packages.
 69 +         """
 70 +         keep_empty = keep_empty or []
 71 +         for group in self.tree.xpath("/comps/group"):
 72 +             if not group.xpath("packagelist/packagereq"):
 73 +                 group_id = group.xpath("id/text()")[0]
 74 +                 found = False
 75 +                 for pattern in keep_empty:
 76 +                     if fnmatch.fnmatch(group_id, pattern):
 77 +                         found = True
 78 +                         break
 79 +                 if found:
 80 +                     continue
 81 +                 group.getparent().remove(group)
 82 + 
 83 +     def remove_empty_categories(self):
 84 +         """
 85 +         Remove all categories without groups.
 86 +         """
 87 +         for category in self.tree.xpath("/comps/category"):
 88 +             if not category.xpath("grouplist/groupid"):
 89 +                 category.getparent().remove(category)
 90 + 
 91 +     def remove_categories(self):
 92 +         """
 93 +         Remove all categories.
 94 +         """
 95 +         categories = self.tree.xpath("/comps/category")
 96 +         for i in categories:
 97 +             i.getparent().remove(i)
 98 + 
 99 +     def remove_langpacks(self):
100 +         """
101 +         Remove all langpacks.
102 +         """
103 +         langpacks = self.tree.xpath("/comps/langpacks")
104 +         for i in langpacks:
105 +             i.getparent().remove(i)
106 + 
107 +     def remove_translations(self):
108 +         """
109 +         Remove all translations.
110 +         """
111 +         for i in self.tree.xpath("//*[@xml:lang]"):
112 +             i.getparent().remove(i)
113 + 
114 +     def filter_environment_groups(self):
115 +         """
116 +         Remove undefined groups from environments.
117 +         """
118 +         all_groups = self.tree.xpath("/comps/group/id/text()")
119 +         for environment in self.tree.xpath("/comps/environment"):
120 +             for group in environment.xpath("grouplist/groupid"):
121 +                 if group.text not in all_groups:
122 +                     group.getparent().remove(group)
123 + 
124 +     def remove_empty_environments(self):
125 +         """
126 +         Remove all environments without groups.
127 +         """
128 +         for environment in self.tree.xpath("/comps/environment"):
129 +             if not environment.xpath("grouplist/groupid"):
130 +                 environment.getparent().remove(environment)
131 + 
132 +     def remove_environments(self):
133 +         """
134 +         Remove all langpacks.
135 +         """
136 +         environments = self.tree.xpath("/comps/environment")
137 +         for i in environments:
138 +             i.getparent().remove(i)
139 + 
140 +     def write(self, file_obj):
141 +         self.tree.write(file_obj, pretty_print=self.reindent, xml_declaration=True, encoding=self.encoding)
142 +         file_obj.write("\n")
143 + 
144 +     def pprint(self):
145 +         self.write(sys.stdout)
146 + 
147 +     def xml(self):
148 +         io = StringIO()
149 +         self.write(io)
150 +         io.seek(0)
151 +         return io.read()
152 + 
153 + 
154 + def main():
155 +     parser = optparse.OptionParser("%prog [options] <comps.xml>")
156 +     parser.add_option("--output", help="redirect output to a file")
157 +     parser.add_option("--arch", help="filter groups and packagews according to an arch")
158 +     parser.add_option("--arch-only-groups", default=False, action="store_true", help="keep only arch groups, remove the rest")
159 +     parser.add_option("--arch-only-packages", default=False, action="store_true", help="keep only arch packages, remove the rest")
160 +     parser.add_option("--remove-categories", default=False, action="store_true", help="remove all categories")
161 +     parser.add_option("--remove-langpacks", default=False, action="store_true", help="remove the langpacks section")
162 +     parser.add_option("--remove-translations", default=False, action="store_true", help="remove all translations")
163 +     parser.add_option("--remove-environments", default=False, action="store_true", help="remove all environment sections")
164 +     parser.add_option("--keep-empty-group", default=[], action="append", metavar="[GROUPID]", help="keep groups even if they are empty")
165 +     parser.add_option("--no-cleanup", default=False, action="store_true", help="don't remove empty groups and categories")
166 +     parser.add_option("--no-reindent", default=False, action="store_true", help="don't re-indent the output")
167 + 
168 +     opts, args = parser.parse_args()
169 + 
170 +     if len(args) != 1:
171 +         parser.error("please specify exactly one comps file")
172 + 
173 +     comps_file = args[0]
174 + 
175 +     if opts.arch is None:
176 +         parser.error("please specify arch")
177 + 
178 +     file_obj = open(comps_file, "r")
179 +     f = CompsFilter(file_obj, reindent=not opts.no_reindent)
180 +     f.filter_packages(opts.arch, opts.arch_only_packages)
181 +     f.filter_groups(opts.arch, opts.arch_only_groups)
182 + 
183 +     if not opts.no_cleanup:
184 +         f.remove_empty_groups(keep_empty=opts.keep_empty_group)
185 +         f.filter_category_groups()
186 +         f.remove_empty_categories()
187 +         f.filter_environment_groups()
188 +         f.remove_empty_environments()
189 + 
190 +     if opts.remove_categories:
191 +         f.remove_categories()
192 + 
193 +     if opts.remove_langpacks:
194 +         f.remove_langpacks()
195 + 
196 +     if opts.remove_translations:
197 +         f.remove_translations()
198 + 
199 +     if opts.remove_environments:
200 +         f.remove_environments()
201 + 
202 +     if opts.output:
203 +         out = open(opts.output, "w")
204 +         f.write(out)
205 +     else:
206 +         f.pprint()
207 + 
208 + 
209 + if __name__ == "__main__":
210 +     main()
1 @@ -36,6 +36,7 @@
2       scripts         = [
3           'bin/pungi',
4           'bin/pungi-koji',
5 +         'bin/comps_filter',
6       ],
7       data_files      = [
8           ('/usr/share/pungi', glob.glob('share/*.xsl')),