diff --git a/plotter/__main__.py b/plotter/__main__.py index c44222a..11a3c7d 100644 --- a/plotter/__main__.py +++ b/plotter/__main__.py @@ -15,48 +15,67 @@ FLAGS = flags.FLAGS +flags.DEFINE_string("repository", None, + "The repository to execute this script on") flags.DEFINE_string( - 'repository', None, 'The repository to execute this script on') + "input_file", + None, + "Specifies a custom json file to feed the script. It must has been generated during an old execution", +) +flags.DEFINE_string("branch", None, "Select a custom branch of the repository") +flags.DEFINE_bool( + "no_preprocessing", + False, + "Doesn't preprocess data. The script will plot all the commits", +) +flags.DEFINE_bool( + "offline", + False, + "Specify if it should use the current repository in the folder defined with `--dir` of it has to clone a new repository", +) +flags.DEFINE_bool( + "write_output", + False, + "Specifies wether the script has to write the intermediate results to output", +) flags.DEFINE_string( - 'input_file', None, "Specifies a custom json file to feed the script. It must has been generated during an old execution") + "output_folder", + None, + "Specifies the folder where the script has to write the output files", +) flags.DEFINE_string( - 'branch', None, 'Select a custom branch of the repository') -flags.DEFINE_bool('no_preprocessing', False, - 'Doesn\'t preprocess data. The script will plot all the commits') -flags.DEFINE_bool('offline', False, - 'Specify if it should use the current repository in the folder defined with `--dir` of it has to clone a new repository') -flags.DEFINE_bool('write_output', False, - 'Specifies wether the script has to write the intermediate results to output') -flags.DEFINE_string('output_folder', None, - 'Specifies the folder where the script has to write the output files') -flags.DEFINE_string( - 'dir', '.repo', 'Specifies the temporary directory to use to store the repository defined with the flag --repository') + "dir", + ".repo", + "Specifies the temporary directory to use to store the repository defined with the flag --repository", +) def command_exists(command_name: str) -> bool: """ - Checks if `command_name` is a command present in this env + Checks if `command_name` is a command present in this env """ from shutil import which + return which(command_name) is not None def get_commits(folder: str) -> typing.List[str]: """ - Gets all the commits done on the repository's branch `FLAGS.branch` contained in `folder` + Gets all the commits done on the repository's branch `FLAGS.branch` contained in `folder` - At each commit it runs the `cloc` utility to retrieve all data + At each commit it runs the `cloc` utility to retrieve all data """ os.chdir(folder) command = r'git.--no-pager.log.--pretty=format:"%H %ad".--date=format:"%F %H:%m:%S"' p = subprocess.Popen(command.split("."), - stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) lines = p.stdout.readlines() commits = [] for index in progressbar.progressbar(range(len(lines))): line = lines[index] - commit = Commit(line.decode('utf-8').strip()) + commit = Commit(line.decode("utf-8").strip()) commit.checkout_and_get_data() commits.append(commit) @@ -66,7 +85,7 @@ def get_commits(folder: str) -> typing.List[str]: def generate_repo_history(temporary_dir: str) -> RepoHistory: """ - Generates a `RepoHistory` object if `FLAGS.input_file` is not specified + Generates a `RepoHistory` object if `FLAGS.input_file` is not specified """ if not FLAGS.offline: repository = FLAGS.repository @@ -80,27 +99,32 @@ def generate_repo_history(temporary_dir: str) -> RepoHistory: os.mkdir(temporary_dir) try: print("Cloning repository...") - subprocess.call(['git', 'clone', repository, - temporary_dir, '--quiet']) + subprocess.call( + ["git", "clone", repository, temporary_dir, "--quiet"]) except: print("Failed to clone repository") exit(-2) else: if not os.path.exists(temporary_dir): - print("You can't use --offline flag on a non-existent --dir directory") + print( + "You can't use --offline flag on a non-existent --dir directory" + ) exit(-1) if FLAGS.branch is not None: os.chdir(temporary_dir) - p = subprocess.Popen(['git', 'checkout', FLAGS.branch], - stdout=subprocess.PIPE, stderr=subprocess.PIPE) + p = subprocess.Popen( + ["git", "checkout", FLAGS.branch], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) error = list(p.stderr.readlines()) if len(error) > 0: - print("ERROR:", error[0].decode('utf-8').strip()) + print("ERROR:", error[0].decode("utf-8").strip()) print("Continuing anyway...") else: print(f"Changed branch to {FLAGS.branch}") - os.chdir('..') + os.chdir("..") print("Getting commit data") commits = get_commits(temporary_dir) @@ -119,22 +143,26 @@ def plot_data(repo_history: RepoHistory): print(f"- {count}: {lang}") langs[count] = lang count += 1 - langs[count] = 'All' + langs[count] = "All" print(f"- {count}: the sum of all them") retry = True while retry: retry = False try: - languages_to_plot = [int(index) for index in input( - "Which one you want to pick?\n(you can select how many languages you want, just comma-separated '1,2,3')\n").split(",")] + languages_to_plot = [ + int(index) for index in input( + "Which one you want to pick?\n(you can select how many languages you want, just comma-separated '1,2,3')\n" + ).split(",") + ] for item in languages_to_plot: if int(item) > count: print(f"item {item} is not valid") retry = True except: print( - f"The selection must be done with the indexes between 0 and {count}") + f"The selection must be done with the indexes between 0 and {count}" + ) retry = True print(f"Should plot {[langs[index] for index in languages_to_plot]}") @@ -142,7 +170,7 @@ def plot_data(repo_history: RepoHistory): plotter = Plotter(repo_history) plotter.plot([langs[index] for index in languages_to_plot]) should_plot = input("Plot again? Y/n: ") - if should_plot.strip() == 'n': + if should_plot.strip() == "n": exit(1) @@ -154,7 +182,8 @@ def main(args): if write_output: if FLAGS.output_folder is None: print( - 'If you specify --write_output you must specify the ouput folder with --output_folder flag') + "If you specify --write_output you must specify the ouput folder with --output_folder flag" + ) exit(-1) output_folder = FLAGS.output_folder if not os.path.exists(output_folder): @@ -165,25 +194,25 @@ def main(args): else: repo_history = generate_repo_history(temporary_dir) if write_output: - with open(f'{output_folder}/repo_history.json', 'w+') as f: + with open(f"{output_folder}/repo_history.json", "w+") as f: json.dump(repo_history.as_map(), f) - print("Preprocessing repository's history") repo_history.preprocess_commits(FLAGS.no_preprocessing) if write_output: - with open(f'{output_folder}/repo_history_preprocessed.json', 'w+') as f: + with open(f"{output_folder}/repo_history_preprocessed.json", + "w+") as f: json.dump(repo_history.as_map(), f) plot_data(repo_history) -if __name__ == '__main__': - if not command_exists('git'): +if __name__ == "__main__": + if not command_exists("git"): print("Missing `git`") exit(-1) - if not command_exists('cloc'): + if not command_exists("cloc"): print("Missing `cloc` utility") exit(-1) diff --git a/plotter/model/src/aggregated.py b/plotter/model/src/aggregated.py index 8a7ba6f..5c67ae5 100644 --- a/plotter/model/src/aggregated.py +++ b/plotter/model/src/aggregated.py @@ -1,25 +1,24 @@ - class AggregatedData(object): """ - Class that contains the aggregated data calculated by `cloc` + Class that contains the aggregated data calculated by `cloc` """ def __init__(self, data: map): - self.blank = data['blank'] - self.comment = data['comment'] - self.code = data['code'] - self.nFiles = data['nFiles'] + self.blank = data["blank"] + self.comment = data["comment"] + self.code = data["code"] + self.nFiles = data["nFiles"] def to_string(self): return f"Total lines of code: {self.code}" def add(self, other, inPlace=True): """ - Sums the values from self with the values of `other` + Sums the values from self with the values of `other` - `other` must be an `AggregatedData` object + `other` must be an `AggregatedData` object - if `inPlace` is set to `False` this function will return a new `AggregatedData` object + if `inPlace` is set to `False` this function will return a new `AggregatedData` object """ if not isinstance(other, AggregatedData): raise Exception("You can only sum another `AggregatedData` object") @@ -30,16 +29,16 @@ def add(self, other, inPlace=True): self.nFiles += other.nFiles else: return AggregatedData({ - 'blank': self.blank + other.blank, - 'comment': self.comment + other.comment, - 'code': self.code + other.code, - 'nFiles': self.nFiles + other.nFiles, + "blank": self.blank + other.blank, + "comment": self.comment + other.comment, + "code": self.code + other.code, + "nFiles": self.nFiles + other.nFiles, }) def as_map(self) -> map: return { - 'blank': self.blank, - 'comment': self.comment, - 'code': self.code, - 'nFiles': self.nFiles, + "blank": self.blank, + "comment": self.comment, + "code": self.code, + "nFiles": self.nFiles, } diff --git a/plotter/model/src/commit.py b/plotter/model/src/commit.py index 10ee072..236d17a 100644 --- a/plotter/model/src/commit.py +++ b/plotter/model/src/commit.py @@ -1,4 +1,3 @@ - import datetime import os import subprocess @@ -10,21 +9,26 @@ class Commit(object): - """ - Class that contains the info regarding a single commit + Class that contains the info regarding a single commit - It contains the `hash` of the commit, its date and its `cloc` data + It contains the `hash` of the commit, its date and its `cloc` data """ def __init__(self, data: str): - commit, date, time = data.replace('"', '').split(" ") + commit, date, time = data.replace('"', "").split(" ") self.commitHash = commit self.squashed_hashes = [] year, month, day = date.split("-") hour, minute, second = time.strip().split(":") self.date = datetime.datetime( - year=int(year), month=int(month), day=int(day), hour=int(hour), minute=int(minute), second=int(second)) + year=int(year), + month=int(month), + day=int(day), + hour=int(hour), + minute=int(minute), + second=int(second), + ) def add_squashed_hash(self, hash: str): self.squashed_hashes.append(hash) @@ -34,7 +38,7 @@ def __set_commit_hash__(self, hash: str): def get_languages(self) -> typing.List[str]: """ - Returns all the languages contained in this commit + Returns all the languages contained in this commit """ return [item.lang for item in self.langData] @@ -49,9 +53,9 @@ def __set_aggregated_data__(self, data: AggregatedData): def __get_data_for_lang__(self, lang: str) -> FileData: """ - Gets the `FileData` object for the language `lang` + Gets the `FileData` object for the language `lang` - If it doesn't exists, returns `None` + If it doesn't exists, returns `None` """ for data in self.langData: if data.lang == lang: @@ -66,17 +70,19 @@ def add_commit_data(self, other_commit): other_languages = [data.lang for data in other_commit.langData] common_languages = set(self_languages).intersection(other_languages) - missing_from_other_languages = set( - other_languages).difference(self_languages) - missing_from_self_languages = set( - self_languages).difference(other_languages) + missing_from_other_languages = set(other_languages).difference( + self_languages) + missing_from_self_languages = set(self_languages).difference( + other_languages) new_lang_data = [] for data in self.langData: # sums the languages fields that are in common between two commits if data.lang in common_languages: - new_lang_data.append(data.add_other_data( - other_commit.__get_data_for_lang__(data.lang), inPlace=False)) + new_lang_data.append( + data.add_other_data(other_commit.__get_data_for_lang__( + data.lang), + inPlace=False)) elif data.lang in missing_from_self_languages: new_lang_data.append(data) @@ -90,9 +96,9 @@ def add_commit_data(self, other_commit): def checkout_and_get_data(self): """ - Moves into `directory` (which is the same of `--dir` flag), checkouts to `self.commit` and gets the data calculated by cloc + Moves into `directory` (which is the same of `--dir` flag), checkouts to `self.commit` and gets the data calculated by cloc - Sets `self.langData` and `self.aggregated` to the values calculated + Sets `self.langData` and `self.aggregated` to the values calculated """ self.langData, self.aggregated = get_cloc_data_from_commit( self.commitHash) @@ -102,21 +108,21 @@ def to_string(self): def as_map(self) -> map: """ - Returns a map that represents this object + Returns a map that represents this object """ return { - 'hash': self.commitHash, - 'date': f"{self.date.year}-{self.date.month}-{self.date.day}", - 'time': f"{self.date.hour}:{self.date.minute}:{self.date.second}", - 'aggregated': self.aggregated.as_map(), - 'squashed_hashes': self.squashed_hashes, - 'fileData': [item.as_map() for item in self.langData] + "hash": self.commitHash, + "date": f"{self.date.year}-{self.date.month}-{self.date.day}", + "time": f"{self.date.hour}:{self.date.minute}:{self.date.second}", + "aggregated": self.aggregated.as_map(), + "squashed_hashes": self.squashed_hashes, + "fileData": [item.as_map() for item in self.langData], } def parse_commit(data: map) -> Commit: commit = Commit(f"{data['hash']} {data['date']} {data['time']}") - commit.__set_aggregated_data__(AggregatedData(data['aggregated'])) - fileData = [FileData(item) for item in data['fileData']] + commit.__set_aggregated_data__(AggregatedData(data["aggregated"])) + fileData = [FileData(item) for item in data["fileData"]] commit.__set_file_data__(fileData) return commit diff --git a/plotter/model/src/filedata.py b/plotter/model/src/filedata.py index 543ea59..3c3ab47 100644 --- a/plotter/model/src/filedata.py +++ b/plotter/model/src/filedata.py @@ -3,31 +3,31 @@ class FileData(object): """ - Class that contains the data for the single language calculated by `cloc` + Class that contains the data for the single language calculated by `cloc` """ def __init__(self, data: map, lang=None): if lang is None: - if 'lang' not in data: + if "lang" not in data: print("Missing `lang` field in data:", json.dumps(data)) exit(-1) else: - self.lang = data['lang'] + self.lang = data["lang"] else: self.lang = lang - self.numFiles = data['nFiles'] - self.blank = data['blank'] - self.comment = data['comment'] - self.code = data['code'] + self.numFiles = data["nFiles"] + self.blank = data["blank"] + self.comment = data["comment"] + self.code = data["code"] def get_field(self, field: str) -> int: - if field == 'code': + if field == "code": return self.code elif field == "nFiles": return self.numFiles - elif field == 'blank': + elif field == "blank": return self.blank - elif field == 'comment': + elif field == "comment": return self.comment def to_string(self): @@ -35,18 +35,19 @@ def to_string(self): def add_other_data(self, other_data, inPlace=True): """ - Adds another `FileData` object to `self` + Adds another `FileData` object to `self` - `self.lang` and `other_data.lang` must have the same value + `self.lang` and `other_data.lang` must have the same value - if `inPlace` is `True`, this call will modify the internal state of `FileData`, - otherwise it will generate a new `FileData` object + if `inPlace` is `True`, this call will modify the internal state of `FileData`, + otherwise it will generate a new `FileData` object """ if not isinstance(other_data, FileData): raise Exception("You can sum only two FileData objects") if other_data.lang != self.lang: raise Exception( - "It doesn't make sense to sum two FileData that are on different languages") + "It doesn't make sense to sum two FileData that are on different languages" + ) if inPlace: self.numFiles += other_data.numFiles self.blank += other_data.blank @@ -54,18 +55,18 @@ def add_other_data(self, other_data, inPlace=True): self.code += other_data.code else: return FileData({ - 'lang': self.lang, - 'nFiles': self.numFiles + other_data.numFiles, - 'blank': self.blank + other_data.blank, - 'comment': self.comment + other_data.comment, - 'code': self.code + other_data.code, + "lang": self.lang, + "nFiles": self.numFiles + other_data.numFiles, + "blank": self.blank + other_data.blank, + "comment": self.comment + other_data.comment, + "code": self.code + other_data.code, }) def as_map(self) -> map: return { - 'lang': self.lang, - 'nFiles': self.numFiles, - 'blank': self.blank, - 'comment': self.comment, - 'code': self.code + "lang": self.lang, + "nFiles": self.numFiles, + "blank": self.blank, + "comment": self.comment, + "code": self.code, } diff --git a/plotter/model/src/repo_history.py b/plotter/model/src/repo_history.py index fb2e1d6..e7bb5f7 100644 --- a/plotter/model/src/repo_history.py +++ b/plotter/model/src/repo_history.py @@ -4,6 +4,7 @@ import typing import numpy as np + from plotter.model.src import filedata from plotter.model.src.aggregated import AggregatedData from plotter.model.src.filedata import FileData @@ -14,10 +15,12 @@ class RepoHistory(object): """ - Contains all the history of the repository + Contains all the history of the repository """ - def __init__(self, commits=[]): + def __init__(self, commits=None): + if commits is None: + commits = [] self.commits = commits self.initialDate = None self.finalDate = None @@ -42,26 +45,28 @@ def __set_commits__(self, commits: typing.List[Commit]): def as_map(self): if self.initialDate != None and self.finalDate != None: return { - 'initialDate': f"{self.initialDate.year}-{self.initialDate.month}-{self.initialDate.day}", - 'finalDate': f"{self.finalDate.year}-{self.finalDate.month}-{self.finalDate.day}", - 'languages': self.languages, - 'preprocessed': self.preprocessed, - 'commits': [commit.as_map() for commit in self.commits], + "initialDate": + f"{self.initialDate.year}-{self.initialDate.month}-{self.initialDate.day}", + "finalDate": + f"{self.finalDate.year}-{self.finalDate.month}-{self.finalDate.day}", + "languages": self.languages, + "preprocessed": self.preprocessed, + "commits": [commit.as_map() for commit in self.commits], } else: return { - 'initialDate': self.initialDate, - 'finalDate': self.finalDate, - 'preprocessed': self.preprocessed, - 'commits': [commit.as_map() for commit in self.commits], + "initialDate": self.initialDate, + "finalDate": self.finalDate, + "preprocessed": self.preprocessed, + "commits": [commit.as_map() for commit in self.commits], } def get_commit_dates(self) -> typing.List[datetime.datetime]: """ - if `self.preprocessed` is `True` - Returns all the dates of the preprocessed commits - else: - Returns a list of integers + if `self.preprocessed` is `True` + Returns all the dates of the preprocessed commits + else: + Returns a list of integers """ print("Getting preprocessed data:", self.preprocessed) if self.preprocessed: @@ -69,13 +74,17 @@ def get_commit_dates(self) -> typing.List[datetime.datetime]: else: return list(range(len(self.commits))) - def get_commit_data(self, languages=[], field='code') -> typing.List[typing.List[FileData]]: + def get_commit_data(self, + languages=None, + field="code") -> typing.List[typing.List[FileData]]: """ returns a list of list of FileData First level has the same dimension of parameter `languages` and each index refers the the language in the same index in `languages` """ + if languages is None: + languages = [] to_return = [[] for _ in languages] for commit in self.commits: for data in commit.langData: @@ -86,14 +95,14 @@ def get_commit_data(self, languages=[], field='code') -> typing.List[typing.List def preprocess_commits(self, not_preprocessing: bool): """ - Prepares the repo history to be plotted + Prepares the repo history to be plotted - If not_preprocessing is `False` it will: - - squash the commits done on same days on a single fake commit that sums all the stats + If not_preprocessing is `False` it will: + - squash the commits done on same days on a single fake commit that sums all the stats - In very case it will: - - calculate the initial and final dates - - get all languages used in the commits + In very case it will: + - calculate the initial and final dates + - get all languages used in the commits """ commits = self.commits starting_commits = len(self.commits) @@ -116,7 +125,8 @@ def preprocess_commits(self, not_preprocessing: bool): new_commits.append(temp_commits[-1]) print( - f"Reduced from {starting_commits} to {len(new_commits)} total commits.\nThe plot will start in {self.initialDate} and will end on {self.finalDate}") + f"Reduced from {starting_commits} to {len(new_commits)} total commits.\nThe plot will start in {self.initialDate} and will end on {self.finalDate}" + ) self.commits = new_commits self.preprocessed = True self.languages = flatten_list( @@ -129,15 +139,15 @@ def parse_repo_history(input_file: str) -> RepoHistory: print(f"File {input_file} doesn't exists") exit(-1) - with open(input_file, 'r') as f: + with open(input_file, "r") as f: data = json.loads(f.read()) repo_history = RepoHistory() - repo_history.__set_initial_date__(data['initialDate']) - repo_history.__set_final_date__(data['finalDate']) - repo_history.__set_preprocessed__(data['preprocessed']) + repo_history.__set_initial_date__(data["initialDate"]) + repo_history.__set_final_date__(data["finalDate"]) + repo_history.__set_preprocessed__(data["preprocessed"]) - commits = data['commits'] + commits = data["commits"] parsed_commits = [parse_commit(item) for item in commits] repo_history.__set_commits__(parsed_commits) diff --git a/plotter/plot.py b/plotter/plot.py index 553774e..687b8ec 100644 --- a/plotter/plot.py +++ b/plotter/plot.py @@ -1,4 +1,5 @@ from datetime import datetime + import matplotlib.pyplot as plt from .model.src.repo_history import RepoHistory @@ -13,16 +14,23 @@ def __init__(self, repo_history): def plot(self, languages=[]): """ - Plots the `RepoHistory` object + Plots the `RepoHistory` object - if `languages` is empty, it will plot the aggregated data + if `languages` is empty, it will plot the aggregated data """ x_values = [ - f"{date.day}/{date.month}/{date.year}" if isinstance(date, datetime) else date for date in self.repo_history.get_commit_dates()] + f"{date.day}/{date.month}/{date.year}" if isinstance( + date, datetime) else date + for date in self.repo_history.get_commit_dates() + ] print("Which field you want to plot?") - available_fields = [(0, 'code', 'Lines of code'), (1, 'nFiles', 'Number of files'), - (2, 'blank', 'Blank lines'), (3, 'comment', 'Comment lines')] + available_fields = [ + (0, "code", "Lines of code"), + (1, "nFiles", "Number of files"), + (2, "blank", "Blank lines"), + (3, "comment", "Comment lines"), + ] ok = False while not ok: ok = True @@ -35,8 +43,10 @@ def plot(self, languages=[]): ok = False print(f"Field {field} is not available") - field, for_label = available_fields[int( - field)][1], available_fields[int(field)][2] + field, for_label = ( + available_fields[int(field)][1], + available_fields[int(field)][2], + ) y_values = self.repo_history.get_commit_data(languages, field=field) @@ -49,8 +59,13 @@ def plot(self, languages=[]): while len(values) != len(x_values): values.append(value_to_repeat) - plt.plot(x_values, values, linestyle='dashed', marker='s', - label=languages[y_value_index]) + plt.plot( + x_values, + values, + linestyle="dashed", + marker="s", + label=languages[y_value_index], + ) plt.legend() if isinstance(x_values[0], datetime): diff --git a/plotter/utilities.py b/plotter/utilities.py index 667d04f..07c10a8 100644 --- a/plotter/utilities.py +++ b/plotter/utilities.py @@ -6,62 +6,69 @@ from .model.src.filedata import FileData -def get_cloc_data(folder: str) -> typing.Tuple[typing.List[FileData], AggregatedData]: +def get_cloc_data( + folder: str) -> typing.Tuple[typing.List[FileData], AggregatedData]: """ - Get the data of `folder` using the `cloc` utility + Get the data of `folder` using the `cloc` utility - Returns the list of languages contained in the repository + Returns the list of languages contained in the repository """ - p = subprocess.Popen(['cloc', folder, '--json'], - stdout=subprocess.PIPE, stderr=subprocess.PIPE) + p = subprocess.Popen(["cloc", folder, "--json"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) output_lines = p.stdout.readlines() output = "" for line in output_lines: - output += line.decode('utf-8') + output += line.decode("utf-8") if len(output) == 0: print("Output was empty") return (None, None) output = json.loads(output) - languages = [lang for lang in output if lang != 'header'] + languages = [lang for lang in output if lang != "header"] data = [] aggregated = None for lang in languages: - if lang == 'SUM': + if lang == "SUM": aggregated = AggregatedData(output[lang]) else: data.append(FileData(output[lang], lang=lang)) return (data, aggregated) -def get_cloc_data_from_commit(commit_id: str) -> typing.Tuple[typing.List[FileData], AggregatedData]: +def get_cloc_data_from_commit( + commit_id: str, ) -> typing.Tuple[typing.List[FileData], AggregatedData]: """ - Get the data of `folder` using the `cloc` utility + Get the data of `folder` using the `cloc` utility - Returns the list of languages contained in the repository + Returns the list of languages contained in the repository """ - p = subprocess.Popen(['cloc', commit_id, '--json'], - stdout=subprocess.PIPE, stderr=subprocess.PIPE) + p = subprocess.Popen(["cloc", commit_id, "--json"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) output_lines = p.stdout.readlines() output = "" for line in output_lines: - output += line.decode('utf-8') + output += line.decode("utf-8") if len(output) == 0: - return ([], AggregatedData({ - 'blank': 0, - 'comment': 0, - 'code': 0, - 'nFiles': 0, - })) + return ( + [], + AggregatedData({ + "blank": 0, + "comment": 0, + "code": 0, + "nFiles": 0, + }), + ) output = json.loads(output) - languages = [lang for lang in output if lang != 'header'] + languages = [lang for lang in output if lang != "header"] data = [] aggregated = None for lang in languages: - if lang == 'SUM': + if lang == "SUM": aggregated = AggregatedData(output[lang]) else: data.append(FileData(output[lang], lang=lang))