Skip to content

Commit

Permalink
log results updates
Browse files Browse the repository at this point in the history
  • Loading branch information
coloradocolby committed Apr 29, 2022
1 parent 6964695 commit b824d11
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 86 deletions.
18 changes: 16 additions & 2 deletions .github/workflows/docker.yml → .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
name: docker
name: deploy

on:
release:
types: [published]

jobs:
push_to_registry:
docker:
name: push docker image to docker hub
runs-on: ubuntu-latest
steps:
Expand All @@ -30,3 +30,17 @@ jobs:
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

crates:
name: publish crate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- uses: actions-rs/cargo@v1
name: Publish
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_TOKEN }}
with:
command: publish
args: --no-verify
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "thokr"
description = "a sleek typing tui written in rust"
version = "0.1.2"
version = "0.2.0"
readme = "README.md"
repository = "https://github.com/coloradocolby/thokr.git"
homepage = "https://github.com/coloradocolby/thokr"
Expand Down
26 changes: 16 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<hr >

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

## Arch Linux

On Arch Linux, you can install it from AUR:

``` sh
paru -S thokr-git
```

### Arch Linux

Install `thokr-git` from the AUR

## Usage

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

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

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

## Logging

Upon completion of a test, a row outlining your results is appended to the
`log.csv` file found in the following platform-specific folders. This way you
can easily track your progress over time.

| platform | value | example |
| :------- | ---------------------------------------------------------------- | ---------------------------------------------: |
| Linux | $XDG*CONFIG_HOME/\_project_path* or $HOME/.config/_project_path_ | /home/colby/.config/thokr |
| macOS | $HOME/Library/Application Support/_project_path_ | /Users/Colby/Library/Application Support/thokr |
| Windows | {FOLDERID*RoamingAppData}\_project_path*\config | C:\Users\Colby\AppData\Roaming\thokr\config |

## Roadmap

- [ ] ⚡️ Performance
Expand Down
17 changes: 12 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use tui::{
};
use webbrowser::Browser;

const TICK_RATE: u64 = 100;
const TICK_RATE_MS: u64 = 100;

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

language.get_random(cli.number_of_words).join(" ")
};

Self {
thok: Thok::new(prompt, cli.number_of_secs),
thok: Thok::new(
prompt,
cli.number_of_words,
cli.number_of_secs.map(|ns| ns as f64),
),
cli: Some(cli),
}
}
Expand All @@ -93,7 +96,11 @@ impl App {
}
};

self.thok = Thok::new(prompt, cli.number_of_secs);
self.thok = Thok::new(
prompt,
cli.number_of_words,
cli.number_of_secs.map(|ns| ns as f64),
);
}
}

Expand Down Expand Up @@ -241,7 +248,7 @@ fn get_thok_events(should_tick: bool) -> mpsc::Receiver<ThokEvent> {
break;
}

thread::sleep(Duration::from_millis(TICK_RATE))
thread::sleep(Duration::from_millis(TICK_RATE_MS))
});
}

Expand Down
122 changes: 59 additions & 63 deletions src/thok.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::util::std_dev;
use crate::TICK_RATE_MS;
use chrono::prelude::*;
use directories::ProjectDirs;
use itertools::Itertools;
use std::fs::OpenOptions;
use std::io::{self, Write};
Expand Down Expand Up @@ -27,36 +29,35 @@ pub struct Thok {
pub wpm_coords: Vec<(f64, f64)>,
pub cursor_pos: usize,
pub started_at: Option<SystemTime>,
// How much time is left
pub time_remaining: Option<f64>,
// The duration of the test
pub test_duration: Option<f64>,
pub seconds_remaining: Option<f64>,
pub number_of_secs: Option<f64>,
pub number_of_words: usize,
pub wpm: f64,
pub accuracy: f64,
pub std_dev: f64,
}

impl Thok {
pub fn new(prompt_string: String, duration: Option<usize>) -> Self {
let duration = duration.map(|d| d as f64);

pub fn new(prompt: String, number_of_words: usize, number_of_secs: Option<f64>) -> Self {
Self {
prompt: prompt_string,
prompt,
input: vec![],
raw_coords: vec![],
wpm_coords: vec![],
cursor_pos: 0,
started_at: None,
time_remaining: duration,
test_duration: duration,
number_of_secs,
number_of_words,
seconds_remaining: number_of_secs,
wpm: 0.0,
accuracy: 0.0,
std_dev: 0.0,
}
}

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

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

pub fn save_results(&self) -> io::Result<()> {
let project_dir = directories::ProjectDirs::from("", "", "thokr").unwrap();
let config_dir = project_dir.config_dir();
let log_path = config_dir.join("log.csv");
dbg!(&log_path);

// Make sure the directory exists. There's no reason to check if it exists before doing
// this, as this std::fs does that anyways.
std::fs::create_dir_all(config_dir)?;

// If the config file doesn't exist, we need to emit a header
let needs_header = !log_path.exists();

let mut log_file = OpenOptions::new()
.write(true)
.append(true)
.create(true)
.open(log_path)?;

if needs_header {
writeln!(
log_file,
"time, wpm, accuracy, standard deviation, words, duration"
)?;
}

writeln!(
log_file,
"{},{},{},{},{},{}",
Local::now(),
self.wpm,
self.accuracy,
self.std_dev,
// The number of words in the prompt
// TODO: it may be best to pre-calculate this, but it's not super important'
// as the prompt will likely be replaced on the next test
self.prompt.split_whitespace().count(),
// Don't log anything if duration is None. Log the float otherwise
self.test_duration.map_or(String::new(), |f| f.to_string())
)?;

Ok(())
}

pub fn calc_results(&mut self) {
let elapsed = self.started_at.unwrap().elapsed();

let correct_chars = self
.input
.clone()
.into_iter()
.filter(|i| i.outcome == Outcome::Correct)
.collect::<Vec<Input>>();

let total_time = elapsed.unwrap().as_millis() as f64 / 1000.0;
let elapsed_secs = self.started_at.unwrap().elapsed().unwrap().as_millis() as f64;

let whole_second_limit = total_time.floor();
let whole_second_limit = elapsed_secs.floor();

let correct_chars_per_sec: Vec<(f64, f64)> = correct_chars
.clone()
Expand All @@ -141,8 +96,7 @@ impl Thok {
.timestamp
.duration_since(self.started_at.unwrap())
.unwrap()
.as_millis() as f64
/ 1000.0;
.as_secs_f64();

if num_secs == 0.0 {
num_secs = 1.;
Expand All @@ -154,7 +108,7 @@ impl Thok {
num_secs = num_secs.ceil()
}
} else {
num_secs = total_time;
num_secs = elapsed_secs;
}

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

pub fn has_finished(&self) -> bool {
(self.input.len() == self.prompt.len())
|| (self.time_remaining.is_some() && self.time_remaining.unwrap() <= 0.0)
|| (self.seconds_remaining.is_some() && self.seconds_remaining.unwrap() <= 0.0)
}

pub fn save_results(&self) -> io::Result<()> {
if let Some(proj_dirs) = ProjectDirs::from("", "", "thokr") {
let config_dir = proj_dirs.config_dir();
let log_path = config_dir.join("log.csv");

std::fs::create_dir_all(config_dir)?;

// If the config file doesn't exist, we need to emit a header
let needs_header = !log_path.exists();

let mut log_file = OpenOptions::new()
.write(true)
.append(true)
.create(true)
.open(log_path)?;

if needs_header {
writeln!(
log_file,
"date,num_words,num_secs,elapsed_secs,wpm,accuracy,std_dev"
)?;
}

let elapsed_secs = self.started_at.unwrap().elapsed().unwrap().as_secs_f64();

writeln!(
log_file,
"{},{},{},{:.2},{},{},{:.2}",
Local::now().format("%c"),
self.number_of_words,
self.number_of_secs
.map_or(String::from(""), |ns| format!("{:.2}", ns)),
elapsed_secs,
self.wpm, // already rounded, no need to round to two decimal places
self.accuracy, // already rounded, no need to round to two decimal places
self.std_dev,
)?;
}

Ok(())
}
}
8 changes: 4 additions & 4 deletions src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ impl Widget for &Thok {
let mut prompt_occupied_lines =
((self.prompt.width() as f64 / max_chars_per_line as f64).ceil() + 1.0) as u16;

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

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

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

if self.time_remaining.is_some() {
if self.seconds_remaining.is_some() {
let timer = Paragraph::new(Span::styled(
format!("{:.1}", self.time_remaining.unwrap()),
format!("{:.1}", self.seconds_remaining.unwrap()),
dim_bold_style,
))
.alignment(Alignment::Center);
Expand Down Expand Up @@ -142,7 +142,7 @@ impl Widget for &Thok {

let mut overall_duration = match self.wpm_coords.last() {
Some(x) => x.0,
_ => self.time_remaining.unwrap_or(1.0),
_ => self.seconds_remaining.unwrap_or(1.0),
};

overall_duration = if overall_duration < 1.0 {
Expand Down

0 comments on commit b824d11

Please sign in to comment.