#!/usr/bin/python3 # -*- coding: utf-8 -*- # # apply-languages.py -- generate language subpackages from LANGUAGES comment # in RPM spec files # Copyright © 2012, 2014 Red Hat, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Author: Nils Philippsen import os import sys import tempfile import atexit import re from stat import S_IMODE from itertools import islice def usage(): print("Usage: {} .spec".format(sys.argv[0]), file=sys.stderr) sys.exit(1) def cleantmpfile(): global newfspath try: os.unlink(newsfpath) except OSError: pass if len(sys.argv) != 2: usage() sfpath = sys.argv[1] if not os.access(sfpath, os.R_OK | os.W_OK): print("Not readable/writable:", sfpath, file=sys.stderr) sys.exit(2) sfdir = os.path.dirname(sfpath) sfbase = os.path.basename(sfpath) (fd, newsfpath) = tempfile.mkstemp(prefix=sfbase + ".", dir=sfdir) atexit.register(cleantmpfile) newsf = os.fdopen(fd, "w") sf = open(sfpath) sfcontent = sf.read() sf.close() sfmode = S_IMODE(os.stat(sfpath).st_mode) languages_re = re.compile( r"^#\s*LANGUAGES:\s*(?P[^\n\r]+)\s*$", re.MULTILINE) langsplit_re = re.compile(r"\s+") begin_obsoletes_re = re.compile( r"^#\s*BEGIN:\s*OBSOLETE\s+LANGUAGES\s*$", re.MULTILINE) end_obsoletes_re = re.compile( r"^#\s*END:\s*OBSOLETE\s+LANGUAGES\s*$", re.MULTILINE) begin_langpkgs_re = re.compile( r"^#\s*BEGIN:\s*LANGUAGE\s+SUB\s+PACKAGES\s*$", re.MULTILINE) end_langpkgs_re = re.compile( r"^#\s*END:\s*LANGUAGE\s+SUB\s+PACKAGES\s*$", re.MULTILINE) begin_langfiles_re = re.compile( r"^#\s*BEGIN:\s*LANGUAGE\s+FILE\s+LISTS\s*$", re.MULTILINE) end_langfiles_re = re.compile( r"^#\s*END:\s*LANGUAGE\s+FILE\s+LISTS\s*$", re.MULTILINE) name_re = re.compile(r"^name:\s*(?P[^\n\r]+)\s*$", re.MULTILINE | re.IGNORECASE) version_re = re.compile(r"^version:\s*(?P[^\n\r]+)\s*$", re.MULTILINE | re.IGNORECASE) release_re = re.compile(r"^release:\s*(?P[^\n\r]+)\s*$", re.MULTILINE | re.IGNORECASE) license_re = re.compile(r"^license:\s*(?P[^\n\r]+)\s*$", re.MULTILINE | re.IGNORECASE) pkg_re = re.compile(r"^%package\s*(?P\S+)\s*$", re.MULTILINE | re.IGNORECASE) group_re = re.compile(r"^group:\s*(?P[^\n\r]+)\s*$", re.MULTILINE | re.IGNORECASE) missing = False for what, what_re in (("name tag", name_re), ("license tag", license_re), ("group tag", group_re), ("LANGUAGES comment", languages_re), ("BEGIN: OBSOLETE LANGUAGES comment", begin_obsoletes_re), ("END: OBSOLETE LANGUAGES comment", end_obsoletes_re), ("BEGIN: LANGUAGE SUB PACKAGES comment", begin_langpkgs_re), ("END: LANGUAGE SUB PACKAGES comment", end_langpkgs_re), ("BEGIN: LANGUAGE FILE LISTS comment", begin_langfiles_re), ("END: LANGUAGE FILE LISTS comment", end_langfiles_re), ): found = what_re.search(sfcontent) if found is None: print("{} not found".format(what), file=sys.stderr) missing = True if missing: sys.exit(2) langspecs = langsplit_re.split( languages_re.search(sfcontent).group('languages')) #languages = [] #for ls in langspecs: # langcode, langname = ls.split(",") # languages.append((langcode, langname)) languages = [(x.split(",")[0], x.split(",")[1].replace('_', ' ')) for x in langspecs] langcodes = set((x[0] for x in languages)) name = name_re.search(sfcontent).group('name') version = version_re.search(sfcontent).group('version') release = release_re.search(sfcontent).group('release') license = license_re.search(sfcontent).group('license') group = group_re.search(sfcontent).group('group') obsoletes_re = re.compile( r"^obsoletes:\s*{}-(?P(?P\S+)\s*.*)$".format(name), re.MULTILINE | re.IGNORECASE) conflicts_re = re.compile( r"^conflicts:\s*{}-(?P(?P\S+)\s*.*)$".format(name), re.MULTILINE | re.IGNORECASE) numlang = len(languages) replacing = None sflines = sfcontent.split("\n") if sflines[-1] == "": del sflines[-1] # handle obsoleting language subpackages preprocess_state_transitions = { 'out': ( begin_obsoletes_re, 'in_obsoletes', begin_langpkgs_re, 'in_langpkgs'), 'in_obsoletes': (end_obsoletes_re, 'out'), 'in_langpkgs': (end_langpkgs_re, 'out'), } state = 'out' state_change = True found_obsoleted_langs = set() found_lang_pkgs = set() for line in sflines: if state_change: transitions = preprocess_state_transitions[state] packed_transitions = zip( islice(transitions, 0, None, 2), islice(transitions, 1, None, 2)) state_change = False for regex, new_state in packed_transitions: if regex.match(line): state_change = True state = new_state break if state_change: continue if state == 'in_obsoletes': m = obsoletes_re.match(line) if m: found_obsoleted_langs.add(m.group('lang')) elif state == 'in_langpkgs': m = pkg_re.match(line) if m: found_lang_pkgs.add(m.group('pkg')) langcodes_to_obsolete = found_lang_pkgs - langcodes langcodes_to_unobsolete = found_obsoleted_langs & langcodes gobble = False # update spec file for line in sflines: if not replacing: print(line, file=newsf) if begin_obsoletes_re.match(line): replacing = 'obsoletes' elif begin_langpkgs_re.match(line): replacing = 'langpkgs' elif begin_langfiles_re.match(line): replacing = 'langfiles' elif replacing == 'obsoletes': om = obsoletes_re.match(line) cm = conflicts_re.match(line) em = end_obsoletes_re.match(line) if em: gobble = False replacing = None for lang in langcodes_to_obsolete: print( "Obsoletes: {name}-{lang} < {version}-{release}\n" "Conflicts: {name}-{lang} < {version}-{release}".format( **locals()), file=newsf) print(line, file=newsf) elif not (om or cm): if not gobble: print(line, file=newsf) elif ( om and om.group('lang') not in langcodes_to_unobsolete or cm and cm.group('lang') not in langcodes_to_unobsolete): gobble = False print(line, file=newsf) else: gobble = True elif replacing == 'langpkgs': if end_langpkgs_re.match(line): replacing = None for no, lang in enumerate(languages): langcode, langname = lang print("""%package {langcode} Summary: {langname} ({langcode}) language support for {name} Requires: %{{name}} = %{{?epoch:%{{epoch}}:}}%{{version}}-%{{release}} Supplements: (%{{name}} = %{{?epoch:%{{epoch}}:}}%{{version}}-%{{release}} and langpacks-{langcode}) %description {langcode} {langname} language support for {name}.""".format(**locals()), file=newsf) if no < numlang: print(file=newsf) print(line, file=newsf) elif replacing == 'langfiles': if end_langfiles_re.match(line): replacing = None for lang in languages: langcode, langname = lang print("%files {langcode} -f files.list.{langcode}".format( langcode=langcode), file=newsf) print(line, file=newsf) newsf.close() os.rename(newsfpath, sfpath) os.chmod(sfpath, sfmode)