#!/usr/bin/env python3 ########################################################################### # doxygen_space.pl # --------------------- # begin : October 2016 # copyright : (C) 2016 by Nyall Dawson # email : nyall dot dawson at gmail dot com # ########################################################################### # # # This program is free software; you can redistribute it and/or modify # # it under the terms of the GNU General Public License as published by # # the Free Software Foundation; either version 2 of the License, or # # (at your option) any later version. # # # ########################################################################### import sys import re def process_file(file_path): with open(file_path, 'r') as file: input_lines = file.readlines() output = [] inside_dox_block = False inside_dox_list = False previous_was_blankline = False previous_was_dox_blankline = False just_finished_a_list = False buffered_line = '' i = 0 while i < len(input_lines): line = input_lines[i].rstrip() is_blank_line = not line.strip() if re.match(r'^\s*(?:#ifdef|#ifndef|#else|#endif)', line): output.append(line) i += 1 continue if match := re.match(r'^(\s*)/\*[*!]\s*([^\s*].*)\s*$', line): # Convert blocks starting with /*! format to /** standard, # and convert # /**Some docs # to # /** # * Some docs indent, content = match.groups() output.append(f'{indent}/**') line = f'{indent} * {content[0].upper()}{content[1:]}' if match := re.match(r'^(.*)/\*[!*](?!\*)(<*)[ \t\r\n\f]*(.*?)[ \t\r\n\f]*\*/[ \t\r\n\f]*$', line): # Convert single line doxygen blocks: # /*!< comment */ to //!< comment # /** comment */ to //! comment prefix, tag, content = match.groups() line = f'{prefix}//!{tag} {content}' if match := re.match(r'^(.*)//!<\s*(.)(.*)$', line): # Uppercase initial character in //!< comment prefix, first, remaining = match.groups() line = f'{prefix}//!< {first.upper()}{remaining}' if '//!<' in line and (match := re.match(r'^(.*)\.\s*[Ss]ince (?:QGIS )?(\d+\.\d+(?:\.\d+)?)[.]?$', line)): # Use \since annotation prefix, version = match.groups() line = f'{prefix} \\since QGIS {version}' if '//!<' in line and (match := re.match(r'^(.*?)\s*\([Ss]ince (?:QGIS )?(\d+\.\d+(?:\.\d+)?)[.)]+$', line)): # Use \since annotation prefix, version = match.groups() line = f'{prefix} \\since QGIS {version}' if match := re.match(r'^(.*)\\since (?:QGIS )?(\d+\.\d+(?:\.\d+)?)[.]?$', line): # Standard since annotation prefix, version = match.groups() line = f'{prefix}\\since QGIS {version}' if match := re.match(r'^(.*)\\deprecated[,.:]? (?:[dD]eprecated )?(?:since )?(?:QGIS )?(\d+\.\d+(?:\.\d+)?)[,\s.\-]*(.*?)$', line): # Standard since annotation prefix, version, suffix = match.groups() if suffix: if suffix.startswith('(') and suffix.endswith(')'): suffix = suffix[1:-1] suffix = suffix[0].upper() + suffix[1:] if not suffix.endswith('.'): suffix += "." line = f'{prefix}\\deprecated QGIS {version}. {suffix}' else: line = f'{prefix}\\deprecated QGIS {version}' if match := re.match(r'^(\s*)//!\s*(.*?)$', line): indentation, comment = match.groups() # found a //! comment # check next line to see if it also begins with //! if i + 1 < len(input_lines) and re.match(r'^\s*//!\s*(.*?)$', input_lines[i + 1]): # we are in a multiline //! comment block, convert to /** block if not previous_was_blankline: output.append('') output.append(f"{indentation}/**") output.append(f"{indentation} * {comment}") while i + 1 < len(input_lines) and ( next_match := re.match(r'^\s*//!\s*(.*?)$', input_lines[i + 1])): next_comment = next_match.group(1) if next_comment: output.append(f"{indentation} * {next_comment}") else: output.append(f"{indentation} *") i += 1 output.append(f"{indentation} */") else: output.append(line) elif inside_dox_block: # replace "* abc" style doxygen lists with correct "- abc" formatting line = re.sub(r'^(\s+)\*\s{1,10}\*', r'\1* -', line) if re.match(r'^\s*\*\s*$', line): previous_was_dox_blankline = True if inside_dox_list: inside_dox_list = False just_finished_a_list = True buffered_line = line # print("end list") else: output.append(line) elif match := re.match(r'^(\s*)\*\s*-(?![-\d>]) *(.*)$', line): indent, content = match.groups() if not inside_dox_list and not previous_was_dox_blankline: output.append(f"{indent}*") if just_finished_a_list: # print("just finished a list, continuing the same one!!") buffered_line = "" # print("start list") output.append(f"{indent}* - {content}") inside_dox_list = True just_finished_a_list = False elif inside_dox_list and ( match := re.match(r'^(\s*)\*\s{2,}(.*)$', line)): # print("list continuation") indent, content = match.groups() output.append(f"{indent}* {content}") elif inside_dox_list and (match := re.match(r'^(\s*)\*(?!/)', line)): inside_dox_list = False indent = match.group(1) # print("end list without line break") output.append(f"{indent}*") output.append(line) just_finished_a_list = True elif re.match(r'^(\s*)\*/\s*$', line): inside_dox_block = False inside_dox_list = False just_finished_a_list = False if buffered_line: output.append(buffered_line) buffered_line = '' output.append(line) # print("end_block") else: if buffered_line: output.append(buffered_line) buffered_line = '' if not re.match(r'^\s*[#*]', line) and ( match := re.match(r'^(\s*?)(\s?)(.+?)$', line)): indent, space, content = match.groups() line = f"{indent}* {content}" output.append(line) # print("normal dox") previous_was_dox_blankline = False just_finished_a_list = False elif (match := re.match(r'^(\s*)/\*\*(?!\*)\s*(.*)$', line)): indent, content = match.groups() # Space around doxygen start blocks (force blank line before /**) if not previous_was_blankline: output.append('') if content: # new line after /** begin block output.append(f"{indent}/**") output.append(f"{indent} * {content}") else: output.append(line) inside_dox_block = True # print("start_block") else: if buffered_line: output.append(buffered_line) buffered_line = '' output.append(line) i += 1 previous_was_blankline = is_blank_line with open(file_path, 'w') as file: file.write('\n'.join(output) + '\n') if __name__ == "__main__": if len(sys.argv) != 2: print("Usage: python doxygen_space.py ") sys.exit(1) file_path = sys.argv[1] process_file(file_path)