1#!/usr/bin/env python
2# Copyright 2016 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5"""Generates a JSON typemap from its command-line arguments and dependencies.
6
7Each typemap should be specified in an command-line argument of the form
8key=value, with an argument of "--start-typemap" preceding each typemap.
9
10For example,
11generate_type_mappings.py --output=foo.typemap --start-typemap \\
12    public_headers=foo.h traits_headers=foo_traits.h \\
13    type_mappings=mojom.Foo=FooImpl
14
15generates a foo.typemap containing
16{
17  "c++": {
18    "mojom.Foo": {
19      "typename": "FooImpl",
20      "traits_headers": [
21        "foo_traits.h"
22      ],
23      "public_headers": [
24        "foo.h"
25      ]
26    }
27  }
28}
29
30Then,
31generate_type_mappings.py --dependency foo.typemap --output=bar.typemap \\
32    --start-typemap public_headers=bar.h traits_headers=bar_traits.h \\
33    type_mappings=mojom.Bar=BarImpl
34
35generates a bar.typemap containing
36{
37  "c++": {
38    "mojom.Bar": {
39      "typename": "BarImpl",
40      "traits_headers": [
41        "bar_traits.h"
42      ],
43      "public_headers": [
44        "bar.h"
45      ]
46    },
47    "mojom.Foo": {
48      "typename": "FooImpl",
49      "traits_headers": [
50        "foo_traits.h"
51      ],
52      "public_headers": [
53        "foo.h"
54      ]
55    }
56  }
57}
58"""
59
60import argparse
61import json
62import os
63import re
64
65
66def ReadTypemap(path):
67  with open(path) as f:
68    return json.load(f)['c++']
69
70
71def ParseTypemapArgs(args):
72  typemaps = [s for s in '\n'.join(args).split('--start-typemap\n') if s]
73  result = {}
74  for typemap in typemaps:
75    result.update(ParseTypemap(typemap))
76  return result
77
78
79def ParseTypemap(typemap):
80  values = {'type_mappings': [], 'public_headers': [], 'traits_headers': []}
81  for line in typemap.split('\n'):
82    if not line:
83      continue
84    key, _, value = line.partition('=')
85    values[key].append(value.lstrip('/'))
86  result = {}
87  mapping_pattern = \
88      re.compile(r"""^([^=]+)           # mojom type
89                     =
90                     ([^[]+)            # native type
91                     (?:\[([^]]+)\])?$  # optional attribute in square brackets
92                 """, re.X)
93  for typename in values['type_mappings']:
94    match_result = mapping_pattern.match(typename)
95    assert match_result, (
96        "Cannot parse entry in the \"type_mappings\" section: %s" % typename)
97
98    mojom_type = match_result.group(1)
99    native_type = match_result.group(2)
100    attributes = []
101    if match_result.group(3):
102      attributes = match_result.group(3).split(',')
103
104    assert mojom_type not in result, (
105        "Cannot map multiple native types (%s, %s) to the same mojom type: %s" %
106        (result[mojom_type]['typename'], native_type, mojom_type))
107
108    result[mojom_type] = {
109        'typename': native_type,
110        'non_copyable_non_movable': 'non_copyable_non_movable' in attributes,
111        'move_only': 'move_only' in attributes,
112        'copyable_pass_by_value': 'copyable_pass_by_value' in attributes,
113        'nullable_is_same_type': 'nullable_is_same_type' in attributes,
114        'hashable': 'hashable' in attributes,
115        'public_headers': values['public_headers'],
116        'traits_headers': values['traits_headers'],
117    }
118  return result
119
120
121def main():
122  parser = argparse.ArgumentParser(
123      description=__doc__,
124      formatter_class=argparse.RawDescriptionHelpFormatter)
125  parser.add_argument(
126      '--dependency',
127      type=str,
128      action='append',
129      default=[],
130      help=('A path to another JSON typemap to merge into the output. '
131            'This may be repeated to merge multiple typemaps.'))
132  parser.add_argument('--output',
133                      type=str,
134                      required=True,
135                      help='The path to which to write the generated JSON.')
136  params, typemap_params = parser.parse_known_args()
137  typemaps = ParseTypemapArgs(typemap_params)
138  missing = [path for path in params.dependency if not os.path.exists(path)]
139  if missing:
140    raise IOError('Missing dependencies: %s' % ', '.join(missing))
141  for path in params.dependency:
142    typemaps.update(ReadTypemap(path))
143  with open(params.output, 'w') as f:
144    json.dump({'c++': typemaps}, f, indent=2)
145
146
147if __name__ == '__main__':
148  main()
149