From 3f79147d7d87e7e7420530ae97a8796390219e27 Mon Sep 17 00:00:00 2001 From: Doman Date: Sat, 10 Jul 2021 19:13:38 +0200 Subject: [PATCH] added configuration and stripping the end of reference buffer --- doc/example_config.json | 1 + example_config.json | 1 + fasttyper/__main__.py | 62 +++++++++++++++++----------------------- fasttyper/application.py | 3 +- fasttyper/buffer.py | 15 ++++++++++ fasttyper/components.py | 39 ++++++++++++++++++++----- fasttyper/config.py | 29 +++++++++++++++++++ fasttyper/interface.py | 2 +- fasttyper/stats.py | 30 +++++++++++++++++++ 9 files changed, 138 insertions(+), 44 deletions(-) create mode 100644 doc/example_config.json create mode 100644 example_config.json create mode 100644 fasttyper/config.py diff --git a/doc/example_config.json b/doc/example_config.json new file mode 100644 index 0000000..14775ad --- /dev/null +++ b/doc/example_config.json @@ -0,0 +1 @@ +{"user_input_valid_color": 3, "user_input_invalid_color": 2, "reference_text_color": 8, "stats_template": "\n\nwpm: {stats.wpm}\ntime: {stats.total_seconds}s", "stats_color": 5, "summary_template": "WPM: {stats.wpm}\nCPM: {stats.cpm}\nRAW WPM: {stats.raw_wpm}\nRAW CPM: {stats.raw_cpm}\ntotal seconds: {stats.total_seconds}\ntotal minutes: {stats.total_minutes}\ncorrect words: {stats.correct_words}\ncorrect chars: {stats.correct_chars}\nincorrect words: {stats.incorrect_words}\nincorrect chars: {stats.incorrect_chars}\ntotal words: {stats.total_words}\ntotal chars: {stats.total_chars}\naccuracy: {stats.accuracy}%"} \ No newline at end of file diff --git a/example_config.json b/example_config.json new file mode 100644 index 0000000..14775ad --- /dev/null +++ b/example_config.json @@ -0,0 +1 @@ +{"user_input_valid_color": 3, "user_input_invalid_color": 2, "reference_text_color": 8, "stats_template": "\n\nwpm: {stats.wpm}\ntime: {stats.total_seconds}s", "stats_color": 5, "summary_template": "WPM: {stats.wpm}\nCPM: {stats.cpm}\nRAW WPM: {stats.raw_wpm}\nRAW CPM: {stats.raw_cpm}\ntotal seconds: {stats.total_seconds}\ntotal minutes: {stats.total_minutes}\ncorrect words: {stats.correct_words}\ncorrect chars: {stats.correct_chars}\nincorrect words: {stats.incorrect_words}\nincorrect chars: {stats.incorrect_chars}\ntotal words: {stats.total_words}\ntotal chars: {stats.total_chars}\naccuracy: {stats.accuracy}%"} \ No newline at end of file diff --git a/fasttyper/__main__.py b/fasttyper/__main__.py index 3ec16b6..2dd3ca9 100644 --- a/fasttyper/__main__.py +++ b/fasttyper/__main__.py @@ -3,32 +3,42 @@ from .interface import Interface from .components import UserInput, CursorComponent, ReferenceText, StatsComponent from .listener import Listener from .buffer import UserBuffer, Buffer +from .config import Config from curses import wrapper import sys import io import os - - -def helpexit(): - print( - "USAGE: fasttyper FILE or pipe text into fasttyper after executing 'exec 3<&0' in your shell" - ) - sys.exit(1) +import argparse +import json def main(): - if len(sys.argv) == 1: + parser = argparse.ArgumentParser() + parser.add_argument("file", metavar="FILE", nargs="?") + parser.add_argument( + "--config", + "-c", + metavar="FILE", + help="configuration file", + default="~/.config/fasttyper/config.json", + ) + args = parser.parse_args() + + if args.file is None: input_lines = sys.stdin.readlines() os.dup2(3, 0) rbuffer = io.StringIO("".join(input_lines)) - elif len(sys.argv) == 2: - f = sys.argv[1] - try: - rbuffer = open(f) - except: - helpexit() else: - helpexit() + with open(args.file) as f: + rbuffer = io.StringIO(f.read()) + + try: + with open(args.config) as f: + configmap = json.load(f) + except FileNotFoundError: + configmap = {} + + config = Config(configmap) reference_buffer = Buffer(rbuffer) user_buffer = UserBuffer() @@ -39,7 +49,7 @@ def main(): stats_component = StatsComponent() listener = Listener() - application = Application(listener, user_buffer, reference_buffer) + application = Application(listener, user_buffer, reference_buffer, config) interface = Interface( application, @@ -49,25 +59,7 @@ def main(): user_buffer.close() reference_buffer.close() - stats = application.stats - print( - "\n".join( - [ - f"WPM: {stats.correct_words / stats.total_minutes}", - f"CPM: {stats.correct_chars / stats.total_minutes}", - f"RAW WPM: {(stats.correct_words + stats.incorrect_words) / stats.total_minutes}", - f"RAW CPM: {(stats.correct_chars + stats.incorrect_chars) / stats.total_minutes}", - f"total seconds: {stats.total_seconds}", - f"total minutes: {stats.total_minutes}", - f"correct words: {stats.correct_words}", - f"correct chars: {stats.correct_chars}", - f"incorrect words: {stats.incorrect_words}", - f"incorrect chars: {stats.incorrect_chars}", - f"total words: {stats.incorrect_words + stats.correct_words}", - f"total chars: {stats.incorrect_chars + stats.correct_chars}", - ] - ) - ) + print(config.get("summary_template").format(stats=application.stats)) if __name__ == "__main__": diff --git a/fasttyper/application.py b/fasttyper/application.py index 02515fe..9c69bc6 100644 --- a/fasttyper/application.py +++ b/fasttyper/application.py @@ -1,9 +1,10 @@ class Application: - def __init__(self, listener, user_buffer, reference_buffer): + def __init__(self, listener, user_buffer, reference_buffer, config): self._running = False self.listener = listener self.user_buffer = user_buffer self.reference_buffer = reference_buffer + self.config = config from .state import StateMachine diff --git a/fasttyper/buffer.py b/fasttyper/buffer.py index 830697c..eb731cf 100644 --- a/fasttyper/buffer.py +++ b/fasttyper/buffer.py @@ -5,6 +5,7 @@ from .listener import Action class Buffer: def __init__(self, buffer): self.buffer = buffer + self._strip_end() def _write(self, data): self.buffer.write(data) @@ -77,6 +78,20 @@ class Buffer: def close(self): self.buffer.close() + def _strip_end(self): + self.buffer.read() + while True: + last_char = self._last_char() + + if last_char is None: + break + + if last_char.isalnum(): + break + + self._del_char() + self.buffer.seek(0) + class UserBuffer(Buffer): def __init__(self): diff --git a/fasttyper/components.py b/fasttyper/components.py index d953309..2e3699e 100644 --- a/fasttyper/components.py +++ b/fasttyper/components.py @@ -6,7 +6,7 @@ class Base: self.rows = None self.cols = None - def init(self, screen): + def init(self, screen, application): self.rows, self.cols = screen.getmaxyx() def paint(self, screen, application): @@ -29,6 +29,13 @@ class UserInput(Base): def __init__(self, cursor_component): super().__init__() self.cursor_component = cursor_component + self.valid_color = None + self.invalid_color = None + + def init(self, screen, application): + super().init(screen, application) + self.valid_color = application.config.get("user_input_valid_color") + self.invalid_color = application.config.get("user_input_invalid_color") def paint(self, screen, application): valid_text, invalid_text = application.get_user_text() @@ -36,21 +43,39 @@ class UserInput(Base): invalid_text = invalid_text.replace(" ", "_") invalid_text = invalid_text.replace("\n", "\\n") - screen.addstr(valid_text, curses.color_pair(3)) - screen.addstr(invalid_text, curses.color_pair(2)) + screen.addstr(valid_text, curses.color_pair(self.valid_color)) + screen.addstr(invalid_text, curses.color_pair(self.invalid_color)) self.cursor_component.update(screen) class ReferenceText(Base): + def __init__(self): + super().__init__() + self.color = None + + def init(self, screen, application): + super().init(screen, application) + self.color = application.config.get("reference_text_color") + def paint(self, screen, application): valid_user_text_position = application.valid_user_text_position() reference_text = application.get_reference_text(valid_user_text_position) reference_text = reference_text.replace("\n", "\\n\n") - screen.addstr(reference_text, curses.color_pair(8)) + screen.addstr(reference_text, curses.color_pair(self.color)) class StatsComponent(Base): + def __init__(self): + super().__init__() + self.color = None + self.template = None + + def init(self, screen, application): + super().init(screen, application) + self.color = application.config.get("stats_color") + self.template = application.config.get("stats_template") + def paint(self, screen, application): - text = f"\n\nwpm: {application.stats.correct_words / application.stats.total_minutes}" - text += f"\ntime: {application.stats.total_seconds}s" - screen.addstr(text, curses.color_pair(5)) + screen.addstr( + self.template.format(stats=application.stats), curses.color_pair(self.color) + ) diff --git a/fasttyper/config.py b/fasttyper/config.py new file mode 100644 index 0000000..89e60a3 --- /dev/null +++ b/fasttyper/config.py @@ -0,0 +1,29 @@ +class Config: + defaults = { + "user_input_valid_color": 3, + "user_input_invalid_color": 2, + "reference_text_color": 8, + "stats_template": "\n\nwpm: {stats.wpm}\ntime: {stats.total_seconds}s", + "stats_color": 5, + "summary_template": ( + "WPM: {stats.wpm}\n" + "CPM: {stats.cpm}\n" + "RAW WPM: {stats.raw_wpm}\n" + "RAW CPM: {stats.raw_cpm}\n" + "total seconds: {stats.total_seconds}\n" + "total minutes: {stats.total_minutes}\n" + "correct words: {stats.correct_words}\n" + "correct chars: {stats.correct_chars}\n" + "incorrect words: {stats.incorrect_words}\n" + "incorrect chars: {stats.incorrect_chars}\n" + "total words: {stats.total_words}\n" + "total chars: {stats.total_chars}\n" + "accuracy: {stats.accuracy}%" + ), + } + + def __init__(self, configmap): + self.configmap = configmap + + def get(self, key): + return self.configmap.get(key, self.defaults[key]) diff --git a/fasttyper/interface.py b/fasttyper/interface.py index cb960a7..37f6c47 100644 --- a/fasttyper/interface.py +++ b/fasttyper/interface.py @@ -17,7 +17,7 @@ class Interface: def init(self, screen): self.init_colors() for component in self.components: - component.init(screen) + component.init(screen, self.application) def update(self, screen): screen.clear() diff --git a/fasttyper/stats.py b/fasttyper/stats.py index 53bc963..80afe46 100644 --- a/fasttyper/stats.py +++ b/fasttyper/stats.py @@ -42,3 +42,33 @@ class Stats: @property def total_minutes(self): return self.total_seconds / 60 + + @property + def total_words(self): + return self.incorrect_words + self.correct_words + + @property + def total_chars(self): + return self.incorrect_chars + self.correct_chars + + @property + def wpm(self): + return self.correct_words / self.total_minutes + + @property + def cpm(self): + return self.correct_chars / self.total_minutes + + @property + def raw_wpm(self): + return self.total_words / self.total_minutes + + @property + def raw_cpm(self): + return self.total_chars / self.total_minutes + + @property + def accuracy(self): + if self.total_chars: + return self.correct_chars / self.total_chars * 100 + return 100