Exemple #1
0
    def get_remote_schema(self, uri):
        """Attempt to retrieve schema by URI (or find it in our cache)"""

        if '#' in uri:
            uri, path = uri.split('#')

        schema_data = self.remote_schemas.get(uri)

        if schema_data:
            return schema_data

        # Do we have a mapping for this remote URI to a local path?
        if '://' in uri:
            protocol, uri_part = uri.split('://')
        else:
            uri_part = uri
        for partial_uri in self.uri_to_local.keys():
            if uri_part.startswith(partial_uri):
                local_uri = self.uri_to_local[partial_uri] + uri_part[len(partial_uri):]
                schema_data = DocGenUtilities.load_as_json(local_uri)
                # This will fall through to getting the schema remotely if this fails. Correct?
                if schema_data:
                    return schema_data

        schema_data = DocGenUtilities.http_load_as_json(uri)
        if schema_data:
            schema_data['_schema_name'] = self.find_schema_name(uri, schema_data)
            self.remote_schemas[uri] = schema_data
            return schema_data

        return None
def test_payloads_lookup():
    payload_name = DocGenUtilities.get_payload_name('LogService', '1.3.1')
    assert payload_name == 'LogService-v1-example.json'

    payload_name = DocGenUtilities.get_payload_name('LogService', '1.3.1',
                                                    'GoFish')
    assert payload_name == 'LogService-v1-action-GoFish.json'
    def merge_required_profile(self, profile_resources, req_profile_name,
                               req_profile_info):
        """ Merge a required profile into profile_resources (a dict). May result in recursive calls. """

        req_profile_repo = req_profile_info.get(
            'Repository', 'http://redfish.dmtf.org/profiles')
        req_profile_minversion = req_profile_info.get('MinVersion', '1.0.0')
        version_string = req_profile_minversion.replace('.', '_')
        req_profile_data = None

        # Retrieve profile.
        # req_profile_repo will be a fully-qualified URI. It may be overridden by
        # uri-to-local mapping.
        base_uri = '/'.join([req_profile_repo, req_profile_name])
        if '://' in base_uri:  # This is expected.
            protocol, base_uri = base_uri.split('://')

        is_local_file = False
        for partial_uri in self.config['profile_uri_to_local'].keys():
            if base_uri.startswith(partial_uri):
                local_path = self.config['profile_uri_to_local'][partial_uri]
                if partial_uri.endswith(req_profile_name):
                    req_profile_repo = local_path[0:-len(req_profile_name)]
                    pass
                else:
                    req_profile_repo = local_path
                is_local_file = True
                break

        req_profile_uri = self.get_versioned_uri(req_profile_name,
                                                 req_profile_repo,
                                                 version_string, is_local_file)

        if not req_profile_uri:
            warnings.warn("Unable to find Profile for " + req_profile_repo +
                          ", " + req_profile_name + ", minimum version: " +
                          req_profile_minversion)
            return profile_resources

        if is_local_file:
            req_profile_data = DocGenUtilities.load_as_json(req_profile_uri)
        else:
            req_profile_data = DocGenUtilities.http_load_as_json(
                req_profile_uri)

        if req_profile_data:
            if 'RequiredProfiles' in req_profile_data:
                for req_profile_name in req_profile_data[
                        'RequiredProfiles'].keys():
                    profile_resources = self.merge_required_profile(
                        profile_resources, req_profile_name,
                        req_profile_data['RequiredProfiles'][req_profile_name])

            profile_resources = self.merge_dicts(
                profile_resources, req_profile_data.get('Resources', {}))

        return profile_resources
def test_html_get_links(mockRequest):
    sampleFile = open(os.path.join('tests', 'samples', 'schema_index_sample.html'), 'rb')
    expected_links = ['http://www.dmtf.org/standards/redfish',
                      'https://testing.mock/schemas/AccountService.v1_3_0.json',
                      'https://testing.mock/schemas/AccountService_v1.xml',
                      'https://testing.mock/schemas/ActionInfo.v1_0_3.json',
                      'https://testing.mock/schemas/ActionInfo_v1.xml']
    mockRequest.urlopen.return_value = sampleFile
    links = DocGenUtilities.html_get_links("https://testing.mock/foo.html");
    links.sort()
    assert links == expected_links
 def link_to_common_property(self, ref_key):
     """ String for output. Override in HTML formatter to get actual links. """
     ref_info = self.common_properties.get(ref_key)
     if ref_info and ref_info.get('_prop_name'):
         ref_id = 'common-properties-' + ref_info.get('_prop_name')
         # Get the version as well.
         version = ref_info.get('_latest_version')
         if not version:
             version = DocGenUtilities.get_ref_version(ref_info.get('_ref_uri', ''))
         if version:
             ref_id += '_v' + version
         return '<a href="#' + ref_id + '">' + ref_info.get('_prop_name') + '</a>'
     return ref_key
Exemple #6
0
 def link_to_common_property(self, ref_key):
     """ String for output. Override in HTML formatter to get actual links. """
     ref_info = self.common_properties.get(ref_key)
     if ref_info and ref_info.get('_prop_name'):
         ref_id = 'common-properties-' + ref_info.get('_prop_name')
         # Get the version as well.
         version = ref_info.get('_latest_version')
         if not version:
             version = DocGenUtilities.get_ref_version(ref_info.get('_ref_uri', ''))
         if version:
             ref_id += '_v' + version
         return '<a href="#' + ref_id + '">' + ref_info.get('_prop_name') + ' object' + '</a>'
     return ref_key
    def generate_docs(self, level=0):
        """Given a list of files, generate a block of documentation.

        This is the main loop of the product.
        """
        files_to_process = self.get_files(self.import_from)
        files, schema_data = self.group_files(files_to_process)

        property_data = {}
        doc_generator_meta = {}

        for normalized_uri in files.keys():
            data = self.process_files(normalized_uri, files[normalized_uri])
            if not data:
                # If we're in profile mode, this is probably normal.
                if not self.config['profile_mode']:
                    warnings.warn("Unable to process files for " +
                                  normalized_uri)
                continue
            property_data[normalized_uri] = data
            doc_generator_meta[normalized_uri] = property_data[normalized_uri][
                'doc_generator_meta']
            latest_info = files[normalized_uri][-1]
            latest_file = os.path.join(latest_info['root'],
                                       latest_info['filename'])
            latest_data = DocGenUtilities.load_as_json(latest_file)
            latest_data['_is_versioned_schema'] = latest_info.get(
                '_is_versioned_schema')
            latest_data['_is_collection_of'] = latest_info.get(
                '_is_collection_of')
            latest_data['_schema_name'] = latest_info.get('schema_name')
            schema_data[normalized_uri] = latest_data

        traverser = SchemaTraverser(schema_data, doc_generator_meta,
                                    self.config['uri_to_local'])

        # Generate output
        if self.config['output_format'] == 'markdown':
            from doc_formatter import MarkdownGenerator
            generator = MarkdownGenerator(property_data, traverser,
                                          self.config, level)
        elif self.config['output_format'] == 'html':
            from doc_formatter import HtmlGenerator
            generator = HtmlGenerator(property_data, traverser, self.config,
                                      level)
        elif self.config['output_format'] == 'csv':
            from doc_formatter import CsvGenerator
            generator = CsvGenerator(property_data, traverser, self.config,
                                     level)

        return generator.generate_output()
def test_html_get_links(mockRequest):
    sampleFile = open(
        os.path.join('tests', 'samples', 'schema_index_sample.html'), 'rb')
    expected_links = [
        'http://www.dmtf.org/standards/redfish',
        'https://testing.mock/schemas/AccountService.v1_3_0.json',
        'https://testing.mock/schemas/AccountService_v1.xml',
        'https://testing.mock/schemas/ActionInfo.v1_0_3.json',
        'https://testing.mock/schemas/ActionInfo_v1.xml'
    ]
    mockRequest.urlopen.return_value = sampleFile
    links = DocGenUtilities.html_get_links("https://testing.mock/foo.html")
    links.sort()
    assert links == expected_links
    def __init__(self, import_from, outfile, config):
        self.config = config
        self.import_from = import_from
        self.outfile = outfile

        if config['profile_mode']:
            config['profile'] = DocGenUtilities.load_as_json(
                config.get('profile_doc'))
            profile_resources = {}

            if 'RequiredProfiles' in config['profile']:
                for req_profile_name in config['profile'][
                        'RequiredProfiles'].keys():
                    profile_resources = self.merge_required_profile(
                        profile_resources, req_profile_name, config['profile']
                        ['RequiredProfiles'][req_profile_name])

            if 'Registries' in config['profile']:
                config['profile']['registries_annotated'] = {}
                for registry_name in config['profile']['Registries'].keys():
                    registry_summary = self.process_registry(
                        registry_name,
                        config['profile']['Registries'][registry_name])
                    config['profile']['registries_annotated'][
                        registry_name] = registry_summary

            profile_resources = self.merge_dicts(
                profile_resources,
                self.config.get('profile', {}).get('Resources', {}))

            if not profile_resources:
                warnings.warn(
                    'No profile resource data found; unable to produce profile mode documentation.'
                )
                exit()

            # Index profile_resources by Repository & schema name
            profile_resources_indexed = {}
            for schema_name in profile_resources.keys():
                profile_data = profile_resources[schema_name]
                repository = profile_data.get('Repository',
                                              'redfish.dmtf.org/schemas/v1')
                normalized_uri = repository + '/' + schema_name + '.json'
                profile_data['Schema_Name'] = schema_name
                profile_resources_indexed[normalized_uri] = profile_data

            self.config['profile_resources'] = profile_resources_indexed
    def process_registry(self, reg_name, registry_profile):
        """ Given registry requirements from a profile, retrieve the registry data and produce
        a summary based on the profile's requirements.
        """
        registry_reqs = {'name': reg_name}

        # Retrieve registry
        reg_repo = registry_profile.get('Repository')
        reg_minversion = registry_profile.get('MinVersion', '1.0.0')
        registry_reqs['minversion'] = reg_minversion
        registry_reqs['profile_requirement'] = registry_profile.get(
            'ReadRequirement', 'Mandatory')
        reg_uri = self.get_versioned_uri(reg_name, reg_repo, reg_minversion)

        if not reg_uri:
            warnings.warn("Unable to find registry file for " + reg_repo +
                          ", " + reg_name + ", minimum version " +
                          reg_minversion)
            return registry_reqs

        # Generate data based on profile
        registry_data = DocGenUtilities.http_load_as_json(reg_uri)
        if registry_data:
            registry_reqs['current_release'] = registry_data['RegistryVersion']
            registry_reqs.update(registry_data)

            for msg in registry_profile['Messages']:
                if msg in registry_reqs['Messages']:
                    registry_reqs['Messages'][msg][
                        'profile_requirement'] = registry_profile['Messages'][
                            msg].get('ReadRequirement', 'Mandatory')
                else:
                    warnings.warn(
                        "Profile specifies requirement for nonexistent Registry Message: "
                        + reg_name + " " + msg)

        return registry_reqs
Exemple #11
0
    def format_property_details(self,
                                prop_name,
                                prop_type,
                                prop_description,
                                enum,
                                enum_details,
                                supplemental_details,
                                meta,
                                anchor=None,
                                profile=None):
        """Generate a formatted table of enum information for inclusion in Property Details."""

        contents = []
        contents.append(self.formatter.head_three(prop_name + ':', self.level))

        parent_version = meta.get('version')
        enum_meta = meta.get('enum', {})

        # Are we in profile mode? If so, consult the profile passed in for this property.
        # For Action Parameters, look for ParameterValues/RecommendedValues; for
        # Property enums, look for MinSupportValues/RecommendedValues.
        profile_mode = self.config.get('profile_mode')
        if profile_mode:
            if profile is None:
                profile = {}

            profile_values = profile.get('Values', [])
            profile_min_support_values = profile.get('MinSupportValues', [])
            profile_parameter_values = profile.get('ParameterValues', [])
            profile_recommended_values = profile.get('RecommendedValues', [])

            profile_all_values = (profile_values + profile_min_support_values +
                                  profile_parameter_values +
                                  profile_recommended_values)

        if prop_description:
            contents.append(
                self.formatter.para(
                    self.escape_for_markdown(
                        prop_description, self.config.get('escape_chars',
                                                          []))))

        if isinstance(prop_type, list):
            prop_type = ', '.join(prop_type)

        if supplemental_details:
            contents.append('\n' + supplemental_details + '\n')

        if enum_details:
            if profile_mode:
                contents.append('| ' + prop_type +
                                ' | Description | Profile Specifies |')
                contents.append('| --- | --- | --- |')
            else:
                contents.append('| ' + prop_type + ' | Description |')
                contents.append('| --- | --- |')
            enum.sort(key=str.lower)
            for enum_item in enum:
                enum_name = enum_item
                enum_item_meta = enum_meta.get(enum_item, {})
                version_display = None
                deprecated_descr = None
                if 'version' in enum_item_meta:
                    version = enum_item_meta['version']
                    if not parent_version or DocGenUtilities.compare_versions(
                            version, parent_version) > 0:
                        version_display = self.truncate_version(version,
                                                                2) + '+'
                if version_display:
                    if 'version_deprecated' in enum_item_meta:
                        version_depr = enum_item_meta['version_deprecated']
                        deprecated_display = self.truncate_version(
                            version_depr, 2)
                        enum_name += ' ' + self.formatter.italic(
                            '(v' + version_display + ', deprecated v' +
                            deprecated_display + ')')
                        if enum_item_meta.get(
                                'version_deprecated_explanation'):
                            deprecated_descr = (
                                "Deprecated v" + deprecated_display + '+. ' +
                                enum_item_meta[
                                    'version_deprecated_explanation'])
                    else:
                        enum_name += ' ' + self.formatter.italic(
                            '(v' + version_display + ')')
                else:
                    if 'version_deprecated' in enum_item_meta:
                        version_depr = enum_item_meta['version_deprecated']
                        deprecated_display = self.truncate_version(
                            version_depr, 2)
                        enum_name += ' ' + self.formatter.italic(
                            '(deprecated v' + deprecated_display + ')')
                        if enum_item_meta.get(
                                'version_deprecated_explanation'):
                            deprecated_descr = (
                                "Deprecated v" + deprecated_display + '+. ' +
                                enum_item_meta[
                                    'version_deprecated_explanation'])
                descr = enum_details.get(enum_item, '')
                if deprecated_descr:
                    if descr:
                        descr += ' ' + self.formatter.italic(deprecated_descr)
                    else:
                        descr = self.formatter.italic(deprecated_descr)

                if profile_mode:
                    profile_spec = ''
                    if enum_name in profile_values:
                        profile_spec = 'Required'
                    elif enum_name in profile_min_support_values:
                        profile_spec = 'Required'
                    elif enum_name in profile_parameter_values:
                        profile_spec = 'Required'
                    elif enum_name in profile_recommended_values:
                        profile_spec = 'Recommended'
                    contents.append('| ' + enum_name + ' | ' + descr + ' | ' +
                                    profile_spec + ' |')
                else:
                    contents.append('| ' + enum_name + ' | ' + descr + ' |')

        elif enum:
            if profile_mode:
                contents.append('| ' + prop_type + ' | Profile Specifies |')
                contents.append('| --- | --- |')
            else:
                contents.append('| ' + prop_type + ' |')
                contents.append('| --- |')
            for enum_item in enum:
                enum_name = enum_item
                enum_item_meta = enum_meta.get(enum_item, {})
                version_display = None

                if 'version' in enum_item_meta:
                    version = enum_item_meta['version']
                    if not parent_version or DocGenUtilities.compare_versions(
                            version, parent_version) > 0:
                        version_display = self.truncate_version(version,
                                                                2) + '+'
                if version_display:
                    if 'version_deprecated' in enum_item_meta:
                        version_depr = enum_item_meta['version_deprecated']
                        deprecated_display = self.truncate_version(
                            version_depr, 2)
                        enum_name += ' ' + self.formatter.italic(
                            '(v' + version_display + ', deprecated v' +
                            deprecated_display + ')')
                        if enum_item_meta.get(
                                'version_deprecated_explanation'):
                            deprecated_descr = (
                                'Deprecated v' + deprecated_display + '+. ' +
                                enum_item_meta[
                                    'version_deprecated_explanation'])
                    else:
                        enum_name += ' ' + self.formatter.italic(
                            '(v' + version_display + ')')
                else:
                    if 'version_deprecated' in enum_item_meta:
                        version_depr = enum_item_meta['version_deprecated']
                        deprecated_display = self.truncate_version(
                            version_depr, 2)
                        enum_name += ' ' + self.formatter.italic(
                            '(deprecated v' + deprecated_display + ')')
                        if enum_item_meta.get(
                                'version_deprecated_explanation'):
                            enum_name += ' ' + self.formatter.italic(
                                'Deprecated v' + deprecated_display + '+. ' +
                                enum_item_meta['version_deprecated_explanation']
                            )

                if profile_mode:
                    profile_spec = ''
                    if enum_name in profile_values:
                        profile_spec = 'Required'
                    elif enum_name in profile_min_support_values:
                        profile_spec = 'Required'
                    elif enum_name in profile_parameter_values:
                        profile_spec = 'Required'
                    elif enum_name in profile_recommended_values:
                        profile_spec = 'Recommended'

                    contents.append('| ' + enum_name + ' | ' + profile_spec +
                                    ' |')
                else:
                    contents.append('| ' + enum_name + ' | ')

        return '\n'.join(contents) + '\n'
    def format_property_row(self,
                            schema_ref,
                            prop_name,
                            prop_info,
                            prop_path=[],
                            in_array=False,
                            as_action_parameters=False):
        """Format information for a single property.

        Returns an object with 'row', 'details', 'action_details', and 'profile_conditional_details':

        'row': content for the main table being generated.
        'details': content for the Property details section.
        'action_details': content for the Actions section.
        'profile_conditional_details': populated only in profile_mode, formatted conditional details

        This may include embedded objects with their own properties.
        """

        traverser = self.traverser
        formatted = []  # The row itself

        within_action = prop_path == ['Actions']
        current_depth = len(prop_path)
        if in_array:
            current_depth = current_depth - 1

        # strip_top_object is used for fragments, to allow output of just the properties
        # without the enclosing object:
        if self.config.get('strip_top_object') and current_depth > 0:
            indentation_string = '&nbsp;' * 6 * (current_depth - 1)
        else:
            indentation_string = '&nbsp;' * 6 * current_depth

        # If prop_path starts with Actions and is more than 1 deep, we are outputting for an Actions
        # section and should dial back the indentation by one level.
        if len(prop_path) > 1 and prop_path[0] == 'Actions':
            indentation_string = '&nbsp;' * 6 * (current_depth - 1)

        collapse_array = False  # Should we collapse a list description into one row? For lists of simple types
        has_enum = False

        if current_depth < self.current_depth:
            for i in range(current_depth, self.current_depth):
                if i in self.current_version:
                    del self.current_version[i]
        self.current_depth = current_depth
        parent_depth = current_depth - 1

        version_added = None
        version_deprecated = None
        version_deprecated_explanation = ''
        if isinstance(prop_info, list):
            version_added = prop_info[0].get('versionAdded')
            version_deprecated = prop_info[0].get('versionDeprecated')
            version_deprecated_explanation = prop_info[0].get('deprecated')
            has_enum = 'enum' in prop_info[0]
            is_excerpt = prop_info[0].get('_is_excerpt') or prop_info[0].get(
                'excerptCopy')
        elif isinstance(prop_info, dict):
            version_added = prop_info.get('versionAdded')
            version_deprecated = prop_info.get('versionDeprecated')
            version_deprecated_explanation = prop_info.get('deprecated')
            has_enum = 'enum' in prop_info
            is_excerpt = prop_info[0].get('_is_excerpt')

        if prop_name:
            name_and_version = self.formatter.bold(
                self.escape_for_markdown(prop_name,
                                         self.config.get('escape_chars', [])))
        else:
            name_and_version = ''

        deprecated_descr = None

        version = None
        if version_added:
            version = self.format_version(version_added)
        if version_deprecated:
            version_depr = self.format_version(version_deprecated)
        self.current_version[current_depth] = version

        # Don't display version if there is a parent version and this is not newer:
        if version and self.current_version.get(parent_depth):
            if DocGenUtilities.compare_versions(
                    version, self.current_version.get(parent_depth)) <= 0:
                version = None

        if version and version != '1.0.0':
            version_display = self.truncate_version(version, 2) + '+'
            if version_deprecated:
                deprecated_display = self.truncate_version(version_depr, 2)
                name_and_version += ' ' + self.formatter.italic(
                    '(v' + version_display + ', deprecated v' +
                    deprecated_display + ')')
                deprecated_descr = ("Deprecated in v" + deprecated_display +
                                    ' and later. ' + self.escape_for_markdown(
                                        version_deprecated_explanation,
                                        self.config.get('escape_chars', [])))
            else:
                name_and_version += ' ' + self.formatter.italic(
                    '(v' + version_display + ')')
        elif version_deprecated:
            deprecated_display = self.truncate_version(version_depr, 2)
            name_and_version += ' ' + self.formatter.italic(
                '(deprecated v' + deprecated_display + ')')
            deprecated_descr = (
                "Deprecated in v" + deprecated_display + ' and later. ' +
                self.escape_for_markdown(version_deprecated_explanation,
                                         self.config.get('escape_chars', [])))

        formatted_details = self.parse_property_info(schema_ref, prop_name,
                                                     prop_info, prop_path)

        if formatted_details.get('promote_me'):
            return ({
                'row': '\n'.join(formatted_details['item_description']),
                'details': formatted_details['prop_details'],
                'action_details': formatted_details.get('action_details')
            })

        if self.config.get('strip_top_object') and current_depth == 0:
            # In this case, we're done for this bit of documentation, and we just want the properties of this object.
            formatted.append('\n'.join(
                formatted_details['object_description']))
            return ({
                'row':
                '\n'.join(formatted),
                'details':
                formatted_details['prop_details'],
                'action_details':
                formatted_details.get('action_details'),
                'profile_conditional_details':
                formatted_details.get('profile_conditional_details')
            })

        # Eliminate dups in these these properties and join with a delimiter:
        props = {
            'prop_type': self.separators['inline'],
            'descr': self.separators['linebreak'],
            'object_description': self.separators['linebreak'],
            'item_description': self.separators['linebreak']
        }

        for property_name, delim in props.items():
            if isinstance(formatted_details[property_name], list):
                property_values = []
                self.append_unique_values(formatted_details[property_name],
                                          property_values)
                formatted_details[property_name] = delim.join(property_values)

        if formatted_details['prop_is_object'] and not in_array:
            if formatted_details['object_description'] == '':
                name_and_version += ' {}'
            else:
                name_and_version += ' {'

        if formatted_details['prop_is_array']:
            if formatted_details['item_description'] == '':
                if formatted_details['array_of_objects']:
                    name_and_version += ' [ {} ]'
                else:
                    name_and_version += ' [ ]'
            else:
                if formatted_details['array_of_objects']:
                    name_and_version += ' [ {'
                else:
                    collapse_array = True
                    name_and_version += ' [ ]'
        elif in_array:
            if formatted_details['prop_is_object']:
                name_and_version += ' [ { } ]'
            else:
                name_and_version += ' [ ]'

        if formatted_details['descr'] is None:
            formatted_details['descr'] = ''

        if formatted_details['profile_purpose'] and (
                self.config.get('profile_mode') != 'subset'):
            if formatted_details['descr']:
                formatted_details['descr'] += ' '
            formatted_details['descr'] += self.formatter.bold(
                formatted_details['profile_purpose'])

        if formatted_details['add_link_text']:
            if formatted_details['descr']:
                formatted_details['descr'] += ' '
            formatted_details['descr'] += formatted_details['add_link_text']

        # Append reference info to descriptions, if appropriate:
        if not formatted_details.get('fulldescription_override'):
            if formatted_details[
                    'has_direct_prop_details'] and not formatted_details[
                        'has_action_details']:
                # If there are prop_details (enum details), add a note to the description:
                if has_enum:
                    text_descr = 'For the possible property values, see ' + prop_name + ' in Property details.'
                else:
                    text_descr = 'For more information about this property, see Property details.'
                formatted_details['descr'] += ' ' + self.formatter.italic(
                    text_descr)

            if formatted_details['has_action_details']:
                text_descr = 'For more information, see the Actions section below.'
                formatted_details['descr'] += ' ' + self.formatter.italic(
                    text_descr)

        if deprecated_descr:
            formatted_details['descr'] += ' ' + self.formatter.italic(
                deprecated_descr)

        prop_type = formatted_details['prop_type']
        if has_enum:
            prop_type += '<br>(enum)'

        if formatted_details['prop_units']:
            prop_type += '<br>(' + formatted_details['prop_units'] + ')'

        if is_excerpt:
            prop_type += '<br>(excerpt)'

        if in_array:
            prop_type = 'array (' + prop_type + ')'

        if collapse_array:
            item_list = formatted_details['item_list']
            if len(item_list):
                if isinstance(item_list, list):
                    item_list = ', '.join(item_list)
                prop_type += ' (' + item_list + ')'

        prop_access = ''
        if (not formatted_details['prop_is_object']
                and not formatted_details.get('array_of_objects')
                and not as_action_parameters):
            if formatted_details['read_only']:
                prop_access = 'read-only'
            else:
                # Special case for subset mode; if profile indicates WriteRequirement === None (present and None),
                # emit read-only.
                if ((self.config.get('profile_mode') == 'subset')
                        and formatted_details.get('profile_write_req') and
                    (formatted_details['profile_write_req'] == 'None')):
                    prop_access = 'read-only'
                else:
                    prop_access = 'read-write'

        # Action parameters don't have read/write properties, but they can be required/optional.
        if as_action_parameters:
            if formatted_details['prop_required'] or formatted_details[
                    'required_parameter']:
                prop_access = 'required'
            else:
                prop_access = 'optional'
        else:
            if formatted_details['prop_required'] or formatted_details[
                    'required_parameter']:
                prop_access += ' required'
            elif formatted_details['prop_required_on_create']:
                prop_access += ' required on create'

        if formatted_details['nullable']:
            prop_access += '<br>(null)'

        # If profile reqs are present, massage them:
        profile_access = self.format_base_profile_access(formatted_details)

        if self.config.get('profile_mode'
                           ) and self.config.get('profile_mode') != 'subset':
            if profile_access:
                prop_type += '<br><br>' + self.formatter.italic(profile_access)
        elif prop_access:
            prop_type += '<br><br>' + self.formatter.italic(prop_access)

        row = []
        row.append(indentation_string + name_and_version)
        row.append(prop_type)
        row.append(formatted_details['descr'])

        formatted.append('| ' + ' | '.join(row) + ' |')

        if len(formatted_details['object_description']) > 0:
            formatted.append(formatted_details['object_description'])
            formatted.append('| ' + indentation_string + '} |   |   |')

        if not collapse_array and len(
                formatted_details['item_description']) > 0:
            formatted.append(formatted_details['item_description'])
            if formatted_details['array_of_objects']:
                formatted.append('| ' + indentation_string + '} ] |   |   |')
            else:
                formatted.append('| ' + indentation_string + '] |   |   |')

        return ({
            'row':
            '\n'.join(formatted),
            'details':
            formatted_details['prop_details'],
            'action_details':
            formatted_details.get('action_details'),
            'profile_conditional_details':
            formatted_details.get('profile_conditional_details')
        })
    def format_property_details(self,
                                prop_name,
                                prop_type,
                                prop_description,
                                enum,
                                enum_details,
                                supplemental_details,
                                parent_prop_info,
                                anchor=None,
                                profile=None):
        """Generate a formatted table of enum information for inclusion in Property details."""

        contents = []
        contents.append(self.formatter.head_three(prop_name + ':', self.level))

        parent_version = parent_prop_info.get('versionAdded')
        if parent_version:
            parent_version = self.format_version(parent_version)

        # Are we in profile mode? If so, consult the profile passed in for this property.
        # For Action Parameters, look for ParameterValues/RecommendedValues; for
        # Property enums, look for MinSupportValues/RecommendedValues.
        profile_mode = self.config.get('profile_mode')
        if profile_mode:
            if profile is None:
                profile = {}

            profile_values = profile.get('Values', [])
            profile_min_support_values = profile.get(
                'MinSupportValues', [])  # No longer a valid name?
            profile_parameter_values = profile.get('ParameterValues', [])
            profile_recommended_values = profile.get('RecommendedValues', [])

            # profile_all_values is not used. What were we going for here?
            profile_all_values = (profile_values + profile_min_support_values +
                                  profile_parameter_values +
                                  profile_recommended_values)

            # In subset mode, an action parameter with no Values (property) or ParameterValues (Action)
            # means all values are supported.
            # Otherwise, Values/ParameterValues specifies the set that should be listed.
            if profile_mode == 'subset':
                if len(profile_values):
                    enum = [x for x in enum if x in profile_values]
                elif len(profile_parameter_values):
                    enum = [x for x in enum if x in profile_parameter_values]

        if prop_description:
            contents.append(
                self.formatter.para(
                    self.escape_for_markdown(
                        prop_description, self.config.get('escape_chars',
                                                          []))))

        if isinstance(prop_type, list):
            prop_type = ', '.join(prop_type)

        if supplemental_details:
            contents.append('\n' + supplemental_details + '\n')

        if enum_details:
            if profile_mode and profile_mode != 'subset':
                contents.append('| ' + prop_type +
                                ' | Description | Profile Specifies |')
                contents.append('| --- | --- | --- |')
            else:
                contents.append('| ' + prop_type + ' | Description |')
                contents.append('| --- | --- |')
            enum.sort(key=str.lower)
            for enum_item in enum:
                enum_name = enum_item
                version = version_depr = deprecated_descr = None
                version_display = None
                if parent_prop_info.get('enumVersionAdded'):
                    version_added = parent_prop_info.get(
                        'enumVersionAdded').get(enum_name)
                    if version_added:
                        version = self.format_version(version_added)
                if parent_prop_info.get('enumVersionDeprecated'):
                    version_deprecated = parent_prop_info.get(
                        'enumVersionDeprecated').get(enum_name)
                    if version_deprecated:
                        version_depr = self.format_version(version_deprecated)
                if parent_prop_info.get('enumDeprecated'):
                    deprecated_descr = parent_prop_info.get(
                        'enumDeprecated').get(enum_name)

                if version:
                    if not parent_version or DocGenUtilities.compare_versions(
                            version, parent_version) > 0:
                        version_display = self.truncate_version(version,
                                                                2) + '+'

                if version_display:
                    if version_depr:
                        deprecated_display = self.truncate_version(
                            version_depr, 2)
                        enum_name += ' ' + self.formatter.italic(
                            '(v' + version_display + ', deprecated v' +
                            deprecated_display + ')')
                        if deprecated_descr:
                            deprecated_descr = 'Deprecated in v' + deprecated_display + ' and later. ' + deprecated_descr
                    else:
                        enum_name += ' ' + self.formatter.italic(
                            '(v' + version_display + ')')
                elif version_depr:
                    deprecated_display = self.truncate_version(version_depr, 2)
                    enum_name += ' ' + self.formatter.italic(
                        '(deprecated v' + deprecated_display + ')')
                    if deprecated_descr:
                        deprecated_descr = 'Deprecated in v' + deprecated_display + ' and later. ' + deprecated_descr

                descr = enum_details.get(enum_item, '')
                if deprecated_descr:
                    if descr:
                        descr += ' ' + self.formatter.italic(deprecated_descr)
                    else:
                        descr = self.formatter.italic(deprecated_descr)

                if profile_mode and profile_mode != 'subset':
                    profile_spec = ''
                    if enum_name in profile_values:
                        profile_spec = 'Required'
                    elif enum_name in profile_min_support_values:
                        profile_spec = 'Required'
                    elif enum_name in profile_parameter_values:
                        profile_spec = 'Required'
                    elif enum_name in profile_recommended_values:
                        profile_spec = 'Recommended'
                    contents.append('| ' + enum_name + ' | ' + descr + ' | ' +
                                    profile_spec + ' |')
                else:
                    contents.append('| ' + enum_name + ' | ' + descr + ' |')

        elif enum:
            if profile_mode and profile_mode != 'subset':
                contents.append('| ' + prop_type + ' | Profile Specifies |')
                contents.append('| --- | --- |')
            else:
                contents.append('| ' + prop_type + ' |')
                contents.append('| --- |')
            for enum_item in enum:
                enum_name = enum_item
                version = version_depr = deprecated_descr = None
                version_display = None

                if parent_prop_info.get('enumVersionAdded'):
                    version_added = parent_prop_info.get(
                        'enumVersionAdded').get(enum_name)
                    if version_added:
                        version = self.format_version(version_added)

                if parent_prop_info('enumVersionDeprecated'):
                    version_deprecated = parent_prop_info.get(
                        'enumVersionDeprecated').get(enum_name)
                    if version_deprecated:
                        version_depr = self.format_version(version_deprecated)

                if parent_prop_info.get('enumDeprecated'):
                    deprecated_descr = parent_prop_info.get(
                        'enumDeprecated').get(enum_name)

                if version:
                    if not parent_version or DocGenUtilities.compare_versions(
                            version, parent_version) > 0:
                        version_text = html.escape(version, False)
                        version_display = self.truncate_version(
                            version_text, 2) + '+'

                if version_display:
                    if version_depr:
                        deprecated_display = self.truncate_version(
                            version_depr, 2)
                        if deprecated_descr:
                            enum_name += ' ' + self.formatter.italic(
                                '(v' + version_display + ', deprecated v' +
                                deprecated_display + '+. ' + deprecated_descr)
                        else:
                            enum_name += ' ' + self.formatter.italic(
                                '(v' + version_display + ', deprecated v' +
                                deprecated_display + ')')

                    else:
                        enum_name += ' ' + self.formatter.italic(
                            '(v' + version_display + ')')
                else:
                    if version_depr:
                        deprecated_display = self.truncate_version(
                            version_depr, 2)
                        if deprecated_descr:
                            enum_name += ' ' + self.formatter.italic(
                                'Deprecated in v' + deprecated_display +
                                ' and later. ' + deprecated_descr)
                        else:
                            enum_name += ' ' + self.formatter.italic(
                                '(deprecated in v' + deprecated_display +
                                ' and later.)')

                if profile_mode and profile_mode != 'subset':
                    profile_spec = ''
                    if enum_name in profile_values:
                        profile_spec = 'Required'
                    elif enum_name in profile_min_support_values:
                        profile_spec = 'Required'
                    elif enum_name in profile_parameter_values:
                        profile_spec = 'Required'
                    elif enum_name in profile_recommended_values:
                        profile_spec = 'Recommended'

                    contents.append('| ' + enum_name + ' | ' + profile_spec +
                                    ' |')
                else:
                    contents.append('| ' + enum_name + ' | ')

        return '\n'.join(contents) + '\n'
def test_load_as_json_file_not_good_warns():
    with pytest.warns(UserWarning):
        data = DocGenUtilities.load_as_json(
            os.path.join(sampledir, 'badjson.json'))
        assert data == {}
def test_payloads_lookup():
    payload_name = DocGenUtilities.get_payload_name('LogService', '1.3.1')
    assert payload_name == 'LogService-v1-example.json'

    payload_name = DocGenUtilities.get_payload_name('LogService', '1.3.1', 'GoFish')
    assert payload_name == 'LogService-v1-action-GoFish.json'
Exemple #16
0
    def format_property_row(self, schema_ref, prop_name, prop_info, prop_path=[], in_array=False):
        """Format information for a single property.

        Returns an object with 'row', 'details', 'action_details', and 'profile_conditional_details':

        'row': content for the main table being generated.
        'details': content for the Property Details section.
        'action_details': content for the Actions section.
        'profile_conditional_details': populated only in profile_mode, formatted conditional details

        This may include embedded objects with their own properties.
        """

        traverser = self.traverser
        formatted = []     # The row itself

        current_depth = len(prop_path)

        if in_array:
            current_depth = current_depth -1

        # strip_top_object is used for fragments, to allow output of just the properties
        # without the enclosing object:
        if self.config.get('strip_top_object') and current_depth > 0:
            indentation_string = '&nbsp;' * 6 * (current_depth -1)
        else:
            indentation_string = '&nbsp;' * 6 * current_depth

        # If prop_path starts with Actions and is more than 1 deep, we are outputting for an Actions
        # section and should dial back the indentation by one level.
        if len(prop_path) > 1 and prop_path[0] == 'Actions':
            indentation_string = '&nbsp;' * 6 * (current_depth -1)

        collapse_array = False # Should we collapse a list description into one row? For lists of simple types
        has_enum = False

        if current_depth < self.current_depth:
            for i in range(current_depth, self.current_depth):
                if i in self.current_version:
                    del self.current_version[i]
        self.current_depth = current_depth
        parent_depth = current_depth - 1

        if isinstance(prop_info, list):
            meta = prop_info[0].get('_doc_generator_meta')
            has_enum = 'enum' in prop_info[0]
            is_excerpt = prop_info[0].get('_is_excerpt') or prop_info[0].get('excerptCopy')
        elif isinstance(prop_info, dict):
            meta = prop_info.get('_doc_generator_meta')
            has_enum = 'enum' in prop_info
            is_excerpt = prop_info.get('_is_excerpt')

        if not meta:
            meta = {}

        # We want to modify a local copy of meta, deleting redundant version info
        meta = copy.deepcopy(meta)

        name_and_version = self.formatter.bold(html.escape(prop_name, False))
        deprecated_descr = None

        version = meta.get('version')
        self.current_version[current_depth] = version

        # Don't display version if there is a parent version and this is not newer:
        if self.current_version.get(parent_depth) and version:
            if DocGenUtilities.compare_versions(version, self.current_version.get(parent_depth)) <= 0:
                del meta['version']

        if meta.get('version', '1.0.0') != '1.0.0':
            version_text = html.escape(meta['version'], False)
            version_display = self.truncate_version(version_text, 2) + '+'
            if 'version_deprecated' in meta:
                version_depr = html.escape(meta['version_deprecated'], False)
                deprecated_display = self.truncate_version(version_depr, 2)
                name_and_version += ' ' + self.formatter.italic('(v' + version_display +
                                                      ', deprecated v' + deprecated_display +  ')')
                deprecated_descr = html.escape("Deprecated v" + deprecated_display + '+. ' +
                                               meta['version_deprecated_explanation'], False)
            else:
                name_and_version += ' ' + self.formatter.italic('(v' + version_display + ')')
        elif 'version_deprecated' in meta:
            version_depr = html.escape(meta['version_deprecated'], False)
            deprecated_display = self.truncate_version(version_depr, 2)
            name_and_version += ' ' + self.formatter.italic('(deprecated v' + deprecated_display +  ')')
            deprecated_descr = html.escape( "Deprecated v" + deprecated_display + '+. ' +
                                            meta['version_deprecated_explanation'], False)

        formatted_details = self.parse_property_info(schema_ref, prop_name, prop_info, prop_path,
                                                     meta.get('within_action'))

        if formatted_details.get('promote_me'):
            return({'row': '\n'.join(formatted_details['item_description']), 'details':formatted_details['prop_details'],
                    'action_details':formatted_details.get('action_details')})


        if self.config.get('strip_top_object') and current_depth == 0:
            # In this case, we're done for this bit of documentation, and we just want the properties of this object.
            formatted.append('\n'.join(formatted_details['object_description']))
            return({'row': '\n'.join(formatted), 'details':formatted_details['prop_details'],
                    'action_details':formatted_details.get('action_details'),
                    'profile_conditional_details':formatted_details.get('profile_conditional_details')})

        # Eliminate dups in these these properties and join with a delimiter:
        props = {
            'prop_type': self.separators['inline'],
            'descr': self.separators['linebreak'],
            'object_description': '\n',
            'item_description': '\n'
            }

        for property_name, delim in props.items():
            if isinstance(formatted_details[property_name], list):
                property_values = []
                self.append_unique_values(formatted_details[property_name], property_values)
                formatted_details[property_name] = delim.join(property_values)

        if formatted_details['prop_is_object'] and not in_array:
            if formatted_details['object_description'] == '':
                name_and_version += ' { }'
            else:
                name_and_version += ' {'

        if formatted_details['prop_is_array']:
            if formatted_details['item_description'] == '':
                if formatted_details['array_of_objects']:
                    name_and_version += ' [ { } ]'
                else:
                    name_and_version += ' [ ] '
            else:
                if formatted_details['array_of_objects']:
                    name_and_version += ' [ {'
                else:
                    collapse_array = True
                    name_and_version += ' [ ] '
        elif in_array:
            if formatted_details['prop_is_object']:
                name_and_version += ' [ { } ]'
            else:
                name_and_version += ' [ ] '

        name_and_version = '<nobr>' + name_and_version  + '</nobr>'

        if formatted_details['descr'] is None:
            formatted_details['descr'] = ''

        if not formatted_details.get('verbatim_description', False):
            formatted_details['descr'] = self.formatter.markdown_to_html(html.escape(formatted_details['descr'], False), no_para=True)

        if formatted_details['add_link_text']:
            if formatted_details['descr']:
                formatted_details['descr'] += self.formatter.br()
            formatted_details['descr'] += self.formatter.italic(formatted_details['add_link_text'])

        # Append reference info to descriptions, if appropriate:
        if not formatted_details.get('fulldescription_override'):
            # If there are prop_details (enum details), add a note to the description:
            if formatted_details['has_direct_prop_details'] and not formatted_details['has_action_details']:
                if has_enum:
                    anchor = schema_ref + '|details|' + prop_name
                    text_descr = 'See <a href="#' + anchor + '">' + prop_name + '</a> in Property Details, below, for the possible values of this property.'
                else:
                    text_descr = 'See Property Details, below, for more information about this property.'


                if formatted_details['descr']:
                    formatted_details['descr'] += '<br>' + self.formatter.italic(text_descr)
                else:
                    formatted_details['descr'] = self.formatter.italic(text_descr)

            # If this is an Action with details, add a note to the description:
            if formatted_details['has_action_details']:
                anchor = schema_ref + '|action_details|' + prop_name
                text_descr = 'For more information, see the <a href="#' + anchor + '">Actions</a> section below.'
                formatted_details['descr'] += '<br>' + self.formatter.italic(text_descr)

        if deprecated_descr:
            formatted_details['descr'] += ' ' + self.formatter.italic(deprecated_descr)

        prop_type = html.escape(formatted_details['prop_type'], False)
        if has_enum:
            prop_type += '<br>(enum)'
        if formatted_details['prop_units']:
            prop_type += '<br>(' + formatted_details['prop_units'] + ')'
        if is_excerpt:
            prop_type += '<br>(excerpt)'

        if in_array:
            prop_type = 'array (' + prop_type + ')'

        if collapse_array:
            item_list = formatted_details['item_list']
            if len(item_list):
                if isinstance(item_list, list):
                    item_list = ', '.join([html.escape(x) for x in item_list])
                prop_type += '<br>(' + item_list + ')'

        prop_access = ''
        if not meta.get('is_pattern') and not formatted_details['prop_is_object']:
            if formatted_details['read_only']:
                prop_access = '<nobr>read-only</nobr>'
            else:
                prop_access = '<nobr>read-write</nobr>'

        if formatted_details['prop_required'] or formatted_details.get('required_parameter'):
            prop_access += ' <nobr>required</nobr>'
        elif formatted_details['prop_required_on_create']:
            prop_access += ' <nobr>required on create</nobr>'

        if formatted_details['nullable']:
            prop_access += ' (null)'

        # If profile reqs are present, massage them:
        profile_access = self.format_base_profile_access(formatted_details)

        descr = formatted_details['descr']
        if formatted_details['profile_purpose']:
            descr += '<br>' + self.formatter.bold("Profile Purpose: " + formatted_details['profile_purpose'])

        # Conditional Requirements
        cond_req = formatted_details['profile_conditional_req']
        if cond_req:
            anchor = schema_ref + '|conditional_reqs|' + prop_name
            cond_req_text = 'See <a href="#' + anchor + '"> Conditional Requirements</a>, below, for more information.'
            descr += ' ' + self.formatter.nobr(self.formatter.italic(cond_req_text))
            profile_access += "<br>" + self.formatter.nobr(self.formatter.italic('Conditional Requirements'))

        if not profile_access:
            profile_access = '&nbsp;' * 10

        # Comparison
        if formatted_details['profile_values']:
            comparison_descr = ('Must be ' + formatted_details['profile_comparison'] + ' ('
                                + ', '.join('"' + x + '"' for x in formatted_details['profile_values'])
                                + ')')
            profile_access += '<br>' + self.formatter.italic(comparison_descr)

        row = []
        row.append(indentation_string + name_and_version)
        if self.config.get('profile_mode'):
            row.append(profile_access)
        row.append(prop_type)
        if not self.config.get('profile_mode'):
            row.append(prop_access)
        row.append(descr)

        formatted.append(self.formatter.make_row(row))

        if len(formatted_details['object_description']) > 0:
            formatted_object = formatted_details['object_description']
            # Add a closing } to the last row of this object.
            formatted_object = self._add_closing_brace(formatted_object, indentation_string, '}')
            formatted.append(formatted_object)

        if not collapse_array and len(formatted_details['item_description']) > 0:
            formatted_array = formatted_details['item_description']
            # Add closing }] or ] to the last row of this array:
            if formatted_details['array_of_objects']:
                formatted_array = self._add_closing_brace(formatted_array, indentation_string, '} ]')
            else:
                formatted_array = self._add_closing_brace(formatted_array, indentation_string, ']')
            formatted.append(formatted_array)

        return({'row': '\n'.join(formatted), 'details':formatted_details['prop_details'],
                'action_details':formatted_details.get('action_details'),
                'profile_conditional_details':formatted_details.get('profile_conditional_details')})
    def process_data_file(self, schema_ref, ref, property_data):
        """Process a single file by ref name, identifying metadata and updating property_data."""

        filename = os.path.join(ref['root'], ref['filename'])
        normalized_uri = self.construct_uri_for_filename(filename)

        # Get the un-versioned filename for match against profile keys
        if '.v' in filename:
            generalized_uri = self.construct_uri_for_filename(
                filename.split('.v')[0]) + '.json'
        else:
            generalized_uri = self.construct_uri_for_filename(filename)

        profile_mode = self.config['profile_mode']
        profile = self.config['profile_resources']

        data = DocGenUtilities.load_as_json(filename)
        schema_name = SchemaTraverser.find_schema_name(filename, data, True)
        version = self.get_version_string(ref['filename'])

        property_data['schema_name'] = schema_name
        property_data['latest_version'] = version
        property_data['name_and_version'] = schema_name
        property_data['normalized_uri'] = normalized_uri

        min_version = False
        if profile_mode:
            schema_profile = profile.get(generalized_uri)
            if schema_profile:
                min_version = schema_profile.get('MinVersion')
                if min_version:
                    if version:
                        property_data[
                            'name_and_version'] += ' v' + min_version + '+ (current release: v' + version + ')'
                    else:
                        # this is unlikely
                        property_data[
                            'name_and_version'] += ' v' + min_version + '+'
            else:
                # Skip schemas that aren't mentioned in the profile:
                return {}
        elif version:
            property_data['name_and_version'] += ' ' + version

        if 'properties' not in property_data:
            property_data['properties'] = {}
        meta = property_data.get('doc_generator_meta',
                                 {'schema_name': schema_name})

        if version == '1.0.0':
            version = None

        if (not version) and (schema_ref in property_data):
            warnings.warn(
                'Check', schema_ref, 'for version problems.',
                'Are there two files with either version 1.0.0 or no version?')

        try:
            property_data['definitions'] = data['definitions']
            for ref_part in ref['ref'].split('/'):
                if not ref_part:
                    continue
                data = data[ref_part]

            # resolve anyOf to embedded object, if present:
            if 'anyOf' in data:
                for elt in data['anyOf']:
                    if ('type' in elt) and (elt['type'] == 'object'):
                        data = elt
                        break

            properties = data['properties']
            property_data['properties'] = properties

        except KeyError:
            warnings.warn('Unable to find properties in path ' + ref['ref'] +
                          ' from ' + filename)
            return {}

        meta = self.extend_metadata(meta, properties, version,
                                    normalized_uri + '#properties/')
        meta['definitions'] = meta.get('definitions', {})
        definitions = property_data['definitions']
        meta['definitions'] = self.extend_metadata(
            meta['definitions'], definitions, version,
            normalized_uri + '#definitions/')
        property_data['doc_generator_meta'] = meta

        return property_data
    def group_files(self, files):
        """Traverse files, grouping any unversioned/versioned schemas together.

        Parses json to identify versioned files.
        Returns a dict of {normalized_uri : [versioned files]} where each
        versioned file is a dict of {root, filename, ref path, schema_name,
        _is_versioned_schema, _is_collection_of}.
        """

        file_list = [os.path.abspath(filename) for filename in files]
        grouped_files = {}
        all_schemas = {}
        missing_files = []
        processed_files = []

        for filename in file_list:
            # Get the (probably versioned) filename, and save the data:
            root, _, fname = filename.rpartition(os.sep)

            data = DocGenUtilities.load_as_json(filename)

            schema_name = SchemaTraverser.find_schema_name(fname, data)
            if schema_name is None: continue

            normalized_uri = self.construct_uri_for_filename(filename)

            data['_schema_name'] = schema_name
            all_schemas[normalized_uri] = data

            if filename in processed_files: continue

            ref = ''
            if '$ref' in data:
                ref = data['$ref'][1:]  # drop initial '#'
            else:
                continue

            if fname.count('.') > 1:
                continue

            original_ref = ref
            for pathpart in ref.split('/'):
                if not pathpart: continue
                data = data[pathpart]

            ref_files = []

            # is_versioned_schema will be True if there is an "anyOf" pointing to one or more versioned files.
            is_versioned_schema = False

            # is_collection_of will contain the type of objects in the collection.
            is_collection_of = None

            if 'anyOf' in data:
                for obj in data['anyOf']:
                    if '$ref' in obj:
                        refpath_uri, refpath_path = obj['$ref'].split('#')
                        if refpath_path == '/definitions/idRef':
                            is_versioned_schema = True
                            continue
                        ref_fn = refpath_uri.split('/')[-1]
                        # Skip files that are not present.
                        ref_filename = os.path.abspath(
                            os.path.join(root, ref_fn))
                        if ref_filename in file_list:
                            ref_files.append({
                                'root': root,
                                'filename': ref_fn,
                                'ref': refpath_path,
                                'schema_name': schema_name
                            })
                        elif ref_filename not in missing_files:
                            missing_files.append(ref_filename)

                    else:
                        # If there is anything that's not a ref, this isn't an unversioned schema.
                        # It's probably a Collection. Zero out ref_files and skip the rest so we
                        # can save this as a single-file group.
                        if 'properties' in obj:
                            if 'Members' in obj['properties']:
                                # It's a collection. What is it a collection of?
                                member_ref = obj['properties']['Members'].get(
                                    'items', {}).get('$ref')
                                if member_ref:
                                    is_collection_of = self.normalize_ref(
                                        member_ref)
                        ref_files = []
                        continue

            elif '$ref' in data:
                refpath_uri, refpath_path = data['$ref'].split('#')
                if refpath_path == '/definitions/idRef':
                    continue

                ref_fn = refpath_uri.split('/')[-1]
                # Skip files that are not present.
                ref_filename = os.path.abspath(os.path.join(root, ref_fn))
                if ref_filename in file_list:
                    ref_files.append({
                        'root': root,
                        'filename': ref_fn,
                        'ref': refpath_path,
                        'schema_name': schema_name
                    })
                elif ref_filename not in missing_files:
                    missing_files.append(ref_filename)

            else:
                ref = original_ref

            if len(ref_files):
                # Add the _is_versioned_schema and  is_collection_of hints to each ref object
                [
                    x.update({
                        '_is_versioned_schema': is_versioned_schema,
                        '_is_collection_of': is_collection_of
                    }) for x in ref_files
                ]
                grouped_files[normalized_uri] = ref_files

            if not normalized_uri in grouped_files:
                # this is not an unversioned schema after all.
                grouped_files[normalized_uri] = [{
                    'root':
                    root,
                    'filename':
                    fname,
                    'ref':
                    ref,
                    'schema_name':
                    schema_name,
                    '_is_versioned_schema':
                    is_versioned_schema,
                    '_is_collection_of':
                    is_collection_of
                }]

            # Note these files as processed:
            processed_files.append(filename)
            for file_refs in grouped_files[normalized_uri]:
                ref_filename = os.path.join(file_refs['root'],
                                            file_refs['filename'])
                processed_files.append(ref_filename)

        if len(missing_files):
            numfiles = len(missing_files)
            if numfiles <= 10:
                missing_files_list = '\n   '.join(missing_files)
            else:
                missing_files_list = '\n   '.join(
                    missing_files[0:9]) + "\n   and " + str(numfiles -
                                                            10) + " more."
            warnings.warn(
                str(numfiles) + " referenced files were missing: \n   " +
                missing_files_list)

        return grouped_files, all_schemas
Exemple #19
0
    def format_property_details(self, prop_name, prop_type, prop_description, enum, enum_details,
                                    supplemental_details, parent_prop_info, profile=None):
        """Generate a formatted table of enum information for inclusion in Property details."""

        contents = []

        parent_version = parent_prop_info.get('versionAdded')
        if parent_version:
            parent_version = self.format_version(parent_version)

        # Are we in profile mode? If so, consult the profile passed in for this property.
        # For Action Parameters, look for ParameterValues/RecommendedValues; for
        # Property enums, look for MinSupportValues/RecommendedValues.
        profile_mode = self.config.get('profile_mode')
        if profile_mode:
            if profile is None:
                profile = {}
            profile_values = profile.get('Values', [])
            profile_min_support_values = profile.get('MinSupportValues', []) # No longer a valid name?
            profile_parameter_values = profile.get('ParameterValues', [])
            profile_recommended_values = profile.get('RecommendedValues', [])

            # profile_all_values is not used. What were we going for here?
            profile_all_values = (profile_values + profile_min_support_values + profile_parameter_values
                                  + profile_recommended_values)

        # In subset mode, an action parameter with no Values (property) or ParameterValues (Action)
        # means all values are supported.
        # Otherwise, Values/ParameterValues specifies the set that should be listed.
        if profile_mode == 'subset':
            if len(profile_values):
                enum = [x for x in enum if x in profile_values]
            elif len(profile_parameter_values):
                enum = [x for x in enum if x in profile_parameter_values]

        if prop_description:
            contents.append(self.formatter.para(prop_description))

        if isinstance(prop_type, list):
            prop_type = ', '.join([html.escape(x, False) for x in prop_type])
        else:
            prop_type = html.escape(prop_type, False)

        if supplemental_details:
            contents.append(self.formatter.markdown_to_html(supplemental_details))

        if enum_details:
            headings = [prop_type, 'Description']
            if profile_mode and profile_mode != 'subset':
                headings.append('Profile Specifies')
            header_row = self.formatter.make_header_row(headings)
            table_rows = []
            enum.sort(key=str.lower)

            for enum_item in enum:
                enum_name = html.escape(enum_item, False)
                version = version_depr = deprecated_descr = None
                version_display = None
                if parent_prop_info.get('enumVersionAdded'):
                    version_added = parent_prop_info.get('enumVersionAdded').get(enum_name)
                    if version_added:
                        version = self.format_version(version_added)
                if parent_prop_info.get('enumVersionDeprecated'):
                    version_deprecated = parent_prop_info.get('enumVersionDeprecated').get(enum_name)
                    if version_deprecated:
                        version_depr = self.format_version(version_deprecated)
                if parent_prop_info.get('enumDeprecated'):
                    deprecated_descr = parent_prop_info.get('enumDeprecated').get(enum_name)

                if version:
                    if not parent_version or DocGenUtilities.compare_versions(version, parent_version) > 0:
                        version_text = html.escape(version, False)
                        version_display = self.truncate_version(version_text, 2) + '+'

                if version_display:
                    if version_depr:
                        version_depr_text = html.escape(version_depr, False)
                        deprecated_display = self.truncate_version(version_depr_text, 2)
                        enum_name += ' ' + self.formatter.italic('(v' + version_display + ', deprecated v' + deprecated_display + ')')
                        if deprecated_descr:
                            deprecated_descr_text = html.escape('Deprecated in v' + deprecated_display
                                                                    + ' and later. ' + deprecated_descr)
                    else:
                        enum_name += ' ' + self.formatter.italic('(v' + version_display + ')')
                elif version_depr:
                    version_depr_text = html.escape(version_depr, False)
                    deprecated_display = self.truncate_version(version_depr_text, 2)
                    enum_name += ' ' + self.formatter.italic('(deprecated v' + deprecated_display + ')')
                    if deprecated_descr:
                        deprecated_descr_text = html.escape('Deprecated in v' + deprecated_display + ' and later. ' +
                                                        deprecated_descr)

                descr = html.escape(enum_details.get(enum_item, ''), False)
                if deprecated_descr:
                    if descr:
                        descr += ' ' + self.formatter.italic(deprecated_descr)
                    else:
                        descr = self.formatter.italic(deprecated_descr)
                cells = [enum_name, descr]

                if profile_mode and profile_mode != 'subset':
                    if enum_name in profile_values:
                        cells.append('Mandatory')
                    elif enum_name in profile_min_support_values:
                        cells.append('Mandatory')
                    elif enum_name in profile_parameter_values:
                        cells.append('Mandatory')
                    elif enum_name in profile_recommended_values:
                        cells.append('Recommended')
                    else:
                        cells.append('')

                table_rows.append(self.formatter.make_row(cells))
            contents.append(self.formatter.make_table(table_rows, [header_row], 'enum enum-details'))

        elif enum:
            headings = [prop_type]
            if profile_mode and profile_mode != 'subset':
                headings.append('Profile Specifies')
            header_row = self.formatter.make_header_row(headings)
            table_rows = []
            enum.sort(key=str.lower)
            for enum_item in enum:
                enum_name = html.escape(enum_item, False)
                version = version_depr = deprecated_descr = None
                version_display = None

                if parent_prop_info.get('enumVersionAdded'):
                    version_added = parent_prop_info.get('enumVersionAdded').get(enum_name)
                    if version_added:
                        version = self.format_version(version_added)

                if parent_prop_info('enumVersionDeprecated'):
                    version_deprecated = parent_prop_info.get('enumVersionDeprecated').get(enum_name)
                    if version_deprecated:
                        version_depr = self.format_version(version_deprecated)

                if parent_prop_info.get('enumDeprecated'):
                    deprecated_descr = parent_prop_info.get('enumDeprecated').get(enum_name)

                if version:
                    if not parent_version or DocGenUtilities.compare_versions(version, parent_version) > 0:
                        version_text = html.escape(version, False)
                        version_display = self.truncate_version(version_text, 2) + '+'

                if version_display:
                    if version_depr:
                        version_depr_text = html.escape(version_depr, False)
                        deprecated_display = self.truncate_version(version_depr_text, 2)
                        enum_name += ' ' + self.formatter.italic('(v' + version_display + ', deprecated v' + deprecated_display + ')')
                        if deprecated_descr:
                            enum_name += '<br>' + self.formatter.italic(html.escape(
                                'Deprecated in v' + deprecated_display
                                + ' and later. ' + deprecated_descr))
                    else:
                        enum_name += ' ' + self.formatter.italic('(v' + version_display + ')')

                elif version_depr:
                    version_depr_text = html.escape(version_depr, False)
                    deprecated_display = self.truncate_version(version_depr_text, 2)
                    enum_name += ' ' + self.formatter.italic('(deprecated v' + deprecated_display + ')')
                    if deprecated_descr:
                        enum_name += '<br>' + self.formatter.italic(html.escape(
                            'Deprecated in v' + deprecated_display + ' and later. ' +
                            deprecated_descr))


                cells = [enum_name]
                if profile_mode and profile_mode != 'subset':
                    if enum_name in profile_values:
                        cells.append('Mandatory')
                    elif enum_name in profile_min_support_values:
                        cells.append('Mandatory')
                    elif enum_name in profile_parameter_values:
                        cells.append('Mandatory')
                    elif enum_name in profile_recommended_values:
                        cells.append('Recommended')
                    else:
                        cells.append('')

                table_rows.append(self.formatter.make_row(cells))
            contents.append(self.formatter.make_table(table_rows, [header_row], 'enum'))

        return '\n'.join(contents) + '\n'
    def format_property_details(self,
                                prop_name,
                                prop_type,
                                prop_description,
                                enum,
                                enum_details,
                                supplemental_details,
                                meta,
                                anchor=None,
                                profile=None):
        """Generate a formatted table of enum information for inclusion in Property Details."""

        contents = []
        contents.append(
            self.formatter.head_four(
                html.escape(prop_name, False) + ':', self.level, anchor))

        parent_version = meta.get('version')
        enum_meta = meta.get('enum', {})

        # Are we in profile mode? If so, consult the profile passed in for this property.
        # For Action Parameters, look for ParameterValues/RecommendedValues; for
        # Property enums, look for MinSupportValues/RecommendedValues.
        profile_mode = self.config.get('profile_mode')
        if profile_mode:
            if profile is None:
                profile = {}
            profile_values = profile.get('Values', [])
            profile_min_support_values = profile.get('MinSupportValues', [])
            profile_parameter_values = profile.get('ParameterValues', [])
            profile_recommended_values = profile.get('RecommendedValues', [])

            profile_all_values = (profile_values + profile_min_support_values +
                                  profile_parameter_values +
                                  profile_recommended_values)

        if prop_description:
            contents.append(self.formatter.para(prop_description))

        if isinstance(prop_type, list):
            prop_type = ', '.join([html.escape(x, False) for x in prop_type])
        else:
            prop_type = html.escape(prop_type, False)

        if supplemental_details:
            contents.append(
                self.formatter.markdown_to_html(supplemental_details))

        if enum_details:
            headings = [prop_type, 'Description']
            if profile_mode:
                headings.append('Profile Specifies')
            header_row = self.formatter.make_header_row(headings)
            table_rows = []
            enum.sort(key=str.lower)

            for enum_item in enum:
                enum_name = html.escape(enum_item, False)
                enum_item_meta = enum_meta.get(enum_item, {})
                version_display = None
                deprecated_descr = None

                if 'version' in enum_item_meta:
                    version = enum_item_meta['version']
                    if not parent_version or DocGenUtilities.compare_versions(
                            version, parent_version) > 0:
                        version_text = html.escape(version, False)
                        version_display = self.truncate_version(
                            version_text, 2) + '+'

                if version_display:
                    if 'version_deprecated' in enum_item_meta:
                        version_depr = html.escape(
                            enum_item_meta['version_deprecated'], False)
                        deprecated_display = self.truncate_version(
                            version_depr, 2)
                        enum_name += ' ' + self.formatter.italic(
                            '(v' + version_display + ', deprecated v' +
                            deprecated_display + ')')
                        if enum_item_meta.get(
                                'version_deprecated_explanation'):
                            deprecated_descr = html.escape(
                                'Deprecated v' + deprecated_display + '+. ' +
                                enum_item_meta[
                                    'version_deprecated_explanation'], False)
                    else:
                        enum_name += ' ' + self.formatter.italic(
                            '(v' + version_display + ')')
                elif 'version_deprecated' in enum_item_meta:
                    version_depr = html.escape(
                        enum_item_meta['version_deprecated'], False)
                    deprecated_display = self.truncate_version(version_depr, 2)
                    enum_name += ' ' + self.formatter.italic(
                        '(deprecated v' + deprecated_display + ')')
                    if enum_item_meta.get('version_deprecated_explanation'):
                        deprecated_descr = html.escape(
                            'Deprecated v' + deprecated_display + '+. ' +
                            enum_item_meta['version_deprecated_explanation'],
                            False)

                descr = html.escape(enum_details.get(enum_item, ''), False)
                if deprecated_descr:
                    if descr:
                        descr += ' ' + self.formatter.italic(deprecated_descr)
                    else:
                        descr = self.formatter.italic(deprecated_descr)
                cells = [enum_name, descr]

                if profile_mode:
                    if enum_name in profile_values:
                        cells.append('Required')
                    elif enum_name in profile_min_support_values:
                        cells.append('Required')
                    elif enum_name in profile_parameter_values:
                        cells.append('Required')
                    elif enum_name in profile_recommended_values:
                        cells.append('Recommended')
                    else:
                        cells.append('')

                table_rows.append(self.formatter.make_row(cells))
            contents.append(
                self.formatter.make_table(table_rows, [header_row],
                                          'enum enum-details'))

        elif enum:
            headings = [prop_type]
            if profile_mode:
                headings.append('Profile Specifies')
            header_row = self.formatter.make_header_row(headings)
            table_rows = []
            enum.sort(key=str.lower)
            for enum_item in enum:
                enum_name = html.escape(enum_item, False)
                enum_item_meta = enum_meta.get(enum_item, {})
                version_display = None

                if 'version' in enum_item_meta:
                    version = enum_item_meta['version']
                    if not parent_version or DocGenUtilities.compare_versions(
                            version, parent_version) > 0:
                        version_text = html.escape(version, False)
                        version_display = self.truncate_version(
                            version_text, 2) + '+'

                if version_display:
                    if 'version_deprecated' in enum_item_meta:
                        version_depr = html.escape(
                            enum_item_meta['version_deprecated'], False)
                        deprecated_display = self.truncate_version(
                            version_depr, 2)
                        enum_name += ' ' + self.formatter.italic(
                            '(v' + version_display + ', deprecated v' +
                            deprecated_display + ')')
                        if enum_item_meta.get(
                                'version_deprecated_explanation'):
                            enum_name += '<br>' + self.formatter.italic(
                                html.escape(
                                    'Deprecated v' + deprecated_display +
                                    '+. ' + enum_item_meta[
                                        'version_deprecated_explanation'],
                                    False))
                    else:
                        enum_name += ' ' + self.formatter.italic(
                            '(v' + version_display + ')')

                elif 'version_deprecated' in enum_item_meta:
                    version_depr = html.escape(
                        enum_item_meta['version_deprecated'], False)
                    deprecated_display = self.truncate_version(version_depr, 2)
                    enum_name += ' ' + self.formatter.italic(
                        '(deprecated v' + deprecated_display + ')')
                    if enum_item_meta.get('version_deprecated_explanation'):
                        enum_name += '<br>' + self.formatter.italic(
                            html.escape(
                                'Deprecated v' + deprecated_display + '+. ' +
                                enum_item_meta[
                                    'version_deprecated_explanation'], False))

                cells = [enum_name]
                if profile_mode:
                    if enum_name in profile_values:
                        cells.append('Required')
                    elif enum_name in profile_min_support_values:
                        cells.append('Required')
                    elif enum_name in profile_parameter_values:
                        cells.append('Required')
                    elif enum_name in profile_recommended_values:
                        cells.append('Recommended')
                    else:
                        cells.append('')

                table_rows.append(self.formatter.make_row(cells))
            contents.append(
                self.formatter.make_table(table_rows, [header_row], 'enum'))

        return '\n'.join(contents) + '\n'
def test_load_as_json():
    data = DocGenUtilities.load_as_json(os.path.join(sampledir, '1.json'))
    assert data['foo'] == 'bar' and data['baz'] == ['foo', 'bar', 'baz']
    def get_versioned_uri(self,
                          base_name,
                          repo,
                          min_version,
                          is_local_file=False):
        """ Get a versioned URI for the base_name schema.
        Parameters:
         base_name     -- Base name of the schema, e.g., "Resource"
         repo          -- URI of the the repo where the schema is expected to be found.
         min_version   -- Minimum acceptable version.
         is_local_file -- Find file on local system.

        If a matching URI is found among links at the repo address, it will be returned.
        If not, a URI will be composed based on repo, base_name, and version. (This
        may succeed if the repo does not provide a directory index.)

        Versions must match on the major version, with minor.errata equal to or greater than
        what is specified in min_version.
        """

        versioned_uri = None

        if is_local_file:
            repo_links = DocGenUtilities.local_get_links(repo)
        else:
            repo_links = DocGenUtilities.html_get_links(repo)

        if repo_links:
            minversion_parts = re.findall('(\d+)', min_version)

            candidate = None
            candidate_strength = 0
            for rl in repo_links:
                base = base_name + '.'
                if rl.startswith(repo) and base in rl and rl.endswith('.json'):
                    parts = rl[0:-5].rsplit(base, 1)
                    if len(parts) == 2:
                        suffix = parts[1]
                        version_parts = re.findall('(\d+)', suffix)
                        # Major version must match; minor.errata must be >=.
                        if version_parts[0] != minversion_parts[0]:
                            continue
                        if version_parts[1] > minversion_parts[1]:
                            strength = self.version_index(version_parts)
                            if strength > candidate_strength:
                                candidate_strength = strength
                                candidate = rl
                            continue
                        if version_parts[1] == minversion_parts[1]:
                            if len(version_parts) == 3 and len(
                                    minversion_parts) == 3:
                                if version_parts[2] >= minversion_parts[2]:
                                    strength = self.version_index(
                                        version_parts)
                                    if strength > candidate_strength:
                                        candidate_strength = strength
                                        candidate = rl
                            elif len(version_parts) == 2:
                                strength = self.version_index(version_parts)
                                if strength > candidate_strength:
                                    candidate_strength = strength
                                    candidate = rl
            if candidate:
                versioned_uri = candidate

        elif is_local_file:
            # Build URI from repo, name, and minversion:
            versioned_uri = '/'.join([repo, base_name
                                      ]) + '.v' + min_version + '.json'

        return versioned_uri
Exemple #23
0
    def format_property_details(self, prop_name, prop_type, prop_description, enum, enum_details,
                                supplemental_details, meta, anchor=None, profile=None):
        """Generate a formatted table of enum information for inclusion in Property Details."""

        contents = []
        contents.append(self.formatter.head_four(html.escape(prop_name, False) + ':', self.level, anchor))

        parent_version = meta.get('version')
        enum_meta = meta.get('enum', {})

        # Are we in profile mode? If so, consult the profile passed in for this property.
        # For Action Parameters, look for ParameterValues/RecommendedValues; for
        # Property enums, look for MinSupportValues/RecommendedValues.
        profile_mode = self.config.get('profile_mode')
        if profile_mode:
            if profile is None:
                profile = {}
            profile_values = profile.get('Values', [])
            profile_min_support_values = profile.get('MinSupportValues', [])
            profile_parameter_values = profile.get('ParameterValues', [])
            profile_recommended_values = profile.get('RecommendedValues', [])

            profile_all_values = (profile_values + profile_min_support_values + profile_parameter_values
                                  + profile_recommended_values)

        if prop_description:
            contents.append(self.formatter.para(prop_description))

        if isinstance(prop_type, list):
            prop_type = ', '.join([html.escape(x, False) for x in prop_type])
        else:
            prop_type = html.escape(prop_type, False)

        if supplemental_details:
            contents.append(self.formatter.markdown_to_html(supplemental_details))

        if enum_details:
            headings = [prop_type, 'Description']
            if profile_mode:
                headings.append('Profile Specifies')
            header_row = self.formatter.make_header_row(headings)
            table_rows = []
            enum.sort(key=str.lower)

            for enum_item in enum:
                enum_name = html.escape(enum_item, False)
                enum_item_meta = enum_meta.get(enum_item, {})
                version_display = None
                deprecated_descr = None

                if 'version' in enum_item_meta:
                    version = enum_item_meta['version']
                    if not parent_version or DocGenUtilities.compare_versions(version, parent_version) > 0:
                        version_text = html.escape(version, False)
                        version_display = self.truncate_version(version_text, 2) + '+'

                if version_display:
                    if 'version_deprecated' in enum_item_meta:
                        version_depr = html.escape(enum_item_meta['version_deprecated'], False)
                        deprecated_display = self.truncate_version(version_depr, 2)
                        enum_name += ' ' + self.formatter.italic('(v' + version_display + ', deprecated v' + deprecated_display + ')')
                        if enum_item_meta.get('version_deprecated_explanation'):
                            deprecated_descr = html.escape('Deprecated v' + deprecated_display + '+. ' +
                                                           enum_item_meta['version_deprecated_explanation'], False)
                    else:
                        enum_name += ' ' + self.formatter.italic('(v' + version_display + ')')
                elif 'version_deprecated' in enum_item_meta:
                    version_depr = html.escape(enum_item_meta['version_deprecated'], False)
                    deprecated_display = self.truncate_version(version_depr, 2)
                    enum_name += ' ' + self.formatter.italic('(deprecated v' + deprecated_display + ')')
                    if enum_item_meta.get('version_deprecated_explanation'):
                        deprecated_descr = html.escape('Deprecated v' + deprecated_display + '+. ' +
                                                       enum_item_meta['version_deprecated_explanation'], False)

                descr = html.escape(enum_details.get(enum_item, ''), False)
                if deprecated_descr:
                    if descr:
                        descr += ' ' + self.formatter.italic(deprecated_descr)
                    else:
                        descr = self.formatter.italic(deprecated_descr)
                cells = [enum_name, descr]

                if profile_mode:
                    if enum_name in profile_values:
                        cells.append('Required')
                    elif enum_name in profile_min_support_values:
                        cells.append('Required')
                    elif enum_name in profile_parameter_values:
                        cells.append('Required')
                    elif enum_name in profile_recommended_values:
                        cells.append('Recommended')
                    else:
                        cells.append('')

                table_rows.append(self.formatter.make_row(cells))
            contents.append(self.formatter.make_table(table_rows, [header_row], 'enum enum-details'))

        elif enum:
            headings = [prop_type]
            if profile_mode:
                headings.append('Profile Specifies')
            header_row = self.formatter.make_header_row(headings)
            table_rows = []
            enum.sort(key=str.lower)
            for enum_item in enum:
                enum_name = html.escape(enum_item, False)
                enum_item_meta = enum_meta.get(enum_item, {})
                version_display = None

                if 'version' in enum_item_meta:
                    version = enum_item_meta['version']
                    if not parent_version or DocGenUtilities.compare_versions(version, parent_version) > 0:
                        version_text = html.escape(version, False)
                        version_display = self.truncate_version(version_text, 2) + '+'

                if version_display:
                    if 'version_deprecated' in enum_item_meta:
                        version_depr = html.escape(enum_item_meta['version_deprecated'], False)
                        deprecated_display = self.truncate_version(version_depr, 2)
                        enum_name += ' ' + self.formatter.italic('(v' + version_display + ', deprecated v' + deprecated_display + ')')
                        if enum_item_meta.get('version_deprecated_explanation'):
                            enum_name += '<br>' + self.formatter.italic(html.escape('Deprecated v' + deprecated_display + '+. ' +
                                                                          enum_item_meta['version_deprecated_explanation'], False))
                    else:
                        enum_name += ' ' + self.formatter.italic('(v' + version_display + ')')

                elif 'version_deprecated' in enum_item_meta:
                    version_depr = html.escape(enum_item_meta['version_deprecated'], False)
                    deprecated_display = self.truncate_version(version_depr, 2)
                    enum_name += ' ' + self.formatter.italic('(deprecated v' + deprecated_display + ')')
                    if enum_item_meta.get('version_deprecated_explanation'):
                        enum_name += '<br>' + self.formatter.italic(html.escape('Deprecated v' + deprecated_display + '+. ' +
                                                                      enum_item_meta['version_deprecated_explanation'], False))

                cells = [enum_name]
                if profile_mode:
                    if enum_name in profile_values:
                        cells.append('Required')
                    elif enum_name in profile_min_support_values:
                        cells.append('Required')
                    elif enum_name in profile_parameter_values:
                        cells.append('Required')
                    elif enum_name in profile_recommended_values:
                        cells.append('Recommended')
                    else:
                        cells.append('')

                table_rows.append(self.formatter.make_row(cells))
            contents.append(self.formatter.make_table(table_rows, [header_row], 'enum'))

        return '\n'.join(contents) + '\n'
def test_load_as_json_file_not_good_warns():
    with pytest.warns(UserWarning):
        data = DocGenUtilities.load_as_json(os.path.join(sampledir, 'badjson.json'))
        assert data == {}
    def format_property_row(self,
                            schema_ref,
                            prop_name,
                            prop_info,
                            prop_path=[],
                            in_array=False):
        """Format information for a single property.

        Returns an object with 'row', 'details', 'action_details', and 'profile_conditional_details':

        'row': content for the main table being generated.
        'details': content for the Property Details section.
        'action_details': content for the Actions section.
        'profile_conditional_details': populated only in profile_mode, formatted conditional details

        This may include embedded objects with their own properties.
        """

        traverser = self.traverser
        formatted = []  # The row itself

        current_depth = len(prop_path)

        if in_array:
            current_depth = current_depth - 1

        # strip_top_object is used for fragments, to allow output of just the properties
        # without the enclosing object:
        if self.config.get('strip_top_object') and current_depth > 0:
            indentation_string = '&nbsp;' * 6 * (current_depth - 1)
        else:
            indentation_string = '&nbsp;' * 6 * current_depth

        # If prop_path starts with Actions and is more than 1 deep, we are outputting for an Action Details
        # section and should dial back the indentation by one level.
        if len(prop_path) > 1 and prop_path[0] == 'Actions':
            indentation_string = '&nbsp;' * 6 * (current_depth - 1)

        collapse_array = False  # Should we collapse a list description into one row? For lists of simple types
        has_enum = False

        if current_depth < self.current_depth:
            for i in range(current_depth, self.current_depth):
                if i in self.current_version:
                    del self.current_version[i]
        self.current_depth = current_depth
        parent_depth = current_depth - 1

        if isinstance(prop_info, list):
            meta = prop_info[0].get('_doc_generator_meta')
            has_enum = 'enum' in prop_info[0]
        elif isinstance(prop_info, dict):
            meta = prop_info.get('_doc_generator_meta')
            has_enum = 'enum' in prop_info
        if not meta:
            meta = {}

        # We want to modify a local copy of meta, deleting redundant version info
        meta = copy.deepcopy(meta)

        name_and_version = self.formatter.bold(html.escape(prop_name, False))
        deprecated_descr = None

        version = meta.get('version')
        self.current_version[current_depth] = version

        # Don't display version if there is a parent version and this is not newer:
        if self.current_version.get(parent_depth) and version:
            if DocGenUtilities.compare_versions(
                    version, self.current_version.get(parent_depth)) <= 0:
                del meta['version']

        if meta.get('version', '1.0.0') != '1.0.0':
            version_text = html.escape(meta['version'], False)
            version_display = self.truncate_version(version_text, 2) + '+'
            if 'version_deprecated' in meta:
                version_depr = html.escape(meta['version_deprecated'], False)
                deprecated_display = self.truncate_version(version_depr, 2)
                name_and_version += ' ' + self.formatter.italic(
                    '(v' + version_display + ', deprecated v' +
                    deprecated_display + ')')
                deprecated_descr = html.escape(
                    "Deprecated v" + deprecated_display + '+. ' +
                    meta['version_deprecated_explanation'], False)
            else:
                name_and_version += ' ' + self.formatter.italic(
                    '(v' + version_display + ')')
        elif 'version_deprecated' in meta:
            version_depr = html.escape(meta['version_deprecated'], False)
            deprecated_display = self.truncate_version(version_depr, 2)
            name_and_version += ' ' + self.formatter.italic(
                '(deprecated v' + deprecated_display + ')')
            deprecated_descr = html.escape(
                "Deprecated v" + deprecated_display + '+. ' +
                meta['version_deprecated_explanation'], False)

        formatted_details = self.parse_property_info(schema_ref, prop_name,
                                                     prop_info, prop_path,
                                                     meta.get('within_action'))

        if formatted_details.get('promote_me'):
            return ({
                'row': '\n'.join(formatted_details['item_description']),
                'details': formatted_details['prop_details'],
                'action_details': formatted_details.get('action_details')
            })

        if self.config.get('strip_top_object') and current_depth == 0:
            # In this case, we're done for this bit of documentation, and we just want the properties of this object.
            formatted.append('\n'.join(
                formatted_details['object_description']))
            return ({
                'row':
                '\n'.join(formatted),
                'details':
                formatted_details['prop_details'],
                'action_details':
                formatted_details.get('action_details'),
                'profile_conditional_details':
                formatted_details.get('profile_conditional_details')
            })

        # Eliminate dups in these these properties and join with a delimiter:
        props = {
            'prop_type': self.separators['inline'],
            'descr': self.separators['linebreak'],
            'object_description': '\n',
            'item_description': '\n'
        }

        for property_name, delim in props.items():
            if isinstance(formatted_details[property_name], list):
                property_values = []
                self.append_unique_values(formatted_details[property_name],
                                          property_values)
                formatted_details[property_name] = delim.join(property_values)

        if formatted_details['prop_is_object'] and not in_array:
            if formatted_details['object_description'] == '':
                name_and_version += ' { }'
            else:
                name_and_version += ' {'

        if formatted_details['prop_is_array']:
            if formatted_details['item_description'] == '':
                if formatted_details['array_of_objects']:
                    name_and_version += ' [ { } ]'
                else:
                    name_and_version += ' [ ] '
            else:
                if formatted_details['array_of_objects']:
                    name_and_version += ' [ {'
                else:
                    collapse_array = True
                    name_and_version += ' [ ] '
        elif in_array:
            if formatted_details['prop_is_object']:
                name_and_version += ' [ { } ]'
            else:
                name_and_version += ' [ ] '

        name_and_version = '<nobr>' + name_and_version + '</nobr>'

        if formatted_details['descr'] is None:
            formatted_details['descr'] = ''

        formatted_details['descr'] = self.formatter.markdown_to_html(
            html.escape(formatted_details['descr'], False), no_para=True)

        if formatted_details['add_link_text']:
            if formatted_details['descr']:
                formatted_details['descr'] += ' '
            formatted_details['descr'] += formatted_details['add_link_text']

        # Append reference info to descriptions, if appropriate:
        if not formatted_details.get('fulldescription_override'):
            # If there are prop_details (enum details), add a note to the description:
            if formatted_details[
                    'has_direct_prop_details'] and not formatted_details[
                        'has_action_details']:
                if has_enum:
                    anchor = schema_ref + '|details|' + prop_name
                    text_descr = 'See <a href="#' + anchor + '">' + prop_name + '</a> in Property Details, below, for the possible values of this property.'
                else:
                    text_descr = 'See Property Details, below, for more information about this property.'

                if formatted_details['descr']:
                    formatted_details[
                        'descr'] += '<br>' + self.formatter.italic(text_descr)
                else:
                    formatted_details['descr'] = self.formatter.italic(
                        text_descr)

            # If this is an Action with details, add a note to the description:
            if formatted_details['has_action_details']:
                anchor = schema_ref + '|action_details|' + prop_name
                text_descr = 'For more information, see the <a href="#' + anchor + '">Action Details</a> section below.'
                formatted_details['descr'] += '<br>' + self.formatter.italic(
                    text_descr)

        if deprecated_descr:
            formatted_details['descr'] += ' ' + self.formatter.italic(
                deprecated_descr)

        prop_type = html.escape(formatted_details['prop_type'], False)
        if has_enum:
            prop_type += '<br>(enum)'
        if formatted_details['prop_units']:
            prop_type += '<br>(' + formatted_details['prop_units'] + ')'

        if in_array:
            prop_type = 'array (' + prop_type + ')'

        if collapse_array:
            item_list = formatted_details['item_list']
            if len(item_list):
                if isinstance(item_list, list):
                    item_list = ', '.join([html.escape(x) for x in item_list])
                prop_type += '<br>(' + item_list + ')'

        prop_access = ''
        if not formatted_details['prop_is_object']:
            if formatted_details['read_only']:
                prop_access = '<nobr>read-only</nobr>'
            else:
                prop_access = '<nobr>read-write</nobr>'

        if formatted_details['prop_required_on_create']:
            prop_access += ' <nobr>required on create</nobr>'
        elif formatted_details['prop_required'] or formatted_details.get(
                'required_parameter'):
            prop_access += ' <nobr>required</nobr>'

        if formatted_details['nullable']:
            prop_access += ' (null)'

        # If profile reqs are present, massage them:
        profile_access = self.format_base_profile_access(formatted_details)

        descr = formatted_details['descr']
        if formatted_details['profile_purpose']:
            descr += '<br>' + self.formatter.bold(
                "Profile Purpose: " + formatted_details['profile_purpose'])

        # Conditional Requirements
        cond_req = formatted_details['profile_conditional_req']
        if cond_req:
            anchor = schema_ref + '|conditional_reqs|' + prop_name
            cond_req_text = 'See <a href="#' + anchor + '"> Conditional Requirements</a>, below, for more information.'
            descr += ' ' + self.formatter.nobr(
                self.formatter.italic(cond_req_text))
            profile_access += "<br>" + self.formatter.nobr(
                self.formatter.italic('Conditional Requirements'))

        if not profile_access:
            profile_access = '&nbsp;' * 10

        # Comparison
        if formatted_details['profile_values']:
            comparison_descr = (
                'Must be ' + formatted_details['profile_comparison'] + ' (' +
                ', '.join('"' + x + '"'
                          for x in formatted_details['profile_values']) + ')')
            profile_access += '<br>' + self.formatter.italic(comparison_descr)

        row = []
        row.append(indentation_string + name_and_version)
        if self.config.get('profile_mode'):
            row.append(profile_access)
        row.append(prop_type)
        if not self.config.get('profile_mode'):
            row.append(prop_access)
        row.append(descr)

        formatted.append(self.formatter.make_row(row))

        if len(formatted_details['object_description']) > 0:
            formatted_object = formatted_details['object_description']
            # Add a closing } to the last row of this object.
            formatted_object = self._add_closing_brace(formatted_object,
                                                       indentation_string, '}')
            formatted.append(formatted_object)

        if not collapse_array and len(
                formatted_details['item_description']) > 0:
            formatted_array = formatted_details['item_description']
            # Add closing }] or ] to the last row of this array:
            if formatted_details['array_of_objects']:
                formatted_array = self._add_closing_brace(
                    formatted_array, indentation_string, '} ]')
            else:
                formatted_array = self._add_closing_brace(
                    formatted_array, indentation_string, ']')
            formatted.append(formatted_array)

        return ({
            'row':
            '\n'.join(formatted),
            'details':
            formatted_details['prop_details'],
            'action_details':
            formatted_details.get('action_details'),
            'profile_conditional_details':
            formatted_details.get('profile_conditional_details')
        })
def test_load_as_json():
    data = DocGenUtilities.load_as_json(os.path.join(sampledir, '1.json'))
    assert data['foo'] == 'bar' and data['baz'] == ['foo', 'bar', 'baz']
    def format_property_details(self,
                                prop_name,
                                prop_type,
                                prop_description,
                                enum,
                                enum_details,
                                supplemental_details,
                                parent_prop_info,
                                profile=None):
        """Generate a formatted table of enum information for inclusion in Property details."""

        contents = []

        parent_version = parent_prop_info.get('versionAdded')
        if parent_version:
            parent_version = self.format_version(parent_version)

        # Are we in profile mode? If so, consult the profile passed in for this property.
        # For Action Parameters, look for ParameterValues/RecommendedValues; for
        # Property enums, look for MinSupportValues/RecommendedValues.
        profile_mode = self.config.get('profile_mode')
        if profile_mode:
            if profile is None:
                profile = {}

            profile_values = profile.get('Values', [])
            profile_min_support_values = profile.get(
                'MinSupportValues', [])  # No longer a valid name?
            profile_parameter_values = profile.get('ParameterValues', [])
            profile_recommended_values = profile.get('RecommendedValues', [])

            # profile_all_values is not used. What were we going for here?
            profile_all_values = (profile_values + profile_min_support_values +
                                  profile_parameter_values +
                                  profile_recommended_values)

            # In subset mode, an action parameter with no Values (property) or ParameterValues (Action)
            # means all values are supported.
            # Otherwise, Values/ParameterValues specifies the set that should be listed.
            if profile_mode == 'subset':
                if len(profile_values):
                    enum = [x for x in enum if x in profile_values]
                elif len(profile_parameter_values):
                    enum = [x for x in enum if x in profile_parameter_values]

        if prop_description:
            contents.append(
                self.formatter.para(
                    self.escape_for_markdown(
                        prop_description, self.config.get('escape_chars',
                                                          []))))

        if isinstance(prop_type, list):
            prop_type = ', '.join(prop_type)

        if supplemental_details:
            contents.append('\n' + supplemental_details + '\n')

        enum_translations = parent_prop_info.get('enumTranslations', {})

        if enum_details:
            if profile_mode and profile_mode != 'subset':
                contents.append('| ' + prop_type + ' | ' + _('Description') +
                                ' | ' + _('Profile Specifies') + ' |')
                contents.append('| --- | --- | --- |')
            else:
                contents.append('| ' + prop_type + ' | ' + _('Description') +
                                ' |')
                contents.append('| --- | --- |')
            enum.sort(key=str.lower)
            for enum_item in enum:
                enum_name = enum_item
                enum_translation = enum_translations.get(enum_item)
                version = version_depr = deprecated_descr = None
                version_display = None
                if parent_prop_info.get('enumVersionAdded'):
                    version_added = parent_prop_info.get(
                        'enumVersionAdded').get(enum_name)
                    if version_added:
                        version = self.format_version(version_added)
                if parent_prop_info.get('enumVersionDeprecated'):
                    version_deprecated = parent_prop_info.get(
                        'enumVersionDeprecated').get(enum_name)
                    if version_deprecated:
                        version_depr = self.format_version(version_deprecated)
                if parent_prop_info.get('enumDeprecated'):
                    deprecated_descr = parent_prop_info.get(
                        'enumDeprecated').get(enum_name)

                if enum_translation:
                    enum_name += ' (' + enum_translation + ')'

                if version:
                    if not parent_version or DocGenUtilities.compare_versions(
                            version, parent_version) > 0:
                        version_display = self.truncate_version(version,
                                                                2) + '+'

                if version_display:
                    if version_depr:
                        deprecated_display = self.truncate_version(
                            version_depr, 2)
                        enum_name += ' ' + self.formatter.italic(
                            _('(v%(version_number)s, deprecated v%(deprecated_version)s)'
                              ) % {
                                  'version_number': version_display,
                                  'deprecated_version': deprecated_display
                              })
                        if deprecated_descr:
                            deprecated_descr = (_(
                                'Deprecated in v%(version_number)s and later. %(explanation)s'
                            ) % {
                                'version_number': deprecated_display,
                                'explanation': deprecated_descr
                            })
                    else:
                        enum_name += ' ' + self.formatter.italic(
                            _('(v%(version_number)s)') %
                            {'version_number': version_display})
                elif version_depr:
                    deprecated_display = self.truncate_version(version_depr, 2)
                    enum_name += ' ' + self.formatter.italic(
                        _('(deprecated v%(version_number)s)') %
                        {'version_number': deprecated_display})
                    if deprecated_descr:
                        deprecated_descr = (_(
                            'Deprecated in v%(version_number)s and later. %(explanation)s'
                        ) % {
                            'version_number': deprecated_display,
                            'explanation': deprecated_descr
                        })

                descr = enum_details.get(enum_item, '')
                if deprecated_descr:
                    if descr:
                        descr += ' ' + self.formatter.italic(deprecated_descr)
                    else:
                        descr = self.formatter.italic(deprecated_descr)

                if profile_mode and profile_mode != 'subset':
                    profile_spec = ''
                    # Note: don't wrap the following strings for trnaslation; self.text_map handles that.
                    if enum_item in profile_values:
                        profile_spec = 'Mandatory'
                    elif enum_item in profile_min_support_values:
                        profile_spec = 'Mandatory'
                    elif enum_item in profile_parameter_values:
                        profile_spec = 'Mandatory'
                    elif enum_item in profile_recommended_values:
                        profile_spec = 'Recommended'
                    contents.append('| ' + enum_name + ' | ' + descr + ' | ' +
                                    self.text_map(profile_spec) + ' |')
                else:
                    contents.append('| ' + enum_name + ' | ' + descr + ' |')

        elif enum:
            if profile_mode and profile_mode != 'subset':
                contents.append('| ' + prop_type + ' | ' +
                                _('Profile Specifies') + ' |')
                contents.append('| --- | --- |')
            else:
                contents.append('| ' + prop_type + ' |')
                contents.append('| --- |')
            for enum_item in enum:
                enum_name = enum_item
                version = version_depr = deprecated_descr = None
                version_display = None

                if parent_prop_info.get('enumVersionAdded'):
                    version_added = parent_prop_info.get(
                        'enumVersionAdded').get(enum_name)
                    if version_added:
                        version = self.format_version(version_added)

                if parent_prop_info('enumVersionDeprecated'):
                    version_deprecated = parent_prop_info.get(
                        'enumVersionDeprecated').get(enum_name)
                    if version_deprecated:
                        version_depr = self.format_version(version_deprecated)

                if parent_prop_info.get('enumDeprecated'):
                    deprecated_descr = parent_prop_info.get(
                        'enumDeprecated').get(enum_name)

                if version:
                    if not parent_version or DocGenUtilities.compare_versions(
                            version, parent_version) > 0:
                        version_text = html.escape(version, False)
                        version_display = self.truncate_version(
                            version_text, 2) + '+'

                if version_display:
                    if version_depr:
                        deprecated_display = self.truncate_version(
                            version_depr, 2)
                        if deprecated_descr:
                            enum_name += ' ' + self.formatter.italic(
                                _('(v%(version_number)s, deprecated v%(deprecated_version)s. %(explanation)s'
                                  ) % {
                                      'version_number': version_display,
                                      'deprecated_version': deprecated_display,
                                      'explanation': deprecated_descr
                                  })
                        else:
                            enum_name += ' ' + self.formatter.italic(
                                _('(v%(version_number)s, deprecated v%(deprecated_version)s)'
                                  ) % {
                                      'version_number': version_display,
                                      'deprecated_version': deprecated_display
                                  })

                    else:
                        enum_name += ' ' + self.formatter.italic(
                            _('(v%(version_number)s)') %
                            {'version_number': version_display})
                else:
                    if version_depr:
                        deprecated_display = self.truncate_version(
                            version_depr, 2)
                        if deprecated_descr:
                            enum_name += ' ' + self.formatter.italic(
                                _('Deprecated in v%(deprecated_version)s and later. %(explanation)s'
                                  ) % {
                                      'deprecated_version': deprecated_display,
                                      'explanation': deprecated_descr
                                  })
                        else:
                            enum_name += ' ' + self.formatter.italic(
                                _('(deprecated in v%(deprecated_version)s and later.)'
                                  ) %
                                {'deprecated_version': deprecated_display})

                if profile_mode and profile_mode != 'subset':
                    profile_spec = ''
                    # Note: don't wrap the following strings for trnaslation; self.text_map handles that.
                    if enum_name in profile_values:
                        profile_spec = 'Mandatory'
                    elif enum_name in profile_min_support_values:
                        profile_spec = 'Mandatory'
                    elif enum_name in profile_parameter_values:
                        profile_spec = 'Mandatory'
                    elif enum_name in profile_recommended_values:
                        profile_spec = 'Recommended'

                    contents.append('| ' + enum_name + ' | ' +
                                    self.text_map(profile_spec) + ' |')
                else:
                    contents.append('| ' + enum_name + ' | ')

        caption = self.formatter.add_table_caption(
            _("%(prop_name)s property values") % {'prop_name': prop_name})
        preamble = self.formatter.add_table_reference(
            _("The defined property values are listed in "))

        return preamble + '\n'.join(contents) + '\n' + caption
    def format_property_details(self, prop_name, prop_type, prop_description, enum, enum_details,
                                supplemental_details, meta, anchor=None, profile=None):
        """Generate a formatted table of enum information for inclusion in Property Details."""

        contents = []
        contents.append(self.formatter.head_three(prop_name + ':', self.level))

        parent_version = meta.get('version')
        enum_meta = meta.get('enum', {})

        # Are we in profile mode? If so, consult the profile passed in for this property.
        # For Action Parameters, look for ParameterValues/RecommendedValues; for
        # Property enums, look for MinSupportValues/RecommendedValues.
        profile_mode = self.config.get('profile_mode')
        if profile_mode:
            if profile is None:
                profile = {}

            profile_values = profile.get('Values', [])
            profile_min_support_values = profile.get('MinSupportValues', [])
            profile_parameter_values = profile.get('ParameterValues', [])
            profile_recommended_values = profile.get('RecommendedValues', [])

            profile_all_values = (profile_values + profile_min_support_values + profile_parameter_values
                                  + profile_recommended_values)

        if prop_description:
            contents.append(self.formatter.para(self.escape_for_markdown(prop_description, self.config.get('escape_chars', []))))

        if isinstance(prop_type, list):
            prop_type = ', '.join(prop_type)

        if supplemental_details:
            contents.append('\n' + supplemental_details + '\n')

        if enum_details:
            if profile_mode:
                contents.append('| ' + prop_type + ' | Description | Profile Specifies |')
                contents.append('| --- | --- | --- |')
            else:
                contents.append('| ' + prop_type + ' | Description |')
                contents.append('| --- | --- |')
            enum.sort(key=str.lower)
            for enum_item in enum:
                enum_name = enum_item
                enum_item_meta = enum_meta.get(enum_item, {})
                version_display = None
                deprecated_descr = None
                if 'version' in enum_item_meta:
                    version = enum_item_meta['version']
                    if not parent_version or DocGenUtilities.compare_versions(version, parent_version) > 0:
                        version_display = self.truncate_version(version, 2) + '+'
                if version_display:
                    if 'version_deprecated' in enum_item_meta:
                        version_depr = enum_item_meta['version_deprecated']
                        deprecated_display = self.truncate_version(version_depr, 2)
                        enum_name += ' ' + self.formatter.italic('(v' + version_display + ', deprecated v' + deprecated_display + ')')
                        if enum_item_meta.get('version_deprecated_explanation'):
                            deprecated_descr = ("Deprecated v" + deprecated_display + '+. ' +
                                                enum_item_meta['version_deprecated_explanation'])
                    else:
                        enum_name += ' ' + self.formatter.italic('(v' + version_display + ')')
                else:
                    if 'version_deprecated' in enum_item_meta:
                        version_depr = enum_item_meta['version_deprecated']
                        deprecated_display = self.truncate_version(version_depr, 2)
                        enum_name += ' ' + self.formatter.italic('(deprecated v' + deprecated_display + ')')
                        if enum_item_meta.get('version_deprecated_explanation'):
                            deprecated_descr = ("Deprecated v" + deprecated_display + '+. ' +
                                                enum_item_meta['version_deprecated_explanation'])
                descr = enum_details.get(enum_item, '')
                if deprecated_descr:
                    if descr:
                        descr += ' ' + self.formatter.italic(deprecated_descr)
                    else:
                        descr = self.formatter.italic(deprecated_descr)

                if profile_mode:
                    profile_spec = ''
                    if enum_name in profile_values:
                        profile_spec = 'Required'
                    elif enum_name in profile_min_support_values:
                        profile_spec = 'Required'
                    elif enum_name in profile_parameter_values:
                        profile_spec = 'Required'
                    elif enum_name in profile_recommended_values:
                        profile_spec = 'Recommended'
                    contents.append('| ' + enum_name + ' | ' + descr + ' | ' + profile_spec + ' |')
                else:
                    contents.append('| ' + enum_name + ' | ' + descr + ' |')

        elif enum:
            if profile_mode:
                contents.append('| ' + prop_type + ' | Profile Specifies |')
                contents.append('| --- | --- |')
            else:
                contents.append('| ' + prop_type + ' |')
                contents.append('| --- |')
            for enum_item in enum:
                enum_name = enum_item
                enum_item_meta = enum_meta.get(enum_item, {})
                version_display = None

                if 'version' in enum_item_meta:
                    version = enum_item_meta['version']
                    if not parent_version or DocGenUtilities.compare_versions(version, parent_version) > 0:
                        version_display = self.truncate_version(version, 2) + '+'
                if version_display:
                    if 'version_deprecated' in enum_item_meta:
                        version_depr = enum_item_meta['version_deprecated']
                        deprecated_display = self.truncate_version(version_depr, 2)
                        enum_name += ' ' + self.formatter.italic('(v' + version_display + ', deprecated v' + deprecated_display + ')')
                        if enum_item_meta.get('version_deprecated_explanation'):
                            deprecated_descr = ('Deprecated v' + deprecated_display + '+. ' +
                                                enum_item_meta['version_deprecated_explanation'])
                    else:
                        enum_name += ' ' + self.formatter.italic('(v' + version_display + ')')
                else:
                    if 'version_deprecated' in enum_item_meta:
                        version_depr = enum_item_meta['version_deprecated']
                        deprecated_display = self.truncate_version(version_depr, 2)
                        enum_name += ' ' + self.formatter.italic('(deprecated v' + deprecated_display + ')')
                        if enum_item_meta.get('version_deprecated_explanation'):
                            enum_name += ' ' + self.formatter.italic('Deprecated v' + deprecated_display + '+. ' +
                                                           enum_item_meta['version_deprecated_explanation'])

                if profile_mode:
                    profile_spec = ''
                    if enum_name in profile_values:
                        profile_spec = 'Required'
                    elif enum_name in profile_min_support_values:
                        profile_spec = 'Required'
                    elif enum_name in profile_parameter_values:
                        profile_spec = 'Required'
                    elif enum_name in profile_recommended_values:
                        profile_spec = 'Recommended'

                    contents.append('| ' + enum_name + ' | ' + profile_spec + ' |')
                else:
                    contents.append('| ' + enum_name + ' | ')

        return '\n'.join(contents) + '\n'