fix pylint warnings
authorAndrea Righi <andrea.righi@canonical.com>
Thu, 15 Jun 2023 15:54:55 +0000 (17:54 +0200)
committerAndrea Righi <andrea.righi@canonical.com>
Fri, 16 Jun 2023 10:10:09 +0000 (12:10 +0200)
Signed-off-by: Andrea Righi <andrea.righi@canonical.com>
Makefile
kconfig/annotations.py
kconfig/run.py
setup.py

index aa3f97f9269be43f587aed31826b772f037790b2..5ccbbd15e2ec9e951296daced7fbd1affe6a2a9f 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -5,7 +5,7 @@ all:  lint
 lint: flake8 pylint
 
 flake8:
-       flake8 sanitize-annotations annotations .
+       flake8 bin/sanitize-annotations kconfig/run.py kconfig/annotations.py kconfig/version.py .
 
 pylint:
-       pylint sanitize-annotations annotations kconfig
+       pylint bin/sanitize-annotations kconfig
index 1d5e40cb67de342cdddb4265a074dc3370ebad48..7a23d2b392b11b4c50c05e33784621bc00bc5767 100644 (file)
@@ -13,7 +13,7 @@ from ast import literal_eval
 from os.path import dirname, abspath
 
 
-class Config():
+class Config:
     def __init__(self, fname):
         """
         Basic configuration file object
@@ -26,12 +26,12 @@ class Config():
 
     @staticmethod
     def _load(fname: str) -> str:
-        with open(fname, 'rt', encoding='utf-8') as fd:
+        with open(fname, "rt", encoding="utf-8") as fd:
             data = fd.read()
         return data.rstrip()
 
     def __str__(self):
-        """ Return a JSON representation of the config """
+        """Return a JSON representation of the config"""
         return json.dumps(self.config, indent=4)
 
     @abstractmethod
@@ -44,14 +44,15 @@ class KConfig(Config):
     Parse a .config file, individual config options can be accessed via
     .config[<CONFIG_OPTION>]
     """
+
     def _parse(self, data: str):
         self.config = {}
         for line in data.splitlines():
-            m = re.match(r'^# (CONFIG_.*) is not set$', line)
+            m = re.match(r"^# (CONFIG_.*) is not set$", line)
             if m:
                 self.config[m.group(1)] = literal_eval("'n'")
                 continue
-            m = re.match(r'^(CONFIG_[A-Za-z0-9_]+)=(.*)$', line)
+            m = re.match(r"^(CONFIG_[A-Za-z0-9_]+)=(.*)$", line)
             if m:
                 self.config[m.group(1)] = literal_eval("'" + m.group(2) + "'")
                 continue
@@ -61,12 +62,13 @@ class Annotation(Config):
     """
     Parse body of annotations file
     """
+
     def _parse_body(self, data: str, parent=True):
         for line in data.splitlines():
             # Replace tabs with spaces, squeeze multiple into singles and
             # remove leading and trailing spaces
-            line = line.replace('\t', ' ')
-            line = re.sub(r' +', ' ', line)
+            line = line.replace("\t", " ")
+            line = re.sub(r" +", " ", line)
             line = line.strip()
 
             # Ignore empty lines
@@ -74,12 +76,12 @@ class Annotation(Config):
                 continue
 
             # Catpure flavors of included files
-            if line.startswith('# FLAVOUR: '):
-                self.include_flavour += line.split(' ')[2:]
+            if line.startswith("# FLAVOUR: "):
+                self.include_flavour += line.split(" ")[2:]
                 continue
 
             # Ignore comments
-            if line.startswith('#'):
+            if line.startswith("#"):
                 continue
 
             # Handle includes (recursively)
@@ -87,44 +89,47 @@ class Annotation(Config):
             if m:
                 if parent:
                     self.include.append(m.group(1))
-                include_fname = dirname(abspath(self.fname)) + '/' + m.group(1)
+                include_fname = dirname(abspath(self.fname)) + "/" + m.group(1)
                 include_data = self._load(include_fname)
                 self._parse_body(include_data, parent=False)
                 continue
 
             # Handle policy and note lines
-            if re.match(r'.* (policy|note)<', line):
+            if re.match(r".* (policy|note)<", line):
                 try:
-                    conf = line.split(' ')[0]
+                    conf = line.split(" ")[0]
                     if conf in self.config:
                         entry = self.config[conf]
                     else:
-                        entry = {'policy': {}}
+                        entry = {"policy": {}}
 
                     match = False
-                    m = re.match(r'.* policy<(.*?)>', line)
+                    m = re.match(r".* policy<(.*?)>", line)
                     if m:
                         match = True
                         try:
-                            entry['policy'] |= literal_eval(m.group(1))
+                            entry["policy"] |= literal_eval(m.group(1))
                         except TypeError:
-                            entry['policy'] = {**entry['policy'], **literal_eval(m.group(1))}
+                            entry["policy"] = {
+                                **entry["policy"],
+                                **literal_eval(m.group(1)),
+                            }
 
-                    m = re.match(r'.* note<(.*?)>', line)
+                    m = re.match(r".* note<(.*?)>", line)
                     if m:
-                        entry['oneline'] = match
+                        entry["oneline"] = match
                         match = True
-                        entry['note'] = "'" + m.group(1).replace("'", '') + "'"
+                        entry["note"] = "'" + m.group(1).replace("'", "") + "'"
 
                     if not match:
-                        raise SyntaxError('syntax error')
+                        raise SyntaxError("syntax error")
                     self.config[conf] = entry
                 except Exception as e:
-                    raise SyntaxError(str(e) + f', line = {line}') from e
+                    raise SyntaxError(str(e) + f", line = {line}") from e
                 continue
 
             # Invalid line
-            raise SyntaxError(f'invalid line: {line}')
+            raise SyntaxError(f"invalid line: {line}")
 
     def _parse(self, data: str):
         """
@@ -136,20 +141,20 @@ class Annotation(Config):
         self.flavour = []
         self.flavour_dep = {}
         self.include = []
-        self.header = ''
+        self.header = ""
         self.include_flavour = []
 
         # Parse header (only main header will considered, headers in includes
         # will be treated as comments)
         for line in data.splitlines():
-            if re.match(r'^#.*', line):
-                m = re.match(r'^# ARCH: (.*)', line)
+            if re.match(r"^#.*", line):
+                m = re.match(r"^# ARCH: (.*)", line)
                 if m:
-                    self.arch = list(m.group(1).split(' '))
-                m = re.match(r'^# FLAVOUR: (.*)', line)
+                    self.arch = list(m.group(1).split(" "))
+                m = re.match(r"^# FLAVOUR: (.*)", line)
                 if m:
-                    self.flavour = list(m.group(1).split(' '))
-                m = re.match(r'^# FLAVOUR_DEP: (.*)', line)
+                    self.flavour = list(m.group(1).split(" "))
+                m = re.match(r"^# FLAVOUR_DEP: (.*)", line)
                 if m:
                     self.flavour_dep = literal_eval(m.group(1))
                 self.header += line + "\n"
@@ -162,9 +167,9 @@ class Annotation(Config):
         # Sanity check: Verify that all FLAVOUR_DEP flavors are valid
         for src, tgt in self.flavour_dep.items():
             if src not in self.flavour:
-                raise SyntaxError(f'Invalid source flavour in FLAVOUR_DEP: {src}')
+                raise SyntaxError(f"Invalid source flavour in FLAVOUR_DEP: {src}")
             if tgt not in self.include_flavour:
-                raise SyntaxError(f'Invalid target flavour in FLAVOUR_DEP: {tgt}')
+                raise SyntaxError(f"Invalid target flavour in FLAVOUR_DEP: {tgt}")
 
     def _remove_entry(self, config: str):
         if self.config[config]:
@@ -175,34 +180,40 @@ class Annotation(Config):
             return
         if arch is not None:
             if flavour is not None:
-                flavour = f'{arch}-{flavour}'
+                flavour = f"{arch}-{flavour}"
             else:
                 flavour = arch
-            del self.config[config]['policy'][flavour]
-            if not self.config[config]['policy']:
+            del self.config[config]["policy"][flavour]
+            if not self.config[config]["policy"]:
                 self._remove_entry(config)
         else:
             self._remove_entry(config)
 
-    def set(self, config: str, arch: str = None, flavour: str = None,
-            value: str = None, note: str = None):
+    def set(
+        self,
+        config: str,
+        arch: str = None,
+        flavour: str = None,
+        value: str = None,
+        note: str = None,
+    ):
         if value is not None:
             if config not in self.config:
-                self.config[config] = {'policy': {}}
+                self.config[config] = {"policy": {}}
             if arch is not None:
                 if flavour is not None:
-                    flavour = f'{arch}-{flavour}'
+                    flavour = f"{arch}-{flavour}"
                 else:
                     flavour = arch
-                self.config[config]['policy'][flavour] = value
+                self.config[config]["policy"][flavour] = value
             else:
                 for a in self.arch:
-                    self.config[config]['policy'][a] = value
+                    self.config[config]["policy"][a] = value
         if note is not None:
-            self.config[config]['note'] = "'" + note.replace("'", '') + "'"
+            self.config[config]["note"] = "'" + note.replace("'", "") + "'"
 
     def update(self, c: KConfig, arch: str, flavour: str = None, configs: list = None):
-        """ Merge configs from a Kconfig object into Annotation object """
+        """Merge configs from a Kconfig object into Annotation object"""
 
         # Determine if we need to import all configs or a single config
         if not configs:
@@ -210,63 +221,72 @@ class Annotation(Config):
             try:
                 configs |= self.search_config(arch=arch, flavour=flavour).keys()
             except TypeError:
-                configs = {**configs, **self.search_config(arch=arch, flavour=flavour).keys()}
+                configs = {
+                    **configs,
+                    **self.search_config(arch=arch, flavour=flavour).keys(),
+                }
 
         # Import configs from the Kconfig object into Annotations
         if flavour is not None:
-            flavour = arch + f'-{flavour}'
+            flavour = arch + f"-{flavour}"
         else:
             flavour = arch
         for conf in configs:
             if conf in c.config:
                 val = c.config[conf]
             else:
-                val = '-'
+                val = "-"
             if conf in self.config:
-                if 'policy' in self.config[conf]:
-                    self.config[conf]['policy'][flavour] = val
+                if "policy" in self.config[conf]:
+                    self.config[conf]["policy"][flavour] = val
                 else:
-                    self.config[conf]['policy'] = {flavour: val}
+                    self.config[conf]["policy"] = {flavour: val}
             else:
-                self.config[conf] = {'policy': {flavour: val}}
+                self.config[conf] = {"policy": {flavour: val}}
 
     def _compact(self):
         # Try to remove redundant settings: if the config value of a flavour is
         # the same as the one of the main arch simply drop it.
         for conf in self.config.copy():
-            if 'policy' not in self.config[conf]:
+            if "policy" not in self.config[conf]:
                 continue
             for flavour in self.flavour:
-                if flavour not in self.config[conf]['policy']:
+                if flavour not in self.config[conf]["policy"]:
                     continue
-                m = re.match(r'^(.*?)-(.*)$', flavour)
+                m = re.match(r"^(.*?)-(.*)$", flavour)
                 if not m:
                     continue
                 arch = m.group(1)
-                if arch in self.config[conf]['policy']:
-                    if self.config[conf]['policy'][flavour] == self.config[conf]['policy'][arch]:
-                        del self.config[conf]['policy'][flavour]
+                if arch in self.config[conf]["policy"]:
+                    if (
+                        self.config[conf]["policy"][flavour]
+                        == self.config[conf]["policy"][arch]
+                    ):
+                        del self.config[conf]["policy"][flavour]
                         continue
                 if flavour not in self.flavour_dep:
                     continue
                 generic = self.flavour_dep[flavour]
-                if generic in self.config[conf]['policy']:
-                    if self.config[conf]['policy'][flavour] == self.config[conf]['policy'][generic]:
-                        del self.config[conf]['policy'][flavour]
+                if generic in self.config[conf]["policy"]:
+                    if (
+                        self.config[conf]["policy"][flavour]
+                        == self.config[conf]["policy"][generic]
+                    ):
+                        del self.config[conf]["policy"][flavour]
                         continue
             # Remove rules for flavours / arches that are not supported (not
             # listed in the annotations header).
-            for flavour in self.config[conf]['policy'].copy():
+            for flavour in self.config[conf]["policy"].copy():
                 if flavour not in list(set(self.arch + self.flavour)):
-                    del self.config[conf]['policy'][flavour]
+                    del self.config[conf]["policy"][flavour]
             # Remove configs that are all undefined across all arches/flavours
             # (unless we have includes)
             if not self.include:
-                if 'policy' in self.config[conf]:
-                    if list(set(self.config[conf]['policy'].values())) == ['-']:
-                        self.config[conf]['policy'] = {}
+                if "policy" in self.config[conf]:
+                    if list(set(self.config[conf]["policy"].values())) == ["-"]:
+                        self.config[conf]["policy"] = {}
             # Drop empty rules
-            if not self.config[conf]['policy']:
+            if not self.config[conf]["policy"]:
                 del self.config[conf]
             else:
                 # Compact same value across all flavour within the same arch
@@ -274,16 +294,16 @@ class Annotation(Config):
                     arch_flavours = [i for i in self.flavour if i.startswith(arch)]
                     value = None
                     for flavour in arch_flavours:
-                        if flavour not in self.config[conf]['policy']:
+                        if flavour not in self.config[conf]["policy"]:
                             break
                         if value is None:
-                            value = self.config[conf]['policy'][flavour]
-                        elif value != self.config[conf]['policy'][flavour]:
+                            value = self.config[conf]["policy"][flavour]
+                        elif value != self.config[conf]["policy"][flavour]:
                             break
                     else:
                         for flavour in arch_flavours:
-                            del self.config[conf]['policy'][flavour]
-                        self.config[conf]['policy'][arch] = value
+                            del self.config[conf]["policy"][flavour]
+                        self.config[conf]["policy"][arch] = value
         # After the first round of compaction we may end up having configs that
         # are undefined across all arches, so do another round of compaction to
         # drop these settings that are not needed anymore
@@ -291,34 +311,34 @@ class Annotation(Config):
         if not self.include:
             for conf in self.config.copy():
                 # Remove configs that are all undefined across all arches/flavours
-                if 'policy' in self.config[conf]:
-                    if list(set(self.config[conf]['policy'].values())) == ['-']:
-                        self.config[conf]['policy'] = {}
+                if "policy" in self.config[conf]:
+                    if list(set(self.config[conf]["policy"].values())) == ["-"]:
+                        self.config[conf]["policy"] = {}
                 # Drop empty rules
-                if not self.config[conf]['policy']:
+                if not self.config[conf]["policy"]:
                     del self.config[conf]
 
     @staticmethod
     def _sorted(config):
-        """ Sort configs alphabetically but return configs with a note first """
+        """Sort configs alphabetically but return configs with a note first"""
         w_note = []
         wo_note = []
         for c in sorted(config):
-            if 'note' in config[c]:
+            if "note" in config[c]:
                 w_note.append(c)
             else:
                 wo_note.append(c)
         return w_note + wo_note
 
     def save(self, fname: str):
-        """ Save annotations data to the annotation file """
+        """Save annotations data to the annotation file"""
         # Compact annotations structure
         self._compact()
 
         # Save annotations to disk
-        with tempfile.NamedTemporaryFile(mode='w+t', delete=False) as tmp:
+        with tempfile.NamedTemporaryFile(mode="w+t", delete=False) as tmp:
             # Write header
-            tmp.write(self.header + '\n')
+            tmp.write(self.header + "\n")
 
             # Write includes
             for i in self.include:
@@ -335,40 +355,45 @@ class Annotation(Config):
             marker = False
             for conf in self._sorted(self.config):
                 new_val = self.config[conf]
-                if 'policy' not in new_val:
+                if "policy" not in new_val:
                     continue
 
                 # If new_val is a subset of old_val, skip it unless there are
                 # new notes that are different than the old ones.
                 old_val = tmp_a.config.get(conf)
-                if old_val and 'policy' in old_val:
+                if old_val and "policy" in old_val:
                     try:
-                        can_skip = old_val['policy'] == old_val['policy'] | new_val['policy']
+                        can_skip = (
+                            old_val["policy"] == old_val["policy"] | new_val["policy"]
+                        )
                     except TypeError:
-                        can_skip = old_val['policy'] == {**old_val['policy'], **new_val['policy']}
+                        can_skip = old_val["policy"] == {
+                            **old_val["policy"],
+                            **new_val["policy"],
+                        }
                     if can_skip:
-                        if 'note' not in new_val:
+                        if "note" not in new_val:
                             continue
-                        if 'note' in old_val and 'note' in new_val:
-                            if old_val['note'] == new_val['note']:
+                        if "note" in old_val and "note" in new_val:
+                            if old_val["note"] == new_val["note"]:
                                 continue
 
                 # Write out the policy (and note) line(s)
-                val = dict(sorted(new_val['policy'].items()))
+                val = dict(sorted(new_val["policy"].items()))
                 line = f"{conf : <47} policy<{val}>"
-                if 'note' in new_val:
-                    val = new_val['note']
-                    if new_val.get('oneline', False):
+                if "note" in new_val:
+                    val = new_val["note"]
+                    if new_val.get("oneline", False):
                         # Single line
-                        line += f' note<{val}>'
+                        line += f" note<{val}>"
                     else:
                         # Separate policy and note lines,
                         # followed by an empty line
-                        line += f'\n{conf : <47} note<{val}>\n'
+                        line += f"\n{conf : <47} note<{val}>\n"
                 elif not marker:
                     # Write out a marker indicating the start of annotations
                     # without notes
-                    tmp.write('\n# ---- Annotations without notes ----\n\n')
+                    tmp.write("\n# ---- Annotations without notes ----\n\n")
                     marker = True
                 tmp.write(line + "\n")
 
@@ -376,11 +401,13 @@ class Annotation(Config):
             tmp.flush()
             shutil.move(tmp.name, fname)
 
-    def search_config(self, config: str = None, arch: str = None, flavour: str = None) -> dict:
-        """ Return config value of a specific config option or architecture """
+    def search_config(
+        self, config: str = None, arch: str = None, flavour: str = None
+    ) -> dict:
+        """Return config value of a specific config option or architecture"""
         if flavour is None:
-            flavour = 'generic'
-        flavour = f'{arch}-{flavour}'
+            flavour = "generic"
+        flavour = f"{arch}-{flavour}"
         if flavour in self.flavour_dep:
             generic = self.flavour_dep[flavour]
         else:
@@ -392,14 +419,14 @@ class Annotation(Config):
             # Get config options of a specific architecture
             ret = {}
             for c, val in self.config.items():
-                if 'policy' not in val:
+                if "policy" not in val:
                     continue
-                if flavour in val['policy']:
-                    ret[c] = val['policy'][flavour]
-                elif generic != flavour and generic in val['policy']:
-                    ret[c] = val['policy'][generic]
-                elif arch in val['policy']:
-                    ret[c] = val['policy'][arch]
+                if flavour in val["policy"]:
+                    ret[c] = val["policy"][flavour]
+                elif generic != flavour and generic in val["policy"]:
+                    ret[c] = val["policy"][generic]
+                elif arch in val["policy"]:
+                    ret[c] = val["policy"][arch]
             return ret
         if config is not None and arch is None:
             # Get a specific config option for all architectures
@@ -407,24 +434,24 @@ class Annotation(Config):
         if config is not None and arch is not None:
             # Get a specific config option for a specific architecture
             if config in self.config:
-                if 'policy' in self.config[config]:
-                    if flavour in self.config[config]['policy']:
-                        return {config: self.config[config]['policy'][flavour]}
-                    if generic != flavour and generic in self.config[config]['policy']:
-                        return {config: self.config[config]['policy'][generic]}
-                    if arch in self.config[config]['policy']:
-                        return {config: self.config[config]['policy'][arch]}
+                if "policy" in self.config[config]:
+                    if flavour in self.config[config]["policy"]:
+                        return {config: self.config[config]["policy"][flavour]}
+                    if generic != flavour and generic in self.config[config]["policy"]:
+                        return {config: self.config[config]["policy"][generic]}
+                    if arch in self.config[config]["policy"]:
+                        return {config: self.config[config]["policy"][arch]}
         return None
 
     @staticmethod
     def to_config(data: dict) -> str:
-        """ Convert annotations data to .config format """
-        s = ''
+        """Convert annotations data to .config format"""
+        s = ""
         for c in data:
             v = data[c]
-            if v == 'n':
+            if v == "n":
                 s += f"# {c} is not set\n"
-            elif v == '-':
+            elif v == "-":
                 pass
             else:
                 s += f"{c}={v}\n"
index cf133b3e97e5de89b018ad016b90b7961ccac482..cfea2e739b598b242f8a2289eb7b66b3bfa4d845 100644 (file)
@@ -4,7 +4,6 @@
 # Copyright © 2022 Canonical Ltd.
 
 import sys
-sys.dont_write_bytecode = True
 import os
 import argparse
 import json
@@ -13,83 +12,127 @@ from argcomplete import autocomplete
 
 from kconfig.annotations import Annotation, KConfig
 
-VERSION = '0.1'
+VERSION = "0.1"
 
 SKIP_CONFIGS = (
     # CONFIG_VERSION_SIGNATURE is dynamically set during the build
-    'CONFIG_VERSION_SIGNATURE',
+    "CONFIG_VERSION_SIGNATURE",
     # Allow to use a different versions of toolchain tools
-    'CONFIG_GCC_VERSION',
-    'CONFIG_CC_VERSION_TEXT',
-    'CONFIG_AS_VERSION',
-    'CONFIG_LD_VERSION',
-    'CONFIG_LLD_VERSION',
-    'CONFIG_CLANG_VERSION',
-    'CONFIG_PAHOLE_VERSION',
-    'CONFIG_RUSTC_VERSION_TEXT',
-    'CONFIG_BINDGEN_VERSION_TEXT',
+    "CONFIG_GCC_VERSION",
+    "CONFIG_CC_VERSION_TEXT",
+    "CONFIG_AS_VERSION",
+    "CONFIG_LD_VERSION",
+    "CONFIG_LLD_VERSION",
+    "CONFIG_CLANG_VERSION",
+    "CONFIG_PAHOLE_VERSION",
+    "CONFIG_RUSTC_VERSION_TEXT",
+    "CONFIG_BINDGEN_VERSION_TEXT",
 )
 
 
 def make_parser():
     parser = argparse.ArgumentParser(
-        description='Manage Ubuntu kernel .config and annotations',
+        description="Manage Ubuntu kernel .config and annotations",
+    )
+    parser.add_argument(
+        "--version", "-v", action="version", version=f"%(prog)s {VERSION}"
+    )
+
+    parser.add_argument(
+        "--file",
+        "-f",
+        action="store",
+        help="Pass annotations or .config file to be parsed",
+    )
+    parser.add_argument("--arch", "-a", action="store", help="Select architecture")
+    parser.add_argument(
+        "--flavour", "-l", action="store", help='Select flavour (default is "generic")'
+    )
+    parser.add_argument(
+        "--config", "-c", action="store", help="Select a specific config option"
+    )
+    parser.add_argument("--query", "-q", action="store_true", help="Query annotations")
+    parser.add_argument(
+        "--note",
+        "-n",
+        action="store",
+        help="Write a specific note to a config option in annotations",
+    )
+    parser.add_argument(
+        "--autocomplete",
+        action="store_true",
+        help="Enable config bash autocomplete: `source <(annotations --autocomplete)`",
+    )
+    parser.add_argument(
+        "--source",
+        "-t",
+        action="store_true",
+        help="Jump to a config definition in the kernel source code",
+    )
+
+    ga = parser.add_argument_group(title="Action").add_mutually_exclusive_group(
+        required=False
+    )
+    ga.add_argument(
+        "--write",
+        "-w",
+        action="store",
+        metavar="VALUE",
+        dest="value",
+        help="Set a specific config value in annotations (use 'null' to remove)",
+    )
+    ga.add_argument(
+        "--export",
+        "-e",
+        action="store_true",
+        help="Convert annotations to .config format",
+    )
+    ga.add_argument(
+        "--import",
+        "-i",
+        action="store",
+        metavar="FILE",
+        dest="import_file",
+        help="Import a full .config for a specific arch and flavour into annotations",
+    )
+    ga.add_argument(
+        "--update",
+        "-u",
+        action="store",
+        metavar="FILE",
+        dest="update_file",
+        help="Import a partial .config into annotations (only resync configs specified in FILE)",
+    )
+    ga.add_argument(
+        "--check",
+        "-k",
+        action="store",
+        metavar="FILE",
+        dest="check_file",
+        help="Validate kernel .config with annotations",
     )
-    parser.add_argument('--version', '-v', action='version', version=f'%(prog)s {VERSION}')
-
-    parser.add_argument('--file', '-f', action='store',
-                        help='Pass annotations or .config file to be parsed')
-    parser.add_argument('--arch', '-a', action='store',
-                        help='Select architecture')
-    parser.add_argument('--flavour', '-l', action='store',
-                        help='Select flavour (default is "generic")')
-    parser.add_argument('--config', '-c', action='store',
-                        help='Select a specific config option')
-    parser.add_argument('--query', '-q', action='store_true',
-                        help='Query annotations')
-    parser.add_argument('--note', '-n', action='store',
-                        help='Write a specific note to a config option in annotations')
-    parser.add_argument('--autocomplete', action='store_true',
-                        help='Enable config bash autocomplete: `source <(annotations --autocomplete)`')
-    parser.add_argument('--source', '-t', action='store_true',
-                        help='Jump to a config definition in the kernel source code')
-
-    ga = parser.add_argument_group(title='Action').add_mutually_exclusive_group(required=False)
-    ga.add_argument('--write', '-w', action='store',
-                    metavar='VALUE', dest='value',
-                    help='Set a specific config value in annotations (use \'null\' to remove)')
-    ga.add_argument('--export', '-e', action='store_true',
-                    help='Convert annotations to .config format')
-    ga.add_argument('--import', '-i', action='store',
-                    metavar="FILE", dest='import_file',
-                    help='Import a full .config for a specific arch and flavour into annotations')
-    ga.add_argument('--update', '-u', action='store',
-                    metavar="FILE", dest='update_file',
-                    help='Import a partial .config into annotations (only resync configs specified in FILE)')
-    ga.add_argument('--check', '-k', action='store',
-                    metavar="FILE", dest='check_file',
-                    help='Validate kernel .config with annotations')
     return parser
 
 
 _ARGPARSER = make_parser()
 
 
-def arg_fail(message):
+def arg_fail(message, show_usage=True):
     print(message)
-    _ARGPARSER.print_usage()
+    if show_usage:
+        _ARGPARSER.print_usage()
     sys.exit(1)
 
 
 def print_result(config, res):
     if res is not None and config not in res:
-        res = {config or '*': res}
+        res = {config or "*": res}
     print(json.dumps(res, indent=4))
 
 
 def do_query(args):
     if args.arch is None and args.flavour is not None:
-        arg_fail('error: --flavour requires --arch')
+        arg_fail("error: --flavour requires --arch")
     a = Annotation(args.file)
     res = a.search_config(config=args.config, arch=args.arch, flavour=args.flavour)
     print_result(args.config, res)
@@ -97,23 +140,23 @@ def do_query(args):
 
 def do_autocomplete(args):
     a = Annotation(args.file)
-    res = (c.removeprefix('CONFIG_') for c in a.search_config())
-    res_str = ' '.join(res)
+    res = (c.removeprefix("CONFIG_") for c in a.search_config())
+    res_str = " ".join(res)
     print(f'complete -W "{res_str}" annotations')
 
 
 def do_source(args):
     if args.config is None:
-        arg_fail('error: --source requires --config')
-    if not os.path.exists('tags'):
-        print('tags not found in the current directory, try: `make tags`')
+        arg_fail("error: --source requires --config")
+    if not os.path.exists("tags"):
+        print("tags not found in the current directory, try: `make tags`")
         sys.exit(1)
-    os.system(f'vim -t {args.config}')
+    os.system(f"vim -t {args.config}")
 
 
 def do_note(args):
     if args.config is None:
-        arg_fail('error: --note requires --config')
+        arg_fail("error: --note requires --config")
 
     # Set the note in annotations
     a = Annotation(args.file)
@@ -130,14 +173,20 @@ def do_note(args):
 
 def do_write(args):
     if args.config is None:
-        arg_fail('error: --write requires --config')
+        arg_fail("error: --write requires --config")
 
     # Set the value in annotations ('null' means remove)
     a = Annotation(args.file)
-    if args.value == 'null':
+    if args.value == "null":
         a.remove(args.config, arch=args.arch, flavour=args.flavour)
     else:
-        a.set(args.config, arch=args.arch, flavour=args.flavour, value=args.value, note=args.note)
+        a.set(
+            args.config,
+            arch=args.arch,
+            flavour=args.flavour,
+            value=args.value,
+            note=args.note,
+        )
 
     # Save back to annotations
     a.save(args.file)
@@ -150,7 +199,7 @@ def do_write(args):
 
 def do_export(args):
     if args.arch is None:
-        arg_fail('error: --export requires --arch')
+        arg_fail("error: --export requires --arch")
     a = Annotation(args.file)
     conf = a.search_config(config=args.config, arch=args.arch, flavour=args.flavour)
     if conf:
@@ -159,11 +208,11 @@ def do_export(args):
 
 def do_import(args):
     if args.arch is None:
-        arg_fail('error: --arch is required with --import')
+        arg_fail("error: --arch is required with --import")
     if args.flavour is None:
-        arg_fail('error: --flavour is required with --import')
+        arg_fail("error: --flavour is required with --import")
     if args.config is not None:
-        arg_fail('error: --config cannot be used with --import (try --update)')
+        arg_fail("error: --config cannot be used with --import (try --update)")
 
     # Merge with the current annotations
     a = Annotation(args.file)
@@ -176,7 +225,7 @@ def do_import(args):
 
 def do_update(args):
     if args.arch is None:
-        arg_fail('error: --arch is required with --update')
+        arg_fail("error: --arch is required with --update")
 
     # Merge with the current annotations
     a = Annotation(args.file)
@@ -193,7 +242,7 @@ def do_update(args):
 def do_check(args):
     # Determine arch and flavour
     if args.arch is None:
-        arg_fail('error: --arch is required with --check')
+        arg_fail("error: --arch is required with --check")
 
     print(f"check-config: loading annotations from {args.file}")
     total = good = ret = 0
@@ -211,11 +260,11 @@ def do_check(args):
         if conf in SKIP_CONFIGS:
             continue
         entry = a.search_config(config=conf, arch=args.arch, flavour=args.flavour)
-        expected = entry[conf] if entry else '-'
-        value = c.config[conf] if conf in c.config else '-'
+        expected = entry[conf] if entry else "-"
+        value = c.config[conf] if conf in c.config else "-"
         if value != expected:
-            policy = a.config[conf] if conf in a.config else 'undefined'
-            if 'policy' in policy:
+            policy = a.config[conf] if conf in a.config else "undefined"
+            if "policy" in policy:
                 policy = f"policy<{policy['policy']}>"
             print(f"check-config: FAIL: ({value} != {expected}): {conf} {policy})")
             ret = 1
@@ -233,10 +282,12 @@ def autodetect_annotations(args):
     # If --file/-f isn't specified try to automatically determine the right
     # location of the annotations file looking at debian/debian.env.
     try:
-        with open('debian/debian.env', 'rt', encoding='utf-8') as fd:
-            args.file = fd.read().rstrip().split('=')[1] + '/config/annotations'
+        with open("debian/debian.env", "rt", encoding="utf-8") as fd:
+            args.file = fd.read().rstrip().split("=")[1] + "/config/annotations"
     except (FileNotFoundError, IndexError):
-        arg_fail('error: could not determine DEBDIR, try using: --file/-f')
+        arg_fail(
+            "error: could not determine DEBDIR, try using: --file/-f", show_usage=False
+        )
 
 
 def main():
@@ -249,8 +300,8 @@ def main():
     args = _ARGPARSER.parse_args()
     autodetect_annotations(args)
 
-    if args.config and not args.config.startswith('CONFIG_'):
-        args.config = 'CONFIG_' + args.config
+    if args.config and not args.config.startswith("CONFIG_"):
+        args.config = "CONFIG_" + args.config
 
     if args.value:
         do_write(args)
@@ -272,5 +323,5 @@ def main():
         do_query(args)
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     main()
index 622a856df63d307bbbdcbba8fb79797b14449c80..c8e377e5b214304e86388349f680e19e22dbefbd 100755 (executable)
--- a/setup.py
+++ b/setup.py
@@ -1,38 +1,38 @@
 #!/usr/bin/env python3
 
 import os
-import sys
 from setuptools import setup
 from kconfig.version import VERSION
 
 setup(
-    name='annotations',
+    name="annotations",
     version=VERSION,
-    author='Andrea Righi',
-    author_email='andrea.righi@canonical.com',
-    description='Manage Ubuntu kernel .config',
-    url='https://git.launchpad.net/~arighi/+git/annotations-tools',
-    license='GPLv2',
-    long_description=open(os.path.join(os.path.dirname(__file__),
-                                       'README.rst'), 'r').read(),
+    author="Andrea Righi",
+    author_email="andrea.righi@canonical.com",
+    description="Manage Ubuntu kernel .config",
+    url="https://git.launchpad.net/~arighi/+git/annotations-tools",
+    license="GPLv2",
+    long_description=open(
+        os.path.join(os.path.dirname(__file__), "README.rst"), "r"
+    ).read(),
     long_description_content_type="text/x-rts",
-    packages=['kconfig'],
-    install_requires=['argcomplete'],
-    entry_points = {
-        'console_scripts': [
-            'annotations = kconfig.run:main',
+    packages=["kconfig"],
+    install_requires=["argcomplete"],
+    entry_points={
+        "console_scripts": [
+            "annotations = kconfig.run:main",
         ]
     },
-    scripts = [
-        'bin/sanitize-annotations',
+    scripts=[
+        "bin/sanitize-annotations",
     ],
     include_package_data=True,
-    classifiers=['Environment :: Console',
-                 'Intended Audience :: Developers',
-                 'Intended Audience :: System Administrators',
-                 'License :: OSI Approved :: GNU General Public License v2 (GPLv2)',
-                 'Operating System :: POSIX :: Linux',
-             ],
-
-    zip_safe = False,
+    classifiers=[
+        "Environment :: Console",
+        "Intended Audience :: Developers",
+        "Intended Audience :: System Administrators",
+        "License :: OSI Approved :: GNU General Public License v2 (GPLv2)",
+        "Operating System :: POSIX :: Linux",
+    ],
+    zip_safe=False,
 )