summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/source/support/optexcount/counter.py
diff options
context:
space:
mode:
Diffstat (limited to 'Master/texmf-dist/source/support/optexcount/counter.py')
-rw-r--r--Master/texmf-dist/source/support/optexcount/counter.py466
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)