Skip to content

Commit b824d11

Browse files
committed
log results updates
1 parent 6964695 commit b824d11

File tree

7 files changed

+109
-86
lines changed

7 files changed

+109
-86
lines changed

.github/workflows/docker.yml .github/workflows/deploy.yml

+16-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
name: docker
1+
name: deploy
22

33
on:
44
release:
55
types: [published]
66

77
jobs:
8-
push_to_registry:
8+
docker:
99
name: push docker image to docker hub
1010
runs-on: ubuntu-latest
1111
steps:
@@ -30,3 +30,17 @@ jobs:
3030
push: true
3131
tags: ${{ steps.meta.outputs.tags }}
3232
labels: ${{ steps.meta.outputs.labels }}
33+
34+
crates:
35+
name: publish crate
36+
runs-on: ubuntu-latest
37+
steps:
38+
- uses: actions/checkout@v3
39+
40+
- uses: actions-rs/cargo@v1
41+
name: Publish
42+
env:
43+
CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_TOKEN }}
44+
with:
45+
command: publish
46+
args: --no-verify

Cargo.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "thokr"
33
description = "a sleek typing tui written in rust"
4-
version = "0.1.2"
4+
version = "0.2.0"
55
readme = "README.md"
66
repository = "https://github.com/coloradocolby/thokr.git"
77
homepage = "https://github.com/coloradocolby/thokr"

README.md

+16-10
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<hr >
66

77
[![GitHub Build Workflow](https://github.com/coloradocolby/thokr/actions/workflows/build.yml/badge.svg)](https://github.com/coloradocolby/thokr/actions/workflows/build.yml)
8-
[![GitHub Docker Workflow](https://github.com/coloradocolby/thokr/actions/workflows/docker.yml/badge.svg)](https://github.com/coloradocolby/thokr/actions/workflows/docker.yml)
8+
[![GitHub Deploy Workflow](https://github.com/coloradocolby/thokr/actions/workflows/deploy.yml/badge.svg)](https://github.com/coloradocolby/thokr/actions/workflows/deploy.yml)
99
[![License](https://img.shields.io/badge/License-MIT-default.svg)](.github/LICENSE.md)
1010
[![Crate Version](https://img.shields.io/crates/v/thokr)](https://crates.io/crates/thokr)
1111
[![Github Stars](https://img.shields.io/github/stars/coloradocolby/thokr)](https://github.com/coloradocolby/thokr/stargazers)
@@ -26,15 +26,9 @@ $ cargo install thokr
2626
$ docker run -it coloradocolby/thokr
2727
```
2828

29-
## Arch Linux
30-
31-
On Arch Linux, you can install it from AUR:
32-
33-
``` sh
34-
paru -S thokr-git
35-
```
36-
29+
### Arch Linux
3730

31+
Install `thokr-git` from the AUR
3832

3933
## Usage
4034

@@ -50,7 +44,7 @@ For detailed usage run `thokr -h`.
5044
| `thokr -w 10 -s 5` | 10 of the 200 most common English words (hard stop at 5 seconds) |
5145
| `thokr -p "$(cat foo.txt)"` | custom prompt with the output of `cat foo.txt` |
5246

53-
_During a test, you can press ← to start over or → to see a new prompt (assuming
47+
_During a test you can press ← to start over or → to see a new prompt (assuming
5448
you didn't supply a custom one)_
5549

5650
## Supported Languages
@@ -63,6 +57,18 @@ The following languages are available by default:
6357
| `english1k` | 1000 most common English words |
6458
| `english10k` | 10000 most common English words |
6559

60+
## Logging
61+
62+
Upon completion of a test, a row outlining your results is appended to the
63+
`log.csv` file found in the following platform-specific folders. This way you
64+
can easily track your progress over time.
65+
66+
| platform | value | example |
67+
| :------- | ---------------------------------------------------------------- | ---------------------------------------------: |
68+
| Linux | $XDG*CONFIG_HOME/\_project_path* or $HOME/.config/_project_path_ | /home/colby/.config/thokr |
69+
| macOS | $HOME/Library/Application Support/_project_path_ | /Users/Colby/Library/Application Support/thokr |
70+
| Windows | {FOLDERID*RoamingAppData}\_project_path*\config | C:\Users\Colby\AppData\Roaming\thokr\config |
71+
6672
## Roadmap
6773

6874
- [ ] ⚡️ Performance

src/main.rs

+12-5
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use tui::{
2424
};
2525
use webbrowser::Browser;
2626

27-
const TICK_RATE: u64 = 100;
27+
const TICK_RATE_MS: u64 = 100;
2828

2929
/// a sleek typing tui written in rust
3030
#[derive(Parser, Debug, Clone)]
@@ -75,9 +75,12 @@ impl App {
7575

7676
language.get_random(cli.number_of_words).join(" ")
7777
};
78-
7978
Self {
80-
thok: Thok::new(prompt, cli.number_of_secs),
79+
thok: Thok::new(
80+
prompt,
81+
cli.number_of_words,
82+
cli.number_of_secs.map(|ns| ns as f64),
83+
),
8184
cli: Some(cli),
8285
}
8386
}
@@ -93,7 +96,11 @@ impl App {
9396
}
9497
};
9598

96-
self.thok = Thok::new(prompt, cli.number_of_secs);
99+
self.thok = Thok::new(
100+
prompt,
101+
cli.number_of_words,
102+
cli.number_of_secs.map(|ns| ns as f64),
103+
);
97104
}
98105
}
99106

@@ -241,7 +248,7 @@ fn get_thok_events(should_tick: bool) -> mpsc::Receiver<ThokEvent> {
241248
break;
242249
}
243250

244-
thread::sleep(Duration::from_millis(TICK_RATE))
251+
thread::sleep(Duration::from_millis(TICK_RATE_MS))
245252
});
246253
}
247254

src/thok.rs

+59-63
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use crate::util::std_dev;
2+
use crate::TICK_RATE_MS;
23
use chrono::prelude::*;
4+
use directories::ProjectDirs;
35
use itertools::Itertools;
46
use std::fs::OpenOptions;
57
use std::io::{self, Write};
@@ -27,36 +29,35 @@ pub struct Thok {
2729
pub wpm_coords: Vec<(f64, f64)>,
2830
pub cursor_pos: usize,
2931
pub started_at: Option<SystemTime>,
30-
// How much time is left
31-
pub time_remaining: Option<f64>,
32-
// The duration of the test
33-
pub test_duration: Option<f64>,
32+
pub seconds_remaining: Option<f64>,
33+
pub number_of_secs: Option<f64>,
34+
pub number_of_words: usize,
3435
pub wpm: f64,
3536
pub accuracy: f64,
3637
pub std_dev: f64,
3738
}
3839

3940
impl Thok {
40-
pub fn new(prompt_string: String, duration: Option<usize>) -> Self {
41-
let duration = duration.map(|d| d as f64);
42-
41+
pub fn new(prompt: String, number_of_words: usize, number_of_secs: Option<f64>) -> Self {
4342
Self {
44-
prompt: prompt_string,
43+
prompt,
4544
input: vec![],
4645
raw_coords: vec![],
4746
wpm_coords: vec![],
4847
cursor_pos: 0,
4948
started_at: None,
50-
time_remaining: duration,
51-
test_duration: duration,
49+
number_of_secs,
50+
number_of_words,
51+
seconds_remaining: number_of_secs,
5252
wpm: 0.0,
5353
accuracy: 0.0,
5454
std_dev: 0.0,
5555
}
5656
}
5757

5858
pub fn on_tick(&mut self) {
59-
self.time_remaining = Some(self.time_remaining.unwrap() - 0.1);
59+
self.seconds_remaining =
60+
Some(self.seconds_remaining.unwrap() - (TICK_RATE_MS as f64 / 1000_f64));
6061
}
6162

6263
pub fn get_expected_char(&self, idx: usize) -> char {
@@ -75,63 +76,17 @@ impl Thok {
7576
}
7677
}
7778

78-
pub fn save_results(&self) -> io::Result<()> {
79-
let project_dir = directories::ProjectDirs::from("", "", "thokr").unwrap();
80-
let config_dir = project_dir.config_dir();
81-
let log_path = config_dir.join("log.csv");
82-
dbg!(&log_path);
83-
84-
// Make sure the directory exists. There's no reason to check if it exists before doing
85-
// this, as this std::fs does that anyways.
86-
std::fs::create_dir_all(config_dir)?;
87-
88-
// If the config file doesn't exist, we need to emit a header
89-
let needs_header = !log_path.exists();
90-
91-
let mut log_file = OpenOptions::new()
92-
.write(true)
93-
.append(true)
94-
.create(true)
95-
.open(log_path)?;
96-
97-
if needs_header {
98-
writeln!(
99-
log_file,
100-
"time, wpm, accuracy, standard deviation, words, duration"
101-
)?;
102-
}
103-
104-
writeln!(
105-
log_file,
106-
"{},{},{},{},{},{}",
107-
Local::now(),
108-
self.wpm,
109-
self.accuracy,
110-
self.std_dev,
111-
// The number of words in the prompt
112-
// TODO: it may be best to pre-calculate this, but it's not super important'
113-
// as the prompt will likely be replaced on the next test
114-
self.prompt.split_whitespace().count(),
115-
// Don't log anything if duration is None. Log the float otherwise
116-
self.test_duration.map_or(String::new(), |f| f.to_string())
117-
)?;
118-
119-
Ok(())
120-
}
121-
12279
pub fn calc_results(&mut self) {
123-
let elapsed = self.started_at.unwrap().elapsed();
124-
12580
let correct_chars = self
12681
.input
12782
.clone()
12883
.into_iter()
12984
.filter(|i| i.outcome == Outcome::Correct)
13085
.collect::<Vec<Input>>();
13186

132-
let total_time = elapsed.unwrap().as_millis() as f64 / 1000.0;
87+
let elapsed_secs = self.started_at.unwrap().elapsed().unwrap().as_millis() as f64;
13388

134-
let whole_second_limit = total_time.floor();
89+
let whole_second_limit = elapsed_secs.floor();
13590

13691
let correct_chars_per_sec: Vec<(f64, f64)> = correct_chars
13792
.clone()
@@ -141,8 +96,7 @@ impl Thok {
14196
.timestamp
14297
.duration_since(self.started_at.unwrap())
14398
.unwrap()
144-
.as_millis() as f64
145-
/ 1000.0;
99+
.as_secs_f64();
146100

147101
if num_secs == 0.0 {
148102
num_secs = 1.;
@@ -154,7 +108,7 @@ impl Thok {
154108
num_secs = num_secs.ceil()
155109
}
156110
} else {
157-
num_secs = total_time;
111+
num_secs = elapsed_secs;
158112
}
159113

160114
*map.entry(num_secs.to_string()).or_insert(0) += 1;
@@ -236,6 +190,48 @@ impl Thok {
236190

237191
pub fn has_finished(&self) -> bool {
238192
(self.input.len() == self.prompt.len())
239-
|| (self.time_remaining.is_some() && self.time_remaining.unwrap() <= 0.0)
193+
|| (self.seconds_remaining.is_some() && self.seconds_remaining.unwrap() <= 0.0)
194+
}
195+
196+
pub fn save_results(&self) -> io::Result<()> {
197+
if let Some(proj_dirs) = ProjectDirs::from("", "", "thokr") {
198+
let config_dir = proj_dirs.config_dir();
199+
let log_path = config_dir.join("log.csv");
200+
201+
std::fs::create_dir_all(config_dir)?;
202+
203+
// If the config file doesn't exist, we need to emit a header
204+
let needs_header = !log_path.exists();
205+
206+
let mut log_file = OpenOptions::new()
207+
.write(true)
208+
.append(true)
209+
.create(true)
210+
.open(log_path)?;
211+
212+
if needs_header {
213+
writeln!(
214+
log_file,
215+
"date,num_words,num_secs,elapsed_secs,wpm,accuracy,std_dev"
216+
)?;
217+
}
218+
219+
let elapsed_secs = self.started_at.unwrap().elapsed().unwrap().as_secs_f64();
220+
221+
writeln!(
222+
log_file,
223+
"{},{},{},{:.2},{},{},{:.2}",
224+
Local::now().format("%c"),
225+
self.number_of_words,
226+
self.number_of_secs
227+
.map_or(String::from(""), |ns| format!("{:.2}", ns)),
228+
elapsed_secs,
229+
self.wpm, // already rounded, no need to round to two decimal places
230+
self.accuracy, // already rounded, no need to round to two decimal places
231+
self.std_dev,
232+
)?;
233+
}
234+
235+
Ok(())
240236
}
241237
}

src/ui.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ impl Widget for &Thok {
3939
let mut prompt_occupied_lines =
4040
((self.prompt.width() as f64 / max_chars_per_line as f64).ceil() + 1.0) as u16;
4141

42-
let time_left_lines = if self.test_duration.is_some() { 2 } else { 0 };
42+
let time_left_lines = if self.number_of_secs.is_some() { 2 } else { 0 };
4343

4444
if self.prompt.width() <= max_chars_per_line as usize {
4545
prompt_occupied_lines = 1;
@@ -100,9 +100,9 @@ impl Widget for &Thok {
100100

101101
widget.render(chunks[2], buf);
102102

103-
if self.time_remaining.is_some() {
103+
if self.seconds_remaining.is_some() {
104104
let timer = Paragraph::new(Span::styled(
105-
format!("{:.1}", self.time_remaining.unwrap()),
105+
format!("{:.1}", self.seconds_remaining.unwrap()),
106106
dim_bold_style,
107107
))
108108
.alignment(Alignment::Center);
@@ -142,7 +142,7 @@ impl Widget for &Thok {
142142

143143
let mut overall_duration = match self.wpm_coords.last() {
144144
Some(x) => x.0,
145-
_ => self.time_remaining.unwrap_or(1.0),
145+
_ => self.seconds_remaining.unwrap_or(1.0),
146146
};
147147

148148
overall_duration = if overall_duration < 1.0 {

0 commit comments

Comments
 (0)