def __init__(self, property_data, traverser, config, level=0): super(MarkdownGenerator, self).__init__(property_data, traverser, config, level) self.separators = { 'inline': ', ', 'linebreak': '\n', 'pattern': ', ' } self.formatter = FormatUtils() if self.markdown_mode == 'slate': self.layout_payloads = 'top' else: self.layout_payloads = 'bottom' # Add some functions we'll use to selectively promote headings when the output mode is slate. self.format_head_two = self.formatter.head_two if self.markdown_mode == 'slate': self.format_head_two = self.formatter.head_one self.format_head_three = self.formatter.head_three if self.markdown_mode == 'slate': self.format_head_three = self.formatter.head_two self.format_head_four = self.formatter.head_four if self.markdown_mode == 'slate': self.format_head_four = self.formatter.head_three
def __init__(self, property_data, traverser, config, level=0): """ property_data: pre-processed schemas. traverser: SchemaTraverser object config: configuration dict """ super(PropertyIndexGenerator, self).__init__(property_data, traverser, config, level) self.collapse_list_of_simple_type = False # If there's a file to write config to, check it now. self.write_config_fh = False if config.get('write_config_to'): try: config_out = open(config['write_config_to'], 'w', encoding="utf8") self.write_config_fh = config_out except (OSError) as ex: warnings.warn('Unable to open %(filename)s to write: %(message)s', {'fileanme': config['write_config_to'], 'message': str(ex)}) self.properties_by_name = {} self.coalesced_properties = {} # Shorthand for the overrides. self.overrides = config.get('description_overrides', {}) # Force some config here: self.config['omit_version_in_headers'] = True # This puts just the schema name in the section head. self.config['wants_common_objects'] = True # get the formatter, so we can use the appropriate markup. output_format = self.config.get('output_format', 'slate') if output_format == 'html': from format_utils import HtmlUtils self.formatter = HtmlUtils() else: # CSV also uses the markdown formatter. from format_utils import FormatUtils self.formatter = FormatUtils()
def formateo_de_tiempos(objeto_json): for i in range(3): objeto_json['steps'][i]['time'] = FormatUtils.truncar_float_cadena(objeto_json['steps'][i]['time']) objeto_json['time'] = FormatUtils.truncar_float_cadena(objeto_json['time']) return objeto_json
def __init__(self, property_data, traverser, config, level=0): super(MarkdownGenerator, self).__init__(property_data, traverser, config, level) self.separators = { 'inline': ', ', 'linebreak': '\n', 'pattern': ', ' } self.formatter = FormatUtils() self.layout_payloads = 'top'
def test_result(self): """测试查重方法是否正确""" self.postfix_formula1 = FormatUtils.get_result_formula( self.source_expr) # 转换成后缀表达式 self.postfix_formula2 = FormatUtils.get_result_formula( self.target_expr) self.check_formula1 = FormatUtils.get_check_formula( self.postfix_formula1) # 转换成查重表达式 self.check_formula2 = FormatUtils.get_check_formula( self.postfix_formula2) self.assertEqual( True, Tree.duplicate_check(self.check_formula1, [self.check_formula2]), """查重失败""")
def __init__(self, property_data, traverser, config, level=0): """ property_data: pre-processed schemas. traverser: SchemaTraverser object config: configuration dict """ # parse the property index config data config_data = config['property_index_config'] excluded_props = config_data.get('ExcludedProperties', []) config['excluded_properties'].extend( [x for x in excluded_props if not x.startswith('*')]) config['excluded_by_match'].extend( [x[1:] for x in excluded_props if x.startswith('*')]) super(PropertyIndexGenerator, self).__init__(property_data, traverser, config, level) self.collapse_list_of_simple_type = False # If there's a file to write config to, check it now. self.write_config_fh = False if config.get('write_config_to'): try: config_out = open(config['write_config_to'], 'w', encoding="utf8") self.write_config_fh = config_out except (OSError) as ex: warnings.warn('Unable to open ' + config['write_config_to'] + ' to write: ' + str(ex)) self.properties_by_name = {} self.coalesced_properties = {} # Shorthand for the overrides. self.overrides = config_data.get('DescriptionOverrides', {}) # Force some config here: self.config[ 'omit_version_in_headers'] = True # This puts just the schema name in the section head. self.config['wants_common_objects'] = True # get the formatter, so we can use the appropriate markup. output_format = self.config.get('output_format', 'markdown') if output_format == 'html': from format_utils import HtmlUtils self.formatter = HtmlUtils() else: # CSV also uses the markdown formatter. from format_utils import FormatUtils self.formatter = FormatUtils()
def configuracion_log(correo_por_probar): # verifica si el folder del log existe if not os.path.isdir(constantes_json.DIR_BASE_LOG): try: os.mkdir(constantes_json.DIR_BASE_LOG) except OSError as e: print('sucedio un error al crear el directorio del log {} : {}'. format(constantes_json.DIR_BASE_LOG, e)) print('Favor de establecer la carpeta Logs dentro del proyecto con los'\ ' permisos necesarios, se procede a terminar el script') sys.exit() # se verifica si el nombre del archivo existe, en caso contrario # crea el nuevo archivo log y sale del ciclo while True: # verifica que el archivo del log exista en caso contrario lo crea FormatUtils.generar_nombre_log(correo_por_probar) if not os.path.exists(constantes_json.PATH_ABSOLUTO_LOG): try: log = open(constantes_json.PATH_ABSOLUTO_LOG, 'x') log.close() break except OSError as e: print('Se tiene acceso denegado para escribir el archivo {}, '\ 'favor de establecer los permisos necesarios en el directorio Logs'.format(e)) print('Favor de establecer los permisos necesarios para escribir'\ ' ficheros dentro del directorio Logs. Se procede a finalizar el script') sys.exit() else: print('El log {}, ya existe, se procede a generar un nuevo log'. format(constantes_json.PATH_ABSOLUTO_LOG)) continue logging.basicConfig( level=logging.INFO, filename=constantes_json.PATH_ABSOLUTO_LOG, filemode='w+', format='%(asctime)s %(name)s %(levelname)s: %(message)s', datefmt='%d-%m-%YT%H:%M:%S') logging.info('Inicializando log: {}'.format( constantes_json.PATH_ABSOLUTO_LOG)) # verifica si es necesario la depuracion del directorio en donde residen los logs FormatUtils.verificacion_depuracion_de_logs(constantes_json.DIR_BASE_LOG)
def iniciar_prueba(correo, url_exchange): # obtiene los datos del archivo de configuracion archivo_configuracion_ini = FormatUtils.lector_archivo_ini() driver_por_usar = FormatUtils.CADENA_VACIA ruta_driver_navegador = FormatUtils.CADENA_VACIA driver = None objeto_json = None try: # url_exchange = archivo_configuracion_ini.get('UrlPorProbar','urlPortalExchange') driver_por_usar = archivo_configuracion_ini.get( 'Driver', 'driverPorUtilizar') ruta_driver_navegador = archivo_configuracion_ini.get('Driver', 'ruta') except configparser.Error as e: logging.error( 'Sucedio un error al momento de leer el archivo de configuracion') logging.error('{}'.format(e.message)) sys.exit() # lista de carpetas por navegar (estos los obtenemos por medio del webdriver) carpetas_formateadas = [] # obtiene los datos necesarios desde el archivo de configuracion # establece el driver por utilizar (chrome o firefox) driver = configurar_webdriver(driver_por_usar, ruta_driver_navegador) # se generan las validaciones y el resultado por medio de un objeto JSON objeto_json = generar_test_json(driver, url_exchange, correo) # como salida, muestra/imprime el json generado print(json.dumps(objeto_json))
def emit(self): """ Return the data! """ self.coalesce_properties() output_format = self.config.get('output_format', 'markdown') output = '' if output_format == 'html': from format_utils import HtmlUtils formatter = HtmlUtils() output = formatter.head_one("Property Index", 0) output += self.format_tabular_output(formatter) output = self.add_html_boilerplate(output) if output_format == 'markdown': from format_utils import FormatUtils formatter = FormatUtils() output = formatter.head_one("Property Index", 0) output += self.format_tabular_output(formatter) if output_format == 'csv': output = self.output_csv() return output
def emit(self): """ Return the data! """ self.coalesce_properties() output_format = self.config.get('output_format', 'markdown') output = '' frontmatter = backmatter = '' if 'property_index_boilerplate' in self.config: boilerplate = self.config['property_index_boilerplate'] frontmatter, backmatter = boilerplate.split('[insert property index]') if output_format == 'html': from format_utils import HtmlUtils formatter = HtmlUtils() if frontmatter: output = formatter.markdown_to_html(frontmatter) else: output = formatter.head_one("Property Index", 0) output += self.format_tabular_output(formatter) output += formatter.markdown_to_html(backmatter) toc = self.generate_toc(output) if '[add_toc]' in output: output = output.replace('[add_toc]', toc, 1) output = self.add_html_boilerplate(output) if output_format == 'markdown': from format_utils import FormatUtils formatter = FormatUtils() if frontmatter: output = frontmatter else: output = formatter.head_one("Property Index", 0) output += self.format_tabular_output(formatter) output += backmatter if output_format == 'csv': output = self.output_csv() return output
def main(): args = sys.argv[1:] cadena_json = args[0] response = FormatUtils.CADENA_VACIA correo_a_probar = None constantes_json.configurar_paths_constantes(__file__) # verifica que la cadena sea un json valido en caso contrario # se omite la experiencia de usuario cadena_json = cadena_json.strip() if FormatUtils.cadena_a_json_valido(cadena_json): objeto_json = json.loads(cadena_json) url_exchange = objeto_json['url'] usuario = objeto_json['user'] password = objeto_json['password'] configuracion_log(usuario) correo_a_probar = Correo(usuario, password, url_exchange) logging.info('"{}" - JSON valido'.format(cadena_json)) response = iniciar_prueba(correo_a_probar, correo_a_probar.url) else: configuracion_log(constantes_json.JSON_INVALIDO) logging.error('"{}" - JSON invalido, se omite exp. de usuario'.format( cadena_json)) print('"{}" - JSON invalido, se omite exp. de usuario'.format( cadena_json)) response = '"{}" - JSON invalido, se omite exp. de usuario'.format( cadena_json) logging.info('Response generado: {}'.format(response)) print(response)
def generate_formula(self, num_range, number, negative): """随机生成式子""" num = 0 degree = random.randrange(3, 4) # 随机设置操作数的个数 while num < number: empty_node = [self.root] for _ in range(degree): '''生成操作符号节点''' node = random.choice(empty_node) empty_node.remove(node) node.operator = random.choices(self.op_list, cum_weights=self.op_weight)[0] # node.operator = random.choices(self.op_list)[0] node.type = 2 # 每生成一个操作符号节点,生成两个空节点 node.left = Node() node.right = Node() empty_node.append(node.left) empty_node.append(node.right) for node in empty_node: '''将所有空结点变为数字结点''' node.type = 1 # 设置真分数的比重 1为整数 0为分数 num_type = random.choices(self.type_list, self.num_weight)[0] if num_type == 1: # 生成一个整数 node.number = random.randint(1, num_range) else: # 生成一个真分数 node.number = Fraction(random.randint(1, num_range), random.randint(1, num_range)) try: # self.root.show_node() # 获取生成的二叉树结构 self.root.get_answer(negative) # 计算答案 if self.root.number.denominator > 99: # 分母超过99抛出异常 raise DifficultError() self.pre_formula = self.root.get_formula() # 获取前缀表达式 self.post_formula = FormatUtils.get_result_formula( self.pre_formula) # 获取后缀表达式 self.check_formula = FormatUtils.get_check_formula( self.post_formula) # 获取查重表达式 a.append(self.pre_formula) b.append(self.post_formula) c.append(self.check_formula) # 进行查重 if not Tree.duplicate_check(self.check_formula, self.result_formula): # 返回false 则表明没有重复 self.result_formula.append(self.check_formula) else: raise DuplicateError output = FormatUtils.standard_output( self.pre_formula) # 格式化前缀表达式 if isinstance(self.root.number, Fraction): answer = FormatUtils.standard_format( self.root.number) # 格式化答案 else: answer = self.root.number # print(output, answer) self.formula.append(output) self.answer.append(answer) except ZeroDivisionError: # print("除数为零,删除该式子") continue except NegativeError: # print("出现负数,删除该式子") continue except DifficultError: # print("题目较难,删除该式子") continue except DuplicateError: # print("题目重复,删除该式子") continue else: num += 1 return self.formula, self.answer
class MarkdownGenerator(DocFormatter): """Provides methods for generating markdown from Redfish schemas. Markdown is targeted to the Slate documentation tool: https://github.com/lord/slate """ def __init__(self, property_data, traverser, config, level=0): super(MarkdownGenerator, self).__init__(property_data, traverser, config, level) self.separators = {'inline': ', ', 'linebreak': '\n'} self.formatter = FormatUtils() 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) 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 = 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: version = meta.get('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_display = self.truncate_version(meta['version'], 2) + '+' if 'version_deprecated' in meta: deprecated_display = self.truncate_version( meta['version_deprecated'], 2) name_and_version += ' ' + self.formatter.italic( '(v' + version_display + ', deprecated v' + deprecated_display + ')') deprecated_descr = ("Deprecated v" + deprecated_display + '+. ' + self.escape_for_markdown( meta['version_deprecated_explanation'], self.config.get('escape_chars', []))) else: name_and_version += ' ' + self.formatter.italic( '(v' + version_display + ')') elif 'version_deprecated' in meta: deprecated_display = self.truncate_version( meta['version_deprecated'], 2) name_and_version += ' ' + self.formatter.italic( '(deprecated v' + deprecated_display + ')') deprecated_descr = ("Deprecated v" + deprecated_display + '+. ' + self.escape_for_markdown( meta['version_deprecated_explanation'], self.config.get('escape_chars', []))) 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': 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']: if formatted_details['descr']: formatted_details['descr'] += ' ' formatted_details['descr'] += self.formatter.bold( formatted_details['profile_purpose']) if formatted_details['descr'] is None: formatted_details['descr'] = '' if formatted_details['profile_purpose']: 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 = 'See ' + prop_name + ' in Property Details, below, for the possible values of this property.' else: text_descr = 'See Property Details, below, for more information about this property.' formatted_details['descr'] += ' ' + self.formatter.italic( text_descr) if formatted_details['has_action_details']: text_descr = 'For more information, see the Action Details 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 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']: if formatted_details['read_only']: prop_access = 'read-only' else: prop_access = 'read-write' if formatted_details['prop_required_on_create']: prop_access += ' required on create' elif formatted_details['prop_required'] or formatted_details[ 'required_parameter']: prop_access += ' required' 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'): 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, 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_action_details(self, prop_name, action_details): """Generate a formatted Actions section from supplemental markup.""" contents = [] contents.append( self.formatter.head_three( action_details.get('action_name', prop_name), self.level)) if action_details.get('text'): contents.append(action_details.get('text')) if action_details.get('example'): example = '```json\n' + action_details['example'] + '\n```\n' contents.append('Example Action POST:\n') contents.append(example) return '\n'.join(contents) + '\n' def format_action_parameters(self, schema_ref, prop_name, prop_descr, action_parameters): """Generate a formatted Actions section from parameter data. """ formatted = [] if prop_name.startswith('#'): # expected prop_name_parts = prop_name.split('.') prop_name = prop_name_parts[-1] formatted.append(self.formatter.head_four(prop_name, self.level)) formatted.append(self.formatter.para(prop_descr)) if action_parameters: rows = [] # Table start: rows.append("| | | |") rows.append("| --- | --- | --- |") # Add a "start object" row for this parameter: rows.append('| ' + ' | '.join(['{', ' ', ' ', ' ']) + ' |') param_names = [x for x in action_parameters.keys()] param_names.sort(key=str.lower) for param_name in param_names: formatted_parameters = self.format_property_row( schema_ref, param_name, action_parameters[param_name], ['Actions', prop_name]) rows.append(formatted_parameters.get('row')) # Add a closing } row: rows.append('| ' + ' | '.join(['}', ' ', ' ', ' ']) + ' |') formatted.append( self.formatter.para( 'The following table shows the parameters for the action which are included in the POST body to the URI shown in the "target" property of the Action.' )) formatted.append('\n'.join(rows)) else: formatted.append( self.formatter.para("(This action takes no parameters.)")) return "\n".join(formatted) def _format_profile_access(self, read_only=False, read_req=None, write_req=None, min_count=None): """Common formatting logic for profile_access column""" profile_access = '' if not self.config['profile_mode']: return profile_access # Each requirement may be Mandatory, Recommended, IfImplemented, Conditional, or (None) if not read_req: read_req = 'Mandatory' # This is the default if nothing is specified. if read_only: profile_access = self.formatter.nobr( self.text_map(read_req)) + ' (Read-only)' elif read_req == write_req: profile_access = self.formatter.nobr( self.text_map(read_req)) + ' (Read/Write)' elif not write_req: profile_access = self.formatter.nobr( self.text_map(read_req)) + ' (Read)' else: # Presumably Read is Mandatory and Write is Recommended; nothing else makes sense. profile_access = (self.formatter.nobr(self.text_map(read_req)) + ' (Read),' + self.formatter.nobr(self.text_map(write_req)) + ' (Read/Write)') if min_count: if profile_access: profile_access += ", " profile_access += self.formatter.nobr("Minimum " + str(min_count)) return profile_access def link_to_own_schema(self, schema_ref, schema_full_uri): """Format a reference to a schema.""" result = super().link_to_own_schema(schema_ref, schema_full_uri) return self.formatter.italic(result) def link_to_outside_schema(self, schema_full_uri): """Format a reference to a schema_uri, which should be a valid URI""" return self.formatter.italic('[' + schema_full_uri + '](' + schema_full_uri + ')') def emit(self): """ Output contents thus far """ contents = [] for section in self.sections: contents.append(section.get('heading')) if section.get('description'): contents.append(section['description']) if section.get('uris'): contents.append(section['uris']) if section.get('json_payload'): contents.append(section['json_payload']) # something is awry if there are no properties, but ... if section.get('properties'): contents.append('| | | |') contents.append('| --- | --- | --- |') contents.append('\n'.join(section['properties'])) if section.get('profile_conditional_details'): # sort them now; these can be sub-properties so may not be in alpha order. conditional_details = '\n'.join( sorted(section['profile_conditional_details'], key=str.lower)) contents.append('\n' + self.formatter.head_two( 'Conditional Requirements', self.level)) contents.append(conditional_details) if len(section.get('action_details', [])): contents.append( '\n' + self.formatter.head_two('Action Details', self.level)) contents.append('\n\n'.join(section.get('action_details'))) if section.get('property_details'): contents.append( '\n' + self.formatter.head_two('Property Details', self.level)) contents.append('\n'.join(section['property_details'])) self.sections = [] # Profile output may include registry sections for section in self.registry_sections: contents.append(section.get('heading')) contents.append(section.get('requirement')) if section.get('description'): contents.append(self.formatter.para(section['description'])) if section.get('messages'): contents.append(self.formatter.head_two( 'Messages', self.level)) message_rows = [ self.formatter.make_row(x) for x in section['messages'] ] header_cells = ['', 'Requirement'] if self.config.get('profile_mode') != 'terse': header_cells.append('Description') header_row = self.formatter.make_row(header_cells) contents.append( self.formatter.make_table(message_rows, [header_row], 'messages')) contents.append('\n') return '\n'.join(contents) def output_document(self): """Return full contents of document""" body = self.emit() common_properties = self.generate_common_properties_doc() supplemental = self.config.get('supplemental', {}) if 'Title' in supplemental: doc_title = supplemental['Title'] else: doc_title = 'Schema Documentation' prelude = "---\ntitle: " + doc_title + """ search: true --- """ intro = supplemental.get('Introduction') if intro: intro = self.process_intro(intro) prelude += '\n' + intro + '\n' contents = [prelude, body] if 'Postscript' in supplemental: contents.append('\n' + supplemental['Postscript']) output = '\n'.join(contents) if '[insert_common_objects]' in output: output = output.replace('[insert_common_objects]', common_properties, 1) if '[insert_collections]' in output: collections_doc = self.generate_collections_doc() output = output.replace('[insert_collections]', collections_doc, 1) return output def process_intro(self, intro_blob): """ Process the intro text, generating and inserting any schema fragments """ parts = [] intro = [] part_text = [] fragment_config = { 'output_format': 'markdown', 'normative': self.config.get('normative'), 'cwd': self.config.get('cwd'), 'schema_supplement': {}, 'supplemental': {}, 'excluded_annotations': [], 'excluded_annotations_by_match': [], 'excluded_properties': [], 'excluded_by_match': [], 'excluded_schemas': [], 'excluded_schemas_by_match': [], 'escape_chars': [], 'uri_replacements': {}, 'units_translation': self.config.get('units_translation'), 'profile': self.config.get('profile'), 'profile_mode': self.config.get('profile_mode'), 'profile_resources': self.config.get('profile_resources', {}), 'wants_common_objects': self.config.get('wants_common_objects'), } for line in intro_blob.splitlines(): if line.startswith('#include_fragment'): if len(part_text): parts.append({ 'type': 'markdown', 'content': '\n'.join(part_text) }) part_text = [] fragment_id = line[17:].strip() fragment_content = self.generate_fragment_doc( fragment_id, fragment_config) parts.append({ 'type': 'fragment', 'content': fragment_content }) else: part_text.append(line) if len(part_text): parts.append({'type': 'markdown', 'content': '\n'.join(part_text)}) for part in parts: if part['type'] == 'markdown': intro.append(part['content']) elif part['type'] == 'fragment': intro.append(part['content']) return '\n'.join(intro) def add_section(self, text, link_id=False): """ Add a top-level heading """ self.this_section = { 'head': text, 'heading': '\n' + self.formatter.head_one(text, self.level), 'properties': [], 'property_details': [] } self.sections.append(self.this_section) def add_description(self, text): """ Add the schema description """ self.this_section['description'] = text + '\n' def add_uris(self, uris): """ Add the URIs (which should be a list) """ uri_block = "**URIs**:\n" for uri in sorted(uris, key=str.lower): uri_block += "\n" + self.format_uri(uri) self.this_section['uris'] = uri_block + "\n" def add_json_payload(self, json_payload): """ Add a JSON payload for the current section """ if json_payload: self.this_section['json_payload'] = '\n' + json_payload + '\n' else: self.this_section['json_payload'] = None def add_property_row(self, formatted_text): """Add a row (or group of rows) for an individual property in the current section/schema. formatted_row should be a chunk of text already formatted for output""" self.this_section['properties'].append(formatted_text) def add_property_details(self, formatted_details): """Add a chunk of property details information for the current section/schema.""" self.this_section['property_details'].append(formatted_details) def add_registry_reqs(self, registry_reqs): """Add registry messages. registry_reqs includes profile annotations.""" terse_mode = self.config.get('profile_mode') == 'terse' reg_names = [x for x in registry_reqs.keys()] reg_names.sort(key=str.lower) for reg_name in reg_names: reg = registry_reqs[reg_name] this_section = { 'head': reg_name, 'description': reg.get('Description', ''), 'messages': [] } heading = reg_name + ' Registry v' + reg['minversion'] + '+' if reg.get('current_release', reg['minversion']) != reg['minversion']: heading += ' (current release: v' + reg['current_release'] + ')' this_section['heading'] = self.formatter.head_one( heading, self.level) this_section['requirement'] = 'Requirement: ' + reg.get( 'profile_requirement', '') msgs = reg.get('Messages', {}) msg_keys = [x for x in msgs.keys()] msg_keys.sort(key=str.lower) for msg in msg_keys: this_msg = msgs[msg] if terse_mode and not this_msg.get('profile_requirement'): continue msg_row = [msg, this_msg.get('profile_requirement', '')] if not terse_mode: msg_row.append(this_msg.get('Description', '')) this_section['messages'].append(msg_row) self.registry_sections.append(this_section) @staticmethod def escape_for_markdown(text, chars): """Escape selected characters in text to prevent auto-formatting in markdown.""" for char in chars: text = text.replace(char, '\\' + char) return text
class MarkdownGenerator(DocFormatter): """Provides methods for generating markdown from Redfish schemas. Markdown is targeted to the Slate documentation tool: https://github.com/lord/slate """ def __init__(self, property_data, traverser, config, level=0): super(MarkdownGenerator, self).__init__(property_data, traverser, config, level) self.separators = {'inline': ', ', 'linebreak': '\n', 'pattern': ', '} self.formatter = FormatUtils() self.layout_payloads = 'top' # Add some functions we'll use to selectively promote headings when the output mode is slate. self.format_head_two = self.formatter.head_two if self.markdown_mode == 'slate': self.format_head_two = self.formatter.head_one self.format_head_three = self.formatter.head_three if self.markdown_mode == 'slate': self.format_head_three = self.formatter.head_two self.format_head_four = self.formatter.head_four if self.markdown_mode == 'slate': self.format_head_four = self.formatter.head_three def format_property_row(self, schema_ref, prop_name, prop_info, prop_path=[], in_array=False, as_action_parameters=False, in_schema_ref=None): """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. """ 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 format_annotation = None 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): has_enum = 'enum' in prop_info[0] is_excerpt = prop_info[0].get('_is_excerpt') or prop_info[0].get( 'excerptCopy') translated_name = prop_info[0].get('translation') if 'format' in prop_info[0]: format_annotation = prop_info[0]['format'] elif isinstance(prop_info, dict): has_enum = 'enum' in prop_info is_excerpt = prop_info.get('_is_excerpt') translated_name = prop_info.get('translation') if 'format' in prop_info: format_annotation = prop_info['format'] format_annotation = self.format_annotation_strings.get( format_annotation, format_annotation) version_strings = self.format_version_strings(prop_info) if prop_name: name_and_version = self.formatter.bold( self.escape_for_markdown(prop_name, self.config.get('escape_chars', []))) if translated_name: name_and_version += ' ' + self.formatter.italic( self.escape_for_markdown( '(' + translated_name + ')', self.config.get('escape_chars', []))) else: name_and_version = '' if version_strings['version_string']: name_and_version += ' ' + self.formatter.italic( version_strings['version_string']) deprecated_descr = version_strings['deprecated_descr'] 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 %(link)s in Property details.' ) % { 'link': prop_name } 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 format_annotation: prop_type += '<br>(' + format_annotation + ')' 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, 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_action_details(self, prop_name, action_details): """Generate a formatted Actions section from supplemental markup.""" contents = [] contents.append( self.format_head_four(action_details.get('action_name', prop_name), self.level)) if action_details.get('text'): contents.append(action_details.get('text')) if action_details.get('example'): example = '```json\n' + action_details['example'] + '\n```\n' contents.append(_('Example Action POST:') + '\n') contents.append(example) return '\n'.join(contents) + '\n' def format_action_parameters(self, schema_ref, prop_name, prop_descr, action_parameters, profile, version_strings=None): """Generate a formatted Actions section from parameter data. """ formatted = [] version_string = deprecated_descr = None if version_strings: version_string = version_strings.get('version_string') deprecated_descr = version_strings.get('deprecated_descr') action_name = prop_name if prop_name.startswith('#'): # expected # Example: from #Bios.ResetBios, we want prop_name "ResetBios" and action_name "Bios.ResetBios" prop_name_parts = prop_name.split('.') prop_name = prop_name_parts[-1] action_name = action_name[1:] name_and_version = prop_name if version_string: name_and_version += ' ' + self.formatter.italic( version_strings['version_string']) if self.markdown_mode == 'slate': formatted.append( self.formatter.head_five(name_and_version, self.level)) else: formatted.append( self.formatter.head_three(name_and_version, self.level)) if deprecated_descr: formatted.append(self.formatter.para(italic(deprecated_descr))) formatted.append(self.formatter.head_four(_("Description"), self.level)) formatted.append(self.formatter.para(prop_descr)) # Add the URIs for this action. formatted.append( self.format_uri_block_for_action(action_name, self.current_uris)) param_names = [] if action_parameters: rows = [] # Table start: rows.append("| " + _('Parameter Name') + " | " + _('Type') + " | " + _('Notes') + " |") rows.append("| --- | --- | --- |") param_names = [x for x in action_parameters.keys()] if self.config.get('profile_mode') == 'subset': if profile.get('Parameters'): param_names = [ x for x in profile['Parameters'].keys() if x in param_names ] # If there is no profile for this action, all parameters should be output. param_names.sort(key=str.lower) heading = self.formatter.head_four(_("Action parameters"), self.level) if len(param_names): for param_name in param_names: formatted_parameters = self.format_property_row( schema_ref, param_name, action_parameters[param_name], ['Actions', prop_name], False, True) rows.append(formatted_parameters.get('row')) caption = self.formatter.add_table_caption( _("%(prop_name)s action parameters") % {'prop_name': prop_name}) preamble = "\n" + heading + "\n\n" + self.formatter.add_table_reference( _("The parameters for the action which are included in the POST body to the URI shown in the 'target' property of the Action are summarized in " )) formatted.append(preamble + "\n\n" + '\n'.join(rows) + "\n\n" + caption) else: formatted.append(self.formatter.para(heading)) formatted.append( self.formatter.para(_("This action takes no parameters."))) return "\n".join(formatted) def _format_profile_access(self, read_only=False, read_req=None, write_req=None, min_count=None): """Common formatting logic for profile_access column""" profile_access = '' if not self.config['profile_mode']: return profile_access # Each requirement may be Mandatory, Recommended, IfImplemented, Conditional, or (None) if not read_req: read_req = 'Mandatory' # This is the default if nothing is specified. if read_only: profile_access = self.formatter.nobr( self.text_map(read_req)) + ' ' + _('(Read-only)') elif read_req == write_req: profile_access = self.formatter.nobr( self.text_map(read_req)) + ' ' + _('(Read/Write)') elif not write_req: profile_access = self.formatter.nobr( self.text_map(read_req)) + ' ' + _('(Read)') else: # Presumably Read is Mandatory and Write is Recommended; nothing else makes sense. profile_access = (self.formatter.nobr(self.text_map(read_req)) + ' ' + _('(Read)') + ',' + self.formatter.nobr(self.text_map(write_req)) + ' ' + _('(Read/Write)')) if min_count: if profile_access: profile_access += ", " profile_access += self.formatter.nobr( _('Minimum %(min_count)s') % {'min_count': str(min_count)}) return profile_access def format_as_prop_details(self, prop_name, prop_description, rows): """ Take the formatted rows and other strings from prop_info, and create a formatted block suitable for the prop_details section """ contents = [] if prop_description: contents.append( self.formatter.para( self.escape_for_markdown( prop_description, self.config.get('escape_chars', [])))) obj_table = self.formatter.make_table(rows) contents.append(obj_table) return "\n".join(contents) def link_to_own_schema(self, schema_ref, schema_full_uri): """Format a reference to a schema.""" result = super().link_to_own_schema(schema_ref, schema_full_uri) return self.formatter.italic(result) def link_to_outside_schema(self, schema_full_uri): """Format a reference to a schema_uri, which should be a valid URI""" return self.formatter.italic('[' + schema_full_uri + '](' + schema_full_uri + ')') def emit(self): """ Output contents thus far """ contents = [] for section in self.sections: contents.append(section.get('heading')) if section.get('release_history'): contents.append(section['release_history']) if section.get('conditional_requirements'): contents.append(section['conditional_requirements']) if section.get('deprecation_text'): contents.append(section['deprecation_text']) if section.get('description'): contents.append(section['description']) if section.get('uris'): contents.append(section['uris']) if section.get('json_payload') and ( self.markdown_mode == 'slate'): # If not slate, it goes at the end.: contents.append(section['json_payload']) # something is awry if there are no properties, but ... if section.get('properties'): caption = self.formatter.add_table_caption(section["head"] + " properties") preamble = self.formatter.add_table_reference( "The properties defined for the " + section["head"] + " schema are summarized in ") # properties are a peer of URIs, if they exist # TODO: this should use make_table() contents.append( '\n' + self.format_head_three(_('Properties'), self.level)) contents.append(preamble + "\n") contents.append('|Property |Type |Notes |') contents.append('| --- | --- | --- |') contents.append('\n'.join(section['properties'])) contents.append(caption + '\n') if section.get('profile_conditional_details'): # sort them now; these can be sub-properties so may not be in alpha order. conditional_details = '\n'.join( sorted(section['profile_conditional_details'], key=str.lower)) contents.append('\n' + self.format_head_three( _('Conditional Requirements'), self.level)) contents.append(conditional_details) if len(section.get('action_details', [])): contents.append('\n' + self.format_head_three('Actions', self.level)) contents.append('\n\n'.join(section.get('action_details'))) if section.get('property_details'): contents.append( '\n' + self.format_head_three(_('Property details'), self.level)) detail_names = [x for x in section['property_details'].keys()] detail_names.sort(key=str.lower) for detail_name in detail_names: contents.append(self.format_head_four( detail_name + ':', 0)) det_info = section['property_details'][detail_name] if len(det_info) == 1: for x in det_info.values(): contents.append(x['formatted_descr']) else: path_to_ref = {} # Generate path descriptions and sort them. for ref, info in det_info.items(): paths_as_text = [ ": ".join(x) for x in info['paths'] ] paths_as_text = ', '.join(paths_as_text) path_to_ref[paths_as_text] = ref paths_sorted = [x for x in path_to_ref.keys()] paths_sorted.sort(key=str.lower) for path in paths_sorted: info = det_info[path_to_ref[path]] path_text = _("In %(path)s:") % {'path': path} if self.markdown_mode == 'slate': contents.append( self.formatter.para( self.formatter.bold(path_text))) else: contents.append( self.formatter.head_five(path_text)) contents.append(info['formatted_descr']) if section.get('json_payload') and ( self.markdown_mode != 'slate'): # Otherwise, this was inserted above. contents.append(section['json_payload']) self.sections = [] # Profile output may include registry sections for section in self.registry_sections: contents.append(section.get('heading')) contents.append(section.get('requirement')) if section.get('description'): contents.append(self.formatter.para(section['description'])) if section.get('messages'): contents.append( self.format_head_three(_('Messages'), self.level)) message_rows = [ self.formatter.make_row(x) for x in section['messages'] ] header_cells = ['', _('Requirement')] if self.config.get('profile_mode') != 'terse': header_cells.append(_('Description')) header_row = self.formatter.make_row(header_cells) contents.append( self.formatter.make_table(message_rows, [header_row], 'messages')) contents.append('\n') return '\n'.join(contents) def output_document(self): """Return full contents of document""" body = self.emit() common_properties = self.generate_common_properties_doc() supplemental = self.config.get('supplemental', {}) if 'Title' in supplemental: doc_title = supplemental['Title'] else: doc_title = _('Schema Documentation') prelude = "---\ntitle: " + doc_title + """ search: true --- """ intro = self.config.get('intro_content') if intro: intro = self.process_intro(intro) prelude += '\n' + intro + '\n' contents = [prelude, body] postscript = self.config.get('postscript_content') if postscript: contents.append('\n' + postscript) output = '\n'.join(contents) if '[insert_common_objects]' in output: output = output.replace('[insert_common_objects]', common_properties, 1) if '[insert_collections]' in output: collections_doc = self.generate_collections_doc() output = output.replace('[insert_collections]', collections_doc, 1) # Replace pagebreak markers with HTML pagebreak markup output = output.replace('~pagebreak~', '<p style="page-break-before: always"></p>') return output def process_intro(self, intro_blob): """ Process the intro text, generating and inserting any schema fragments """ parts = [] intro = [] part_text = [] fragment_config = { 'output_format': 'slate', 'normative': self.config.get('normative'), 'cwd': self.config.get('cwd'), 'schema_supplement': {}, 'supplemental': {}, 'excluded_annotations': [], 'excluded_annotations_by_match': [], 'excluded_properties': [], 'excluded_by_match': [], 'excluded_schemas': [], 'excluded_schemas_by_match': [], 'escape_chars': [], 'schema_link_replacements': {}, 'units_translation': self.config.get('units_translation'), 'profile': self.config.get('profile'), 'profile_mode': self.config.get('profile_mode'), 'profile_resources': self.config.get('profile_resources', {}), 'wants_common_objects': self.config.get('wants_common_objects'), 'actions_in_property_table': self.config.get('actions_in_property_table', True), } for line in intro_blob.splitlines(): if line.startswith('#include_fragment'): if len(part_text): parts.append({ 'type': 'markdown', 'content': '\n'.join(part_text) }) part_text = [] fragment_id = line[17:].strip() fragment_content = self.generate_fragment_doc( fragment_id, fragment_config) parts.append({ 'type': 'fragment', 'content': fragment_content }) else: part_text.append(line) if len(part_text): parts.append({'type': 'markdown', 'content': '\n'.join(part_text)}) for part in parts: if part['type'] == 'markdown': intro.append(part['content']) elif part['type'] == 'fragment': intro.append(part['content']) return '\n'.join(intro) def add_section(self, text, link_id=False, schema_ref=False): """ Add a top-level heading """ self.this_section = { 'properties': [], 'property_details': {}, 'head': '', 'heading': '', 'schema_ref': '', } if text: self.this_section['head'] = text self.this_section['heading'] = '\n' + self.format_head_two( text, self.level) self.sections.append(self.this_section) def add_description(self, text): """ Add the schema description """ self.this_section['description'] = self.format_head_three( _('Description'), self.level) + self.formatter.para(text) def add_deprecation_text(self, deprecation_text): """ Add deprecation text for a schema """ depr_text = self.formatter.italic( _('This schema has been deprecated and use in new implementations is discouraged except to retain compatibility with existing products.' )) + ' ' + deprecation_text self.this_section['deprecation_text'] = depr_text + '\n' def add_uris(self, uris): """ Add the URIs (which should be a list) """ uri_block = self.format_head_three(_('URIs'), self.level) for uri in sorted(uris, key=str.lower): uri_block += "\n" + self.format_uri(uri) self.this_section['uris'] = uri_block + "\n" def add_conditional_requirements(self, text): """ Add a conditional requirements, which should already be formatted """ self.this_section['conditional_requirements'] = "\n**" + _( 'Conditional Requirements') + ":**\n\n" + text + "\n" def format_uri_block_for_action(self, action, uris): """ Create a URI block for this action & the resource's URIs """ uri_block = self.formatter.head_four(_("Action URIs"), self.level) for uri in sorted(uris, key=str.lower): uri = uri + "/Actions/" + action uri_block += "\n" + self.format_uri(uri) return uri_block def format_json_payload(self, json_payload): """ Format a json payload for output. """ return '\n' + json_payload + '\n' def add_property_row(self, formatted_text): """Add a row (or group of rows) for an individual property in the current section/schema. formatted_row should be a chunk of text already formatted for output""" self.this_section['properties'].append(formatted_text) def add_registry_reqs(self, registry_reqs): """Add registry messages. registry_reqs includes profile annotations.""" terse_mode = self.config.get('profile_mode') == 'terse' reg_names = [x for x in registry_reqs.keys()] reg_names.sort(key=str.lower) for reg_name in reg_names: reg = registry_reqs[reg_name] this_section = { 'head': reg_name, 'description': reg.get('Description', ''), 'messages': [] } heading = _('%(Name)s Registry v%(version_number)s+') % { 'Name': reg_name, 'version_number': reg['minversion'] } if reg.get('current_release', reg['minversion']) != reg['minversion']: heading += ' ' + (_('(current release: v%(version_number)s)') % { 'version_number': reg['current_release'] }) this_section['heading'] = self.format_head_two(heading, self.level) this_section['requirement'] = _('Requirement: %(req)s') % { 'req': reg.get('profile_requirement') } msgs = reg.get('Messages', {}) msg_keys = [x for x in msgs.keys()] msg_keys.sort(key=str.lower) for msg in msg_keys: this_msg = msgs[msg] if terse_mode and not this_msg.get('profile_requirement'): continue msg_row = [msg, this_msg.get('profile_requirement', '')] if not terse_mode: msg_row.append(this_msg.get('Description', '')) this_section['messages'].append(msg_row) self.registry_sections.append(this_section) def escape_text(self, text, chars=None): """Escape text in whatever way is appropriate to this output format. """ if chars is None: chars = [] return self.escape_for_markdown(text, chars) @staticmethod def escape_for_markdown(text, chars): """Escape selected characters in text to prevent auto-formatting in markdown.""" for char in chars: text = text.replace(char, '\\' + char) return text @staticmethod def escape_regexp(text): """If escaping is necessary to protect patterns when output format is rendered, do that.""" chars_to_escape = r'\`*_{}[]()#+-.!|' escaped_text = '' for char in text: if char in chars_to_escape: escaped_text += '\\' + char else: escaped_text += char return escaped_text
def obtener_carpetas_en_sesion(driver): lista_de_carpetas_localizadas = [] lista_nombres_de_carpetas_formateadas = [] clase_css_carpeta_owa_2016 = "_n_C4" clase_css_carpeta_owa_2013 = '_n_Z6' xpath_carpeta_owa_2010 = "//a[@name='lnkFldr']" se_encontraron_carpetas = False tiempo_de_inicio = Temporizador.obtener_tiempo_timer() tiempo_de_finalizacion = 0 while tiempo_de_finalizacion < 60: time.sleep(10) if SeleniumTesting.verificar_elemento_encontrado_por_clase_js( driver, clase_css_carpeta_owa_2016): SeleniumTesting.owa_descubierto = 2016 se_encontraron_carpetas = True elif SeleniumTesting.verificar_elemento_encontrado_por_clase_js( driver, clase_css_carpeta_owa_2013): SeleniumTesting.owa_descubierto = 2013 se_encontraron_carpetas = True elif SeleniumTesting.verificar_elemento_encontrado_por_xpath( driver, xpath_carpeta_owa_2010): SeleniumTesting.owa_descubierto = 2010 se_encontraron_carpetas = True tiempo_de_finalizacion = Temporizador.obtener_tiempo_timer( ) - tiempo_de_inicio if tiempo_de_finalizacion % 20 == 0: SeleniumTesting.navegar_a_sitio( SeleniumTesting.url_owa_exchange) driver.refresh() if se_encontraron_carpetas: SeleniumTesting.log.info('Se localizan con exito las carpetas dentro de la plataforma OWA, en un lapso aproximado'\ ' de {} seg'.format(FormatUtils.truncar_float_cadena(tiempo_de_finalizacion))) break else: SeleniumTesting.log.info( 'Fue imposible localizar las carpetas dentro de la plataforma OWA, se intentara nuevamente' ) SeleniumTesting.log.info( 'Titulo actual de la plataforma: {}'.format(driver.title)) SeleniumTesting.log.info( 'URL actual de la plataforma: {}'.format( driver.current_url)) if se_encontraron_carpetas == False: tiempo_de_finalizacion = Temporizador.obtener_tiempo_timer( ) - tiempo_de_inicio SeleniumTesting.log.error('Han transcurrido mas de {} seg sin localizar'\ ' las carpetas dentro de la plataforma OWA'.format(FormatUtils.truncar_float_cadena(tiempo_de_finalizacion))) SeleniumTesting.log.error( 'Title actual de la plataforma: {}'.format(driver.title)) SeleniumTesting.log.error('Url actual de la plataforma: {}'.format( driver.current_url)) else: SeleniumTesting.log.info( 'Plataforma OWA version {} identificada'.format( SeleniumTesting.owa_descubierto)) time.sleep(4) if SeleniumTesting.owa_descubierto == 2010: lista_de_carpetas_localizadas = driver.find_elements_by_xpath( xpath_carpeta_owa_2010) elif SeleniumTesting.owa_descubierto == 2013: script_js = ''' var elementos = document.getElementsByClassName('_n_Z6'); return elementos; ''' lista_de_carpetas_localizadas = driver.execute_script( script_js) elif SeleniumTesting.owa_descubierto == 2016: script_js = ''' var elementos = document.getElementsByClassName('_n_C4'); return elementos; ''' lista_de_carpetas_localizadas = driver.execute_script( script_js) for carpeta in lista_de_carpetas_localizadas: if SeleniumTesting.owa_descubierto == 2010: nombre_de_carpeta = carpeta.text else: nombre_de_carpeta = FormatUtils.remover_backspaces( carpeta.get_attribute('innerHTML')) SeleniumTesting.log.info( 'Se obtiene la carpeta: {}'.format(nombre_de_carpeta)) lista_nombres_de_carpetas_formateadas.append(nombre_de_carpeta) return lista_nombres_de_carpetas_formateadas
def iniciar_sesion_en_owa(driver, correo_en_prueba, result_list): # verificacion en texto de mensajes de error en inicio de sesion error_security_context = 'NegotiateSecurityContext' # se obtiene la cuenta sin el origen del dominio SeleniumTesting.cuenta_sin_dominio = FormatUtils.formatear_correo( correo_en_prueba.correo) SeleniumTesting.url_owa_exchange = correo_en_prueba.url #xpath de botones owa 2010, 2016, 2013 xpath_btn_owa_2010 = "//input[@type='submit'][@class='btn']" xpath_btn_owa_2013_2016 = "//div[@class='signinbutton']" driver.accept_insecure_certs = True driver.accept_untrusted_certs = True resultado = Result() resultado.tiempo_inicio_de_ejecucion = Temporizador.obtener_tiempo_timer( ) resultado.datetime_inicial = Temporizador.obtener_fecha_tiempo_actual() # resultado.inicializar_tiempo_de_ejecucion() mensaje_error_de_credenciales = None try: # obtiene los elementos html para los campos de usuario, password y el boton de inicio de # sesion time.sleep(3) input_usuario = driver.find_element_by_id('username') input_password = driver.find_element_by_id('password') check_casilla_owa_2010_version_ligera = None boton_ingreso_correo = None # verifica si se encuentra la casilla con el id chkBsc, el cual pertenece a la version # ligera de la plataforma de Exchange 2010 if SeleniumTesting.verificar_elemento_encontrado_por_id( driver, 'chkBsc'): # selecciona el check para ingresar a la plataforma ligera check_casilla_owa_2010_version_ligera = driver.find_element_by_id( 'chkBsc') check_casilla_owa_2010_version_ligera.click() SeleniumTesting.owa_descubierto = 2010 if SeleniumTesting.verificar_elemento_encontrado_por_xpath( driver, xpath_btn_owa_2010): boton_ingreso_correo = driver.find_element_by_xpath( xpath_btn_owa_2010) SeleniumTesting.owa_descubierto = 2010 elif SeleniumTesting.verificar_elemento_encontrado_por_xpath( driver, xpath_btn_owa_2013_2016): boton_ingreso_correo = driver.find_element_by_xpath( xpath_btn_owa_2013_2016) # establece la bandera version owa por analizar SeleniumTesting.owa_descubierto = 2016 # ingresa los datos en cada uno de los inputs localizados en el sitio de owa, uno por # cada segundo time.sleep(1) input_usuario.send_keys(correo_en_prueba.correo) time.sleep(1) input_password.send_keys(correo_en_prueba.password) time.sleep(1) boton_ingreso_correo.click() time.sleep(18) SeleniumTesting.log.info( 'Titulo actual de la plataforma: {}'.format(driver.title)) SeleniumTesting.log.info('URL actual de la plataforma: {}'.format( driver.current_url)) except NoSuchElementException as e: resultado.mensaje_error = 'No fue posible iniciar sesion dentro de la plataforma OWA, '\ 'no se localizaron los inputs para ingresar las credenciales de la cuenta '\ 'de correo electronico Exchange: {}'.format(SeleniumTesting.formatear_excepcion(e)) resultado.validacion_correcta = False SeleniumTesting.log.error(resultado.mensaje_error) except WebDriverException as e: resultado.mensaje_error = 'No fue posible ingresar a la plataforma de Exchange OWA, favor de verificar'\ ' si se tiene conectividad por internet, error detallado : {}'.format(SeleniumTesting.formatear_excepcion(e)) resultado.validacion_correcta = False SeleniumTesting.log.error(resultado.mensaje_error) # verifica que se haya ingresado correctamente al OWA, se localiza si esta establecido # el mensaje de error de credenciales dentro del aplicativo del OWA if resultado.validacion_correcta == False: try: if SeleniumTesting.owa_descubierto == 2010: mensaje_error_de_credenciales = driver.find_element_by_id( 'trInvCrd') SeleniumTesting.log.error( 'No fue posible ingresar a la plataforma OWA, se tiene error de credenciales' ) mensaje_error_de_credenciales = driver.find_element_by_xpath( "//tr[@id='trInvCrd']/td") texto_mensaje_error = mensaje_error_de_credenciales.get_attribute( 'innerHTML') SeleniumTesting.log.error('Se muestra el siguiente mensaje de error de credenciales: {} '\ .format(texto_mensaje_error)) resultado.mensaje_error = 'No fue posible ingresar a la plataforma OWA, se tiene error de credenciales: {}'\ .format(mensaje_error_de_credenciales.get_attribute('innerHTML')) resultado.validacion_correcta = False elif SeleniumTesting.owa_descubierto == 2016 or SeleniumTesting.owa_descubierto == 2013: mensaje_error_de_credenciales = driver.execute_script(''' var mensaje_error = document.querySelector("#signInErrorDiv").innerText; return mensaje_error; ''') SeleniumTesting.log.error( 'No se puede ingresar al aplicativo debido a error de credenciales:' ) SeleniumTesting.log.error('Se muestra el siguiente mensaje de advertencia: {} '\ .format(mensaje_error_de_credenciales)) resultado.mensaje_error = 'No fue posible ingresar a la plataforma OWA, se tiene error de credenciales: '\ '{}'.format(mensaje_error_de_credenciales) resultado.validacion_correcta = False except NoSuchElementException as e: resultado.mensaje_error = constantes_json.OUTPUT_EXITOSO_1_1 resultado.validacion_correcta = True SeleniumTesting.log.info(resultado.mensaje_error) except InvalidSessionIdException as e: resultado.mensaje_error = 'No fue posible ingresar a la plataforma de Exchange OWA, favor de verificar '\ 'si se tiene conectividad por internet, error detallado : {}'.format(e) resultado.validacion_correcta = False SeleniumTesting.log.error(resultado.mensaje_error) except JavascriptException as e: # Se ingresa correctamente, debido a que no se encontro el mensaje de error de credenciales incorrectas resultado.mensaje_error = constantes_json.OUTPUT_EXITOSO_1_1 resultado.validacion_correcta = True SeleniumTesting.log.info(resultado.mensaje_error) except WebDriverException as e: # Se ingresa correctamente, debido a que no se encontro el mensaje de error de credenciales incorrectas resultado.mensaje_error = constantes_json.OUTPUT_EXITOSO_1_1 resultado.validacion_correcta = True SeleniumTesting.log.info(resultado.mensaje_error) # realiza la validacion de ingreso correcto de sesion # se verifica que no haya algun error que se presente en la plataforma # en caso contrario se obtiene el mensaje del error y se establecer en el # objeto resultado if SeleniumTesting.verificar_error_plataforma(driver): resultado.mensaje_error = 'No fue posible ingresar a la sesion, se presenta '\ 'el siguiente mensaje de error en la plataforma: {}'.format(SeleniumTesting.txt_mensaje_error_encontrado_owa) resultado.validacion_correcta = False resultado.finalizar_tiempo_de_ejecucion() resultado.establecer_tiempo_de_ejecucion() result_list.result_validacion_acceso_portal_owa = resultado return result_list
class PropertyIndexGenerator(DocFormatter): """Provides methods for generating Property Index docs from Redfish schemas.""" def __init__(self, property_data, traverser, config, level=0): """ property_data: pre-processed schemas. traverser: SchemaTraverser object config: configuration dict """ super(PropertyIndexGenerator, self).__init__(property_data, traverser, config, level) self.collapse_list_of_simple_type = False # If there's a file to write config to, check it now. self.write_config_fh = False if config.get('write_config_to'): try: config_out = open(config['write_config_to'], 'w', encoding="utf8") self.write_config_fh = config_out except (OSError) as ex: warnings.warn('Unable to open %(filename)s to write: %(message)s', {'fileanme': config['write_config_to'], 'message': str(ex)}) self.properties_by_name = {} self.coalesced_properties = {} # Shorthand for the overrides. self.overrides = config.get('description_overrides', {}) # Force some config here: self.config['omit_version_in_headers'] = True # This puts just the schema name in the section head. self.config['wants_common_objects'] = True # get the formatter, so we can use the appropriate markup. output_format = self.config.get('output_format', 'slate') if output_format == 'html': from format_utils import HtmlUtils self.formatter = HtmlUtils() else: # CSV also uses the markdown formatter. from format_utils import FormatUtils self.formatter = FormatUtils() def emit(self): """ Return the data! """ self.coalesce_properties() output_format = self.config.get('output_format', 'slate') output = '' frontmatter = self.config.get('intro_content', '') backmatter = self.config.get('postscript_content', '') if output_format == 'html': if frontmatter: output = self.formatter.markdown_to_html(frontmatter) else: output = self.formatter.head_one("Property Index", 0) output += self.format_tabular_output() output += self.formatter.markdown_to_html(backmatter) toc = self.generate_toc(output) if '[add_toc]' in output: output = output.replace('[add_toc]', toc, 1) output = self.add_html_boilerplate(output) if output_format in ['slate', 'markdown']: if frontmatter: output = frontmatter else: output = self.formatter.head_one(_('Property Index'), 0) output += self.format_tabular_output() output += backmatter if output_format == 'csv': output = self.output_csv() return output def add_section(self, text, link_id=False, schema_ref=False): """ Start gathering info for this schema. """ self.this_section = { 'properties': [], 'property_details': {}, 'head': '', 'heading': '', 'schema_name': text } def format_property_row(self, schema_ref, prop_name, prop_info, prop_path=[], in_array=False, as_action_parameters=False, in_schema_ref=None): """ Instead of formatting this data, add info to self.properties_by_name. """ if not prop_name: # We've drilled down to a simple type. return within_action = prop_path == ['Actions'] has_enum = False if isinstance(prop_info, list): has_enum = 'enum' in prop_info[0] elif isinstance(prop_info, dict): has_enum = 'enum' in prop_info if within_action: prop_name_parts = prop_name.split('.') if len(prop_name_parts) == 2: prop_name = _('%(property_name)s (Action)') % {'property_name': prop_name_parts[1]} details = self.parse_property_info(schema_ref, prop_name, prop_info, prop_path) schema_path_formatted = self.this_section['schema_name'] schema_path = [ self.this_section['schema_name'] ] if len(prop_path): schema_path += prop_path prop_type = details.get('prop_type') if isinstance(prop_type, list): prop_type_values = [] self.append_unique_values(prop_type, prop_type_values) prop_type = ', '.join(sorted(prop_type_values)) if has_enum: prop_type += ' ' + _('(enum)') prop_units = details.get('prop_units') if prop_units: prop_type += self.formatter.br() + '(' + prop_units + ')' description_entry = { 'schemas': [ schema_path ], 'prop_type': prop_type, } # Check for an override: override_description = False if self.overrides.get(prop_name): for override_entry in self.overrides.get(prop_name): if not override_entry.get('overrideDescription'): continue if override_entry.get('globalOverride') and override_entry.get('type') == prop_type: override_description = override_entry.get('overrideDescription') if override_description: break elif override_entry.get('type') == prop_type and '/'.join(schema_path) in override_entry.get('schemas', []): override_description = override_entry.get('overrideDescription') if override_description: break if override_description: description_entry['description'] = override_description elif self.config.get('normative') and details.get('normative_descr'): description_entry['description'] = details.get('normative_descr') else: description_entry['description'] = details.get('descr') if prop_name not in self.properties_by_name: self.properties_by_name[prop_name] = [] if description_entry['description']: self.properties_by_name[prop_name].append(description_entry) def append_unique_values(self, value_list, target_list): """ Unwind possibly-nested list, producing a list of unique strings found. We don't want nulls reflected in the property index! """ super(PropertyIndexGenerator, self).append_unique_values(value_list, target_list) for i in range(0, len(target_list)): if target_list[i] == 'null': del target_list[i] def format_property_details(self, prop_name, prop_type, prop_description, enum, enum_details, supplemental_details, parent_prop_info, profile=None): """ Handle enum information """ pass def format_action_parameters(self, schema_ref, prop_name, prop_descr, action_parameters, profile, version_strings): """Generate a formatted Actions section from parameters data""" return '' def format_action_response(self, schema_ref, action_param_name, action_response): """Format the data from an actionResponse""" return '' def add_registry_reqs(self, registry_reqs): """ output doesn't include registry requirements. """ pass # TODO: generate_toc is the same as in html_generator and could probably be moved to HtmlUtils def generate_toc(self, html_blob): """ Generate a TOC for an HTML blob (probably the body of this document) """ toc = '' levels = ['h1', 'h2'] parser = ToCParser(levels) parser.feed(html_blob) toc_data = parser.close() current_level = 0 for entry in toc_data: level = levels.index(entry['level']) if level > current_level: toc += "<ul>\n" if level < current_level: toc += "</ul>\n" current_level = level toc += "<li>" + '<a href="#' + entry['link_id'] +'">' + entry['text'] + "</a></li>\n" while current_level > 0: current_level = current_level - 1 toc += "</ul>\n" toc = '<div class="toc">' + "<ul>\n" + toc + "</ul>\n</div>\n" return toc def is_excluded(self, prop_name): """ True if prop_name is in the excluded or excluded-by-match list. Many properties are excluded in the parent doc_generator code, but for other output modes we sometimes include them in sub-properties. """ if prop_name in self.config['excluded_properties']: return True if prop_name in self.config['excluded_by_match']: pass return False def coalesce_properties(self): """ Group the info in self.properties_by_name based on prop_type and description match. """ # Group the property info by prop_name, type, description: coalesced_info = {} prop_names = self.exclude_prop_names(self.properties_by_name.keys(), self.config['excluded_properties'], self.config['excluded_by_match']) for property_name in prop_names: property_infos = self.properties_by_name[property_name] coalesced_info[property_name] = {} for info in property_infos: prop_type = info['prop_type'] description = info['description'] schemas = info['schemas'] if prop_type not in coalesced_info[property_name]: coalesced_info[property_name][prop_type] = {} if description not in coalesced_info[property_name][prop_type]: coalesced_info[property_name][prop_type][description] = [] coalesced_info[property_name][prop_type][description] += schemas self.coalesced_properties = coalesced_info def generate_updated_config(self): """ Update property_index_config data. Flag any properties that were found to have more than one type, or more than one description. If the property already appears in self.config and it has a globalDescription, flag only entries with a different *type*. """ updated = copy.deepcopy(self.config) overrides = updated['description_overrides'] # NB: this should already be in place. # Sorting isn't necessary in this method, but it's nice to have for troubleshooting. property_names = sorted(self.coalesced_properties.keys(), key=str.lower) for prop_name in property_names: prop_config = overrides.get(prop_name) info = self.coalesced_properties[prop_name] prop_types = sorted(info.keys(), key=str.lower) # If we don't already have prop_config and we have multiple types, capture them all: num_prop_types = len(prop_types) done_with_prop_name = False if not prop_config and num_prop_types > 1: prop_config = overrides[prop_name] = [] for prop_type in prop_types: descriptions = sorted(info[prop_type].keys(), key=str.lower) for description in descriptions: schemas = info[prop_type][description] found_entry = { "type": prop_type, "description": description, 'knownException': False, "schemas": ['/'.join(x) for x in schemas] } prop_config.append(found_entry) done_with_prop_name = True else: for prop_type in prop_types: descriptions = sorted(info[prop_type].keys(), key=str.lower) num_descriptions = len(descriptions) done_with_prop_type = False if not prop_config: # If we found multiple descriptions and we have no overrides, capture each: if num_descriptions > 1: prop_config = overrides[prop_name] = [] for description in descriptions: schemas = info[prop_type][description] found_entry = { "type": prop_type, "description": description, 'knownException': False, "schemas": ['/'.join(x) for x in schemas] } prop_config.append(found_entry) done_with_prop_name = True else: self.update_config_for_prop_name_and_type(prop_name, prop_type, info, prop_config) return updated def update_config_for_prop_name_and_type(self, prop_name, prop_type, info, prop_config): """ Update a property name/type selection of prop_config based on coalesced info. Updates prop_config. """ # Do we have a globalOverride for this prop_type? If so, we're done. Again. for over_info in prop_config: if over_info.get('type') == prop_type and over_info.get('globalOverride', False): return # check each entry against prop_config descriptions = sorted(info[prop_type].keys(), key=str.lower) for description in descriptions: self.update_config_for_prop_name_and_type_and_description(prop_name, prop_type, description, info, prop_config) def update_config_for_prop_name_and_type_and_description(self, prop_name, prop_type, description, info, prop_config): """ Update a property name/type/description selection of prop_config based on coalesced info. Updates prop_config. """ """ Info is arranged by prop_name: prop_type: description: schemas (list). prop_config, conversely, is arranged as a list of dicts with keys schemas, type, description, overrideDescription, knownException. If we applied an override, the description in "info" will match the overrideDescription in prop_config. """ config_by_schema = {} for config in prop_config: # Note, we ignore globalOverrides in this method. if config.get('type') == prop_type: for schema in config.get('schemas', []): config_by_schema[schema] = config schemas = info[prop_type][description] for schema_path in schemas: schema_name = '/'.join(schema_path) if config_by_schema.get(schema_name): # We have an entry for this schema name. It's still good if it has an overrideDescription, or if the description matches. if config_by_schema[schema_name].get('overrideDescription'): break elif config_by_schema[schema_name].get('description') == description: break else: config_by_schema[schema_name]['description'] = description config_by_schema[schema_name]['knownException'] = False else: # If we already have this description, add the schema there. for config in prop_config: if config.get('type') == prop_type and config.get('description') == description: config['schemas'].append(schema_name) break # We didn't find a matching description, so create a new entry: found_entry = { "type": prop_type, "description": description, 'knownException': False, "schemas": [ schema_name ] } prop_config.append(found_entry) config_by_schema[schema_name] = found_entry def escape_text(self, text, chars=None): """Escape text in whatever way is appropriate to this output format. """ return html.escape(text, False) def format_tabular_output(self): """ Format output in the 'usual' way, as a tabular document """ rows = [] property_names = sorted(self.coalesced_properties.keys(), key=str.lower) for prop_name in property_names: info = self.coalesced_properties[prop_name] prop_types = sorted(info.keys(), key=str.lower) first_row = True for prop_type in prop_types: descriptions = sorted(info[prop_type].keys(), key=str.lower) for description in descriptions: schema_list = [self.format_schema_path(x) for x in info[prop_type][description] ] if first_row: first_col = self.formatter.bold(prop_name) first_row = False else: first_col = '' rows.append(self.formatter.make_row([first_col, self.format_schema_list(schema_list, self.formatter), prop_type, description])) if self.write_config_fh: config_out = self.write_config_fh updated_config = self.generate_updated_config() json.dump(updated_config, config_out, indent=4, sort_keys=True) config_out.close() headers = self.formatter.make_header_row([_('Property Name'), _('Defined In Schema(s)'), _('Type'), _('Description')]) table = self.formatter.make_table(rows, [headers]) return table @staticmethod def add_html_boilerplate(htmlblob): headlines = ['<head>', '<meta charset="utf-8">', '<title>' + _('Property Index') + '</title>'] styles = """ <style> table{ max-width: 100%; background-color: transparent; border-collapse: separate; border-spacing: 0; margin-bottom: 1.25em; border: 1px solid #999999; border-width: 0 1px 1px 0; } td, th{ padding: .5em; text-align: left; vertical-align: top; border: 1px solid #999999; border-width: 1px 0 0 1px; } table.properties{ width: 100%; } </style> """ headlines.append(styles) headlines.append('</head>') head = '\n'.join(headlines) return '\n'.join(['<!doctype html>', '<html>', head, '<body>', htmlblob, '</body></html>']) @staticmethod def format_schema_list(schema_list, formatter): sep = ', ' + formatter.br() if len(schema_list) > 10: return formatter.italic(_('various')) + formatter.br() + '(' + sep.join(schema_list[:2]) + ' ... )' else: return sep.join(schema_list) @staticmethod def format_schema_path(sl): formatted = sl[0] if len(sl) > 1: formatted += ' (' + ' > '.join(sl[1:]) + ')' return formatted def output_csv(self): """ Generate CSV output. """ import csv import io csv_out = io.StringIO() writer = csv.writer(csv_out) rows = [] rows.append([_('Property Name'), _('Schema'), _('Type'), _('Description')]) property_names = sorted(self.coalesced_properties.keys()) for prop_name in property_names: info = self.coalesced_properties[prop_name] prop_types = sorted(info.keys()) for prop_type in prop_types: descriptions = sorted(info[prop_type].keys()) for description in descriptions: schema_list = [self.format_schema_path(x) for x in info[prop_type][description] ] for schema_str in schema_list: rows.append([prop_name, schema_str, prop_type, description]) for row in rows: writer.writerow(row) result = csv_out.getvalue() csv_out.close() return result def add_description(self, text): """ This is for the schema description. We don't actually use this. """ pass def add_uris(self, uris): """ omit URIs """ pass def add_json_payload(self, json_payload): """ JSON payloads don't make sense for PropertyIndex """ pass
class MarkdownGenerator(DocFormatter): """Provides methods for generating markdown from Redfish schemas. Markdown is targeted to the Slate documentation tool: https://github.com/lord/slate """ def __init__(self, property_data, traverser, config, level=0): super(MarkdownGenerator, self).__init__(property_data, traverser, config, level) self.separators = { 'inline': ', ', 'linebreak': '\n', 'pattern': ', ' } self.formatter = FormatUtils() self.layout_payloads = 'top' 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[0].get('_is_excerpt') if not meta: meta = {} # We want to modify a local copy of meta, deleting redundant version info meta = copy.deepcopy(meta) 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 = 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: version = meta.get('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_display = self.truncate_version(meta['version'], 2) + '+' if 'version_deprecated' in meta: deprecated_display = self.truncate_version(meta['version_deprecated'], 2) name_and_version += ' ' + self.formatter.italic('(v' + version_display + ', deprecated v' + deprecated_display + ')') deprecated_descr = ("Deprecated v" + deprecated_display + '+. ' + self.escape_for_markdown(meta['version_deprecated_explanation'], self.config.get('escape_chars', []))) else: name_and_version += ' ' + self.formatter.italic('(v' + version_display + ')') elif 'version_deprecated' in meta: deprecated_display = self.truncate_version(meta['version_deprecated'], 2) name_and_version += ' ' + self.formatter.italic('(deprecated v' + deprecated_display + ')') deprecated_descr = ("Deprecated v" + deprecated_display + '+. ' + self.escape_for_markdown(meta['version_deprecated_explanation'], self.config.get('escape_chars', []))) 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': 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']: if formatted_details['descr']: formatted_details['descr'] += ' ' formatted_details['descr'] += self.formatter.bold(formatted_details['profile_purpose']) if formatted_details['descr'] is None: formatted_details['descr'] = '' if formatted_details['profile_purpose']: 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 = 'See ' + prop_name + ' in Property Details, below, for the possible values of this property.' else: text_descr = 'See Property Details, below, for more information about this property.' 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 meta.get('is_pattern') and not formatted_details['prop_is_object']: if formatted_details['read_only']: prop_access = 'read-only' else: prop_access = 'read-write' 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'): 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, 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_action_details(self, prop_name, action_details): """Generate a formatted Actions section from supplemental markup.""" contents = [] contents.append(self.formatter.head_three(action_details.get('action_name', prop_name), self.level)) if action_details.get('text'): contents.append(action_details.get('text')) if action_details.get('example'): example = '```json\n' + action_details['example'] + '\n```\n' contents.append('Example Action POST:\n') contents.append(example) return '\n'.join(contents) + '\n' def format_action_parameters(self, schema_ref, prop_name, prop_descr, action_parameters): """Generate a formatted Actions section from parameter data. """ formatted = [] action_name = prop_name if prop_name.startswith('#'): # expected # Example: from #Bios.ResetBios, we want prop_name "ResetBios" and action_name "Bios.ResetBios" prop_name_parts = prop_name.split('.') prop_name = prop_name_parts[-1] action_name = action_name[1:] formatted.append(self.formatter.head_four(prop_name, self.level)) formatted.append(self.formatter.para(prop_descr)) # Add the URIs for this action. formatted.append(self.format_uri_block_for_action(action_name, self.current_uris)); if action_parameters: rows = [] # Table start: rows.append("| | | |") rows.append("| --- | --- | --- |") # Add a "start object" row for this parameter: rows.append('| ' + ' | '.join(['{', ' ',' ',' ']) + ' |') param_names = [x for x in action_parameters.keys()] param_names.sort(key=str.lower) for param_name in param_names: formatted_parameters = self.format_property_row(schema_ref, param_name, action_parameters[param_name], ['Actions', prop_name]) rows.append(formatted_parameters.get('row')) # Add a closing } row: rows.append('| ' + ' | '.join(['}', ' ',' ',' ']) + ' |') formatted.append(self.formatter.para('The following table shows the parameters for the action which are included in the POST body to the URI shown in the "target" property of the Action.')) formatted.append('\n'.join(rows)) else: formatted.append(self.formatter.para("(This action takes no parameters.)")) return "\n".join(formatted) def _format_profile_access(self, read_only=False, read_req=None, write_req=None, min_count=None): """Common formatting logic for profile_access column""" profile_access = '' if not self.config['profile_mode']: return profile_access # Each requirement may be Mandatory, Recommended, IfImplemented, Conditional, or (None) if not read_req: read_req = 'Mandatory' # This is the default if nothing is specified. if read_only: profile_access = self.formatter.nobr(self.text_map(read_req)) + ' (Read-only)' elif read_req == write_req: profile_access = self.formatter.nobr(self.text_map(read_req)) + ' (Read/Write)' elif not write_req: profile_access = self.formatter.nobr(self.text_map(read_req)) + ' (Read)' else: # Presumably Read is Mandatory and Write is Recommended; nothing else makes sense. profile_access = (self.formatter.nobr(self.text_map(read_req)) + ' (Read),' + self.formatter.nobr(self.text_map(write_req)) + ' (Read/Write)') if min_count: if profile_access: profile_access += ", " profile_access += self.formatter.nobr("Minimum " + str(min_count)) return profile_access def link_to_own_schema(self, schema_ref, schema_full_uri): """Format a reference to a schema.""" result = super().link_to_own_schema(schema_ref, schema_full_uri) return self.formatter.italic(result) def link_to_outside_schema(self, schema_full_uri): """Format a reference to a schema_uri, which should be a valid URI""" return self.formatter.italic('['+ schema_full_uri + '](' + schema_full_uri + ')') def emit(self): """ Output contents thus far """ contents = [] for section in self.sections: contents.append(section.get('heading')) if section.get('release_history'): contents.append(section['release_history']) if section.get('description'): contents.append(section['description']) if section.get('uris'): contents.append(section['uris']) if section.get('json_payload'): contents.append(section['json_payload']) # something is awry if there are no properties, but ... if section.get('properties'): contents.append('| | | |') contents.append('| --- | --- | --- |') contents.append('\n'.join(section['properties'])) if section.get('profile_conditional_details'): # sort them now; these can be sub-properties so may not be in alpha order. conditional_details = '\n'.join(sorted(section['profile_conditional_details'], key=str.lower)) contents.append('\n' + self.formatter.head_two('Conditional Requirements', self.level)) contents.append(conditional_details) if len(section.get('action_details', [])): contents.append('\n' + self.formatter.head_two('Actions', self.level)) contents.append('\n\n'.join(section.get('action_details'))) if section.get('property_details'): contents.append('\n' + self.formatter.head_two('Property Details', self.level)) contents.append('\n'.join(section['property_details'])) self.sections = [] # Profile output may include registry sections for section in self.registry_sections: contents.append(section.get('heading')) contents.append(section.get('requirement')) if section.get('description'): contents.append(self.formatter.para(section['description'])) if section.get('messages'): contents.append(self.formatter.head_two('Messages', self.level)) message_rows = [self.formatter.make_row(x) for x in section['messages']] header_cells = ['', 'Requirement'] if self.config.get('profile_mode') != 'terse': header_cells.append('Description') header_row = self.formatter.make_row(header_cells) contents.append(self.formatter.make_table(message_rows, [header_row], 'messages')) contents.append('\n') return '\n'.join(contents) def output_document(self): """Return full contents of document""" body = self.emit() common_properties = self.generate_common_properties_doc() supplemental = self.config.get('supplemental', {}) if 'Title' in supplemental: doc_title = supplemental['Title'] else: doc_title = 'Schema Documentation' prelude = "---\ntitle: " + doc_title + """ search: true --- """ intro = supplemental.get('Introduction') if intro: intro = self.process_intro(intro) prelude += '\n' + intro + '\n' contents = [prelude, body] if 'Postscript' in supplemental: contents.append('\n' + supplemental['Postscript']) output = '\n'.join(contents) if '[insert_common_objects]' in output: output = output.replace('[insert_common_objects]', common_properties, 1) if '[insert_collections]' in output: collections_doc = self.generate_collections_doc() output = output.replace('[insert_collections]', collections_doc, 1) # Replace pagebreak markers with HTML pagebreak markup output = output.replace('~pagebreak~', '<p style="page-break-before: always"></p>') return output def process_intro(self, intro_blob): """ Process the intro text, generating and inserting any schema fragments """ parts = [] intro = [] part_text = [] fragment_config = { 'output_format': 'markdown', 'normative': self.config.get('normative'), 'cwd': self.config.get('cwd'), 'schema_supplement': {}, 'supplemental': {}, 'excluded_annotations': [], 'excluded_annotations_by_match': [], 'excluded_properties': [], 'excluded_by_match': [], 'excluded_schemas': [], 'excluded_schemas_by_match': [], 'escape_chars': [], 'uri_replacements': {}, 'units_translation': self.config.get('units_translation'), 'profile': self.config.get('profile'), 'profile_mode': self.config.get('profile_mode'), 'profile_resources': self.config.get('profile_resources', {}), 'wants_common_objects': self.config.get('wants_common_objects'), 'actions_in_property_table': self.config.get('actions_in_property_table', True), } for line in intro_blob.splitlines(): if line.startswith('#include_fragment'): if len(part_text): parts.append({'type': 'markdown', 'content': '\n'.join(part_text)}) part_text = [] fragment_id = line[17:].strip() fragment_content = self.generate_fragment_doc(fragment_id, fragment_config) parts.append({'type': 'fragment', 'content': fragment_content}) else: part_text.append(line) if len(part_text): parts.append({'type': 'markdown', 'content': '\n'.join(part_text)}) for part in parts: if part['type'] == 'markdown': intro.append(part['content']) elif part['type'] == 'fragment': intro.append(part['content']) return '\n'.join(intro) def add_section(self, text, link_id=False): """ Add a top-level heading """ self.this_section = {'head': text, 'heading': '\n' + self.formatter.head_one(text, self.level), 'properties': [], 'property_details': [] } self.sections.append(self.this_section) def add_description(self, text): """ Add the schema description """ self.this_section['description'] = text + '\n' def add_uris(self, uris): """ Add the URIs (which should be a list) """ uri_block = "**URIs**:\n" for uri in sorted(uris, key=str.lower): uri_block += "\n" + self.format_uri(uri) self.this_section['uris'] = uri_block + "\n" def format_uri_block_for_action(self, action, uris): """ Create a URI block for this action & the resource's URIs """ uri_block = "**URIs**:\n" for uri in sorted(uris, key=str.lower): uri = uri + "/Actions/" + action uri_block += "\n" + self.format_uri(uri) return uri_block def format_json_payload(self, json_payload): """ Format a json payload for output. """ return '\n' + json_payload + '\n' def add_property_row(self, formatted_text): """Add a row (or group of rows) for an individual property in the current section/schema. formatted_row should be a chunk of text already formatted for output""" self.this_section['properties'].append(formatted_text) def add_property_details(self, formatted_details): """Add a chunk of property details information for the current section/schema.""" self.this_section['property_details'].append(formatted_details) def add_registry_reqs(self, registry_reqs): """Add registry messages. registry_reqs includes profile annotations.""" terse_mode = self.config.get('profile_mode') == 'terse' reg_names = [x for x in registry_reqs.keys()] reg_names.sort(key=str.lower) for reg_name in reg_names: reg = registry_reqs[reg_name] this_section = { 'head': reg_name, 'description': reg.get('Description', ''), 'messages': [] } heading = reg_name + ' Registry v' + reg['minversion'] + '+' if reg.get('current_release', reg['minversion']) != reg['minversion']: heading += ' (current release: v' + reg['current_release'] + ')' this_section['heading'] = self.formatter.head_one(heading, self.level) this_section['requirement'] = 'Requirement: ' + reg.get('profile_requirement', '') msgs = reg.get('Messages', {}) msg_keys = [x for x in msgs.keys()] msg_keys.sort(key=str.lower) for msg in msg_keys: this_msg = msgs[msg] if terse_mode and not this_msg.get('profile_requirement'): continue msg_row = [msg, this_msg.get('profile_requirement', '')] if not terse_mode: msg_row.append(this_msg.get('Description', '')) this_section['messages'].append(msg_row) self.registry_sections.append(this_section) @staticmethod def escape_for_markdown(text, chars): """Escape selected characters in text to prevent auto-formatting in markdown.""" for char in chars: text = text.replace(char, '\\' + char) return text @staticmethod def escape_regexp(text): """If escaping is necessary to protect patterns when output format is rendered, do that.""" chars_to_escape = r'\`*_{}[]()#+-.!|' escaped_text = '' for char in text: if char in chars_to_escape: escaped_text += '\\' + char else: escaped_text += char return escaped_text