| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538 | #!/usr/bin/env python3import pathlibimport typingclass 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 phraseclass 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)
 |