# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at https://mozilla.org/MPL/2.0/. # # This is a simple script to generate some summary lines-of-code metrics. # Use it like this: # # $> python locsummary.py [./path/to/code/directory] # # It shells out to tokei [1] to do the actual counting, which you must have # installed with the `json` feature. It then massages the tokei output to # give us a rough guesstimate at: # # * How much code is shared code, that can be used on multiple platforms. # * How much of it is android-specific code. # * How much of it is ios-specific code. # # Since we aim to reduce total-cost-of-ownership of our storage and sync # infrastructure by re-using code across platforms, we should hope that # a high percentage of the code we've written is shared rather than # platform-specific! The analysis is far from an exact science, but it # provides a nice gut-check for our code re-use story. # # [1] https://github.com/XAMPPRocky/tokei # import argparse import json import os.path import subprocess ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) DEFAULT_PATH = os.path.join(ROOT_DIR, "components") # For each type of file in our repo, is it: # * shared code across platforms? # * specific to android? # * specific to ios? # * some sort of meta file that we shouldn't count at all? FILETYPE_TO_SUMMARYTYPE = { "CHeader": "ios", "Java": "android", "Json": None, "Kotlin": "android", "Markdown": None, "Prolog": "android", # Our .pro files are actually proguard rules, not prolog... "Protobuf": "shared", "Rust": "shared", "Sql": "shared", "Swift": "ios", "Toml": None, "XcodeConfig": "ios", "Xml": None, } def get_loc_summary(path): path = os.path.abspath(path) p = subprocess.run( [ "tokei", "--output", "json", "--exclude", "examples", path, ], stdout=subprocess.PIPE, text=True, check=False, ) p.check_returncode() summary = { "shared": 0, "android": 0, "ios": 0, "all": 0, } lineOfCode = json.loads(p.stdout)["inner"] for fileType in lineOfCode: summaryType = FILETYPE_TO_SUMMARYTYPE[fileType] if summaryType is not None: summary[summaryType] += lineOfCode[fileType]["code"] summary["all"] += lineOfCode[fileType]["code"] if path.startswith(ROOT_DIR): summary["path"] = os.path.join(".", path[len(ROOT_DIR) + 1 :]) else: summary["path"] = path return summary def print_loc_summaries(paths): summaries = [get_loc_summary(path) for path in paths] headers = ["Path", "Shared", "Android", "iOS", "Total", "Shared %"] nameWidth = max( # noqa: PLW3301 len(headers[0]), max(len(summary["path"]) for summary in summaries) ) numWidth = max( # noqa: PLW3301 max(len(h) for h in headers[1:]), max(len(str(summary["all"])) for summary in summaries), ) totalWidth = (nameWidth + 5) + (numWidth + 3) * 5 - 1 print("-" * totalWidth) print( f"| {headers[0]:<{nameWidth}} | " f"{headers[1]:>{numWidth}} | " f"{headers[2]:>{numWidth}} | " f"{headers[3]:>{numWidth}} | " f"{headers[4]:>{numWidth}} | " f"{headers[5]:>{numWidth}} |" ) print("-" * totalWidth) for summary in summaries: print( f"| {summary['path']:<{nameWidth}} | " f"{summary['shared']:>{numWidth}} | " f"{summary['android']:>{numWidth}} | " f"{summary['ios']:>{numWidth}} | " f"{summary['all']:>{numWidth}} | " f"{(summary['shared'] / summary['all']):>{numWidth}.2%} |" ) print("-" * totalWidth) if __name__ == "__main__": parser = argparse.ArgumentParser(description="summarize lines-of-code statistics") parser.add_argument("paths", type=str, nargs="*", default=[DEFAULT_PATH]) args = parser.parse_args() print_loc_summaries(args.paths)