|
| 1 | +#! python3 |
| 2 | +""" |
| 3 | +Automatically generate the keywords.txt from a header file or make an existing |
| 4 | +keywords.txt with a uniform indentation |
| 5 | +
|
| 6 | +@author: aster94 |
| 7 | +""" |
| 8 | +# Settings |
| 9 | +newline = '\n' # compatibility between unix, windows, mac? |
| 10 | +distance = 8 |
| 11 | + |
| 12 | +template_KEYWORD1 = """\ |
| 13 | +####################################### |
| 14 | +# Syntax Coloring Map |
| 15 | +####################################### |
| 16 | +
|
| 17 | +####################################### |
| 18 | +# Datatypes (KEYWORD1) |
| 19 | +####################################### |
| 20 | +""" |
| 21 | + |
| 22 | +template_KEYWORD2 = """\ |
| 23 | +####################################### |
| 24 | +# Methods and Functions (KEYWORD2) |
| 25 | +####################################### |
| 26 | +""" |
| 27 | + |
| 28 | +template_LITERAL1 = """\ |
| 29 | +###################################### |
| 30 | +# Constants (LITERAL1) |
| 31 | +###################################### |
| 32 | +""" |
| 33 | + |
| 34 | +# Modules |
| 35 | +import click, re, os, shutil, sys |
| 36 | + |
| 37 | +############################################################ |
| 38 | +###### Utilities ####### |
| 39 | +############################################################ |
| 40 | +def file_exist(file_path): |
| 41 | + return os.path.isfile(file_path) |
| 42 | + |
| 43 | +def file_read(file_path): |
| 44 | + with open(file_path, 'r') as file: |
| 45 | + return file.read() |
| 46 | + |
| 47 | +def file_write(file_path, content): |
| 48 | + with open(file_path, 'w') as file: |
| 49 | + file.write(content) |
| 50 | + |
| 51 | +def file_copy(source, destination): |
| 52 | + shutil.copy(source, destination) |
| 53 | + |
| 54 | +def file_create(file_path): |
| 55 | + with open(file_path, 'w+'): pass |
| 56 | + |
| 57 | + |
| 58 | +############################################################ |
| 59 | +###### Functions ####### |
| 60 | +############################################################ |
| 61 | +def read_header(file_path, verbose, force): |
| 62 | + |
| 63 | + block_comment = False |
| 64 | + polished = "" |
| 65 | + key = {'KEYWORD1': list(), 'KEYWORD2': list(), 'LITERAL1': list()} |
| 66 | + |
| 67 | + raw = file_read (file_path) |
| 68 | + |
| 69 | + # Integrity checks |
| 70 | + multiline_comment_start = raw.count('/*') |
| 71 | + multiline_comment_end = raw.count('*/') |
| 72 | + if (multiline_comment_start == 0 and multiline_comment_end == 0): |
| 73 | + pass |
| 74 | + elif (multiline_comment_start - multiline_comment_end != 0): |
| 75 | + print('check source file: multiline comment block problem') |
| 76 | + if not force: raise NameError('MultilineBlockUndefinied') |
| 77 | + |
| 78 | + # Clean |
| 79 | + for line in raw.splitlines(): |
| 80 | + line = line.strip() |
| 81 | + if (block_comment): |
| 82 | + # We are inside a multiline block comment |
| 83 | + if (line.find('*/') == -1): |
| 84 | + continue |
| 85 | + else: |
| 86 | + block_comment = False |
| 87 | + line = line[line.find('*/')+2:] |
| 88 | + |
| 89 | + if (line.find('/*') != -1): |
| 90 | + # It is the start of a block comment |
| 91 | + block_comment = True |
| 92 | + line = line[:line.find('/*')] |
| 93 | + |
| 94 | + if (line.find('//') != -1): |
| 95 | + line = line[:line.find('//')] |
| 96 | + |
| 97 | + if (line.find('#') != -1): |
| 98 | + continue |
| 99 | + |
| 100 | + polished += line + '\n' |
| 101 | + |
| 102 | + # Removing empty lines |
| 103 | + polished = re.sub(r'\n\s*\n', '\n', polished.strip()) |
| 104 | + |
| 105 | + if ('public:' in polished): |
| 106 | + start = polished.find('public:') + 8 |
| 107 | + else: |
| 108 | + start = polished.find('{') + 2 |
| 109 | + |
| 110 | + if ('private:' in polished): |
| 111 | + end = polished.find('private:') |
| 112 | + elif ('protected:' in polished): |
| 113 | + end = polished.find('protected:') |
| 114 | + else: |
| 115 | + end = polished.find('}') |
| 116 | + |
| 117 | + if (start > end): |
| 118 | + print('Unexpected error') |
| 119 | + raise NameError('Unexpected error') |
| 120 | + |
| 121 | + function_block = polished[start:end].strip() |
| 122 | + |
| 123 | + for line in function_block.splitlines(): |
| 124 | + line = line.strip() |
| 125 | + |
| 126 | + start = line.find(' ') + 1 |
| 127 | + end = line.find('(') |
| 128 | + |
| 129 | + # Add them to list |
| 130 | + if (start > end): |
| 131 | + to_add = line[:end] |
| 132 | + if (not to_add in key['KEYWORD1']): key['KEYWORD1'].append(to_add) |
| 133 | + else: |
| 134 | + to_add = line[start:end] |
| 135 | + if (not to_add in key['KEYWORD2']): key['KEYWORD2'].append(to_add) |
| 136 | + |
| 137 | + return key |
| 138 | + |
| 139 | +def read_keywords(file_path, verbose, force): |
| 140 | + |
| 141 | + key = {'KEYWORD1': list(), 'KEYWORD2': list(), 'LITERAL1': list()} |
| 142 | + raw = file_read(file_path) |
| 143 | + value = '' |
| 144 | + |
| 145 | + for line in raw.splitlines(): |
| 146 | + line = line.strip() |
| 147 | + if ('# ' in line): |
| 148 | + value = line[line.find('(')+1:line.find(')')] |
| 149 | + elif ('#' in line or not line): |
| 150 | + continue |
| 151 | + else: |
| 152 | + try: |
| 153 | + last = re.search('\s', line).start() |
| 154 | + except: |
| 155 | + last = len(line) |
| 156 | + key[value].append(line[:last]) |
| 157 | + return key |
| 158 | + |
| 159 | +def write_keywords(key_dict, file_path, verbose, soft): |
| 160 | + max_len = 0 |
| 161 | + |
| 162 | + # Get the longest item |
| 163 | + for k in key_dict.keys(): |
| 164 | + for i in key_dict[k]: |
| 165 | + i_len = len (i) |
| 166 | + if (max_len < i_len): max_len = i_len |
| 167 | + |
| 168 | + # Extend it |
| 169 | + while (max_len % 4 != 0): max_len += 1 |
| 170 | + max_len += distance |
| 171 | + |
| 172 | + output = template_KEYWORD1 |
| 173 | + for n in key_dict['KEYWORD1']: |
| 174 | + pos = max_len - len(n) |
| 175 | + output += '{}{}{}\n'.format(n, ' ' * pos, 'KEYWORD1') |
| 176 | + |
| 177 | + output += '\n\n' + template_KEYWORD2 |
| 178 | + for n in key_dict['KEYWORD2']: |
| 179 | + pos = max_len - len(n) |
| 180 | + output += '{}{}{}\n'.format(n, ' ' * pos, 'KEYWORD2') |
| 181 | + |
| 182 | + output += '\n\n' + template_LITERAL1 |
| 183 | + for n in key_dict['LITERAL1']: |
| 184 | + pos = max_len - len(n) |
| 185 | + output += '{}{}{}\n'.format(n, ' ' * pos, 'LITERAL1') |
| 186 | + |
| 187 | + if verbose: print('{} printed/wrote'.format(file_path)) |
| 188 | + if soft: print(output.strip()) |
| 189 | + else: file_write(file_path, output.strip()) |
| 190 | + |
| 191 | + |
| 192 | +@click.command() |
| 193 | +@click.argument('file_path') |
| 194 | +@click.option('--backup' , '-b', is_flag=True, help='Create a backup with the original keywords.txt file.') |
| 195 | +@click.option('--verbose' , '-v', is_flag=True, help='Verbose output.') |
| 196 | +@click.option('--force' , '-f', is_flag=True, help='Overwrite existing files.') |
| 197 | +@click.option('--soft' , '-s', is_flag=True, help='Don\'t write files, print them in the console.') |
| 198 | +@click.option('--preserve' , '-p', is_flag=True, default=True, help='Don\'t preserve LITERAL1 even if keywords.txt already exists.') |
| 199 | +def keywords(file_path, backup, verbose, force, soft, preserve): |
| 200 | + """ |
| 201 | + Automatically generate the keywords.txt from a header file or make an existing |
| 202 | + keywords.txt with a uniform indentation |
| 203 | + """ |
| 204 | + |
| 205 | + if verbose: print('Arguments\nfile_path: {}\nbackup: {}\nverbose: {}\ |
| 206 | + \nforce: {}\nsoft: {}\npreserve: {}\n'.format(\ |
| 207 | + file_path, backup, verbose, force, soft, preserve)) |
| 208 | + |
| 209 | + if not file_exist(file_path): |
| 210 | + print('File not found') |
| 211 | + raise NameError('FileNotExists') |
| 212 | + |
| 213 | + key_dict = dict() |
| 214 | + extension = os.path.splitext(file_path)[1] |
| 215 | + |
| 216 | + if (extension == '.h'): |
| 217 | + key_dict = read_header(file_path, verbose, force) |
| 218 | + keywords_path = os.path.dirname(file_path)+'\keywords.txt' |
| 219 | + if preserve: |
| 220 | + if file_exist(keywords_path): |
| 221 | + if verbose: print('Copying LITERAL1 from {}'.format(keywords_path)) |
| 222 | + key_dict_old = read_keywords(keywords_path, verbose, force) |
| 223 | + key_dict['LITERAL1'] = key_dict_old['LITERAL1'] |
| 224 | + else: |
| 225 | + if verbose: print('Can\'t copy LITERAL1, no keywords.txt found') |
| 226 | + elif (extension == '.txt'): |
| 227 | + key_dict = read_keywords(file_path, verbose, force) |
| 228 | + keywords_path = file_path |
| 229 | + else: |
| 230 | + print('File format not accepted') |
| 231 | + raise NameError('FileNotAccepted') |
| 232 | + |
| 233 | + # Check if keywords.txt exist |
| 234 | + if(file_exist(keywords_path)): |
| 235 | + if force: |
| 236 | + if verbose: print('{} already exist, overwritten'.format(keywords_path)) |
| 237 | + else: |
| 238 | + if verbose: print('{} already exist, not overwritten'.format(keywords_path)) |
| 239 | + sys.exit(0) |
| 240 | + if (backup): |
| 241 | + backup_path = keywords_path + '.old' |
| 242 | + if file_exist(backup_path): |
| 243 | + if force: |
| 244 | + if verbose: print('{} already exist, overwritten'.format(backup_path)) |
| 245 | + file_copy(keywords_path, backup_path) |
| 246 | + else: |
| 247 | + if verbose: print('{} already exist, not overwritten'.format(backup_path)) |
| 248 | + else: |
| 249 | + if verbose: print('{} didn\'t exist, created'.format(backup_path)) |
| 250 | + file_copy(keywords_path, backup_path) |
| 251 | + else: |
| 252 | + if verbose: print('{} created'.format(keywords_path)) |
| 253 | + file_create(keywords_path) |
| 254 | + |
| 255 | + write_keywords(key_dict, keywords_path, verbose, soft) |
| 256 | + |
| 257 | + |
| 258 | +if __name__ == '__main__': |
| 259 | + keywords() |
0 commit comments