├── .gitignore ├── docs ├── err_diag_new.png └── err_diag_old.png ├── g++nice ├── gccnice ├── README.md └── gccnice.py /.gitignore: -------------------------------------------------------------------------------- 1 | cve-2014-1266.c 2 | out.json 3 | test.c 4 | test.cpp 5 | -------------------------------------------------------------------------------- /docs/err_diag_new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstef7/gccnice/HEAD/docs/err_diag_new.png -------------------------------------------------------------------------------- /docs/err_diag_old.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstef7/gccnice/HEAD/docs/err_diag_old.png -------------------------------------------------------------------------------- /g++nice: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | DIR="$(cd "$(dirname "$0")" && pwd)" 3 | g++ -fdiagnostics-format=json "$@" 2>&1 >/dev/null | "$DIR/gccnice.py" >&2 4 | -------------------------------------------------------------------------------- /gccnice: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | DIR="$(cd "$(dirname "$0")" && pwd)" 3 | gcc -fdiagnostics-format=json "$@" 2>&1 >/dev/null | "$DIR/gccnice.py" >&2 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gccnice 2 | 3 | Reject old-fashioned GCC appearance: 4 | 5 | ![Old error diagnostics](docs/err_diag_old.png) 6 | 7 | Embrace modern `gccnice`: 8 | 9 | ![New error diagnostics](docs/err_diag_new.png) 10 | 11 | ## Installation 12 | 13 | Clone the repository and execute the following command, assuming `~/.local/bin` exists and is in `PATH`: 14 | 15 | ```bash 16 | cp gccnice.py gccnice g++nice ~/.local/bin/ 17 | ``` 18 | 19 | Now you can use `gccnice` and `g++nice` instead of `gcc` and `g++`. 20 | -------------------------------------------------------------------------------- /gccnice.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import shutil 5 | import fileinput 6 | import json 7 | import textwrap 8 | import re 9 | 10 | color_normal = '\033[0m' 11 | color_gray = '\033[37m' 12 | color_darkgray = '\033[90m' 13 | color_red = '\033[91m' 14 | color_yellow = '\033[93m' 15 | color_cyan = '\033[96m' 16 | 17 | config = {'frame': 'round', 18 | 'max_width': 60} 19 | 20 | box_lines = { 21 | 'round': {'h': '\u2500', 22 | 'v': '\u2502', 23 | 'top_left': '\u256D', 24 | 'top_right': '\u256E', 25 | 'bottom_left':'\u2570', 26 | 'bottom_right': '\u256F'}, 27 | 'square': {'h': '\u2500', 28 | 'v': '\u2502', 29 | 'top_left': '\u250C', 30 | 'top_right': '\u2510', 31 | 'bottom_left':'\u2514', 32 | 'bottom_right': '\u2518'}, 33 | 'ascii': {'h': '-', 34 | 'v': '|', 35 | 'top_left': '-', 36 | 'top_right': '-', 37 | 'bottom_left': '-', 38 | 'bottom_right': '-'} 39 | } 40 | 41 | def exit_error(error): 42 | print(error) 43 | exit() 44 | 45 | def readConfigFile(): 46 | config_path = os.path.expanduser('~') + '/.config/'; 47 | config_filename = 'gccnicerc' 48 | 49 | try: 50 | config_file = open(config_path + config_filename, 'r') 51 | config_json = json.loads(config_file.read()) 52 | 53 | for key in config: 54 | if key in config_json: 55 | config[key] = config_json[key] 56 | except: 57 | os.makedirs(config_path, exist_ok = True) 58 | 59 | config_file = open(config_path + config_filename, 'w') 60 | config_file.write(json.dumps(config, indent = 4)) 61 | config_file.close() 62 | 63 | def getTerminalWidth(): 64 | return shutil.get_terminal_size((80, 24)).columns 65 | 66 | def readMessageJson(): 67 | gcc_output = ""; 68 | for line in fileinput.input(): 69 | gcc_output += line 70 | 71 | try: 72 | return json.loads(gcc_output) 73 | except: # This is not a compiler error and was not JSON-formatted. 74 | print(gcc_output) 75 | exit() 76 | 77 | def wrap(text, text_width, horizontal = ' ', vertical = ' ', 78 | top_left = ' ', top_right = ' ', bottom_left = ' ', bottom_right = ' ', 79 | wrap_horizontal = True, wrap_vertical = True): 80 | 81 | final = '' 82 | 83 | if (wrap_vertical): 84 | final = (top_left 85 | + horizontal * text_width 86 | + top_right 87 | + '\n') 88 | 89 | vertical_char = vertical if wrap_horizontal else '' 90 | final += '\n'.join(vertical_char 91 | + line 92 | + ' ' * (text_width - len(removeColorSequences(line))) 93 | + vertical_char 94 | for line in text.split('\n')) 95 | 96 | if (wrap_vertical): 97 | final += ('\n' 98 | + bottom_left 99 | + horizontal * text_width 100 | + bottom_right) 101 | 102 | return final 103 | 104 | def removeColorSequences(text): 105 | return re.sub(r'\x1B\[[\d;]*m', '', text) 106 | 107 | def colorText(text, color): 108 | return color_normal + color + text + color_normal 109 | 110 | def underline(color): 111 | return '\033[4;' + color[2:] 112 | 113 | def bold(color): 114 | return '\033[1;' + color[2:] 115 | 116 | def wrapInOutline(text, text_width, label = None, color = color_normal, 117 | label_left = True, label_color = None): 118 | 119 | lines = box_lines[config['frame']] 120 | 121 | text = wrap(text, text_width, 122 | horizontal = lines['h'], 123 | vertical = color + lines['v'] + color_normal, 124 | top_left = color + lines['top_left'], 125 | top_right = lines['top_right'] + color_normal, 126 | bottom_left = color + lines['bottom_left'], 127 | bottom_right = lines['bottom_right'] + color_normal) 128 | 129 | final_label = label 130 | if label_color: 131 | final_label = label_color + label + color; 132 | 133 | label_start = len(color) + (2 if label_left else (text_width - len(label) - 2)) 134 | text = (text[:label_start] 135 | + ' ' 136 | + final_label 137 | + ' ' 138 | + text[label_start + len(label) + 2 :]) 139 | 140 | return text 141 | 142 | def getLocationFile(location): 143 | return location['caret']['file'] 144 | 145 | def getLocationLine(location): 146 | return location['caret']['line'] 147 | 148 | def getCodeLines(filename, line_min, line_max): 149 | line_min -= 1 150 | line_max -= 1 151 | source_lines = [None for _ in range(line_min, line_max + 1)] 152 | with open(filename) as source_file: 153 | for i, line in enumerate(source_file): 154 | if i >= line_min and i <= line_max: 155 | source_lines[i - line_min] = line.expandtabs(4) 156 | elif i > line_max: 157 | break 158 | return source_lines 159 | 160 | def lineNumberMaxWidth(line): 161 | # We show 3 lines (line - 1, line, line + 1). The maximum number 162 | # of digits is the number of digits of the largest line number, 163 | # which is line + 1. 164 | 165 | # FIXME - This will be too large if the line is the last in the 166 | # file, and it also happens to be of form 10^n - 1 (at least 999) 167 | return max(len(str(line + 1)), 3) 168 | 169 | def getLinePrefix(width, number = ' ', color = color_gray): 170 | spaces = width - len(str(number)) + 1 171 | padding = '{s:{w}}'.format(w = spaces, s = '') 172 | line_number = colorText(str(number), color) 173 | bar = colorText(' \u2502', color_darkgray) 174 | return padding + line_number + bar 175 | 176 | def getCodeBox(location, width, color): 177 | code_width = width - 2 178 | 179 | location_file = getLocationFile(location) 180 | location_line = getLocationLine(location) 181 | location_column = location['caret']['column'] 182 | code_lines = getCodeLines(location_file, location_line - 1, location_line + 1) 183 | num_width = lineNumberMaxWidth(location_line) 184 | 185 | code_lines_final = [] 186 | for i, line in enumerate(code_lines): 187 | if (line == None): 188 | continue 189 | 190 | if i == 1: 191 | num_color = bold(color) 192 | else: 193 | num_color = color_gray 194 | line_prefix = getLinePrefix(num_width, location_line - 1 + i, num_color) 195 | 196 | wrapped_line = textwrap.fill(line, code_width - 1 - (num_width + 3)) 197 | 198 | column = 1; 199 | 200 | finish_line = location_line 201 | finish_column = location_column 202 | if 'finish' in location: 203 | finish_line = location['finish']['line'] 204 | finish_column = location['finish']['column'] 205 | 206 | color_wrapped_line = [] 207 | wrapped_line_split = wrapped_line.split('\n') 208 | for sub_line in wrapped_line_split: 209 | color_line = "" 210 | for char in sub_line: 211 | file_line = location_line - 1 + i 212 | if (file_line >= location_line and file_line <= finish_line and 213 | column >= location_column and column <= finish_column): 214 | color_line += colorText(char, bold(color)) 215 | else: 216 | color_line += char 217 | column += 1 218 | color_wrapped_line.append(color_line) 219 | wrapped_line = '\n'.join(color_wrapped_line) 220 | 221 | code_lines_final.append(line_prefix + wrapped_line.split('\n')[0] + '\n') 222 | for sub_line in wrapped_line.split('\n')[1:]: 223 | code_lines_final.append(getLinePrefix(num_width) + sub_line + '\n') 224 | 225 | code = ''.join(code_lines_final)[:-1] 226 | box = wrapInOutline(code, code_width, 227 | label = location['caret']['file'], 228 | color = color_darkgray, label_color = color_gray, 229 | label_left = False) 230 | return box 231 | 232 | def getMessageBox(message, width): 233 | box_padding = 2 234 | content_width = width - 2 * box_padding 235 | 236 | content = textwrap.fill(message['message'][0].upper() 237 | + message['message'][1:] + ':', content_width) 238 | 239 | label, color = {'w': ('WARNING', color_yellow), 240 | 'e': ('ERROR', color_red), 241 | 'n': ('NOTE', color_cyan)}.get(message['kind'][0], (None, color_normal)) 242 | 243 | for location in message['locations']: 244 | content += '\n' + getCodeBox(location, content_width, color) 245 | 246 | if ('children' in message): 247 | for child in message['children']: 248 | content += '\n' + getMessageBox(child, content_width) 249 | 250 | box = wrap(content, content_width, wrap_vertical = False) 251 | box = wrapInOutline(box, content_width + 2, label, color) 252 | return box 253 | 254 | def printMessage(message, width): 255 | message_box = getMessageBox(message, width - 2) 256 | for line in message_box.split('\n'): 257 | print(' ' + line) 258 | 259 | if __name__ == '__main__': 260 | readConfigFile() 261 | 262 | term_width = min(getTerminalWidth(), config['max_width']) 263 | gcc_json = readMessageJson() 264 | 265 | print("") 266 | for message in gcc_json: 267 | printMessage(message, term_width) 268 | --------------------------------------------------------------------------------