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
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
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 = ' ' * 6 * (current_depth - 1) else: indentation_string = ' ' * 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 = ' ' * 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 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 = ' ' * 6 * (current_depth -1) else: indentation_string = ' ' * 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 = ' ' * 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 = ' ' * 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
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
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 = ' ' * 6 * (current_depth - 1) else: indentation_string = ' ' * 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 = ' ' * 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 = ' ' * 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 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'