PySimpleGUI/UserCreatedPrograms/Code Counter.py

250 lines
8.5 KiB
Python
Raw Normal View History

"""
Code Counter
A program that counts the lines of code and code characters in a code-base
Author : Israel Dryer
Modified : 2019-11-01
You can find the original repository with the latest updates here:
https://github.com/israel-dryer/Code-Counter
"""
import PySimpleGUI as sg
import statistics as stats
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
WINDOW_SIZE = (1280, 720)
def clean_data(window):
""" clean and parse the raw data """
raw = window.AllKeysDict['INPUT'].DefaultText.split('\n')
# remove whitespace
data = [row.strip() for row in raw if row.strip()]
# remove hash comments
stage1 = []
for row in data:
if row.find('#') != -1:
stage1.append(row[:row.find('#')])
else:
stage1.append(row)
# remove " multiline comments
stage2 = []
ml_flag = False # multiline comment flag
for row in stage1:
if row.count(r'"""') == 0 and not ml_flag: # not a comment line
stage2.append(row)
elif row.count(r'"""') == 1 and not ml_flag: # starting comment line
ml_flag = True
stage2.append(row[:row.find('"""')])
elif row.count(r'"""') == 1 and ml_flag: # ending comment line
ml_flag = False
stage2.append(row[row.find('"""') + 1:])
else:
continue
# remove ' multiline comments
stage3 = []
ml_flag = False # multiline comment flag
for row in stage2:
if row.count(r"'''") == 0 and not ml_flag: # not a comment line
stage3.append(row)
elif row.count(r"'''") == 1 and not ml_flag: # starting comment line
ml_flag = True
stage3.append(row[:row.find("'''")])
elif row.count(r"'''") == 1 and ml_flag: # ending comment line
ml_flag = False
stage3.append(row[row.find("'''") + 1:])
else:
continue
clean_code = [row for row in stage3 if row not in ('', "''", '""')]
# row and character rounds / for calc stats, histogram, charts
char_cnt = [len(row) for row in clean_code]
# statistics
if len(clean_code) == 0:
char_per_line = 1
else:
char_per_line = sum(char_cnt) // len(clean_code)
code_stats = {
'lines': len(clean_code), 'char_per_line': char_per_line,
'count': sum(char_cnt), 'mean': stats.mean(char_cnt), 'median': stats.median(char_cnt),
'pstdev': stats.pstdev(char_cnt), 'min': min(char_cnt), 'max': max(char_cnt)}
return clean_code, char_cnt, code_stats
def process_data(window):
""" clean and save data ... previous executed manually with submit button """
# try:
clean_code, char_cnt, code_stats = clean_data(window)
save_data(clean_code, code_stats, window)
display_charts(char_cnt, window)
display_stats(code_stats, window)
window['T2'].select()
def save_data(clean_code, code_stats, window):
window['OUTPUT'].update('\n'.join([row for row in clean_code]))
return
""" save clean code and stats to file """
with open('output.txt', 'w') as f:
for row in clean_code:
f.write(row + '\n')
# update display
with open('output.txt', 'r') as f:
window['OUTPUT'].update(f.read())
def display_charts(char_cnt, window):
""" create charts to display in window """
def draw_figure(canvas, figure, loc=(0, 0)):
""" matplotlib helper function """
figure_canvas_agg = FigureCanvasTkAgg(figure, canvas)
figure_canvas_agg.draw()
figure_canvas_agg.get_tk_widget().pack()
return figure_canvas_agg
figure = plt.figure(num=1, figsize=(4, 5))
# histogram
plt.subplot(211)
plt.hist(char_cnt)
plt.title('character count per line')
plt.ylabel('frequency')
plt.tight_layout()
# line plot
plt.subplot(212)
x = range(0, len(char_cnt))
y = char_cnt
plt.plot(y)
plt.fill_between(x, y)
plt.title('compressed code line counts')
plt.xlabel('code line number')
plt.ylabel('number of characters')
plt.tight_layout()
draw_figure(window['IMG'].TKCanvas, figure)
def display_stats(code_stats, window):
""" display code stats in the window """
window['LINES'].update('{:,d}'.format(code_stats['lines']))
window['CHARS'].update('{:,d}'.format(code_stats['count']))
window['CPL'].update('{:,d}'.format(code_stats['char_per_line']))
window['MEAN'].update('{:,.0f}'.format(code_stats['mean']))
window['MEDIAN'].update('{:,.0f}'.format(code_stats['median']))
window['PSTDEV'].update('{:,.0f}'.format(code_stats['pstdev']))
window['MAX'].update('{:,d}'.format(code_stats['max']))
window['MIN'].update('{:,d}'.format(code_stats['min']))
def click_file(window):
""" file button click event; open file and load to screen """
filename = sg.popup_get_file('Select a file containing Python code:', title='Code Counter')
if filename is None:
return
with open(filename) as f:
raw = f.read()
window['INPUT'].update(raw)
def click_clipboard(window):
""" get data from clipboard and paste to input """
try:
clip = window['INPUT'].Widget.clipboard_get()
window['INPUT'].update(clip)
except:
sg.popup_error('Clipboard is empty', no_titlebar=True)
def click_reset(window):
""" reset the windows and data fields """
window['INPUT'].update('')
window['OUTPUT'].update('')
reset_stats(window)
window['T1'].select()
def reset_stats(window):
""" clear the stats fields """
window['LINES'].update('{:,d}'.format(0))
window['CHARS'].update('{:,d}'.format(0))
window['CPL'].update('{:,d}'.format(0))
window['MEAN'].update('{:,.0f}'.format(0))
window['MEDIAN'].update('{:,.0f}'.format(0))
window['PSTDEV'].update('{:,.0f}'.format(0))
window['MAX'].update('{:,d}'.format(0))
window['MIN'].update('{:,d}'.format(0))
def btn(name, **kwargs):
""" create button with default settings """
return sg.Button(name, size=(16, 1), font=(sg.DEFAULT_FONT, 12), **kwargs)
def stat(text, width=10, relief=None, justification='left', key=None):
elem = sg.Text(text, size=(width, 1), relief=relief, justification=justification, key=key)
return elem
def main():
""" main program and GUI loop """
sg.ChangeLookAndFeel('BrownBlue')
tab1 = sg.Tab('Raw Code',
[[sg.Multiline(key='INPUT', pad=(0, 0), font=(sg.DEFAULT_FONT, 12))]],
background_color='gray', key='T1')
tab2 = sg.Tab('Clean Code',
[[sg.Multiline(key='OUTPUT', pad=(0, 0), font=(sg.DEFAULT_FONT, 12))]],
background_color='gray25', key='T2')
stat_col = sg.Column([
[stat('Lines of code'), stat(0, 8, 'sunken', 'right', 'LINES'),
stat('Total chars'), stat(0, 8, 'sunken', 'right', 'CHARS')],
[stat('Chars per line'), stat(0, 8, 'sunken', 'right', 'CPL'),
stat('Mean'), stat(0, 8, 'sunken', 'right', 'MEAN')],
[stat('Median'), stat(0, 8, 'sunken', 'right', 'MEDIAN'),
stat('PStDev'), stat(0, 8, 'sunken', 'right', 'PSTDEV')],
[stat('Max'), stat(0, 8, 'sunken', 'right', 'MAX'),
stat('Min'), stat(0, 8, 'sunken', 'right', 'MIN')]], pad=(5, 10), key='STATS')
lf_col = [
[btn('Load FILE'), btn('Clipboard'), btn('RESET')],
[sg.TabGroup([[tab1, tab2]], title_color='black', key='TABGROUP')]]
rt_col = [
[sg.Text('LOAD a file or PASTE code from Clipboard', pad=(5, 15))],
[sg.Text('Statistics', size=(20, 1), pad=((5, 5), (15, 5)),
font=(sg.DEFAULT_FONT, 14, 'bold'), justification='center')],
[stat_col],
[sg.Text('Visualization', size=(20, 1),
font=(sg.DEFAULT_FONT, 14, 'bold'), justification='center')],
[sg.Canvas(key='IMG')]]
layout = [[sg.Column(lf_col, element_justification='left', pad=(0, 10), key='LCOL'),
sg.Column(rt_col, element_justification='center', key='RCOL')]]
window = sg.Window('Code Counter', layout, resizable=True, size=WINDOW_SIZE, finalize=True)
for elem in ['INPUT', 'OUTPUT', 'LCOL', 'TABGROUP']:
window[elem].expand(expand_x=True, expand_y=True)
# main event loop
while True:
event, values = window.read()
if event is None:
break
if event == 'Load FILE':
click_file(window)
process_data(window)
if event == 'Clipboard':
click_clipboard(window)
process_data(window)
if event == 'RESET':
click_reset(window)
if __name__ == '__main__':
main()