Last active
June 5, 2019 20:30
-
-
Save victor-iyi/015915e7c68d7475fd1fea5db7ae9403 to your computer and use it in GitHub Desktop.
Improved estimation of how many lines of codes are in a project (using recursion)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"""Improved estimation of how many lines of codes are in a project (using recursion) | |
@author | |
Victor I. Afolabi | |
Director of AI, NioCraft, 115Garage. | |
Email: [email protected] | |
GitHub: https://github.com/victor-iyiola | |
@project | |
File: lines-of-code.py | |
Created on 5th June, 2019 @08:59 PM. | |
@license | |
MIT License | |
Copyright (c) 2019. Victor I. Afolabi. All rights reserved. | |
""" | |
import os | |
class ProjectAnalytics(object): | |
"""Analyse the files, folders and how many lines of code are in your project. | |
Methods: | |
def __init__(self, project_root: str): | |
def count_lines_of_code(self, file: str) -> int: | |
# Count how many lines of codes are in a given file. | |
def get_extension(self, file: str) -> str: | |
# Returns the extension name of a given file path. | |
def ignore(self, files: List[str]) -> List[str]: | |
# Filter ignored files, folders and extensions. | |
def organize(self, files: List[str]) -> None: | |
# Recursively visit folders if they exists. | |
def start(self, directory: str) -> None: | |
# List all contents in given directory and organises them | |
# into files & folders. | |
Attributes: | |
files (List[str]): List of files in the project. | |
folders (List[str]): List of folders in the project. | |
lines (int): Total count of lines of codes in the project. | |
n_files (int): Total number of files in the project. | |
n_folders (int): Total number of folders in the project. | |
Raises: | |
FileNotFoundError: `project_root` was not found. | |
""" | |
# Files, Folders & File-extensions to be ignored. | |
IGNORED = { | |
# Files to be ignored. | |
'files': ['package-lock.json', 'bootstrap.min.css', 'font-awesome.min.css'], | |
# Folders to be ignored. | |
'folders': ['node_modules', 'uploads', 'fonts', 'dataTables'], | |
# File-extensions to be ignored. | |
'extensions': ['pdf', 'txt', 'md', 'ini', 'gif', 'jpg', 'png', 'jpeg'], | |
} | |
def __init__(self, project_root: str): | |
if not os.path.exists(project_root): | |
raise FileNotFoundError('{} was not found'.format(project_root)) | |
self.root = project_root | |
self.files = [] | |
self.folders = [] | |
self.lines = 0 | |
# Start | |
self.start(project_root) | |
# Get file & folder number. | |
self.n_files = len(self.files) | |
self.n_folders = len(self.folders) | |
def __repr__(self): | |
return '{}("{}")'.format(self.__class__.__name__, self.root) | |
def count_lines_of_code(self, file: str): | |
"""Count how many lines of codes are in a given file. | |
Arguments: | |
file (str): Source file to be counted. | |
Returns: | |
int - Number of lines in `file`. | |
""" | |
# Start with 0. | |
with open(file, mode='r', encoding='latin-1') as f: | |
return len(f.readlines()) | |
def get_extension(self, file: str): | |
"""Returns the extension name of a given file path. | |
Arguments: | |
file (AnyStr): Filename or file path. | |
Returns: | |
str - File extension name without trailing ".". | |
""" | |
file_split = os.path.basename(file).split('.') | |
return file_split[-1] if len(file_split) > 1 else '' | |
def ignore(self, files): | |
"""Filter ignored files, folders and extensions. | |
Arguments: | |
files (List[str]): Original list of files and folders | |
(including those to be ignored). | |
Returns: | |
List[str] - Modified/Filtered list of files. | |
""" | |
for f in files: | |
# Get the file basename. | |
base = os.path.basename(f) | |
# Remove files with matching filenames. | |
if base in ProjectAnalytics.IGNORED['files']: | |
files.remove(f) | |
# Remove files with maching folder names. | |
elif base in ProjectAnalytics.IGNORED['folders']: | |
files.remove(f) | |
# Remove files with matching extensions. | |
elif self.get_extension(f) in ProjectAnalytics.IGNORED['extensions']: | |
files.remove(f) | |
# New list of files to keep. | |
return files | |
def organize(self, files: str): | |
"""Recursively visit folders if they exists. | |
Arguments: | |
files (List[str]) - List of file paths. | |
Returns: | |
None | |
""" | |
for file in files: | |
if os.path.isdir(file): | |
self.folders.append(file) | |
self.start(file) # recursive call | |
else: | |
self.files.append(file) | |
self.lines += self.count_lines_of_code(file) | |
def start(self, directory: str): | |
"""List all contents in given directory and organises them into files & folders. | |
Arguments: | |
directory (str): Current directory to list it's contents. | |
Returns: | |
None | |
""" | |
files = [os.path.join(directory, f) | |
for f in os.listdir(directory)] | |
# fileter ignored here... | |
self.ignore(files) | |
self.organize(files) # recursive call | |
if __name__ == '__main__': | |
# Prompt user to input full path to project root. | |
project_dir = input('Enter the full path to the project root directory: ') | |
# Create a new ProjectAnalytics object. | |
analytics = ProjectAnalytics(project_dir) | |
# Log analysis. | |
print('{:,} files'.format(analytics.n_files)) | |
print('{:,} folders'.format(analytics.n_folders)) | |
print('{:,} lines of codes'.format(analytics.lines)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment