#!/usr/bin/python3
# Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
# For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
import collections
import os
import os.path as path
import argparse
import json
import sys
import logging
import textwrap
logging.basicConfig(level=logging.DEBUG, format='%(levelname)s: %(message)s')
logger = logging.getLogger(__name__)
handler = logging.StreamHandler(sys.stdout)
logger.addHandler(handler)
def detect_duplicate_keys(list_of_pairs: list):
key_count = collections.Counter(k for k, v in list_of_pairs)
duplicate_keys = ', '.join(k for k, v in key_count.items() if v > 1)
if duplicate_keys:
raise ValueError(duplicate_keys)
def validate_data(list_of_pairs: list):
detect_duplicate_keys(list_of_pairs)
# More detection, each of them will raise exception upon invalid
# data
return dict(list_of_pairs)
def perform_on_files_from_path(json_path: path, operation):
dir_list = os.listdir(json_path)
ret = 0
for file in dir_list:
file_path = path.join(json_path, file)
with open(file_path) as json_file:
ret |= operation(file_path, json_file)
return ret
def check_duplicates(file_path: path, json_file):
try:
_ = json.load(json_file, object_pairs_hook=validate_data)
except ValueError as e:
dup_list = str(e).split(',')
logger.warning(f"[{path.basename(file_path)}]: duplicate {len(dup_list)}: {dup_list}")
return 1
return 0
def check_empty_entries(file_path: path, json_file):
json_data = json.load(json_file)
empty_entries = [entry for entry in json_data if len(str(json_data[entry])) == 0]
if empty_entries:
logger.warning(f"[{path.basename(file_path)}]: empty entries {len(empty_entries)}: {empty_entries}")
return 1
return 0
def get_all_keys_from_path(json_path: path):
dir_list = os.listdir(json_path)
json_keys = []
# iterate to get all possible keys and check for key duplicates
for file in dir_list:
file_path = path.join(json_path, file)
with open(file_path) as json_file:
json_data = json.load(json_file)
json_keys.append(set(json_data))
return set.union(*json_keys)
def check_missing_entries_from_path(json_path: path):
ret = 0
dir_list = os.listdir(json_path)
all_keys = get_all_keys_from_path(json_path)
# iterate to find missing keys
for file in dir_list:
file_path = path.join(json_path, file)
with open(file_path) as json_file:
json_data = json.load(json_file)
missing_keys_in_file = all_keys.difference(set(json_data))
if missing_keys_in_file:
logger.warning(f"[{file}]: missing {len(missing_keys_in_file)}: {sorted(missing_keys_in_file)}")
ret |= 1
return ret
def fix_jsons(json_src_path: path, json_dst_path: path):
dir_list = os.listdir(json_src_path)
for file in dir_list:
src_file_path = path.join(json_src_path, file)
dst_file_path = path.join(json_dst_path, file)
if not path.exists(json_dst_path):
os.makedirs(json_dst_path)
with open(src_file_path) as json_file, open(dst_file_path, 'w') as outfile:
json_data = json.load(json_file)
json.dump(json_data, outfile, indent=4, sort_keys=True)
logger.info("Translation files fixed")
def main(args):
ret = 0
if args.fix:
fix_jsons(args.src, args.dst)
ret |= check_missing_entries_from_path(args.src)
ret |= perform_on_files_from_path(args.src, check_empty_entries)
ret |= perform_on_files_from_path(args.src, check_duplicates)
return ret
if __name__ == "__main__":
parser = argparse.ArgumentParser(
prog='verify_translations',
description='Script for checking the inconsistency of lang jsons',
formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('-s', '--src', metavar='path', help="source path to the json files", required=True)
parser.add_argument('--fix', action='store_true', help=textwrap.dedent('''\
fix the translation files: remove duplicates and sort
WARNING! this will overwrite your destination files!
Use with caution!'''))
parser.add_argument('-d', '--dst', metavar='path', help="destination path for the fixed json files")
parser.add_argument('-v', '--verbose', action='store_true')
args = parser.parse_args()
if args.fix and not args.dst:
parser.error("The destination path must be specified")
sys.exit(1)
if args.verbose:
logger.setLevel(logging.DEBUG)
else:
logger.setLevel(logging.CRITICAL)
error_code = main(args)
if error_code:
logging.error("Verification failed")
sys.exit(error_code)