Skip to content

Instantly share code, notes, and snippets.

@victor-iyi
Last active June 5, 2019 20:30
Show Gist options
  • Save victor-iyi/015915e7c68d7475fd1fea5db7ae9403 to your computer and use it in GitHub Desktop.
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)
"""Improved estimation of how many lines of codes are in a project (using recursion)
@author
Victor I. Afolabi
Director of AI, NioCraft, 115Garage.
Email: javafolabi@gmail.com
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