stunning-train 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- stunning_train-0.1.0/PKG-INFO +6 -0
- stunning_train-0.1.0/README.md +1 -0
- stunning_train-0.1.0/pyproject.toml +19 -0
- stunning_train-0.1.0/setup.cfg +4 -0
- stunning_train-0.1.0/src/stunning_train/__init__.py +0 -0
- stunning_train-0.1.0/src/stunning_train/cli.py +160 -0
- stunning_train-0.1.0/src/stunning_train/core.py +81 -0
- stunning_train-0.1.0/src/stunning_train.egg-info/PKG-INFO +6 -0
- stunning_train-0.1.0/src/stunning_train.egg-info/SOURCES.txt +10 -0
- stunning_train-0.1.0/src/stunning_train.egg-info/dependency_links.txt +1 -0
- stunning_train-0.1.0/src/stunning_train.egg-info/entry_points.txt +2 -0
- stunning_train-0.1.0/src/stunning_train.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# stunning-train
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "stunning-train"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Terminal-based Fast Screen Reader"
|
|
9
|
+
requires-python = ">=3.7"
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Programming Language :: Python :: 3",
|
|
12
|
+
]
|
|
13
|
+
dependencies = []
|
|
14
|
+
|
|
15
|
+
[project.scripts]
|
|
16
|
+
stt = "stunning_train.cli:run"
|
|
17
|
+
|
|
18
|
+
[tool.setuptools.packages.find]
|
|
19
|
+
where = ["src"]
|
|
File without changes
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import curses
|
|
3
|
+
import time
|
|
4
|
+
import os
|
|
5
|
+
import argparse
|
|
6
|
+
try:
|
|
7
|
+
from stunning_train.core import reader
|
|
8
|
+
except ImportError:
|
|
9
|
+
from core import reader
|
|
10
|
+
|
|
11
|
+
def choose_file(stdscr):
|
|
12
|
+
all_items = os.listdir('.')
|
|
13
|
+
files = []
|
|
14
|
+
for f in all_items:
|
|
15
|
+
if os.path.isfile(f):
|
|
16
|
+
files.append(f)
|
|
17
|
+
if len(files) == 0:
|
|
18
|
+
return None
|
|
19
|
+
selected = 0
|
|
20
|
+
num_files = len(files)
|
|
21
|
+
while True:
|
|
22
|
+
stdscr.clear()
|
|
23
|
+
max_y, max_x = stdscr.getmaxyx()
|
|
24
|
+
start_y = (max_y - num_files) // 2
|
|
25
|
+
if start_y < 0:
|
|
26
|
+
start_y = 0
|
|
27
|
+
for idx in range(num_files):
|
|
28
|
+
filename = files[idx]
|
|
29
|
+
if idx == selected:
|
|
30
|
+
prefix = "> "
|
|
31
|
+
else:
|
|
32
|
+
prefix = " "
|
|
33
|
+
line = prefix + filename
|
|
34
|
+
line_len = len(line)
|
|
35
|
+
diff_x = max_x - line_len
|
|
36
|
+
x = diff_x // 2
|
|
37
|
+
if x < 0:
|
|
38
|
+
x = 0
|
|
39
|
+
y = start_y + idx
|
|
40
|
+
stdscr.addstr(y, x, line)
|
|
41
|
+
|
|
42
|
+
stdscr.refresh()
|
|
43
|
+
key = stdscr.getch()
|
|
44
|
+
if key == curses.KEY_UP:
|
|
45
|
+
selected = selected - 1
|
|
46
|
+
if selected < 0:
|
|
47
|
+
selected = num_files - 1
|
|
48
|
+
elif key == curses.KEY_DOWN:
|
|
49
|
+
selected = selected + 1
|
|
50
|
+
if selected >= num_files:
|
|
51
|
+
selected = 0
|
|
52
|
+
elif key == 10 or key == 13:
|
|
53
|
+
chosen_file = files[selected]
|
|
54
|
+
return chosen_file
|
|
55
|
+
|
|
56
|
+
def main(stdscr, args):
|
|
57
|
+
curses.curs_set(0)
|
|
58
|
+
stdscr.keypad(True)
|
|
59
|
+
text = None
|
|
60
|
+
if args.string is not None:
|
|
61
|
+
text = args.string
|
|
62
|
+
elif args.file is not None:
|
|
63
|
+
f = open(args.file, 'r')
|
|
64
|
+
text = f.read()
|
|
65
|
+
f.close()
|
|
66
|
+
else:
|
|
67
|
+
filename = choose_file(stdscr)
|
|
68
|
+
if filename is None:
|
|
69
|
+
return
|
|
70
|
+
f = open(filename, 'r')
|
|
71
|
+
text = f.read()
|
|
72
|
+
f.close()
|
|
73
|
+
r = reader(text, wpm=args.wpm)
|
|
74
|
+
while r.has_next() == True or r.current is not None:
|
|
75
|
+
if r.current is None:
|
|
76
|
+
r.next_word()
|
|
77
|
+
stdscr.clear()
|
|
78
|
+
word = r.current
|
|
79
|
+
history_list, current_word, lookahead_list = r.nearby_words()
|
|
80
|
+
history_str = ""
|
|
81
|
+
for h_word in history_list:
|
|
82
|
+
history_str = history_str + h_word + " "
|
|
83
|
+
lookahead_str = ""
|
|
84
|
+
for l_word in lookahead_list:
|
|
85
|
+
lookahead_str = lookahead_str + " " + l_word
|
|
86
|
+
max_y, max_x = stdscr.getmaxyx()
|
|
87
|
+
y = max_y // 2
|
|
88
|
+
word_len = len(word)
|
|
89
|
+
diff_x = max_x - word_len
|
|
90
|
+
x = diff_x // 2
|
|
91
|
+
if x < 0:
|
|
92
|
+
x = 0
|
|
93
|
+
hx = x - len(history_str)
|
|
94
|
+
if hx < 0:
|
|
95
|
+
slice_start = len(history_str) - x
|
|
96
|
+
history_str = history_str[slice_start:]
|
|
97
|
+
hx = 0
|
|
98
|
+
lx = x + word_len
|
|
99
|
+
if lx < max_x:
|
|
100
|
+
space_left = max_x - lx
|
|
101
|
+
lookahead_str = lookahead_str[:space_left]
|
|
102
|
+
if len(history_str) > 0:
|
|
103
|
+
stdscr.addstr(y, hx, history_str, curses.A_DIM)
|
|
104
|
+
stdscr.addstr(y, x, word)
|
|
105
|
+
if lx < max_x:
|
|
106
|
+
if len(lookahead_str) > 0:
|
|
107
|
+
stdscr.addstr(y, lx, lookahead_str, curses.A_DIM)
|
|
108
|
+
pct = r.words_read / r.size
|
|
109
|
+
if pct > 1.0:
|
|
110
|
+
pct = 1.0
|
|
111
|
+
bar_width = 20
|
|
112
|
+
filled = int(pct * bar_width)
|
|
113
|
+
empty = bar_width - filled
|
|
114
|
+
bar_str = "["
|
|
115
|
+
for _ in range(filled):
|
|
116
|
+
bar_str = bar_str + "-"
|
|
117
|
+
for _ in range(empty):
|
|
118
|
+
bar_str = bar_str + " "
|
|
119
|
+
bar_str = bar_str + "]"
|
|
120
|
+
percent_num = int(pct * 100)
|
|
121
|
+
percent_str = " " + str(percent_num) + "%"
|
|
122
|
+
full_str = bar_str + percent_str
|
|
123
|
+
bar_len = len(full_str)
|
|
124
|
+
bar_x = (max_x - bar_len) // 2
|
|
125
|
+
if bar_x < 0:
|
|
126
|
+
bar_x = 0
|
|
127
|
+
bar_y = max_y - 2
|
|
128
|
+
if bar_y > y:
|
|
129
|
+
stdscr.addstr(bar_y, bar_x, full_str)
|
|
130
|
+
stdscr.refresh()
|
|
131
|
+
delay_ms = int(r.get_delay() * 1000)
|
|
132
|
+
stdscr.timeout(delay_ms)
|
|
133
|
+
key = stdscr.getch()
|
|
134
|
+
if key == curses.KEY_RIGHT:
|
|
135
|
+
steps = int(r.size * 0.02)
|
|
136
|
+
if steps < 1:
|
|
137
|
+
steps = 1
|
|
138
|
+
r.skip_forward(steps)
|
|
139
|
+
elif key == curses.KEY_LEFT:
|
|
140
|
+
steps = int(r.size * 0.02)
|
|
141
|
+
if steps < 1:
|
|
142
|
+
steps = 1
|
|
143
|
+
for _ in range(steps):
|
|
144
|
+
r.go_backward()
|
|
145
|
+
else:
|
|
146
|
+
if r.has_next() == True:
|
|
147
|
+
r.next_word()
|
|
148
|
+
else:
|
|
149
|
+
break
|
|
150
|
+
|
|
151
|
+
def run():
|
|
152
|
+
parser = argparse.ArgumentParser(description="Terminal-based Fast Screen Reader ")
|
|
153
|
+
parser.add_argument('-f', '--file', type=str, help="File Path(needed if file is outside the cwd)")
|
|
154
|
+
parser.add_argument('-s', '--string', type=str, help="Input String")
|
|
155
|
+
parser.add_argument('-w', '--wpm', type=int, default=300, help="SET WPM(default: 300)")
|
|
156
|
+
args = parser.parse_args()
|
|
157
|
+
curses.wrapper(main, args)
|
|
158
|
+
|
|
159
|
+
if __name__ == "__main__":
|
|
160
|
+
run()
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import io
|
|
2
|
+
class reader:
|
|
3
|
+
def file_stream_gen(self,obj):
|
|
4
|
+
for line in obj:
|
|
5
|
+
for word in line.split():
|
|
6
|
+
yield word
|
|
7
|
+
def __init__(self, stream, wpm=300, chunk_size= 5):
|
|
8
|
+
self.wpm= wpm
|
|
9
|
+
self.chunk_size = chunk_size
|
|
10
|
+
if isinstance(stream, str):
|
|
11
|
+
self.stream=iter(stream.split())
|
|
12
|
+
self.size= len(stream.split())
|
|
13
|
+
elif isinstance(stream, io.IOBase):
|
|
14
|
+
curr = stream.tell()
|
|
15
|
+
stream.seek(0, 2)
|
|
16
|
+
self.size = stream.tell()
|
|
17
|
+
stream.seek(curr)
|
|
18
|
+
self.stream = self.file_stream_gen(stream)
|
|
19
|
+
else:
|
|
20
|
+
raise TypeError("Stream must be a string or a file-like object")
|
|
21
|
+
self.history = []
|
|
22
|
+
self.lookahead = []
|
|
23
|
+
self.current = None
|
|
24
|
+
self.words_read = 0
|
|
25
|
+
self.fill_lookahead()
|
|
26
|
+
def fill_lookahead(self):
|
|
27
|
+
while len(self.lookahead) < self.chunk_size:
|
|
28
|
+
try:
|
|
29
|
+
word = next(self.stream)
|
|
30
|
+
self.lookahead.append(word)
|
|
31
|
+
except StopIteration:
|
|
32
|
+
break
|
|
33
|
+
|
|
34
|
+
def get_speed(self):
|
|
35
|
+
return self.wpm
|
|
36
|
+
|
|
37
|
+
def set_speed(self, value):
|
|
38
|
+
if value <= 0:
|
|
39
|
+
raise ValueError("Speed must be greater than 0")
|
|
40
|
+
self.wpm = value
|
|
41
|
+
|
|
42
|
+
def get_delay(self):
|
|
43
|
+
return 60.0 / self.wpm
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def has_next(self):
|
|
47
|
+
self.fill_lookahead()
|
|
48
|
+
return len(self.lookahead) > 0
|
|
49
|
+
|
|
50
|
+
def next_word(self):
|
|
51
|
+
if not self.has_next():
|
|
52
|
+
raise StopIteration()
|
|
53
|
+
if self.current is not None:
|
|
54
|
+
self.history.append(self.current)
|
|
55
|
+
if len(self.history) > self.chunk_size:
|
|
56
|
+
self.history.pop(0)
|
|
57
|
+
self.current = self.lookahead.pop(0)
|
|
58
|
+
self.words_read = self.words_read + 1
|
|
59
|
+
self.fill_lookahead()
|
|
60
|
+
return self.current
|
|
61
|
+
|
|
62
|
+
def skip_forward(self, n=1):
|
|
63
|
+
for _ in range(n):
|
|
64
|
+
if not self.has_next():
|
|
65
|
+
break
|
|
66
|
+
self.next_word()
|
|
67
|
+
return self.current
|
|
68
|
+
|
|
69
|
+
def go_backward(self):
|
|
70
|
+
if len(self.history) == 0:
|
|
71
|
+
return self.current
|
|
72
|
+
if self.current is not None:
|
|
73
|
+
self.lookahead.insert(0, self.current)
|
|
74
|
+
self.current = self.history.pop()
|
|
75
|
+
self.words_read = self.words_read - 1
|
|
76
|
+
if self.words_read < 0:
|
|
77
|
+
self.words_read = 0
|
|
78
|
+
return self.current
|
|
79
|
+
|
|
80
|
+
def nearby_words(self):
|
|
81
|
+
return self.history.copy(), self.current, self.lookahead.copy()
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/stunning_train/__init__.py
|
|
4
|
+
src/stunning_train/cli.py
|
|
5
|
+
src/stunning_train/core.py
|
|
6
|
+
src/stunning_train.egg-info/PKG-INFO
|
|
7
|
+
src/stunning_train.egg-info/SOURCES.txt
|
|
8
|
+
src/stunning_train.egg-info/dependency_links.txt
|
|
9
|
+
src/stunning_train.egg-info/entry_points.txt
|
|
10
|
+
src/stunning_train.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
stunning_train
|