diff options
Diffstat (limited to 'Master/texmf-dist/source/support/optexcount/counter.py')
-rw-r--r-- | Master/texmf-dist/source/support/optexcount/counter.py | 466 |
1 files changed, 466 insertions, 0 deletions
diff --git a/Master/texmf-dist/source/support/optexcount/counter.py b/Master/texmf-dist/source/support/optexcount/counter.py new file mode 100644 index 00000000000..b5cc9466cb4 --- /dev/null +++ b/Master/texmf-dist/source/support/optexcount/counter.py @@ -0,0 +1,466 @@ +import keywords as kw +import word_iterator as wi +import header as hd +import color_print as cp + + +class Counter: + """ + Class that analyzes a source code and counts word counts + """ + + def __init__(self, filename, color_print=False, verb_char=None): + self.word_iter = wi.WordIterator(filename) # source of words that will be analyzed + if verb_char is not None: + self.word_iter.add_separator(verb_char) + self.verb_char = verb_char # used for inline verbatim text + self.color_print = color_print # input text will be printed(with colors) + self.printer = cp.Printer() + self.regular_words_count = 0 + self.header_words_count = 0 + self.caption_words_count = 0 + self.figure_float_count = 0 + self.math_inline_count = 0 + self.math_count = 0 + self.all_headers = [] + """ + Three possible types of __context: + 1) regular-text (implicit) + 2) headers + 3) captions + """ + self.__context = "regular-text" + + def run(self): + """ + Main method of the class + Loads all words and initializes the analysis + """ + pair = self.word_iter.read() + while pair is not None and pair[0] != "\\bye": + self.__process_word(pair) + pair = self.word_iter.read() + if pair is not None: + self.print_keyword(pair) + self.print_irrelevant_word(("", "\n")) + + def print_result(self): + """ + Generates formatted output of the counter + """ + print("Text words summary: " + str(self.regular_words_count)) + print("Header words summary: " + str(self.header_words_count)) + print("Caption and notes words summary: " + str(self.caption_words_count)) + print("Headers summary: " + str(len(self.all_headers))) + print("Figure/float count: " + str(self.figure_float_count)) + print("Inline math formulae count: " + str(self.math_inline_count)) + print("Math formulae count: " + str(self.math_count)) + print("Subcounts: (header-words-count + text-words-count + caption-words-count)") + for header in self.all_headers: + print(header) + + def print_counted_word(self, pair): + """ + Prints word that was counted. The color depends on category of the word. + """ + if not self.color_print: + return + if self.__context == "regular-text": + self.printer.blue(pair[0]+pair[1]) + elif self.__context == "header": + self.printer.cyan(pair[0]+pair[1]) + elif self.__context == "caption": + self.printer.green(pair[0]+pair[1]) + + def print_keyword(self, pair): + """ + Prints keyword - word with red color + """ + if not self.color_print: + return + self.printer.red(pair[0]+pair[1]) + + def print_irrelevant_word(self, pair): + """ + Prints words that wasn't counted and are not important for our functionality, like commentary words etc... + """ + if not self.color_print: + return + self.printer.white(pair[0]+pair[1]) + + def __process_word(self, pair): + """ + Decides how to treat with word from argument + """ + if len(pair[0]) == 0 or pair[0] == "\n": # empty word or newline + self.print_irrelevant_word(pair) + elif pair[0] == "%": + self.print_irrelevant_word(pair) + self.__skip_commentary() + elif pair[0] == "{": + self.print_irrelevant_word(pair) + self.__load_curly_brackets() + elif pair[0] == "$": + self.print_keyword(pair) + self.__load_inline_formulae() + elif pair[0] == "$$": + self.print_keyword(pair) + self.__load_formulae() + elif self.verb_char is not None and pair[0] == self.verb_char: + self.print_keyword(pair) + self.__load_verbatim(self.verb_char) + elif self.__is_keyword(pair[0]): + self.__process_keyword(pair) + else: + self.__process_text_word(pair) + + def __process_keyword(self, pair): + """ + Treats with keywords. + Calls action on known keywords that are important for the counter + If keyword is unknown it is skipped + """ + word, arg = self.__split_keyword(pair[0]) + self.print_keyword((word, '')) + if arg is None: + self.print_irrelevant_word(('', pair[1])) + else: + self.print_irrelevant_word((arg, pair[1])) + if word == '\\tit': + self.all_headers.append(hd.Header("title")) + self.__load_header() + elif word == '\\chap': + self.all_headers.append(hd.Header("chapter")) + self.__skip_brackets("[", "]") + self.__load_header() + elif word == '\\sec': + self.all_headers.append(hd.Header("section")) + self.__skip_brackets("[", "]") + self.__load_header() + elif word == '\\secc': + self.all_headers.append(hd.Header("subsection")) + self.__skip_brackets("[", "]") + self.__load_header() + elif word == '\\begitems': + self.__load_list() + elif word == '\\caption': + self.__skip_brackets("[", "]") + self.__load_caption() + elif word == '\\fnote' or word == '\\fnotetext' or word == '\\mnote': + self.__load_footnote() + elif word in kw.keywords_list: + if word in kw.floats_keywords: + self.figure_float_count += 1 + self.__read_arguments(word) + elif word == '\\begtt': + self.__skip_commentary() + self.__load_verbatim() + elif word == '\\verbchar' or word == '\\activettchar': # keywords with same functionality + self.__set_verb_char(word, arg, pair[1]) + elif word == '\\code': + self.__load_code_verbatim() + elif word in kw.logos: + self.__load_logo(word, arg, pair[1]) + else: + pass # skip unknown keywords + + def __process_text_word(self, pair): + """ + Increases word counts based on the word location - word context + """ + # word with only one character that is not alphanumeric won't be counted as word + if len(pair[0]) == 0: + self.print_irrelevant_word(pair) + elif len(pair[0]) == 1 and not pair[0].isalnum(): + self.print_irrelevant_word(pair) + elif self.__context == "regular-text": + self.print_counted_word(pair) + self.regular_words_count += 1 + if len(self.all_headers): + self.all_headers[-1].add_text_word() + elif self.__context == "header": + self.print_counted_word(pair) + self.header_words_count += 1 + self.all_headers[-1].add_header_word(pair[0]) + elif self.__context == "caption": + self.print_counted_word(pair) + self.caption_words_count += 1 + if len(self.all_headers): + self.all_headers[-1].add_caption_word() + + @staticmethod + def __is_keyword(word): + """ + Decides whether word is keyword or not + """ + if len(word) >= 3: + if word[0] == '\\' and word[1].isalpha() and word[2].isalpha(): + return True + return False + + @staticmethod + def __split_keyword(word): + """ + Splits word to two part - keyword and its argument + For example: + '\verbchar"' will be splitted into pair: '\verbchar', '"' + """ + for i in range(1, len(word)): + if not word[i].isalpha(): + return word[:i], word[i:] + return word, None + + def __load_header(self): + """ + Loads header + It reads the source code until new line occurs. + In case of ^^J at the end of the line it reads until next new line occurs. + """ + orig_context = self.__context + self.__context = 'header' + pair = self.word_iter.read() + skip_new_line = False + while pair is not None and pair[0] != "\\bye": + if not len(pair[0]): + self.print_irrelevant_word(pair) + elif pair[0] == "\n": + self.print_irrelevant_word(pair) + if not skip_new_line: + self.__context = orig_context + return + skip_new_line = False + elif pair[0] == "^^J": + self.print_irrelevant_word(pair) + skip_new_line = True + else: + self.__process_word(pair) + skip_new_line = False + pair = self.word_iter.read() + self.__context = orig_context + if pair is not None: + self.__process_keyword(pair) + + def __load_list(self): + """ + Loads list - words that are surrounded by '\begitems' and '\enditems' + """ + pair = self.word_iter.read() + while pair is not None and pair[0] != "\\enditems": + if pair[0] != "*": + self.__process_word(pair) + else: + self.print_irrelevant_word(pair) + pair = self.word_iter.read() + if pair is None: + raise Exception("No list ending found - \\enditems") + else: + self.print_keyword(pair) + + def __load_caption(self): + """ + Loads caption - words until EOL + """ + orig_context = self.__context + self.__context = 'caption' + pair = self.word_iter.read() + while pair is not None and pair[0] != "\\bye": + if pair[0] == "\n": + self.print_irrelevant_word(pair) + self.__context = orig_context + return + else: + self.__process_word(pair) + pair = self.word_iter.read() + self.__context = orig_context + if pair is not None: + self.print_keyword(pair) + + def __load_footnote(self): + """ + Loads footnote(block in curly brackets) + """ + orig_context = self.__context + self.__context = 'caption' + pair = self.word_iter.read() + if pair is None: + raise Exception("No opening curly bracket found!") + while len(pair[0]) == 0 or pair[0].isspace(): + self.print_irrelevant_word(pair) + pair = self.word_iter.read() + if pair[0] != "{": + self.__context = orig_context + self.word_iter.push_back(pair) + return + self.print_irrelevant_word(pair) + self.__load_curly_brackets() + self.__context = orig_context + + def __load_formulae(self): + """ + Loads math formulae($$ as separator) + """ + pair = self.word_iter.read() + while pair is not None and pair[0] != "$$": + self.print_irrelevant_word(pair) + pair = self.word_iter.read() + if pair is None: + raise Exception("No end of math formulae found!") + else: + self.print_keyword(pair) + self.math_count += 1 + + def __load_inline_formulae(self): + """ + Loads inline math formulae($ as separator) + """ + pair = self.word_iter.read() + while pair is not None and pair[0] != "$": + self.print_irrelevant_word(pair) + pair = self.word_iter.read() + self.math_inline_count += 1 + if pair is None: + raise Exception("No end of inline math formulae found!") + else: + self.print_keyword(pair) + + def __load_verbatim(self, ending="\\endtt", keyword_print=True): + """ + Reads words until ending(param) word occurs. + These words are processed as regular word(not keywords etc...) + """ + pair = self.word_iter.read() + while pair is not None and ending != pair[0]: + self.__process_text_word(pair) + pair = self.word_iter.read() + if pair is None: + raise Exception("Verbatim text not terminated!") + else: + if keyword_print: + self.print_keyword(pair) + else: + self.print_irrelevant_word(pair) + + def __load_code_verbatim(self): + """ + In case of verbatim using "\code" keyword + """ + pair = self.word_iter.read() + while len(pair[0]) == 0 or pair[0].isspace(): + self.print_irrelevant_word(pair) + pair = self.word_iter.read() + if pair[0] != "{": + raise Exception("\\Code must be followed be opening curly bracket('{')!") + self.print_irrelevant_word(pair) + self.__load_verbatim("}", False) + + def __load_logo(self, logo, arg, sep): + """ + Checks whether logo keyword is followed by slash etc... + """ + if arg != '/' or len(sep) > 0: + pass + elif self.__context == "regular-text": + self.regular_words_count += 1 + if len(self.all_headers): + self.all_headers[-1].add_text_word() + elif self.__context == "header": + self.header_words_count += 1 + self.all_headers[-1].add_header_word(logo) + elif self.__context == "caption": + self.caption_words_count += 1 + if len(self.all_headers): + self.all_headers[-1].add_caption_word() + + def __set_verb_char(self, word, arg, sep): + """ + Loads new character for inline verbatim + """ + if arg is not None and len(arg) == 1: + self.verb_char = arg + self.word_iter.add_separator(arg) + elif arg is None and sep == '': + pair = self.word_iter.read() + if len(pair[0]) != 1: + raise Exception("Invalid use of " + word) + else: + self.print_irrelevant_word(pair) + self.verb_char = pair[0] + self.word_iter.add_separator(pair[0]) + else: + raise Exception("Invalid use of " + word) + + def __read_arguments(self, word): + """ + Loads from keywords file what arguments has got particular keyword from argument + These arguments are skipped - not important for our purpose + """ + params = kw.keywords_list[word] + for p in params: + if p == "O": + pass # this case is managed by '__process_keyword' method + elif p == "W": + self.__obligatory_argument() + elif p == "S": + self.__skip_brackets("[", "]") + elif p == "P": + self.__skip_brackets("(", ")") + elif p == "C": + self.__skip_brackets("{", "}") + else: # unknown specifier - no argument expected + pass + + def __obligatory_argument(self): + """ + Just reads another word. In case of the end of source file Exception is thrown + """ + pair = self.word_iter.read() + if pair is None or pair[0] == "\\bye": + raise Exception("No obligatory argument found") + self.print_irrelevant_word(pair) + return pair + + def __load_curly_brackets(self): + """ + Loads block of source code in curly brackets + """ + pair = self.word_iter.read() + while pair is not None and pair[0] != "}": + self.__process_word(pair) + pair = self.word_iter.read() + if pair is None: + raise Exception("No closing bracket ('}') found.") + else: + self.print_irrelevant_word(pair) + + def __skip_brackets(self, opening, closing): + """ + Skips block of source code in brackets opening-closing + """ + pair = self.word_iter.read() + bracket_count = 0 + while True: + if pair is None: + raise Exception("No closing bracket ('" + closing + "') found.") + elif pair[0] == opening: + bracket_count += 1 + self.print_irrelevant_word(pair) + elif pair[0] == closing: + bracket_count -= 1 + self.print_irrelevant_word(pair) + if bracket_count <= 0: + if pair[0] != closing: + self.word_iter.push_back(pair) + break + elif pair[0] != opening and pair[0] != closing: + self.print_irrelevant_word(pair) + pair = self.word_iter.read() + + def __skip_commentary(self): + """ + Skips entire line + """ + pair = self.word_iter.read() + while pair is not None and pair[0] != "\n": + self.print_irrelevant_word(pair) + pair = self.word_iter.read() + self.print_irrelevant_word(pair) |