added configuration and stripping the end of reference buffer

This commit is contained in:
Doman 2021-07-10 19:13:38 +02:00
parent 6c5d5b141d
commit 3f79147d7d
9 changed files with 138 additions and 44 deletions

1
doc/example_config.json Normal file
View 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
View 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}%"}

View file

@ -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__":

View file

@ -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

View file

@ -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):

View file

@ -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
View 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])

View file

@ -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()

View file

@ -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