text: #!/usr/bin/env python
  # Licensed to the Apache Software Foundation (ASF) under one or more
  # contributor license agreements.  See the NOTICE file distributed with
  # this work for additional information regarding copyright ownership.
  # The ASF licenses this file to You under the Apache License, Version 2.0
  # (the "License"); you may not use this file except in compliance with
  # the License.  You may obtain a copy of the License at
  #
  #     http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  from __future__ import with_statement
  
  import os
  import sys
  import inspect
  from collections import OrderedDict
  from os.path import join as pjoin
  
  this_dir = os.path.abspath(os.path.split(__file__)[0])
  sys.path.insert(0, os.path.join(this_dir, '../'))
  
  from libcloud.compute .base import NodeDriver
  from libcloud.compute.providers import get_driver as get_compute_driver
  from libcloud.compute.providers import DRIVERS as COMPUTE_DRIVERS
  from libcloud.compute.types import Provider as ComputeProvider
  
  from libcloud.loadbalancer.base import Driver as LBDriver
  from libcloud.loadbalancer.providers import get_driver as get_lb_driver
  from libcloud.loadbalancer.providers import DRIVERS as LB_DRIVERS
  from libcloud.loadbalancer.types import Provider as LBProvider
  
  from libcloud.storage.base import StorageDriver
  from libcloud.storage.providers import get_driver as get_storage_driver
  from libcloud.storage.providers import DRIVERS as STORAGE_DRIVERS
  from libcloud.storage.types import Provider as StorageProvider
  
  from libcloud.dns.base import DNSDriver
  from libcloud.dns.providers import get_driver as get_dns_driver
  from libcloud.dns.providers import DRIVERS as DNS_DRIVERS
  from libcloud.dns.types import Provider as DNSProvider
  
  from libcloud.container.base import ContainerDriver
  from libcloud.container.providers import get_driver as get_container_driver
  from libcloud.container.providers import DRIVERS as CONTAINER_DRIVERS
  from libcloud.container.types import Provider as ContainerProvider
  
  from libcloud.backup.base import BackupDriver
  from libcloud.backup.providers import get_driver as get_backup_driver
  from libcloud.backup.providers import DRIVERS as BACKUP_DRIVERS
  from libcloud.backup.types import Provider as BackupProvider
  
  
  HEADER = ('.. NOTE: This file has been generated automatically using '
            'generate_provider_feature_matrix_table.py script, don\'t manually '
            'edit it')
  
  BASE_API_METHODS = {
      'compute_main': ['list_nodes', 'create_node', 'reboot_node',
                       'destroy_node', 'list_images', 'list_sizes',
                       'deploy_node'],
      'compute_image_management': ['list_images', 'get_image',
                                   'create_image', 'delete_image', 'copy_image'],
      'compute_block_storage': ['list_volumes', 'create_volume',
                                'destroy_volume',
                                'attach_volume', 'detach_volume',
                                'list_volume_snapshots',
                                'create_volume_snapshot'],
      'compute_key_pair_management': ['list_key_pairs', 'get_key_pair',
                                      'create_key_pair',
                                      'import_key_pair_from_string',
                                      'import_key_pair_from_file',
                                      'delete_key_pair'],
      'loadbalancer': ['create_balancer', 'list_balancers',
                       'balancer_list_members', 'balancer_attach_member',
                       'balancer_detach_member', 'balancer_attach_compute_node'],
      'storage_main': ['list_containers', 'list_container_objects',
                       'iterate_containers', 'iterate_container_objects',
                       'create_container', 'delete_container', 'upload_object',
                       'upload_object_via_stream', 'download_object',
                       'download_object_as_stream', 'delete_object'],
      'storage_cdn': ['enable_container_cdn', 'enable_object_cdn',
                      'get_container_cdn_url', 'get_object_cdn_url'],
      'dns': ['list_zones', 'list_records', 'iterate_zones', 'iterate_records',
              'create_zone', 'update_zone', 'create_record', 'update_record',
              'delete_zone', 'delete_record'],
      'container': ['install_image', 'list_images', 'deploy_container',
                    'get_container', 'start_container', 'stop_container',
                    'restart_container', 'destroy_container', 'list_containers',
                    'list_locations', 'create_cluster', 'destroy_cluster',
                    'list_clusters'],
      'backup': ['get_supported_target_types', 'list_targets', 'create_target', 'create_target_from_node',
                 'create_target_from_storage_container', 'update_target', 'delete_target', 'list_recovery_points',
                 'recover_target', 'recover_target_out_of_place', 'list_target_jobs', 'create_target_job',
                 'resume_target_job', 'suspend_target_job', 'cancel_target_job']
  }
  
  FRIENDLY_METHODS_NAMES = {
      'compute_main': {
          'list_nodes': 'list nodes',
          'create_node': 'create node',
          'reboot_node': 'reboot node',
          'destroy_node': 'destroy node',
          'list_images': 'list images',
          'list_sizes': 'list sizes',
          'deploy_node': 'deploy node'
      },
      'compute_image_management': {
          'list_images': 'list images',
          'get_image': 'get image',
          'create_image': 'create image',
          'copy_image': 'copy image',
          'delete_image': 'delete image'
      },
      'compute_block_storage': {
          'list_volumes': 'list volumes',
          'create_volume': 'create volume',
          'destroy_volume': 'destroy volume',
          'attach_volume': 'attach volume',
          'detach_volume': 'detach volume',
          'list_volume_snapshots': 'list snapshots',
          'create_volume_snapshot': 'create snapshot'
      },
      'compute_key_pair_management': {
          'list_key_pairs': 'list key pairs',
          'get_key_pair': 'get key pair',
          'create_key_pair': 'create key pair',
          'import_key_pair_from_string': 'import public key from string',
          'import_key_pair_from_file': 'import public key from file',
          'delete_key_pair': 'delete key pair'
      },
      'loadbalancer': {
          'create_balancer': 'create balancer',
          'list_balancers': 'list balancers',
          'balancer_list_members': 'list members',
          'balancer_attach_member': 'attach member',
          'balancer_detach_member': 'detach member',
          'balancer_attach_compute_node': 'attach compute node'
      },
      'storage_main': {
          'list_containers': 'list containers',
          'list_container_objects': 'list objects',
          'create_container': 'create container',
          'delete_container': 'delete container',
          'upload_object': 'upload object',
          'upload_object_via_stream': 'streaming object upload',
          'download_object': 'download object',
          'download_object_as_stream': 'streaming object download',
          'delete_object': 'delete object'
      },
      'storage_cdn': {
          'enable_container_cdn': 'enable container cdn',
          'enable_object_cdn': 'enable object cdn',
          'get_container_cdn_url': 'get container cdn URL',
          'get_object_cdn_url': 'get object cdn URL',
      },
      'dns': {
          'list_zones': 'list zones',
          'list_records': 'list records',
          'create_zone': 'create zone',
          'update_zone': 'update zone',
          'create_record': 'create record',
          'update_record': 'update record',
          'delete_zone': 'delete zone',
          'delete_record': 'delete record'
      },
      'container': {
          'install_image': 'install image',
          'list_images': 'list images',
          'deploy_container': 'deploy container',
          'get_container': 'get container',
          'list_containers': 'list containers',
          'start_container': 'start container',
          'stop_container': 'stop container',
          'restart_container': 'restart container',
          'destroy_container': 'destroy container',
          'list_locations': 'list locations',
          'create_cluster': 'create cluster',
          'destroy_cluster': 'destroy cluster',
          'list_clusters': 'list clusters'
      },
      'backup': {
          'get_supported_target_types': 'get supported target types',
          'list_targets': 'list targets',
          'create_target': 'create target',
          'create_target_from_node': 'create target from node',
          'create_target_from_storage_container': 'create target from storage container',
          'update_target': 'update target',
          'delete_target': 'delete target',
          'list_recovery_points': 'list recovery points',
          'recover_target': 'recover target',
          'recover_target_out_of_place': 'recover target out of place',
          'list_target_jobs': 'list target jobs',
          'create_target_job': 'create target job',
          'resume_target_job': 'resume target job',
          'suspend_target_job': 'suspend target job',
          'cancel_target_job': 'cancel target job'
      }
  }
  
  IGNORED_PROVIDERS = [
      'dummy',
  
      # Deprecated constants
      'cloudsigma_us',
      'cloudfiles_swift'
  ]
  
  
  def get_provider_api_names(Provider):
      names = [key for key, value in Provider.__dict__.items() if
               not key.startswith('__')]
      return names
  
  
  def generate_providers_table(api):
      result = {}
  
      if api in ['compute_main', 'compute_image_management',
                 'compute_block_storage', 'compute_key_pair_management']:
          driver = NodeDriver
          drivers = COMPUTE_DRIVERS
          provider = ComputeProvider
          get_driver_method = get_compute_driver
      elif api == 'loadbalancer':
          driver = LBDriver
          drivers = LB_DRIVERS
          provider = LBProvider
          get_driver_method = get_lb_driver
      elif api in ['storage_main', 'storage_cdn']:
          driver = StorageDriver
          drivers = STORAGE_DRIVERS
          provider = StorageProvider
          get_driver_method = get_storage_driver
      elif api == 'dns':
          driver = DNSDriver
          drivers = DNS_DRIVERS
          provider = DNSProvider
          get_driver_method = get_dns_driver
      elif api == 'container':
          driver = ContainerDriver
          drivers = CONTAINER_DRIVERS
          provider = ContainerProvider
          get_driver_method = get_container_driver
      elif api == 'backup':
          driver = BackupDriver
          drivers = BACKUP_DRIVERS
          provider = BackupProvider
          get_driver_method = get_backup_driver
      else:
          raise Exception('Invalid api: %s' % (api))
  
      names = get_provider_api_names(provider)
  
      result = OrderedDict()
      for name in names:
          enum = getattr(provider, name)
  
          try:
              cls = get_driver_method(enum)
          except Exception as e:
              # Deprecated providers throw an exception
              print('Ignoring deprecated constant "%s": %s' % (enum, str(e)))
              continue
  
          # Hack for providers which expose multiple classes and support multiple
          # API versions
          # TODO: Make entry per version
          if name.lower() == 'cloudsigma':
              from libcloud.compute.drivers.cloudsigma import \
                  CloudSigma_2_0_NodeDriver
              cls = CloudSigma_2_0_NodeDriver
          elif name.lower() == 'opennebula':
              from libcloud.compute.drivers.opennebula import \
                  OpenNebula_3_8_NodeDriver
              cls = OpenNebula_3_8_NodeDriver
          elif name.lower() == 'digital_ocean' and api.startswith('compute'):
              from libcloud.compute.drivers.digitalocean import \
                  DigitalOcean_v2_NodeDriver
              cls = DigitalOcean_v2_NodeDriver
  
          if name.lower() in IGNORED_PROVIDERS:
              continue
  
          def is_function_or_method(*args, **kwargs):
              return (inspect.isfunction(*args, **kwargs) or
                      inspect.ismethod(*args, **kwargs))
  
          driver_methods = dict(inspect.getmembers(cls,
                                                   predicate=is_function_or_method))
          base_methods = dict(inspect.getmembers(driver,
                                                 predicate=is_function_or_method))
          base_api_methods = BASE_API_METHODS[api]
  
          result[name] = {'name': cls.name, 'website': cls.website,
                          'constant': name, 'module': drivers[enum][0],
                          'class': drivers[enum][1],
                          'cls': cls,
                          'methods': {}}
  
          print('Generating tables for provider: %s' % (name))
  
          for method_name in base_api_methods:
              base_method = base_methods[method_name]
              driver_method = driver_methods[method_name]
  
              if method_name == 'deploy_node':
                  features = getattr(cls, 'features', {}).get('create_node', [])
                  is_implemented = len(features) >= 1
              else:
                  is_implemented = (id(driver_method) != id(base_method))
  
              result[name]['methods'][method_name] = is_implemented
  
      return result
  
  
  def generate_rst_table(data):
      cols = len(data[0])
      col_len = [max(len(r[i]) for r in data) for i in range(cols)]
      formatter = ' '.join('{:<%d}' % c for c in col_len)
  
      header = formatter.format(*['=' * c for c in col_len])
      rows = [formatter.format(*row) for row in data]
      result = header + '\n' + rows[0] + '\n' + header + '\n' +\
          '\n'.join(rows[1:]) + '\n' + header
  
      return result
  
  
  def generate_supported_methods_table(api, provider_matrix):
      base_api_methods = BASE_API_METHODS[api]
      data = []
      header = [FRIENDLY_METHODS_NAMES[api][method_name] for method_name in
                base_api_methods if not method_name.startswith('iterate_')]
      data.append(['Provider'] + header)
  
      for provider, values in sorted(provider_matrix.items()):
          provider_name = '`%s`_' % (values['name'])
          row = [provider_name]
  
          # TODO: Make it nicer
          # list_* methods don't need to be implemented if iterate_* methods are
          # implemented
          if api == 'storage_main':
              if values['methods']['iterate_containers']:
                  values['methods']['list_containers'] = True
  
              if values['methods']['iterate_container_objects']:
                  values['methods']['list_container_objects'] = True
          elif api == 'dns':
              # list_zones and list_records don't need to be implemented if
              if values['methods']['iterate_zones']:
                  values['methods']['list_zones'] = True
  
              if values['methods']['iterate_records']:
                  values['methods']['list_records'] = True
  
          for method in base_api_methods:
              # TODO: ghetto
              if method.startswith('iterate_'):
                  continue
  
              supported = values['methods'][method]
  
              if supported:
                  row.append('yes')
              else:
                  row.append('no')
          data.append(row)
  
      result = generate_rst_table(data)
  
      result += '\n\n'
      for provider, values in sorted(provider_matrix.items()):
          result += '.. _`%s`: %s\n' % (values['name'], values['website'])
      return result
  
  
  def generate_supported_providers_table(api, provider_matrix):
      data = []
      header = ['Provider', 'Documentation', 'Provider Constant',
                'Supported Regions', 'Module', 'Class Name']
  
      data.append(header)
      for provider, values in sorted(provider_matrix.items()):
          name_str = '`%s`_' % (values['name'])
          module_str = ':mod:`%s`' % (values['module'])
          class_str = ':class:`%s`' % (values['class'])
  
          params = {'api': api, 'provider': provider.lower()}
          driver_docs_path = pjoin(this_dir,
                                   '../docs/%(api)s/drivers/%(provider)s.rst'
                                   % params)
  
          if os.path.exists(driver_docs_path):
              docs_link = ':doc:`Click </%(api)s/drivers/%(provider)s>`' % params
          else:
              docs_link = ''
  
          cls = values['cls']
          supported_regions = cls.list_regions() if hasattr(cls, 'list_regions') \
              else None
  
          if supported_regions:
              # Sort the regions to achieve stable output
              supported_regions = sorted(supported_regions)
              supported_regions = ', '.join(supported_regions)
          else:
              supported_regions = 'single region driver'
  
          row = [name_str, docs_link, values['constant'], supported_regions,
                 module_str, class_str]
          data.append(row)
  
      result = generate_rst_table(data)
  
      result += '\n\n'
      for provider, values in sorted(provider_matrix.items()):
          result += '.. _`%s`: %s\n' % (values['name'], values['website'])
      return result
  
  
  def generate_tables():
      apis = BASE_API_METHODS.keys()
      for api in apis:
          result = generate_providers_table(api)
  
          docs_dir = api
  
          if api.startswith('compute'):
              docs_dir = 'compute'
          elif api.startswith('storage'):
              docs_dir = 'storage'
  
          supported_providers = generate_supported_providers_table(docs_dir,
                                                                   result)
          supported_methods = generate_supported_methods_table(api, result)
  
          current_path = os.path.dirname(__file__)
          target_dir = os.path.abspath(pjoin(current_path,
                                             '../docs/%s/' % (docs_dir)))
  
          file_name_1 = '_supported_providers.rst'
          file_name_2 = '_supported_methods.rst'
  
          if api == 'compute_main':
              file_name_2 = '_supported_methods_main.rst'
          elif api == 'compute_image_management':
              file_name_2 = '_supported_methods_image_management.rst'
          elif api == 'compute_block_storage':
              file_name_2 = '_supported_methods_block_storage.rst'
          elif api == 'compute_key_pair_management':
              file_name_2 = '_supported_methods_key_pair_management.rst'
          elif api == 'storage_main':
              file_name_2 = '_supported_methods_main.rst'
          elif api == 'storage_cdn':
              file_name_2 = '_supported_methods_cdn.rst'
  
          supported_providers_path = pjoin(target_dir, file_name_1)
          supported_methods_path = pjoin(target_dir, file_name_2)
  
          with open(supported_providers_path, 'w') as fp:
              fp.write(HEADER + '\n\n')
              fp.write(supported_providers)
  
          with open(supported_methods_path, 'w') as fp:
              fp.write(HEADER + '\n\n')
              fp.write(supported_methods)
  
  
  generate_tables()
  