#!/usr/bin/env python3 import pathlib import typing class file: """ This class parse single file, and returns all phrases to translate. This open file, and trying to find all phrases to translate in the file. Process is separated to few steps, first run filter() to get all lines when translate functions exists. Then run parse(), which would get phrases and append it to list. On the end, could get results as list by result() function, which return list with all phrases, or phrases() generator, which iterate on all of them. """ def __init__(self, target: pathlib.Path) -> None: """ It creates new file file parser. Parameters ---------- target : pathlib.Path It is path to the file in the filesystem to parse. Raises ------ TypeError When target is not afile or not exists. """ if not target.is_file(): raise TypeError("Target \"" + str(target) + "\" not exists.") self.__to_parse = None self.__phrases = None with target.open() as handle: self.__content = handle.read().split("\n") def __targets(self) -> list: """ This return targets function which libtranslate use to translates. Returns ------- list List with targets to search for in the code. """ return [ "_(", ".translate(", ".tr(", ".phrase ", ".phrase=" ] def __string(self) -> list: """ This returns javascript string openers and closers. Returns ------- list List chars with open and close strings in js. """ return [ '"', "'", "`" ] def __has_phrase(self, line: str) -> bool: """ This check that given line of code has translation instructions. Parameters ---------- line : str Line of code to search for instructions in. Returns ------- bool True when line has translations or False when not. """ for target in self.__targets(): if line.find(target) != -1: return True return False def filter(self) -> object: """ This filter file contents for lines with translation content. This search for translate instructions in all file content. When line contain translation instructions, then it append it to new list, which would be processed in the next step. Returns ------- file Own instanct for chain loading. """ self.__to_parse = list() for line in self.__content: if self.__has_phrase(line): self.__to_parse.append(line) return self def __get_next(self, line: str, start: int) -> int: """ This return next speech of the translation instruction. This function trying to find next speech of any from translation instructions. When it found, then return position of the end of that function, but when could not found anything, then return -1. Parameters ---------- line : str Line to found instruction in. start : int This is position when start to searching for. Returns ------- int Position of the end of found instruction or -1 when not found. """ result = -1 for target in self.__targets(): position = line.find(target, start) if position == -1: continue position = position + len(target) if result == -1: result = position continue if result > position: result = position return result def __get_string_pos(self, line: str, start: int) -> int: """ This return next position of the string in the line. This trying to search string in the line, when found it, then return it position, or return -1 when not found anything. It return position of the start string char, in opposition to __get_next. Parameters ---------- line : str Line to search for string in. start : int Position to start searching in. Returns ------- int Position of the string, or -1. """ for target in self.__string(): position = line.find(target, start) if position == -1: continue return position return -1 def __cut_text(self, line: str, position: int) -> str|None: """ This get string from given line next to given position. This trying to find string next to given position. When found it, then get it, and return its content. When not found anything, then return None. Parameters ---------- line : str Line to search for string in. position : int Position to search for string on. Returns ------- str Content of the found string. None When not found any string. """ start = self.__get_string_pos(line, position) if start == -1: return None char = line[start] start = start + 1 end = line.find(char, start) if end == -1: return None return line[start:end] def __parse_line(self, line: str) -> typing.Iterator[str]: """ This parse single line, and return generator with found phrases. Parameters ---------- line : str Line to parse. Returns ------- Iterator[str] All found phrases in the line. """ current = 0 while True: current = self.__get_next(line, current) if current == -1: break text = self.__cut_text(line, current) if text is None: continue current = current + len(text) yield text def parse(self) -> object: """ This parse all lines in the file. Returns ------- file Own instance to chain loading. """ if self.__to_parse is None: raise RuntimeError("Run filter() first.") self.__phrases = list() for line in self.__to_parse: for count in self.__parse_line(line): if not count in self.__phrases: self.__phrases.append(count) return self def result(self) -> list: """ This return all founded phrases as list. Raises ------ RuntimeError When not run parse() before. Returns ------- list List with all found phrases. """ if self.__phrases is None: raise RuntimeError("Run parse() first.") return self.__phrases def phrases(self) ->typing.Iterator[str]: """ This returns generator with all phrases. Raises ------ RuntimeError When not run parse() before. Returns ------- Iterator[str] Generator with all phrases. """ for phrase in self.result(): yield phrase class directory: """ This open all Javascript files in the directory and search phrases. This trying to open all Javascript files and all Javascript in the subdirectories. Then parsing all that files, and adding phrases from its to the list. On the end that phrases could be returned as list, or loaded from Iterator. """ def __init__(self, target: pathlib.Path) -> None: """ This create new directory instance. Raises ------ TypeError When target path is not directory. Parameters ---------- target : pathlib.Path Directory to work in. """ if not target.is_dir(): raise TypeError("Target \"" + str(target) + "\" is not dir.") self.__target = target self.__phrases = list() def __append(self, phrases: list) -> None: """ This append new phrase to phrases list. Parameters ---------- phrases : list List of phrases to add. """ for phrase in phrases: if phrase in self.__phrases: continue self.__phrases.append(phrase) def process(self) -> object: """ This process given directory. Returns ------- directory Own instance to chain loading. """ self.__process_directory(self.__target) return self def result(self) -> list: """ This return list with result. Returns ------- list List with phrases from the files. """ return self.__phrases def phrases(self) -> typing.Iterator[str]: """ This return all phrases as iterator. Returns ------- Iterator[str] All phrases from files. """ for phrase in self.__phrases: yield phrase def __js_extensions(self) -> list: """ This return all extensions for js files. Returns ------- list All js files extensions. """ return [ "js", "mjs", "ts" ] def __process_directory(self, directory: pathlib.Path) -> None: """ This process given directory. This process given directory, when in directory exists any diretory, then it would be processed by that function recursive. When found file, then parse it, and add phrases from it to the list. Parameters ---------- directory : pathlib.Path Directory to work on """ for count in directory.iterdir(): if count.is_dir(): self.__process_directory(count) continue if count.is_file(): self.__process_file(count) continue def __process_file(self, target: pathlib.Path) -> None: """ This process single file. This process single file. When file is not Javasocript file, then ti skip it, but when file is Javascript source code, then it trying to extract all phrases from it and adds it to phrases list. Parameters ---------- target : pathlib.Path Target file to process. """ suffix = target.suffix[1:] if not suffix in self.__js_extensions(): return self.__append(file(target).filter().parse().result()) class dictionary: """ This create new sample dictionary with phrases. This class create sample dictionary file from phrases list. It could return result as string, or save it to file. """ def __init__(self, phrases: list) -> None: """ This create new dictionary instance. Parameters ---------- phrases : list List of phrases to prepare dictionary from. """ self.__phrases = phrases def __process_single_phrase(self, phrase: str) -> str: """ This process single phrase to line in the dictionary. Parameters ---------- phrase : str Phrase to make line from. Returns ------- str Phrase as line in dictionary. """ return (" " * 4) + "\"" + phrase + "\": \"\"" @property def result(self) -> str: """ It process all phrases to the dictionary file. Returns ------- str Parsed dictionary. """ lines = [] start = "{\n" stop = "\n}\n" for phrase in self.__phrases: lines.append(self.__process_single_phrase(phrase)) return start + str(",\n").join(lines) + stop def write(self, target: pathlib.Path) -> None: """ It write dictionary to the file. Parameters ---------- target : pathlib.Path Target file to write dictionary into. """ with target.open("w+") as handle: handle.write(self.result) if __name__ == "__main__": import argparse parser = argparse.ArgumentParser( description = "This script helps to create phrasebook from scripts." ) parser.add_argument( "source", type = pathlib.Path, help = "This is source file or directory to parse from." ) parser.add_argument( "--output", type = pathlib.Path, default = "output.json", help = "This is output phrasebook file, to save into." ) arguments = parser.parse_args() if not arguments.source.exists(): print("Source \"" + str(arguments.source) + "\" not exists.") exit(127) if arguments.source.is_file(): target = file(arguments.source).filter().parse().result() elif arguments.source.is_dir(): target = directory(arguments.source).process().result() else: print("Source is not file or directory.") exit(126) try: dictionary(target).write(arguments.output) print("Processed successfull.") print("Processed: " + str(len(target)) + " phrases.") except Exception as error: print(str(error)) print("Can not save \"" + str(arguments.output) + "\" output file.") exit(125)