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