[DevTools] Refactor CodeGenerator.

Simplified, improved up_to_date, prepared to more config params.

BUG=637032

Review-Url: https://codereview.chromium.org/2248783003
Cr-Original-Commit-Position: refs/heads/master@{#412361}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 426a8b81c5c4cb53748907d8f0b77cd85e88a0d6
diff --git a/BackendCallback_h.template b/BackendCallback_h.template
index f301ede..0eb44bf 100644
--- a/BackendCallback_h.template
+++ b/BackendCallback_h.template
@@ -7,12 +7,12 @@
 
 //#include "ErrorSupport.h"
 //#include "Platform.h"
-#include "{{export_macro_include}}"
+#include "{{config.class_export.header}}"
 
 namespace blink {
 namespace protocol {
 
-class {{export_macro}} BackendCallback {
+class {{config.class_export.macro}} BackendCallback {
 public:
     virtual ~BackendCallback() { }
     virtual void sendFailure(const ErrorString&) = 0;
diff --git a/CodeGenerator.py b/CodeGenerator.py
index 128dde3..d6ab65e 100644
--- a/CodeGenerator.py
+++ b/CodeGenerator.py
@@ -5,6 +5,8 @@
 import os.path
 import sys
 import optparse
+import collections
+import functools
 try:
     import json
 except ImportError:
@@ -47,129 +49,52 @@
 
 import jinja2
 
-cmdline_parser = optparse.OptionParser()
-cmdline_parser.add_option("--output_base")
-cmdline_parser.add_option("--config")
 
+def read_config():
+    # pylint: disable=W0703
+    def json_to_object(data, output_base, config_base):
+        def json_object_hook(object_dict):
+            items = [(k, os.path.join(config_base, v) if k == "path" else v) for (k, v) in object_dict.items()]
+            items = [(k, os.path.join(output_base, v) if k == "output" else v) for (k, v) in items]
+            keys, values = zip(*items)
+            return collections.namedtuple('X', keys)(*values)
+        return json.loads(data, object_hook=json_object_hook)
 
-try:
-    arg_options, arg_values = cmdline_parser.parse_args()
-    output_base = arg_options.output_base
-    if not output_base:
-        raise Exception("Base output directory must be specified")
-    config_file = arg_options.config
-    if not config_file:
-        raise Exception("Config file name must be specified")
-    config_dir = os.path.dirname(config_file)
-except Exception:
-    # Work with python 2 and 3 http://docs.python.org/py3k/howto/pyporting.html
-    exc = sys.exc_info()[1]
-    sys.stderr.write("Failed to parse command-line arguments: %s\n\n" % exc)
-    exit(1)
+    try:
+        cmdline_parser = optparse.OptionParser()
+        cmdline_parser.add_option("--output_base")
+        cmdline_parser.add_option("--config")
+        arg_options, _ = cmdline_parser.parse_args()
+        output_base = arg_options.output_base
+        if not output_base:
+            raise Exception("Base output directory must be specified")
+        config_file = arg_options.config
+        if not config_file:
+            raise Exception("Config file name must be specified")
+        config_base = os.path.dirname(config_file)
+    except Exception:
+        # Work with python 2 and 3 http://docs.python.org/py3k/howto/pyporting.html
+        exc = sys.exc_info()[1]
+        sys.stderr.write("Failed to parse command-line arguments: %s\n\n" % exc)
+        exit(1)
 
-
-try:
-    config_json_string = open(config_file, "r").read()
-    config = json.loads(config_json_string)
-
-    protocol_file = config["protocol"]["path"]
-    if not protocol_file:
-        raise Exception("Config is missing protocol.path")
-    protocol_file = os.path.join(config_dir, protocol_file)
-    output_dirname = config["protocol"]["output"]
-    if not output_dirname:
-        raise Exception("Config is missing protocol.output")
-    output_dirname = os.path.join(output_base, output_dirname)
-    output_package = config["protocol"]["package"]
-    if not output_package:
-        raise Exception("Config is missing protocol.package")
-
-    importing = False
-    if "import" in config:
-        importing = True
-        imported_file = config["import"]["path"]
-        if not imported_file:
-            raise Exception("Config is missing import.path")
-        imported_file = os.path.join(config_dir, imported_file)
-        imported_package = config["import"]["package"]
-        if not imported_package:
-            raise Exception("Config is missing import.package")
-
-    exporting = False
-    if "export" in config:
-        exporting = True
-        exported_dirname = config["export"]["output"]
-        if not exported_dirname:
-            raise Exception("Config is missing export.output")
-        exported_dirname = os.path.join(output_base, exported_dirname)
-        exported_package = config["export"]["package"]
-        if not exported_package:
-            raise Exception("Config is missing export.package")
-
-    lib = False
-    if "lib" in config:
-        lib = True
-        lib_dirname = config["lib"]["output"]
-        if not lib_dirname:
-            raise Exception("Config is missing lib.output")
-        lib_dirname = os.path.join(output_base, lib_dirname)
-        lib_string16_include = config["lib"]["string16_impl_header_path"]
-        if not lib_string16_include:
-            raise Exception("Config is missing lib.string16_impl_header_path")
-        lib_platform_include = config["lib"]["platform_impl_header_path"]
-        if not lib_platform_include:
-            raise Exception("Config is missing lib.platform_impl_header_path")
-
-    string_type = config["string"]["class_name"]
-    if not string_type:
-        raise Exception("Config is missing string.class_name")
-
-    export_macro = config["class_export"]["macro"]
-    if not export_macro:
-        raise Exception("Config is missing class_export.macro")
-    export_macro_include = config["class_export"]["header_path"]
-    if not export_macro_include:
-        raise Exception("Config is missing class_export.header_path")
-
-    lib_package = config["lib_package"]
-    if not lib_package:
-        raise Exception("Config is missing lib_package")
-except Exception:
-    # Work with python 2 and 3 http://docs.python.org/py3k/howto/pyporting.html
-    exc = sys.exc_info()[1]
-    sys.stderr.write("Failed to parse config file: %s\n\n" % exc)
-    exit(1)
-
-
-# Make gyp / make generatos happy, otherwise make rebuilds world.
-def up_to_date():
-    template_ts = max(
-        os.path.getmtime(__file__),
-        os.path.getmtime(os.path.join(templates_dir, "TypeBuilder_h.template")),
-        os.path.getmtime(os.path.join(templates_dir, "TypeBuilder_cpp.template")),
-        os.path.getmtime(os.path.join(templates_dir, "Exported_h.template")),
-        os.path.getmtime(os.path.join(templates_dir, "Imported_h.template")),
-        os.path.getmtime(config_file),
-        os.path.getmtime(protocol_file))
-    if importing:
-        template_ts = max(template_ts, os.path.getmtime(imported_file))
-
-    for domain in json_api["domains"]:
-        name = domain["domain"]
-        paths = []
-        if name in generate_domains:
-            paths = [os.path.join(output_dirname, name + ".h"), os.path.join(output_dirname, name + ".cpp")]
-            if domain["has_exports"]:
-                paths.append(os.path.join(exported_dirname, name + ".h"))
-        if name in imported_domains and domain["has_exports"]:
-            paths = [os.path.join(output_dirname, name + '.h')]
-        for path in paths:
-            if not os.path.exists(path):
-                return False
-            generated_ts = os.path.getmtime(path)
-            if generated_ts < template_ts:
-                return False
-    return True
+    try:
+        config_json_file = open(config_file, "r")
+        config_json_string = config_json_file.read()
+        config_partial = json_to_object(config_json_string, output_base, config_base)
+        keys = list(config_partial._fields)  # pylint: disable=E1101
+        values = [getattr(config_partial, k) for k in keys]
+        for optional in ["imported", "exported", "lib"]:
+            if optional not in keys:
+                keys.append(optional)
+                values.append(False)
+        config_json_file.close()
+        return (config_file, collections.namedtuple('X', keys)(*values))
+    except Exception:
+        # Work with python 2 and 3 http://docs.python.org/py3k/howto/pyporting.html
+        exc = sys.exc_info()[1]
+        sys.stderr.write("Failed to parse config file: %s\n\n" % exc)
+        exit(1)
 
 
 def to_title_case(name):
@@ -198,11 +123,7 @@
     return jinja_env
 
 
-def output_file(file_name):
-    return open(file_name, "w")
-
-
-def patch_full_qualified_refs():
+def patch_full_qualified_refs(protocol):
     def patch_full_qualified_refs_in_domain(json, domain_name):
         if isinstance(json, list):
             for item in json:
@@ -220,11 +141,11 @@
                 json["$ref"] = domain_name + "." + json["$ref"]
         return
 
-    for domain in json_api["domains"]:
+    for domain in protocol.json_api["domains"]:
         patch_full_qualified_refs_in_domain(domain, domain["domain"])
 
 
-def calculate_exports():
+def calculate_exports(protocol):
     def calculate_exports_in_json(json_value):
         has_exports = False
         if isinstance(json_value, list):
@@ -236,15 +157,11 @@
                 has_exports = calculate_exports_in_json(json_value[key]) or has_exports
         return has_exports
 
-    json_api["has_exports"] = False
-    for domain_json in json_api["domains"]:
-        domain_name = domain_json["domain"]
+    protocol.json_api["has_exports"] = False
+    for domain_json in protocol.json_api["domains"]:
         domain_json["has_exports"] = calculate_exports_in_json(domain_json)
-        if domain_json["has_exports"] and domain_name in generate_domains:
-            if not exporting:
-                sys.stderr.write("Domain %s is exported, but config is missing export entry\n\n" % domain_name)
-                exit(1)
-            json_api["has_exports"] = True
+        if domain_json["has_exports"] and domain_json["domain"] in protocol.generate_domains:
+            protocol.json_api["has_exports"] = True
 
 
 def create_imported_type_definition(domain_name, type):
@@ -307,7 +224,7 @@
     }
 
 
-def create_string_type_definition(domain):
+def create_string_type_definition(string_type):
     # pylint: disable=W0622
     return {
         "return_type": string_type,
@@ -353,14 +270,6 @@
     }
 
 
-type_definitions = {}
-type_definitions["number"] = create_primitive_type_definition("number")
-type_definitions["integer"] = create_primitive_type_definition("integer")
-type_definitions["boolean"] = create_primitive_type_definition("boolean")
-type_definitions["object"] = create_object_type_definition()
-type_definitions["any"] = create_any_type_definition()
-
-
 def wrap_array_definition(type):
     # pylint: disable=W0622
     return {
@@ -378,36 +287,42 @@
     }
 
 
-def create_type_definitions():
-    for domain in json_api["domains"]:
-        type_definitions[domain["domain"] + ".string"] = create_string_type_definition(domain["domain"])
+def create_type_definitions(protocol, string_type):
+    protocol.type_definitions = {}
+    protocol.type_definitions["number"] = create_primitive_type_definition("number")
+    protocol.type_definitions["integer"] = create_primitive_type_definition("integer")
+    protocol.type_definitions["boolean"] = create_primitive_type_definition("boolean")
+    protocol.type_definitions["object"] = create_object_type_definition()
+    protocol.type_definitions["any"] = create_any_type_definition()
+    for domain in protocol.json_api["domains"]:
+        protocol.type_definitions[domain["domain"] + ".string"] = create_string_type_definition(string_type)
         if not ("types" in domain):
             continue
         for type in domain["types"]:
             type_name = domain["domain"] + "." + type["id"]
-            if type["type"] == "object" and domain["domain"] in imported_domains:
-                type_definitions[type_name] = create_imported_type_definition(domain["domain"], type)
+            if type["type"] == "object" and domain["domain"] in protocol.imported_domains:
+                protocol.type_definitions[type_name] = create_imported_type_definition(domain["domain"], type)
             elif type["type"] == "object":
-                type_definitions[type_name] = create_user_type_definition(domain["domain"], type)
+                protocol.type_definitions[type_name] = create_user_type_definition(domain["domain"], type)
             elif type["type"] == "array":
                 items_type = type["items"]["type"]
-                type_definitions[type_name] = wrap_array_definition(type_definitions[items_type])
+                protocol.type_definitions[type_name] = wrap_array_definition(protocol.type_definitions[items_type])
             elif type["type"] == domain["domain"] + ".string":
-                type_definitions[type_name] = create_string_type_definition(domain["domain"])
+                protocol.type_definitions[type_name] = create_string_type_definition(string_type)
             else:
-                type_definitions[type_name] = create_primitive_type_definition(type["type"])
+                protocol.type_definitions[type_name] = create_primitive_type_definition(type["type"])
 
 
-def type_definition(name):
-    return type_definitions[name]
+def type_definition(protocol, name):
+    return protocol.type_definitions[name]
 
 
-def resolve_type(property):
-    if "$ref" in property:
-        return type_definitions[property["$ref"]]
-    if property["type"] == "array":
-        return wrap_array_definition(resolve_type(property["items"]))
-    return type_definitions[property["type"]]
+def resolve_type(protocol, prop):
+    if "$ref" in prop:
+        return protocol.type_definitions[prop["$ref"]]
+    if prop["type"] == "array":
+        return wrap_array_definition(resolve_type(protocol, prop["items"]))
+    return protocol.type_definitions[prop["type"]]
 
 
 def join_arrays(dict, keys):
@@ -425,124 +340,144 @@
     return False
 
 
-def generate(domain_object, template, file_name):
-    template_context = {
-        "domain": domain_object,
-        "join_arrays": join_arrays,
-        "resolve_type": resolve_type,
-        "type_definition": type_definition,
-        "has_disable": has_disable,
-        "export_macro": export_macro,
-        "export_macro_include": export_macro_include,
-        "output_package": output_package,
-        "lib_package": lib_package
-    }
-    if exporting:
-        template_context["exported_package"] = exported_package
-    if importing:
-        template_context["imported_package"] = imported_package
-    out_file = output_file(file_name)
-    out_file.write(template.render(template_context))
-    out_file.close()
-
-
-def read_protocol_file(file_name, all_domains):
+def read_protocol_file(file_name, json_api):
     input_file = open(file_name, "r")
     json_string = input_file.read()
+    input_file.close()
     parsed_json = json.loads(json_string)
     domains = []
     for domain in parsed_json["domains"]:
         domains.append(domain["domain"])
-    all_domains["domains"] += parsed_json["domains"]
+    json_api["domains"] += parsed_json["domains"]
     return domains
 
 
-def generate_lib():
-    template_context = {
-        "string16_impl_h_include": lib_string16_include,
-        "platform_impl_h_include": lib_platform_include,
-        "lib_package": lib_package,
-        "export_macro": export_macro,
-        "export_macro_include": export_macro_include
-    }
+class Protocol(object):
+    def __init__(self):
+        self.json_api = {}
+        self.generate_domains = []
+        self.imported_domains = []
 
-    def generate_file(file_name, template_files):
-        out_file = output_file(file_name)
-        for template_file in template_files:
-            template = jinja_env.get_template("/" + template_file)
-            out_file.write(template.render(template_context))
-            out_file.write("\n\n")
+
+def main():
+    config_file, config = read_config()
+
+    protocol = Protocol()
+    protocol.json_api = {"domains": []}
+    protocol.generate_domains = read_protocol_file(config.protocol.path, protocol.json_api)
+    protocol.imported_domains = read_protocol_file(config.imported.path, protocol.json_api) if config.imported else []
+    patch_full_qualified_refs(protocol)
+    calculate_exports(protocol)
+    create_type_definitions(protocol, config.string.class_name)
+
+    if not config.exported:
+        for domain_json in protocol.json_api["domains"]:
+            if domain_json["has_exports"] and domain_json["domain"] in protocol.generate_domains:
+                sys.stderr.write("Domain %s is exported, but config is missing export entry\n\n" % domain_json["domain"])
+                exit(1)
+
+    if not os.path.exists(config.protocol.output):
+        os.mkdir(config.protocol.output)
+    if protocol.json_api["has_exports"] and not os.path.exists(config.exported.output):
+        os.mkdir(config.exported.output)
+    jinja_env = initialize_jinja_env(config.protocol.output)
+
+    inputs = []
+    inputs.append(__file__)
+    inputs.append(config_file)
+    inputs.append(config.protocol.path)
+    if config.imported:
+        inputs.append(config.imported.path)
+    inputs.append(os.path.join(templates_dir, "TypeBuilder_h.template"))
+    inputs.append(os.path.join(templates_dir, "TypeBuilder_cpp.template"))
+    inputs.append(os.path.join(templates_dir, "Exported_h.template"))
+    inputs.append(os.path.join(templates_dir, "Imported_h.template"))
+
+    h_template = jinja_env.get_template("TypeBuilder_h.template")
+    cpp_template = jinja_env.get_template("TypeBuilder_cpp.template")
+    exported_template = jinja_env.get_template("Exported_h.template")
+    imported_template = jinja_env.get_template("Imported_h.template")
+
+    outputs = dict()
+
+    for domain in protocol.json_api["domains"]:
+        class_name = domain["domain"]
+        template_context = {
+            "config": config,
+            "domain": domain,
+            "join_arrays": join_arrays,
+            "resolve_type": functools.partial(resolve_type, protocol),
+            "type_definition": functools.partial(type_definition, protocol),
+            "has_disable": has_disable
+        }
+
+        if domain["domain"] in protocol.generate_domains:
+            outputs[os.path.join(config.protocol.output, class_name + ".h")] = h_template.render(template_context)
+            outputs[os.path.join(config.protocol.output, class_name + ".cpp")] = cpp_template.render(template_context)
+            if domain["has_exports"]:
+                outputs[os.path.join(config.exported.output, class_name + ".h")] = exported_template.render(template_context)
+        if domain["domain"] in protocol.imported_domains and domain["has_exports"]:
+            outputs[os.path.join(config.protocol.output, class_name + ".h")] = imported_template.render(template_context)
+
+    if config.lib:
+        template_context = {
+            "config": config
+        }
+
+        # Note these should be sorted in the right order.
+        # TODO(dgozman): sort them programmatically based on commented includes.
+        lib_h_templates = [
+            "Allocator_h.template",
+            "Platform_h.template",
+            "Collections_h.template",
+            "String16_h.template",
+            "ErrorSupport_h.template",
+            "Values_h.template",
+            "Object_h.template",
+            "ValueConversions_h.template",
+            "Maybe_h.template",
+            "Array_h.template",
+            "FrontendChannel_h.template",
+            "BackendCallback_h.template",
+            "DispatcherBase_h.template",
+            "Parser_h.template",
+        ]
+
+        lib_cpp_templates = [
+            "InspectorProtocol_cpp.template",
+            "String16_cpp.template",
+            "ErrorSupport_cpp.template",
+            "Values_cpp.template",
+            "Object_cpp.template",
+            "DispatcherBase_cpp.template",
+            "Parser_cpp.template",
+        ]
+
+        def generate_lib_file(file_name, template_files):
+            parts = []
+            for template_file in template_files:
+                inputs.append(os.path.join(templates_dir, template_file))
+                template = jinja_env.get_template(template_file)
+                parts.append(template.render(template_context))
+            outputs[file_name] = "\n\n".join(parts)
+
+        generate_lib_file(os.path.join(config.lib.output, "InspectorProtocol.h"), lib_h_templates)
+        generate_lib_file(os.path.join(config.lib.output, "InspectorProtocol.cpp"), lib_cpp_templates)
+
+    # Make gyp / make generatos happy, otherwise make rebuilds world.
+    inputs_ts = max(map(os.path.getmtime, inputs))
+    up_to_date = True
+    for output_file in outputs.iterkeys():
+        if not os.path.exists(output_file) or os.path.getmtime(output_file) < inputs_ts:
+            up_to_date = False
+            break
+    if up_to_date:
+        sys.exit()
+
+    for file_name, content in outputs.iteritems():
+        out_file = open(file_name, "w")
+        out_file.write(content)
         out_file.close()
 
-    # Note these should be sorted in the right order.
-    # TODO(dgozman): sort them programmatically based on commented includes.
-    lib_h_templates = [
-        "Allocator_h.template",
-        "Platform_h.template",
-        "Collections_h.template",
-        "String16_h.template",
 
-        "ErrorSupport_h.template",
-        "Values_h.template",
-        "Object_h.template",
-        "ValueConversions_h.template",
-        "Maybe_h.template",
-        "Array_h.template",
-
-        "FrontendChannel_h.template",
-        "BackendCallback_h.template",
-        "DispatcherBase_h.template",
-
-        "Parser_h.template",
-    ]
-
-    lib_cpp_templates = [
-        "InspectorProtocol_cpp.template",
-
-        "String16_cpp.template",
-
-        "ErrorSupport_cpp.template",
-        "Values_cpp.template",
-        "Object_cpp.template",
-
-        "DispatcherBase_cpp.template",
-
-        "Parser_cpp.template",
-    ]
-
-    generate_file(os.path.join(lib_dirname, "InspectorProtocol.h"), lib_h_templates)
-    generate_file(os.path.join(lib_dirname, "InspectorProtocol.cpp"), lib_cpp_templates)
-
-
-json_api = {"domains": []}
-generate_domains = read_protocol_file(protocol_file, json_api)
-imported_domains = read_protocol_file(imported_file, json_api) if importing else []
-patch_full_qualified_refs()
-calculate_exports()
-create_type_definitions()
-
-if up_to_date():
-    sys.exit()
-if not os.path.exists(output_dirname):
-    os.mkdir(output_dirname)
-if json_api["has_exports"] and not os.path.exists(exported_dirname):
-    os.mkdir(exported_dirname)
-
-jinja_env = initialize_jinja_env(output_dirname)
-h_template = jinja_env.get_template("/TypeBuilder_h.template")
-cpp_template = jinja_env.get_template("/TypeBuilder_cpp.template")
-exported_template = jinja_env.get_template("/Exported_h.template")
-imported_template = jinja_env.get_template("/Imported_h.template")
-
-for domain in json_api["domains"]:
-    class_name = domain["domain"]
-    if domain["domain"] in generate_domains:
-        generate(domain, h_template, os.path.join(output_dirname, class_name + ".h"))
-        generate(domain, cpp_template, os.path.join(output_dirname, class_name + ".cpp"))
-        if domain["has_exports"]:
-            generate(domain, exported_template, os.path.join(exported_dirname, class_name + ".h"))
-    if domain["domain"] in imported_domains and domain["has_exports"]:
-        generate(domain, imported_template, os.path.join(output_dirname, class_name + ".h"))
-
-if lib:
-    generate_lib()
+main()
diff --git a/DispatcherBase_h.template b/DispatcherBase_h.template
index 76b7958..4402d96 100644
--- a/DispatcherBase_h.template
+++ b/DispatcherBase_h.template
@@ -11,7 +11,7 @@
 //#include "Platform.h"
 //#include "String16.h"
 //#include "Values.h"
-#include "{{export_macro_include}}"
+#include "{{config.class_export.header}}"
 
 namespace blink {
 namespace protocol {
@@ -19,11 +19,11 @@
 class FrontendChannel;
 class WeakPtr;
 
-class {{export_macro}} DispatcherBase {
+class {{config.class_export.macro}} DispatcherBase {
     PROTOCOL_DISALLOW_COPY(DispatcherBase);
 public:
     static const char kInvalidRequest[];
-    class {{export_macro}} WeakPtr {
+    class {{config.class_export.macro}} WeakPtr {
     public:
         explicit WeakPtr(DispatcherBase*);
         ~WeakPtr();
@@ -34,7 +34,7 @@
         DispatcherBase* m_dispatcher;
     };
 
-    class {{export_macro}} Callback : public protocol::BackendCallback {
+    class {{config.class_export.macro}} Callback : public protocol::BackendCallback {
     public:
         Callback(std::unique_ptr<WeakPtr> backendImpl, int callId);
         virtual ~Callback();
@@ -78,7 +78,7 @@
     protocol::HashSet<WeakPtr*> m_weakPtrs;
 };
 
-class {{export_macro}} UberDispatcher {
+class {{config.class_export.macro}} UberDispatcher {
     PROTOCOL_DISALLOW_COPY(UberDispatcher);
 public:
     explicit UberDispatcher(FrontendChannel*);
diff --git a/ErrorSupport_h.template b/ErrorSupport_h.template
index 50557c0..f837bff 100644
--- a/ErrorSupport_h.template
+++ b/ErrorSupport_h.template
@@ -7,7 +7,7 @@
 
 //#include "Platform.h"
 //#include "String16.h"
-#include "{{export_macro_include}}"
+#include "{{config.class_export.header}}"
 
 #include <vector>
 
@@ -16,7 +16,7 @@
 
 using ErrorString = String16;
 
-class {{export_macro}} ErrorSupport {
+class {{config.class_export.macro}} ErrorSupport {
 public:
     ErrorSupport();
     ErrorSupport(String16* errorString);
diff --git a/Exported_h.template b/Exported_h.template
index 116a441..66cf7f9 100644
--- a/Exported_h.template
+++ b/Exported_h.template
@@ -7,8 +7,8 @@
 #ifndef protocol_{{domain.domain}}_api_h
 #define protocol_{{domain.domain}}_api_h
 
-#include "{{export_macro_include}}"
-#include "{{lib_package}}/InspectorProtocol.h"
+#include "{{config.class_export.header}}"
+#include "{{config.lib_package}}/InspectorProtocol.h"
 
 namespace blink {
 namespace protocol {
@@ -21,7 +21,7 @@
 
 namespace {{type.id}}Enum {
       {% for literal in type.enum %}
-{{export_macro}} extern const char* {{ literal | dash_to_camelcase}};
+{{config.class_export.macro}} extern const char* {{ literal | dash_to_camelcase}};
       {% endfor %}
 } // {{type.id}}Enum
     {% endif %}
@@ -33,7 +33,7 @@
 namespace {{command.name | to_title_case}} {
 namespace {{param.name | to_title_case}}Enum {
         {% for literal in param.enum %}
-{{export_macro}} extern const char* {{ literal | dash_to_camelcase}};
+{{config.class_export.macro}} extern const char* {{ literal | dash_to_camelcase}};
         {% endfor %}
 } // {{param.name | to_title_case}}Enum
 } // {{command.name | to_title_case }}
@@ -45,7 +45,7 @@
   {% for type in domain.types %}
     {% if not (type.type == "object") or not ("properties" in type) or not (type.exported) %}{% continue %}{% endif %}
 
-class {{export_macro}} {{type.id}} {
+class {{config.class_export.macro}} {{type.id}} {
 public:
     virtual String16 toJSONString() const = 0;
     virtual ~{{type.id}}() { }
diff --git a/FrontendChannel_h.template b/FrontendChannel_h.template
index 0b7025f..ead1491 100644
--- a/FrontendChannel_h.template
+++ b/FrontendChannel_h.template
@@ -6,12 +6,12 @@
 #define FrontendChannel_h
 
 //#include "String16.h"
-#include "{{export_macro_include}}"
+#include "{{config.class_export.header}}"
 
 namespace blink {
 namespace protocol {
 
-class {{export_macro}} FrontendChannel {
+class {{config.class_export.macro}} FrontendChannel {
 public:
     virtual ~FrontendChannel() { }
     virtual void sendProtocolResponse(int callId, const String16& message) = 0;
diff --git a/Imported_h.template b/Imported_h.template
index 2dd30bc..2d4e11a 100644
--- a/Imported_h.template
+++ b/Imported_h.template
@@ -7,8 +7,8 @@
 #ifndef protocol_{{domain.domain}}_imported_h
 #define protocol_{{domain.domain}}_imported_h
 
-#include "{{lib_package}}/InspectorProtocol.h"
-#include "{{imported_package}}/{{domain.domain}}.h"
+#include "{{config.lib_package}}/InspectorProtocol.h"
+#include "{{config.imported.package}}/{{domain.domain}}.h"
 
 namespace blink {
 namespace protocol {
diff --git a/InspectorProtocol_cpp.template b/InspectorProtocol_cpp.template
index 153dbf0..e7c49fd 100644
--- a/InspectorProtocol_cpp.template
+++ b/InspectorProtocol_cpp.template
@@ -2,4 +2,4 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "{{lib_package}}/InspectorProtocol.h"
+#include "{{config.lib_package}}/InspectorProtocol.h"
diff --git a/Object_h.template b/Object_h.template
index 8b3e3ec..dc7f44b 100644
--- a/Object_h.template
+++ b/Object_h.template
@@ -8,12 +8,12 @@
 //#include "ErrorSupport.h"
 //#include "Platform.h"
 //#include "Values.h"
-#include "{{export_macro_include}}"
+#include "{{config.class_export.header}}"
 
 namespace blink {
 namespace protocol {
 
-class {{export_macro}} Object {
+class {{config.class_export.macro}} Object {
 public:
     static std::unique_ptr<Object> parse(protocol::Value*, ErrorSupport*);
     ~Object();
diff --git a/Parser_h.template b/Parser_h.template
index eab025f..1f3a1b6 100644
--- a/Parser_h.template
+++ b/Parser_h.template
@@ -7,14 +7,14 @@
 
 //#include "Platform.h"
 //#include "String16.h"
-#include "{{export_macro_include}}"
+#include "{{config.class_export.header}}"
 
 namespace blink {
 namespace protocol {
 
 class Value;
 
-{{export_macro}} std::unique_ptr<Value> parseJSON(const String16& json);
+{{config.class_export.macro}} std::unique_ptr<Value> parseJSON(const String16& json);
 
 } // namespace platform
 } // namespace blink
diff --git a/Platform_h.template b/Platform_h.template
index f09f192..41b8ef2 100644
--- a/Platform_h.template
+++ b/Platform_h.template
@@ -5,6 +5,6 @@
 #ifndef protocol_Platform_h
 #define protocol_Platform_h
 
-#include "{{platform_impl_h_include}}"
+#include "{{config.lib.platform_header}}"
 
 #endif // !defined(protocol_Platform_h)
diff --git a/String16_h.template b/String16_h.template
index 005f349..e6f8f0c 100644
--- a/String16_h.template
+++ b/String16_h.template
@@ -7,7 +7,7 @@
 
 //#include "Collections.h"
 //#include "Platform.h"
-#include "{{export_macro_include}}"
+#include "{{config.class_export.header}}"
 
 #include <vector>
 
@@ -15,16 +15,16 @@
 namespace protocol {
 
 namespace internal {
-{{export_macro}} void intToStr(int, char*, size_t);
-{{export_macro}} void doubleToStr(double, char*, size_t);
-{{export_macro}} void doubleToStr3(double, char*, size_t);
-{{export_macro}} void doubleToStr6(double, char*, size_t);
-{{export_macro}} double strToDouble(const char*, bool*);
-{{export_macro}} int strToInt(const char*, bool*);
+{{config.class_export.macro}} void intToStr(int, char*, size_t);
+{{config.class_export.macro}} void doubleToStr(double, char*, size_t);
+{{config.class_export.macro}} void doubleToStr3(double, char*, size_t);
+{{config.class_export.macro}} void doubleToStr6(double, char*, size_t);
+{{config.class_export.macro}} double strToDouble(const char*, bool*);
+{{config.class_export.macro}} int strToInt(const char*, bool*);
 } // namespace internal
 
 template <typename T, typename C>
-class {{export_macro}} String16Base {
+class {{config.class_export.macro}} String16Base {
 public:
     static bool isASCII(C c)
     {
@@ -152,12 +152,12 @@
 } // namespace protocol
 } // namespace blink
 
-#include "{{string16_impl_h_include}}"
+#include "{{config.lib.string16_header}}"
 
 namespace blink {
 namespace protocol {
 
-class {{export_macro}} String16Builder {
+class {{config.class_export.macro}} String16Builder {
 public:
     String16Builder();
     void append(const String16&);
diff --git a/TypeBuilder_cpp.template b/TypeBuilder_cpp.template
index 1e76823..8f0412a 100644
--- a/TypeBuilder_cpp.template
+++ b/TypeBuilder_cpp.template
@@ -4,7 +4,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "{{output_package}}/{{domain.domain}}.h"
+#include "{{config.protocol.package}}/{{domain.domain}}.h"
 
 namespace blink {
 namespace protocol {
@@ -37,7 +37,7 @@
       {% if "enum" in property %}
 
         {% for literal in property.enum %}
-const char* {{type.id}}::{{property.name | to_title_case}}Enum::{{ literal | dash_to_camelcase}} = "{{literal}}";
+const char* {{type.id}}::{{property.name | to_title_case}}Enum::{{literal | dash_to_camelcase}} = "{{literal}}";
         {% endfor %}
       {% endif %}
     {% endfor %}
diff --git a/TypeBuilder_h.template b/TypeBuilder_h.template
index eb7fe2f..1b421d6 100644
--- a/TypeBuilder_h.template
+++ b/TypeBuilder_h.template
@@ -7,15 +7,15 @@
 #ifndef protocol_{{domain.domain}}_h
 #define protocol_{{domain.domain}}_h
 
-#include "{{export_macro_include}}"
-#include "{{lib_package}}/InspectorProtocol.h"
+#include "{{config.class_export.header}}"
+#include "{{config.lib_package}}/InspectorProtocol.h"
 // For each imported domain we generate a ValueConversions struct instead of a full domain definition
 // and include Domain::API version from there.
 {% for name in domain.dependencies %}
-#include "{{output_package}}/{{name}}.h"
+#include "{{config.protocol.package}}/{{name}}.h"
 {% endfor %}
 {% if domain["has_exports"] %}
-#include "{{exported_package}}/{{domain.domain}}.h"
+#include "{{config.exported.package}}/{{domain.domain}}.h"
 {% endif %}
 
 namespace blink {
@@ -42,7 +42,7 @@
 
 namespace {{type.id}}Enum {
       {% for literal in type.enum %}
-{{export_macro}} extern const char* {{ literal | dash_to_camelcase}};
+{{config.class_export.macro}} extern const char* {{ literal | dash_to_camelcase}};
       {% endfor %}
 } // namespace {{type.id}}Enum
     {% endif %}
@@ -54,7 +54,7 @@
 namespace {{command.name | to_title_case}} {
 namespace {{param.name | to_title_case}}Enum {
         {% for literal in param.enum %}
-{{export_macro}} extern const char* {{ literal | dash_to_camelcase}};
+{{config.class_export.macro}} extern const char* {{literal | dash_to_camelcase}};
         {% endfor %}
 } // {{param.name | to_title_case}}Enum
 } // {{command.name | to_title_case }}
@@ -68,7 +68,7 @@
     {% set type_def = type_definition(domain.domain + "." + type.id)%}
 
 // {{type.description}}
-class {{export_macro}} {{type.id}} {% if type.exported %}: public API::{{type.id}} {% endif %}{
+class {{config.class_export.macro}} {{type.id}} {% if type.exported %}: public API::{{type.id}} {% endif %}{
     PROTOCOL_DISALLOW_COPY({{type.id}});
 public:
     static std::unique_ptr<{{type.id}}> parse(protocol::Value* value, ErrorSupport* errors);
@@ -77,9 +77,9 @@
     {% for property in type.properties %}
       {% if "enum" in property %}
 
-    struct {{export_macro}} {{property.name | to_title_case}}Enum {
+    struct {{config.class_export.macro}} {{property.name | to_title_case}}Enum {
         {% for literal in property.enum %}
-        static const char* {{ literal | dash_to_camelcase}};
+        static const char* {{literal | dash_to_camelcase}};
         {% endfor %}
     }; // {{property.name | to_title_case}}Enum
       {% endif %}
@@ -180,13 +180,13 @@
 
 // ------------- Backend interface.
 
-class {{export_macro}} Backend {
+class {{config.class_export.macro}} Backend {
 public:
   {% for command in domain.commands %}
     {% if "redirect" in command %}{% continue %}{% endif %}
     {% if ("handlers" in command) and not ("renderer" in command["handlers"]) %}{% continue %}{% endif %}
     {% if "async" in command %}
-    class {{export_macro}} {{command.name | to_title_case}}Callback : public BackendCallback {
+    class {{config.class_export.macro}} {{command.name | to_title_case}}Callback : public BackendCallback {
     public:
         virtual void sendSuccess(
             {%- for parameter in command.returns -%}
@@ -237,7 +237,7 @@
 
 // ------------- Frontend interface.
 
-class {{export_macro}} Frontend {
+class {{config.class_export.macro}} Frontend {
 public:
     Frontend(FrontendChannel* frontendChannel) : m_frontendChannel(frontendChannel) { }
   {% for event in domain.events %}
@@ -260,7 +260,7 @@
 
 // ------------- Dispatcher.
 
-class {{export_macro}} Dispatcher {
+class {{config.class_export.macro}} Dispatcher {
 public:
     static void wire(UberDispatcher*, blink::protocol::{{domain.domain}}::Backend*);
 
@@ -270,7 +270,7 @@
 
 // ------------- Metainfo.
 
-class {{export_macro}} Metainfo {
+class {{config.class_export.macro}} Metainfo {
 public:
     using BackendClass = Backend;
     using FrontendClass = Frontend;
diff --git a/Values_h.template b/Values_h.template
index 840d119..9874183 100644
--- a/Values_h.template
+++ b/Values_h.template
@@ -9,7 +9,7 @@
 //#include "Collections.h"
 //#include "Platform.h"
 //#include "String16.h"
-#include "{{export_macro_include}}"
+#include "{{config.class_export.header}}"
 
 #include <vector>
 
@@ -20,7 +20,7 @@
 class DictionaryValue;
 class Value;
 
-class {{export_macro}} Value {
+class {{config.class_export.macro}} Value {
     PROTOCOL_DISALLOW_COPY(Value);
 public:
     static const int maxDepth = 1000;
@@ -68,7 +68,7 @@
     ValueType m_type;
 };
 
-class {{export_macro}} FundamentalValue : public Value {
+class {{config.class_export.macro}} FundamentalValue : public Value {
 public:
     static std::unique_ptr<FundamentalValue> create(bool value)
     {
@@ -103,7 +103,7 @@
     };
 };
 
-class {{export_macro}} StringValue : public Value {
+class {{config.class_export.macro}} StringValue : public Value {
 public:
     static std::unique_ptr<StringValue> create(const String16& value)
     {
@@ -126,7 +126,7 @@
     String16 m_stringValue;
 };
 
-class {{export_macro}} SerializedValue : public Value {
+class {{config.class_export.macro}} SerializedValue : public Value {
 public:
     static std::unique_ptr<SerializedValue> create(const String16& value)
     {
@@ -144,7 +144,7 @@
     String16 m_serializedValue;
 };
 
-class {{export_macro}} DictionaryValue : public Value {
+class {{config.class_export.macro}} DictionaryValue : public Value {
 public:
     using Entry = std::pair<String16, Value*>;
     static std::unique_ptr<DictionaryValue> create()
@@ -211,7 +211,7 @@
     std::vector<String16> m_order;
 };
 
-class {{export_macro}} ListValue : public Value {
+class {{config.class_export.macro}} ListValue : public Value {
 public:
     static std::unique_ptr<ListValue> create()
     {
diff --git a/sample_config.json b/sample_config.json
index 48cad2e..5ad6722 100644
--- a/sample_config.json
+++ b/sample_config.json
@@ -5,12 +5,12 @@
         "output": "place/generated/files/here"
     },
 
-    "import": {
+    "imported": {
         "path": "../relative/path/imported_protocol.json",
         "package": "include/imported/files/like/this"
     },
 
-    "export": {
+    "exported": {
         "package": "include/exported/files/like/this",
         "output": "place/exported/files/here"
     },
@@ -21,14 +21,14 @@
 
     "lib": {
         "output": "place/generated/lib/files/here",
-        "string16_impl_header_path": "sting16/implementation.h",
-        "platform_impl_header_path": "platform/implementation.h"
+        "string16_header": "sting16/implementation.h",
+        "platform_header": "platform/implementation.h"
     },
 
     "lib_package": "include/lib/files/like/this",
 
     "class_export": {
         "macro": "LIB_EXPORT",
-        "header_path": "lib/export.h"
+        "header": "lib/export.h"
     }
 }