diff --git a/PySimpleGUI.py b/PySimpleGUI.py index 3ef7fd66..3460ffb1 100644 --- a/PySimpleGUI.py +++ b/PySimpleGUI.py @@ -1,6 +1,6 @@ #!/usr/bin/python3 -version = __version__ = "4.12.0.2 Unreleased - Element.expand added expand_row parm, spin - defaults to first entry if none specified" +version = __version__ = "4.12.0.3 Unreleased - Element.expand added expand_row parm, spin - defaults to first entry if none specified, use math.floor to convert in Graph element" port = 'PySimpleGUI' @@ -123,6 +123,7 @@ import inspect # from typing import List, Any, Union, Tuple, Dict # because this code has to run on 2.7 can't use real type hints. Must do typing only in comments from random import randint import warnings +from math import floor warnings.simplefilter('always', UserWarning) @@ -3039,7 +3040,7 @@ class Graph(Element): if self.FloatValues: return new_x, new_y else: - return int(new_x), int(new_y) + return floor(new_x), floor(new_y) def DrawLine(self, point_from, point_to, color='black', width=1): """ diff --git a/UserCreatedPrograms/Code Counter.py b/UserCreatedPrograms/Code Counter.py new file mode 100644 index 00000000..e9ace52b --- /dev/null +++ b/UserCreatedPrograms/Code Counter.py @@ -0,0 +1,250 @@ +""" + 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() \ No newline at end of file