Add initial version of c-picker

Introduce the following features to c-picker:

* Picking of the elements from C source files:
  * Include directives
  * Functions
  * Variables
* Removing 'static' keyword from declarations
* Mapping coverage to the original source
* Documentation of the system

Signed-off-by: Imre Kis <imre.kis@arm.com>
Change-Id: Ia5cb90d3096b16b15aafb86363b8cabfe7d2ab72
diff --git a/c_picker/runner.py b/c_picker/runner.py
new file mode 100644
index 0000000..b1d02a2
--- /dev/null
+++ b/c_picker/runner.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python3
+# Copyright (c) 2020-2021, Arm Limited. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+
+"""
+This module wraps the c-picker functionality into a command line interface.
+"""
+
+import argparse
+import json
+import sys
+import yaml
+
+from c_picker import __version__ as CPICKER_VERSION
+from c_picker.picker import CPicker
+from c_picker.picker import __doc__ as c_picker_doc
+
+class CPickerRunner:
+    """ Command line wrapper for CPicker """
+
+    def __init__(self):
+        self.root = ""
+        self.args = []
+        self.options = []
+
+        self.parser = argparse.ArgumentParser(prog="c-picker", description=c_picker_doc)
+
+        self.parser.add_argument("--root", help="Root source directory")
+        self.parser.add_argument("--config", help="Configuration file (.json|.yml)", required=True)
+        self.parser.add_argument("--output", help="Output file")
+        self.parser.add_argument("--print-dependencies", help="Print dependencies",
+                                 action="store_true")
+        self.parser.add_argument("--version", action="version", version=CPICKER_VERSION)
+        self.parser.add_argument("--args", help="clang arguments", nargs=argparse.REMAINDER)
+
+    @staticmethod
+    def error(message):
+        """ Print error message to stderr """
+        print("c-picker error\n" + message, file=sys.stderr)
+
+    def generate_element_descriptors(self, elements):
+        """ Converts the structure of the config file to a list of ElementDescriptors """
+        element_descriptors = []
+
+        for element in elements:
+            file_name = self.root + element["file"]
+            element_name = element["name"] if "name" in element else None
+            element_type = CPicker.Type[element["type"]]
+            element_args = self.args + element.get("args", [])
+            element_options = [
+                CPicker.Option[opt]
+                for opt in self.options + element.get("options", [])]
+
+            descriptor = CPicker.ElementDescriptor(file_name, element_type, element_name)
+            descriptor.set_args(element_args)
+            descriptor.set_options(element_options)
+
+            element_descriptors.append(descriptor)
+
+        return element_descriptors
+
+    @staticmethod
+    def create_config(args):
+        """ Create a configuration object from the command line arguments and config files. """
+        try:
+            if args.config.endswith(".json"):
+                with open(args.config) as json_file:
+                    return json.load(json_file)
+            elif args.config.endswith(".yml"):
+                with open(args.config) as yml_file:
+                    return yaml.safe_load(yml_file)
+            else:
+                raise Exception("Invalid configuration file %s" % args.config)
+        except json.decoder.JSONDecodeError as exception:
+            raise Exception("Invalid JSON format: " + str(exception))
+        except yaml.YAMLError as exception:
+            raise Exception("Invalid YAML format: " + str(exception))
+        except FileNotFoundError as exception:
+            raise Exception("File not found: " + str(exception))
+
+    def main(self):
+        """ Runs CPicker configured by the command line arguments  """
+        try:
+            args = self.parser.parse_args()
+            config = self.create_config(args)
+
+            output_fp = open(args.output, "w") if args.output else None
+
+            self.root = args.root + "/" if args.root else ""
+            self.args = args.args if args.args else [] + config.get("args", [])
+            self.options = config.get("options", [])
+
+            elements = self.generate_element_descriptors(config.get("elements", []))
+
+            c_picker = CPicker(output_fp, args.print_dependencies)
+            c_picker.process(elements)
+
+            if output_fp:
+                output_fp.close()
+
+            return 0
+        except KeyError as exception:
+            self.error("Key error: " + str(exception))
+        except Exception as exception: # pylint: disable=broad-except
+            self.error("Error: " + str(exception))
+
+        return 1
+
+def main():
+    """ Command line main function """
+    c_picker_runner = CPickerRunner()
+    result = c_picker_runner.main()
+    sys.exit(result)
+
+if __name__ == '__main__':
+    main()