summaryrefslogtreecommitdiff
path: root/chromium/components/policy/tools/template_writers/policy_template_generator.py
blob: 99a9e03c89f97e7ff69cd5f6a76ff720553d2cbf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
#!/usr/bin/env python3
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import copy
import os
import re
import sys

sys.path.insert(
    0,
    os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, os.pardir,
                 os.pardir, 'third_party', 'six', 'src'))

import six


def IsGroupOrAtomicGroup(policy):
  return policy['type'] == 'group' or policy['type'] == 'atomic_group'


class PolicyTemplateGenerator:
  '''Generates template text for a particular platform.

  This class is used to traverse a JSON structure from a .json template
  definition metafile and merge GUI message string definitions that come
  from a .grd resource tree onto it. After this, it can be used to output
  this data to policy template files using TemplateWriter objects.
  '''

  def _ImportMessage(self, msg_txt):
    msg_txt = six.ensure_text(msg_txt)
    lines = msg_txt.split('\n')

    # Strip any extra leading spaces, but keep useful indentation:
    min_leading_spaces = min(list(self._IterateLeadingSpaces(lines)) or [0])
    if min_leading_spaces > 0:
      lstrip_pattern = re.compile('^[ ]{1,%s}' % min_leading_spaces)
      lines = [lstrip_pattern.sub('', line) for line in lines]
    # Strip all trailing spaces:
    lines = [line.rstrip() for line in lines]
    return "\n".join(lines)

  def _IterateLeadingSpaces(self, lines):
    '''Yields the number of leading spaces on each line, skipping lines which
    have no leading spaces.'''
    for line in lines:
      match = re.search('^[ ]+', line)
      if match:
        yield len(match.group(0))

  def __init__(self, config, policy_data):
    '''Initializes this object with all the data necessary to output a
    policy template.

    Args:
      config: Writer configuration.
      policy_data: The list of defined policies and groups, as parsed from the
        policy metafile. See
          components/policy/resources/policy_templates.json
        for description and content.
            '''
    # List of all the policies. Create a copy since the data is modified.
    self._policy_data = copy.deepcopy(policy_data)
    # Localized messages to be inserted to the policy_definitions structure:
    self._messages = self._policy_data['messages']
    self._config = config
    for key in self._messages.keys():
      self._messages[key]['text'] = self._ImportMessage(
          self._messages[key]['text'])
    self._AddGroups(self._policy_data['policy_definitions'])
    self._AddAtomicGroups(self._policy_data['policy_definitions'],
                          self._policy_data['policy_atomic_group_definitions'])
    self._policy_data[
        'policy_atomic_group_definitions'] = self._ExpandAtomicGroups(
            self._policy_data['policy_definitions'],
            self._policy_data['policy_atomic_group_definitions'])
    self._ProcessPolicyList(
        self._policy_data['policy_atomic_group_definitions'])
    self._policy_data['policy_definitions'] = self._ExpandGroups(
        self._policy_data['policy_definitions'])
    self._policy_definitions = self._policy_data['policy_definitions']
    self._ProcessPolicyList(self._policy_definitions)

  def _ProcessProductPlatformString(self, product_platform_string):
    '''Splits the |product_platform_string| string to product and a list of
    platforms.'''
    if '.' in product_platform_string:
      product, platform = product_platform_string.split('.')
      if platform == '*':
        # e.g.: 'chrome.*:8-10'
        platforms = ['linux', 'mac', 'win']
      else:
        # e.g.: 'chrome.win:-10'
        platforms = [platform]
    else:
      # e.g.: 'chrome_frame:7-'
      product, platform = {
          'android': ('chrome', 'android'),
          'webview_android': ('webview', 'android'),
          'ios': ('chrome', 'ios'),
          'chrome_os': ('chrome_os', 'chrome_os'),
          'chrome_frame': ('chrome_frame', 'win'),
      }[product_platform_string]
      platforms = [platform]
    return product, platforms

  def _ProcessSupportedOn(self, supported_on):
    '''Parses and converts the string items of the list of supported platforms
    into dictionaries.

    Args:
      supported_on: The list of supported platforms. E.g.:
        ['chrome.win:8-10', 'chrome_frame:10-']

    Returns:
      supported_on: The list with its items converted to dictionaries. E.g.:
      [{
        'product': 'chrome',
        'platform': 'win',
        'since_version': '8',
        'until_version': '10'
      }, {
        'product': 'chrome_frame',
        'platform': 'win',
        'since_version': '10',
        'until_version': ''
      }]
    '''
    result = []
    for supported_on_item in supported_on:
      product_platform_part, version_part = supported_on_item.split(':')
      product, platforms = self._ProcessProductPlatformString(
          product_platform_part)

      since_version, until_version = version_part.split('-')
      for platform in platforms:
        result.append({
            'product': product,
            'platform': platform,
            'since_version': since_version,
            'until_version': until_version
        })
    return result

  def _ProcessFutureOn(self, future_on):
    '''Parses and converts the |future_on| strings into a list of dictionaries
    contain product and platform string pair.

    Args:
      future_on: A list of platform strings. E.g.:
      ['chrome.win', 'chromeos']
    Returns:
      future_on: A list of dictionaries. E.g.:
      [{
        'product': 'chrome',
        'platform': 'win',
      },{
        'product': 'chrome_os',
        'platform': 'chrome_os',
      }]
    '''
    result = []
    for future in future_on:
      product, platforms = self._ProcessProductPlatformString(future)
      for platform in platforms:
        result.append({
            'product': product,
            'platform': platform,
        })
    return result

  def _ProcessPolicy(self, policy):
    '''Processes localized message strings in a policy or a group.
     Also breaks up the content of 'supported_on' attribute into a list.

    Args:
      policy: The data structure of the policy or group, that will get message
        strings here.
    '''
    if policy['type'] != 'atomic_group':
      policy['desc'] = self._ImportMessage(policy['desc'])
    policy['caption'] = self._ImportMessage(policy['caption'])
    if 'label' in policy:
      policy['label'] = self._ImportMessage(policy['label'])
    if 'arc_support' in policy:
      policy['arc_support'] = self._ImportMessage(policy['arc_support'])

    if IsGroupOrAtomicGroup(policy):
      self._ProcessPolicyList(policy['policies'])
    elif policy['type'] in ('string-enum', 'int-enum', 'string-enum-list'):
      # Iterate through all the items of an enum-type policy, and add captions.
      for item in policy['items']:
        item['caption'] = self._ImportMessage(item['caption'])
        if 'supported_on' in item:
          item['supported_on'] = self._ProcessSupportedOn(item['supported_on'])
    if not IsGroupOrAtomicGroup(policy):
      if not 'label' in policy:
        # If 'label' is not specified, then it defaults to 'caption':
        policy['label'] = policy['caption']
      policy['supported_on'] = self._ProcessSupportedOn(
          policy.get('supported_on', []))
      policy['future_on'] = self._ProcessFutureOn(policy.get('future_on', []))

  def _ProcessPolicyList(self, policy_list):
    '''Adds localized message strings to each item in a list of policies and
    groups. Also breaks up the content of 'supported_on' attributes into lists
    of dictionaries.

    Args:
      policy_list: A list of policies and groups. Message strings will be added
        for each item and to their child items, recursively.
    '''
    for policy in policy_list:
      self._ProcessPolicy(policy)

  def GetTemplateText(self, template_writer):
    '''Generates the text of the template from the arguments given
    to the constructor, using a given TemplateWriter.

    Args:
      template_writer: An object implementing TemplateWriter. Its methods
        are called here for each item of self._policy_data.

    Returns:
      The text of the generated template.
    '''
    # Create a copy, so that writers can't screw up subsequent writers.
    policy_data_copy = copy.deepcopy(self._policy_data)
    return template_writer.WriteTemplate(policy_data_copy)


  def _AddGroups(self, policy_list):
    '''Adds a 'group' field, which is set to be the group's name, to the
       policies that are part of a group.

    Args:
      policy_list: A list of policies and groups whose policies will have a
      'group' field added.
    '''
    groups = [policy for policy in policy_list if policy['type'] == 'group']
    policy_lookup = {
        policy['name']: policy
        for policy in policy_list
        if not IsGroupOrAtomicGroup(policy)
    }
    for group in groups:
      for policy_name in group['policies']:
        policy_lookup[policy_name]['group'] = group['name']

  def _AddAtomicGroups(self, policy_list, policy_atomic_groups):
    '''Adds an 'atomic_group' field to the policies that are part of an atomic
    group.

    Args:
      policy_list: A list of policies and groups.
      policy_atomic_groups: A list of policy atomic groups
    '''
    policy_lookup = {
        policy['name']: policy
        for policy in policy_list
        if not IsGroupOrAtomicGroup(policy)
    }
    for group in policy_atomic_groups:
      for policy_name in group['policies']:
        policy_lookup[policy_name]['atomic_group'] = group['name']
        break

  def _ExpandAtomicGroups(self, policy_list, policy_atomic_groups):
    '''Replaces policies names inside atomic group definitions for actual
    policies definitions.

    Args:
      policy_list: A list of policies and groups.

    Returns:
      Modified policy_list
    '''
    policies = [
        policy for policy in policy_list if not IsGroupOrAtomicGroup(policy)
    ]
    for group in policy_atomic_groups:
      group['type'] = 'atomic_group'
    expanded = self._ExpandGroups(policies + policy_atomic_groups)
    expanded = [policy for policy in expanded if IsGroupOrAtomicGroup(policy)]
    return copy.deepcopy(expanded)

  def _ExpandGroups(self, policy_list):
    '''Replaces policies names inside group definitions for actual policies
    definitions. If policy does not belong to any group, leave it as is.

    Args:
      policy_list: A list of policies and groups.

    Returns:
      Modified policy_list
    '''
    groups = [policy for policy in policy_list if IsGroupOrAtomicGroup(policy)]
    policies = {
        policy['name']: policy
        for policy in policy_list
        if not IsGroupOrAtomicGroup(policy)
    }
    policies_in_groups = set()
    result_policies = []
    for group in groups:
      group_policies = group['policies']
      expanded_policies = [
          policies[policy_name] for policy_name in group_policies
      ]
      assert policies_in_groups.isdisjoint(group_policies)
      policies_in_groups.update(group_policies)
      group['policies'] = expanded_policies
      result_policies.append(group)

    result_policies.extend([
        policy for policy in policy_list if not IsGroupOrAtomicGroup(policy) and
        policy['name'] not in policies_in_groups
    ])
    return result_policies