added configuration and stripping the end of reference buffer
This commit is contained in:
		
							parent
							
								
									6c5d5b141d
								
							
						
					
					
						commit
						3f79147d7d
					
				
					 9 changed files with 138 additions and 44 deletions
				
			
		
							
								
								
									
										1
									
								
								doc/example_config.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								doc/example_config.json
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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}%"}
 | 
			
		||||
							
								
								
									
										1
									
								
								example_config.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								example_config.json
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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}%"}
 | 
			
		||||
| 
						 | 
				
			
			@ -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__":
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
        )
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										29
									
								
								fasttyper/config.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								fasttyper/config.py
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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])
 | 
			
		||||
| 
						 | 
				
			
			@ -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()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue