Init
This commit is contained in:
commit
b7299f7c1c
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
# Rust
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Nix
|
||||||
|
result
|
||||||
|
|
||||||
|
# Direnv
|
||||||
|
.direnv/
|
||||||
|
|
||||||
|
# Editor
|
||||||
|
.vim/
|
||||||
|
.nvimrc
|
1909
Cargo.lock
generated
Normal file
1909
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
38
Cargo.toml
Normal file
38
Cargo.toml
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
[package]
|
||||||
|
name = "dttyper"
|
||||||
|
description = "Terminal-based typing test."
|
||||||
|
version = "1.4.2"
|
||||||
|
readme = "README.md"
|
||||||
|
repository = "https://git.berryez.xyz/berry/dttyper.git"
|
||||||
|
homepage = "https://git.berryez.xyz/berry/dttyper"
|
||||||
|
license = "MIT"
|
||||||
|
authors = [
|
||||||
|
"Max Niederman <max@maxniederman.com>",
|
||||||
|
"berry <neurofen@fedora.email>",
|
||||||
|
]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
reqwest = { version = "0.11.25", features = ["blocking"] }
|
||||||
|
|
||||||
|
structopt = "^0.3"
|
||||||
|
dirs = "^5.0"
|
||||||
|
crossterm = "^0.27"
|
||||||
|
rust-embed = "^8.2"
|
||||||
|
toml = "^0.8"
|
||||||
|
serde_json = "1.0.114"
|
||||||
|
|
||||||
|
chrono = "0.4.35"
|
||||||
|
[dependencies.ratatui]
|
||||||
|
version = "^0.25"
|
||||||
|
|
||||||
|
[dependencies.rand]
|
||||||
|
version = "^0.8"
|
||||||
|
features = ["alloc"]
|
||||||
|
|
||||||
|
[dependencies.serde]
|
||||||
|
version = "^1.0"
|
||||||
|
features = ["derive"]
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
dirs = "^5.0"
|
21
LICENSE.md
Normal file
21
LICENSE.md
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 Max Niederman
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
236
README.md
Normal file
236
README.md
Normal file
|
@ -0,0 +1,236 @@
|
||||||
|
# dttyper
|
||||||
|
|
||||||
|
dttyper is a terminal-based typing test built with Rust and tui-rs forked from [ttyper](https://github.com/max-niederman/ttyper), that exports each tests into an influx database.
|
||||||
|
|
||||||
|
![Recording](./resources/recording.gif)
|
||||||
|
|
||||||
|
## installation
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo build
|
||||||
|
cargo install --path .
|
||||||
|
```
|
||||||
|
|
||||||
|
# Develop (using crate2nix)
|
||||||
|
|
||||||
|
a. Enter dev shell via nix
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix develop
|
||||||
|
```
|
||||||
|
|
||||||
|
b. modify code and then run with nix
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix run
|
||||||
|
```
|
||||||
|
|
||||||
|
## usage
|
||||||
|
|
||||||
|
For usage instructions, you can run `dttyper --help`:
|
||||||
|
|
||||||
|
```
|
||||||
|
dttyper
|
||||||
|
Terminal-based typing test.
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
dttyper [FLAGS] [OPTIONS] [contents]
|
||||||
|
|
||||||
|
FLAGS:
|
||||||
|
-d, --debug
|
||||||
|
-h, --help Prints help information
|
||||||
|
--list-languages List installed languages
|
||||||
|
--no-backtrack Disable backtracking to completed words
|
||||||
|
-V, --version Prints version information
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
-c, --config <config> Use config file
|
||||||
|
-l, --language <language> Specify test language
|
||||||
|
--language-file <language-file> Specify test language in file
|
||||||
|
-w, --words <words> Specify word count [default: 50]
|
||||||
|
|
||||||
|
ARGS:
|
||||||
|
<contents>
|
||||||
|
```
|
||||||
|
|
||||||
|
### examples
|
||||||
|
|
||||||
|
| command | test contents |
|
||||||
|
| :------------------------------ | ----------------------------------------: |
|
||||||
|
| `dttyper` | 50 of the 200 most common english words |
|
||||||
|
| `dttyper -w 100` | 100 of the 200 most common English words |
|
||||||
|
| `dttyper -w 100 -l english1000` | 100 of the 1000 most common English words |
|
||||||
|
| `dttyper --language-file lang` | 50 random words from the file `lang` |
|
||||||
|
| `dttyper text.txt` | contents of `text.txt` split at newlines |
|
||||||
|
|
||||||
|
## languages
|
||||||
|
|
||||||
|
The following languages are available by default:
|
||||||
|
|
||||||
|
| name | description |
|
||||||
|
| :----------------- | ----------------------------------: |
|
||||||
|
| `c` | The C programming language |
|
||||||
|
| `csharp` | The C# programming language |
|
||||||
|
| `english100` | 100 most common English words |
|
||||||
|
| `english200` | 200 most common English words |
|
||||||
|
| `english1000` | 1000 most common English words |
|
||||||
|
| `english5000` | 5000 most common English words |
|
||||||
|
| `english10000` | 10000 most common English words |
|
||||||
|
| `english-advanced` | Advanced English words |
|
||||||
|
| `english-pirate` | 50 pirate speak English words |
|
||||||
|
| `german` | 207 most common German words |
|
||||||
|
| `german1000` | 1000 most common German words |
|
||||||
|
| `german10000` | 10000 most common German words |
|
||||||
|
| `go` | The Go programming language |
|
||||||
|
| `html` | HyperText Markup Language |
|
||||||
|
| `java` | The Java programming language |
|
||||||
|
| `javascript` | The Javascript programming language |
|
||||||
|
| `norwegian` | 200 most common Norwegian words |
|
||||||
|
| `php` | The PHP programming language |
|
||||||
|
| `portuguese` | 100 most common Portuguese words |
|
||||||
|
| `python` | The Python programming language |
|
||||||
|
| `qt` | The QT GUI framework |
|
||||||
|
| `ruby` | The Ruby programming language |
|
||||||
|
| `rust` | The Rust programming language |
|
||||||
|
| `spanish` | 100 most common Spanish words |
|
||||||
|
| `ukrainian` | 100 most common Ukrainian words |
|
||||||
|
|
||||||
|
Additional languages can be added by creating a file in `DTTYPER_CONFIG_DIR/language` with a word on each line. On Linux, the config directory is `$HOME/.config/dttyper`; on Windows, it's `C:\Users\user\AppData\Roaming\dttyper`; and on macOS it's `$HOME/Library/Application Support/dttyper`.
|
||||||
|
|
||||||
|
# Statistics review
|
||||||
|
|
||||||
|
A [grafana panel](./panel.json) can be imported to view your reports!
|
||||||
|
![GrafanaStats](./resources/grafana.png)
|
||||||
|
|
||||||
|
## config
|
||||||
|
|
||||||
|
Configuration is specified by the `config.toml` file in the config directory (e.g. `$HOME/.config/dttyper/config.toml`).
|
||||||
|
|
||||||
|
The default values with explanations are below:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
default_language = "english1000"
|
||||||
|
server = "http://localhost:8086"
|
||||||
|
token = "token"
|
||||||
|
bucket = "dttyper"
|
||||||
|
org = "dttyper"
|
||||||
|
keyboard = "Generic"
|
||||||
|
|
||||||
|
|
||||||
|
[theme]
|
||||||
|
# default style (this includes empty cells)
|
||||||
|
default = "none"
|
||||||
|
|
||||||
|
# title text styling
|
||||||
|
title = "white;bold"
|
||||||
|
|
||||||
|
## test styles ##
|
||||||
|
|
||||||
|
# input box border
|
||||||
|
input_border = "cyan"
|
||||||
|
# prompt box border
|
||||||
|
prompt_border = "green"
|
||||||
|
|
||||||
|
# correctly typed words
|
||||||
|
prompt_correct = "green"
|
||||||
|
# incorrectly typed words
|
||||||
|
prompt_incorrect = "red"
|
||||||
|
# untyped words
|
||||||
|
prompt_untyped = "gray"
|
||||||
|
|
||||||
|
# correctly typed letters in current word
|
||||||
|
prompt_current_correct = "green;bold"
|
||||||
|
# incorrectly typed letters in current word
|
||||||
|
prompt_current_incorrect = "red;bold"
|
||||||
|
# untyped letters in current word
|
||||||
|
prompt_current_untyped = "blue;bold"
|
||||||
|
|
||||||
|
# cursor character
|
||||||
|
prompt_cursor = "none;underlined"
|
||||||
|
|
||||||
|
## results styles ##
|
||||||
|
|
||||||
|
# overview text
|
||||||
|
results_overview = "cyan;bold"
|
||||||
|
# overview border
|
||||||
|
results_overview_border = "cyan"
|
||||||
|
|
||||||
|
# worst keys text
|
||||||
|
results_worst_keys = "cyan;bold"
|
||||||
|
# worst keys border
|
||||||
|
results_worst_keys_border = "cyan"
|
||||||
|
|
||||||
|
# results chart default (includes plotted data)
|
||||||
|
results_chart = "cyan"
|
||||||
|
# results chart x-axis label
|
||||||
|
results_chart_x = "cyan"
|
||||||
|
# results chart y-axis label
|
||||||
|
results_chart_y = "gray;italic"
|
||||||
|
|
||||||
|
# restart/quit prompt in results ui
|
||||||
|
results_restart_prompt = "gray;italic"
|
||||||
|
```
|
||||||
|
|
||||||
|
### style format
|
||||||
|
|
||||||
|
The configuration uses a custom style format which can specify most [ANSI escape styling codes](<https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters>), encoded as a string.
|
||||||
|
|
||||||
|
Styles begin with the color specification, which can be a single color (the foreground), or two colors separated by a colon (the foreground and background). Colors can be one of sixteen specified by your terminal, a 24-bit hex color code, `none`, or `reset`.
|
||||||
|
|
||||||
|
After the colors, you can optionally specify modifiers separated by a semicolon. A list of modifiers is below:
|
||||||
|
|
||||||
|
- `bold`
|
||||||
|
- `crossed_out`
|
||||||
|
- `dim`
|
||||||
|
- `hidden`
|
||||||
|
- `italic`
|
||||||
|
- `rapid_blink`
|
||||||
|
- `slow_blink`
|
||||||
|
- `reversed`
|
||||||
|
- `underlined`
|
||||||
|
|
||||||
|
Some examples:
|
||||||
|
|
||||||
|
- `blue:white;italic` specifies italic blue text on a white background.
|
||||||
|
- `none;italic;bold;underlined` specifies underlined, italicized, and bolded text with no set color or background.
|
||||||
|
- `00ff00:000000` specifies text of color `#00ff00` (pure green) on a background of `#000000` (pure black).
|
||||||
|
|
||||||
|
In [extended Backus-Naur form](https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_form):
|
||||||
|
|
||||||
|
```ebnf
|
||||||
|
style = colors, { ";", modifier }, [ ";" ] ;
|
||||||
|
|
||||||
|
colors = color, [ ":", color ] ;
|
||||||
|
color = "none"
|
||||||
|
| "reset"
|
||||||
|
| "black"
|
||||||
|
| "white"
|
||||||
|
| "red"
|
||||||
|
| "green"
|
||||||
|
| "yellow"
|
||||||
|
| "blue"
|
||||||
|
| "magenta"
|
||||||
|
| "cyan"
|
||||||
|
| "gray"
|
||||||
|
| "darkgray"
|
||||||
|
| "lightred"
|
||||||
|
| "lightgreen"
|
||||||
|
| "lightyellow"
|
||||||
|
| "lightblue"
|
||||||
|
| "lightmagenta"
|
||||||
|
| "lightcyan"
|
||||||
|
| 6 * hex digit ;
|
||||||
|
hex digit = ? hexadecimal digit; 1-9, a-z, and A-Z ? ;
|
||||||
|
|
||||||
|
modifier = "bold"
|
||||||
|
| "crossed_out"
|
||||||
|
| "dim"
|
||||||
|
| "hidden"
|
||||||
|
| "italic"
|
||||||
|
| "rapid_blink"
|
||||||
|
| "slow_blink"
|
||||||
|
| "reversed"
|
||||||
|
| "underlined" ;
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're familiar with [serde](https://serde.rs), you can also read [the deserialization code](./src/config.rs).
|
54
build.rs
Normal file
54
build.rs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
use std::env;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
fn copy<U: AsRef<Path>, V: AsRef<Path>>(from: U, to: V) -> std::io::Result<()> {
|
||||||
|
let mut stack = vec![PathBuf::from(from.as_ref())];
|
||||||
|
|
||||||
|
let output_root = PathBuf::from(to.as_ref());
|
||||||
|
let input_root = PathBuf::from(from.as_ref()).components().count();
|
||||||
|
|
||||||
|
while let Some(working_path) = stack.pop() {
|
||||||
|
// Generate a relative path
|
||||||
|
let src: PathBuf = working_path.components().skip(input_root).collect();
|
||||||
|
|
||||||
|
// Create a destination if missing
|
||||||
|
let dest = if src.components().count() == 0 {
|
||||||
|
output_root.clone()
|
||||||
|
} else {
|
||||||
|
output_root.join(&src)
|
||||||
|
};
|
||||||
|
if fs::metadata(&dest).is_err() {
|
||||||
|
fs::create_dir_all(&dest)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for entry in fs::read_dir(working_path)? {
|
||||||
|
let entry = entry?;
|
||||||
|
let path = entry.path();
|
||||||
|
if path.is_dir() {
|
||||||
|
stack.push(path);
|
||||||
|
} else if let Some(filename) = path.file_name() {
|
||||||
|
let dest_path = dest.join(filename);
|
||||||
|
fs::copy(&path, &dest_path)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused_must_use)]
|
||||||
|
fn main() -> std::io::Result<()> {
|
||||||
|
let install_path = dirs::config_dir()
|
||||||
|
.expect("Couldn't find a configuration directory to install to.")
|
||||||
|
.join("dttyper");
|
||||||
|
fs::create_dir_all(&install_path);
|
||||||
|
|
||||||
|
let resources_path = env::current_dir()
|
||||||
|
.expect("Couldn't find the source directory.")
|
||||||
|
.join("resources")
|
||||||
|
.join("runtime");
|
||||||
|
copy(resources_path, &install_path);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
121
flake.lock
Normal file
121
flake.lock
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"advisory-db": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1711896428,
|
||||||
|
"narHash": "sha256-cZfXcw6dkd+00dOnD0tD/GLX7gEU/piVUF8SOKRIjf4=",
|
||||||
|
"owner": "rustsec",
|
||||||
|
"repo": "advisory-db",
|
||||||
|
"rev": "799ff4a10673405b2334f6653519fb092aa99845",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "rustsec",
|
||||||
|
"repo": "advisory-db",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"crane": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1712015038,
|
||||||
|
"narHash": "sha256-opeWL/FPV7nnbfUavSWIDy+N5bUshF2CyJK6beVvjv4=",
|
||||||
|
"owner": "ipetkov",
|
||||||
|
"repo": "crane",
|
||||||
|
"rev": "b245ee3472cbfd82394047b536e117a32b4c7850",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "ipetkov",
|
||||||
|
"repo": "crane",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fenix": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"rust-analyzer-src": []
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1712038998,
|
||||||
|
"narHash": "sha256-bVIEz07/SLxPRRo+1G0cUd26KhoCj8yQc8myhf/93FM=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "fenix",
|
||||||
|
"rev": "b1b59b4d908d3e64a7e923a7b434e94e03626ec0",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "fenix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1710146030,
|
||||||
|
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1711715736,
|
||||||
|
"narHash": "sha256-9slQ609YqT9bT/MNX9+5k5jltL9zgpn36DpFB7TkttM=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "807c549feabce7eddbf259dbdcec9e0600a0660d",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixpkgs-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"advisory-db": "advisory-db",
|
||||||
|
"crane": "crane",
|
||||||
|
"fenix": "fenix",
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
134
flake.nix
Normal file
134
flake.nix
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
{
|
||||||
|
description = "Build a cargo project";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||||
|
|
||||||
|
crane = {
|
||||||
|
url = "github:ipetkov/crane";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
|
||||||
|
fenix = {
|
||||||
|
url = "github:nix-community/fenix";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
inputs.rust-analyzer-src.follows = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
|
||||||
|
advisory-db = {
|
||||||
|
url = "github:rustsec/advisory-db";
|
||||||
|
flake = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, crane, fenix, flake-utils, advisory-db, ... }:
|
||||||
|
flake-utils.lib.eachDefaultSystem (system:
|
||||||
|
let
|
||||||
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
|
|
||||||
|
inherit (pkgs) lib;
|
||||||
|
|
||||||
|
craneLib = crane.lib.${system};
|
||||||
|
src = craneLib.cleanCargoSource (craneLib.path ./.);
|
||||||
|
|
||||||
|
# Common arguments can be set here to avoid repeating them later
|
||||||
|
commonArgs = {
|
||||||
|
inherit src;
|
||||||
|
strictDeps = true;
|
||||||
|
|
||||||
|
buildInputs = [
|
||||||
|
# Add additional build inputs here
|
||||||
|
] ++ lib.optionals pkgs.stdenv.isDarwin [
|
||||||
|
# Additional darwin specific inputs can be set here
|
||||||
|
pkgs.libiconv
|
||||||
|
];
|
||||||
|
|
||||||
|
# Additional environment variables can be set directly
|
||||||
|
# MY_CUSTOM_VAR = "some value";
|
||||||
|
};
|
||||||
|
|
||||||
|
craneLibLLvmTools = craneLib.overrideToolchain
|
||||||
|
(fenix.packages.${system}.complete.withComponents [
|
||||||
|
"cargo"
|
||||||
|
"llvm-tools"
|
||||||
|
"rustc"
|
||||||
|
]);
|
||||||
|
|
||||||
|
# Build *just* the cargo dependencies, so we can reuse
|
||||||
|
# all of that work (e.g. via cachix) when running in CI
|
||||||
|
cargoArtifacts = craneLib.buildDepsOnly commonArgs;
|
||||||
|
|
||||||
|
# Build the actual crate itself, reusing the dependency
|
||||||
|
# artifacts from above.
|
||||||
|
dttyper = craneLib.buildPackage (commonArgs // {
|
||||||
|
inherit cargoArtifacts;
|
||||||
|
});
|
||||||
|
in
|
||||||
|
{
|
||||||
|
checks = {
|
||||||
|
# Build the crate as part of `nix flake check` for convenience
|
||||||
|
inherit dttyper;
|
||||||
|
|
||||||
|
# Run clippy (and deny all warnings) on the crate source,
|
||||||
|
# again, reusing the dependency artifacts from above.
|
||||||
|
#
|
||||||
|
# Note that this is done as a separate derivation so that
|
||||||
|
# we can block the CI if there are issues here, but not
|
||||||
|
# prevent downstream consumers from building our crate by itself.
|
||||||
|
dttyper-clippy = craneLib.cargoClippy (commonArgs // {
|
||||||
|
inherit cargoArtifacts;
|
||||||
|
cargoClippyExtraArgs = "--all-targets -- --deny warnings";
|
||||||
|
});
|
||||||
|
|
||||||
|
dttyper-doc = craneLib.cargoDoc (commonArgs // {
|
||||||
|
inherit cargoArtifacts;
|
||||||
|
});
|
||||||
|
|
||||||
|
# Check formatting
|
||||||
|
dttyper-fmt = craneLib.cargoFmt {
|
||||||
|
inherit src;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Audit dependencies
|
||||||
|
dttyper-audit = craneLib.cargoAudit {
|
||||||
|
inherit src advisory-db;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Audit licenses
|
||||||
|
dttyper-deny = craneLib.cargoDeny {
|
||||||
|
inherit src;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Run tests with cargo-nextest
|
||||||
|
# Consider setting `doCheck = false` on `dttyper` if you do not want
|
||||||
|
# the tests to run twice
|
||||||
|
dttyper-nextest = craneLib.cargoNextest (commonArgs // {
|
||||||
|
inherit cargoArtifacts;
|
||||||
|
partitions = 1;
|
||||||
|
partitionType = "count";
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
packages = {
|
||||||
|
default = dttyper;
|
||||||
|
} // lib.optionalAttrs (!pkgs.stdenv.isDarwin) {
|
||||||
|
dttyper-llvm-coverage = craneLibLLvmTools.cargoLlvmCov (commonArgs // {
|
||||||
|
inherit cargoArtifacts;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
apps.default = flake-utils.lib.mkApp {
|
||||||
|
drv = dttyper;
|
||||||
|
};
|
||||||
|
|
||||||
|
devShells.default = craneLib.devShell {
|
||||||
|
checks = self.checks.${system};
|
||||||
|
OPENSSL_NO_VENDOR = 1;
|
||||||
|
packages = [
|
||||||
|
pkgs.openssl
|
||||||
|
];
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
976
panel.json
Normal file
976
panel.json
Normal file
|
@ -0,0 +1,976 @@
|
||||||
|
{
|
||||||
|
"__inputs": [
|
||||||
|
{
|
||||||
|
"name": "DS_INFLUXDB",
|
||||||
|
"label": "influxdb",
|
||||||
|
"description": "",
|
||||||
|
"type": "datasource",
|
||||||
|
"pluginId": "influxdb",
|
||||||
|
"pluginName": "InfluxDB"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"__elements": {},
|
||||||
|
"__requires": [
|
||||||
|
{
|
||||||
|
"type": "grafana",
|
||||||
|
"id": "grafana",
|
||||||
|
"name": "Grafana",
|
||||||
|
"version": "10.3.3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "datasource",
|
||||||
|
"id": "influxdb",
|
||||||
|
"name": "InfluxDB",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "panel",
|
||||||
|
"id": "piechart",
|
||||||
|
"name": "Pie chart",
|
||||||
|
"version": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "panel",
|
||||||
|
"id": "stat",
|
||||||
|
"name": "Stat",
|
||||||
|
"version": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "panel",
|
||||||
|
"id": "table",
|
||||||
|
"name": "Table",
|
||||||
|
"version": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "panel",
|
||||||
|
"id": "timeseries",
|
||||||
|
"name": "Time series",
|
||||||
|
"version": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"annotations": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"builtIn": 1,
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana",
|
||||||
|
"uid": "-- Grafana --"
|
||||||
|
},
|
||||||
|
"enable": true,
|
||||||
|
"hide": true,
|
||||||
|
"iconColor": "rgba(0, 211, 255, 1)",
|
||||||
|
"name": "Annotations & Alerts",
|
||||||
|
"type": "dashboard"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"editable": true,
|
||||||
|
"fiscalYearStartMonth": 0,
|
||||||
|
"graphTooltip": 0,
|
||||||
|
"id": null,
|
||||||
|
"links": [],
|
||||||
|
"liveNow": false,
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"gridPos": {
|
||||||
|
"h": 1,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 19,
|
||||||
|
"title": "Stats",
|
||||||
|
"type": "row"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "influxdb",
|
||||||
|
"uid": "${DS_INFLUXDB}"
|
||||||
|
},
|
||||||
|
"description": "",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "yellow",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unitScale": true
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 4,
|
||||||
|
"x": 0,
|
||||||
|
"y": 1
|
||||||
|
},
|
||||||
|
"id": 9,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"showPercentChange": false,
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "10.3.3",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "influxdb",
|
||||||
|
"uid": "${DS_INFLUXDB}"
|
||||||
|
},
|
||||||
|
"query": "from(bucket: \"dttyper\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"test\")\n |> filter(fn: (r) => r[\"_field\"] == \"correct_types\")\n |> aggregateWindow(every: 999d, fn: sum, createEmpty: false)\n |> group()\n |> sum(column: \"_value\")",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Correct words typed",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "influxdb",
|
||||||
|
"uid": "${DS_INFLUXDB}"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 30
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#EAB839",
|
||||||
|
"value": 40
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#6ED0E0",
|
||||||
|
"value": 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "yellow",
|
||||||
|
"value": 60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "dark-orange",
|
||||||
|
"value": 70
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#1F78C1",
|
||||||
|
"value": 80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 90
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "dark-green",
|
||||||
|
"value": 95
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "percent",
|
||||||
|
"unitScale": true
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 3,
|
||||||
|
"x": 4,
|
||||||
|
"y": 1
|
||||||
|
},
|
||||||
|
"id": 18,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"showPercentChange": false,
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "10.3.3",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "influxdb",
|
||||||
|
"uid": "${DS_INFLUXDB}"
|
||||||
|
},
|
||||||
|
"query": "from(bucket: \"dttyper\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"test\")\n |> filter(fn: (r) => r[\"_field\"] == \"accuracy\")\n |> aggregateWindow(every: 999d, fn: mean, createEmpty: false)\n |> group()\n |> mean(column: \"_value\")",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Overall accuracy %",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "influxdb",
|
||||||
|
"uid": "${DS_INFLUXDB}"
|
||||||
|
},
|
||||||
|
"description": "",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unitScale": true
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 4,
|
||||||
|
"x": 7,
|
||||||
|
"y": 1
|
||||||
|
},
|
||||||
|
"id": 22,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"showPercentChange": false,
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "10.3.3",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "influxdb",
|
||||||
|
"uid": "${DS_INFLUXDB}"
|
||||||
|
},
|
||||||
|
"query": "from(bucket: \"dttyper\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"test\")\n |> filter(fn: (r) => r[\"_field\"] == \"incorrect_types\")\n |> aggregateWindow(every: 999d, fn: sum, createEmpty: false)\n |> group()\n |> sum(column: \"_value\")",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Incorrectly typed word",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "influxdb",
|
||||||
|
"uid": "${DS_INFLUXDB}"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unitScale": true
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 4,
|
||||||
|
"x": 11,
|
||||||
|
"y": 1
|
||||||
|
},
|
||||||
|
"id": 12,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"showPercentChange": false,
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "10.3.3",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "influxdb",
|
||||||
|
"uid": "${DS_INFLUXDB}"
|
||||||
|
},
|
||||||
|
"query": "from(bucket: \"dttyper\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"test\")\n // get dummy field\n |> filter(fn: (r) => r[\"_field\"] == \"incorrect_types\")\n |> group()\n |> count()",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Tests completed",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "influxdb",
|
||||||
|
"uid": "${DS_INFLUXDB}"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "dark-red",
|
||||||
|
"value": 40
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#EAB839",
|
||||||
|
"value": 60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#6ED0E0",
|
||||||
|
"value": 70
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#EF843C",
|
||||||
|
"value": 80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#E24D42",
|
||||||
|
"value": 90
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#1F78C1",
|
||||||
|
"value": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 110
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#BA43A9",
|
||||||
|
"value": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "dark-purple",
|
||||||
|
"value": 130
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#705DA0",
|
||||||
|
"value": 140
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#508642",
|
||||||
|
"value": 150
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unitScale": true
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 3,
|
||||||
|
"x": 15,
|
||||||
|
"y": 1
|
||||||
|
},
|
||||||
|
"id": 16,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"max"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"showPercentChange": false,
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "10.3.3",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "influxdb",
|
||||||
|
"uid": "${DS_INFLUXDB}"
|
||||||
|
},
|
||||||
|
"query": "from(bucket: \"dttyper\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"test\")\n |> filter(fn: (r) => r[\"_field\"] == \"wpm\")\n |> aggregateWindow(every: v.windowPeriod, fn: max, createEmpty: false)\n |> group()",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Highest wpm",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "influxdb",
|
||||||
|
"uid": "${DS_INFLUXDB}"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "dark-red",
|
||||||
|
"value": 40
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#EAB839",
|
||||||
|
"value": 60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#6ED0E0",
|
||||||
|
"value": 70
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#EF843C",
|
||||||
|
"value": 80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#E24D42",
|
||||||
|
"value": 90
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#1F78C1",
|
||||||
|
"value": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 110
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#BA43A9",
|
||||||
|
"value": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "dark-purple",
|
||||||
|
"value": 130
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#705DA0",
|
||||||
|
"value": 140
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#508642",
|
||||||
|
"value": 150
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unitScale": true
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 3,
|
||||||
|
"x": 18,
|
||||||
|
"y": 1
|
||||||
|
},
|
||||||
|
"id": 21,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"showPercentChange": false,
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "10.3.3",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "influxdb",
|
||||||
|
"uid": "${DS_INFLUXDB}"
|
||||||
|
},
|
||||||
|
"query": "from(bucket: \"dttyper\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"test\")\n |> filter(fn: (r) => r[\"_field\"] == \"wpm\")\n |> aggregateWindow(every: v.windowPeriod, fn: min, createEmpty: false)\n |> group()",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Average WPM",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "influxdb",
|
||||||
|
"uid": "${DS_INFLUXDB}"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "dark-red",
|
||||||
|
"value": 40
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#EAB839",
|
||||||
|
"value": 60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#6ED0E0",
|
||||||
|
"value": 70
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#EF843C",
|
||||||
|
"value": 80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#E24D42",
|
||||||
|
"value": 90
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#1F78C1",
|
||||||
|
"value": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 110
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#BA43A9",
|
||||||
|
"value": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "dark-purple",
|
||||||
|
"value": 130
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#705DA0",
|
||||||
|
"value": 140
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#508642",
|
||||||
|
"value": 150
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unitScale": true
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 3,
|
||||||
|
"x": 21,
|
||||||
|
"y": 1
|
||||||
|
},
|
||||||
|
"id": 13,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"min"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"showPercentChange": false,
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "10.3.3",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "influxdb",
|
||||||
|
"uid": "${DS_INFLUXDB}"
|
||||||
|
},
|
||||||
|
"query": "from(bucket: \"dttyper\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"test\")\n |> filter(fn: (r) => r[\"_field\"] == \"wpm\")\n |> aggregateWindow(every: 999d, fn: min, createEmpty: false)\n |> group()",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Lowest wpm ",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsed": false,
|
||||||
|
"gridPos": {
|
||||||
|
"h": 1,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 5
|
||||||
|
},
|
||||||
|
"id": 3,
|
||||||
|
"panels": [],
|
||||||
|
"title": "Global",
|
||||||
|
"type": "row"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "influxdb",
|
||||||
|
"uid": "${DS_INFLUXDB}"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unitScale": true
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 11,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 6
|
||||||
|
},
|
||||||
|
"id": 20,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "influxdb",
|
||||||
|
"uid": "${DS_INFLUXDB}"
|
||||||
|
},
|
||||||
|
"query": "from(bucket: \"dttyper\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"test\")\n |> filter(fn: (r) => r[\"_field\"] == \"wpm\")",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "WPM overtime",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "influxdb",
|
||||||
|
"uid": "${DS_INFLUXDB}"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"unitScale": true
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 9,
|
||||||
|
"w": 10,
|
||||||
|
"x": 0,
|
||||||
|
"y": 17
|
||||||
|
},
|
||||||
|
"id": 23,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"pieType": "pie",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"mean"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pluginVersion": "10.3.3",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "influxdb",
|
||||||
|
"uid": "${DS_INFLUXDB}"
|
||||||
|
},
|
||||||
|
"query": "from(bucket: \"dttyper\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"test\")\n |> filter(fn: (r) => r[\"_field\"] == \"wpm\")",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "WPM per test",
|
||||||
|
"transformations": [],
|
||||||
|
"type": "piechart"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "influxdb",
|
||||||
|
"uid": "${DS_INFLUXDB}"
|
||||||
|
},
|
||||||
|
"description": "",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"align": "auto",
|
||||||
|
"cellOptions": {
|
||||||
|
"type": "auto"
|
||||||
|
},
|
||||||
|
"inspect": false
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unitScale": true
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 9,
|
||||||
|
"w": 14,
|
||||||
|
"x": 10,
|
||||||
|
"y": 17
|
||||||
|
},
|
||||||
|
"id": 24,
|
||||||
|
"options": {
|
||||||
|
"cellHeight": "sm",
|
||||||
|
"footer": {
|
||||||
|
"countRows": false,
|
||||||
|
"fields": "",
|
||||||
|
"reducer": [
|
||||||
|
"sum"
|
||||||
|
],
|
||||||
|
"show": false
|
||||||
|
},
|
||||||
|
"frameIndex": 2,
|
||||||
|
"showHeader": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "10.3.3",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "influxdb",
|
||||||
|
"uid": "${DS_INFLUXDB}"
|
||||||
|
},
|
||||||
|
"query": "from(bucket: \"dttyper\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"test\")",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "History",
|
||||||
|
"type": "table"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsed": false,
|
||||||
|
"gridPos": {
|
||||||
|
"h": 1,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 26
|
||||||
|
},
|
||||||
|
"id": 2,
|
||||||
|
"panels": [],
|
||||||
|
"title": "Query",
|
||||||
|
"type": "row"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"refresh": false,
|
||||||
|
"schemaVersion": 39,
|
||||||
|
"tags": [
|
||||||
|
"wpm",
|
||||||
|
"keyboard",
|
||||||
|
"dtyper"
|
||||||
|
],
|
||||||
|
"templating": {
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"from": "now-7d",
|
||||||
|
"to": "now"
|
||||||
|
},
|
||||||
|
"timepicker": {
|
||||||
|
"refresh_intervals": [
|
||||||
|
"5s",
|
||||||
|
"10s",
|
||||||
|
"30s",
|
||||||
|
"1m",
|
||||||
|
"5m",
|
||||||
|
"15m",
|
||||||
|
"30m",
|
||||||
|
"1h",
|
||||||
|
"2h",
|
||||||
|
"1d"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"timezone": "",
|
||||||
|
"title": "Ttyper",
|
||||||
|
"uid": "dd78f974-f0dc-456c-8a59-199df1f81eaf",
|
||||||
|
"version": 34,
|
||||||
|
"weekStart": ""
|
||||||
|
}
|
BIN
resources/grafana.png
Normal file
BIN
resources/grafana.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 162 KiB |
BIN
resources/recording.gif
Normal file
BIN
resources/recording.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 MiB |
52
resources/runtime/language/c
Normal file
52
resources/runtime/language/c
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
int
|
||||||
|
char
|
||||||
|
unsigned
|
||||||
|
float
|
||||||
|
void
|
||||||
|
main
|
||||||
|
union
|
||||||
|
long
|
||||||
|
double
|
||||||
|
printf
|
||||||
|
sprintf
|
||||||
|
if
|
||||||
|
else
|
||||||
|
struct
|
||||||
|
fork
|
||||||
|
switch
|
||||||
|
for
|
||||||
|
define
|
||||||
|
return
|
||||||
|
include
|
||||||
|
case
|
||||||
|
&&
|
||||||
|
||
|
||||||
|
break
|
||||||
|
bool
|
||||||
|
static
|
||||||
|
public
|
||||||
|
enum
|
||||||
|
typedef
|
||||||
|
private
|
||||||
|
exit
|
||||||
|
<stdio.h>
|
||||||
|
scanf
|
||||||
|
NULL
|
||||||
|
malloc
|
||||||
|
calloc
|
||||||
|
free
|
||||||
|
realloc
|
||||||
|
<string.h>
|
||||||
|
fgets
|
||||||
|
strcmp
|
||||||
|
strcpy
|
||||||
|
fputs
|
||||||
|
stdout
|
||||||
|
EOF
|
||||||
|
getc
|
||||||
|
while
|
||||||
|
fclose
|
||||||
|
fopen
|
||||||
|
do
|
||||||
|
fscanf
|
||||||
|
extern
|
139
resources/runtime/language/cpp
Normal file
139
resources/runtime/language/cpp
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
--i
|
||||||
|
-=
|
||||||
|
!=
|
||||||
|
*=
|
||||||
|
/=
|
||||||
|
&&
|
||||||
|
&=
|
||||||
|
#define
|
||||||
|
#include
|
||||||
|
#pragma
|
||||||
|
%=
|
||||||
|
^=
|
||||||
|
++i
|
||||||
|
+=
|
||||||
|
<<
|
||||||
|
<<=
|
||||||
|
<=
|
||||||
|
<iostream>
|
||||||
|
<map>
|
||||||
|
<string>
|
||||||
|
<vector>
|
||||||
|
==
|
||||||
|
>=
|
||||||
|
>>
|
||||||
|
>>=
|
||||||
|
|=
|
||||||
|
||
|
||||||
|
alignas
|
||||||
|
alignof
|
||||||
|
and
|
||||||
|
and_eq
|
||||||
|
asm
|
||||||
|
atomic_cancel
|
||||||
|
atomic_commit
|
||||||
|
atomic_noexcept
|
||||||
|
auto
|
||||||
|
bitand
|
||||||
|
bitor
|
||||||
|
bool
|
||||||
|
break
|
||||||
|
case
|
||||||
|
catch
|
||||||
|
char
|
||||||
|
char16_t
|
||||||
|
char32_t
|
||||||
|
char8_t
|
||||||
|
class
|
||||||
|
co_await
|
||||||
|
co_return
|
||||||
|
co_yield
|
||||||
|
compl
|
||||||
|
concept
|
||||||
|
const
|
||||||
|
const_cast
|
||||||
|
consteval
|
||||||
|
constexpr
|
||||||
|
constinit
|
||||||
|
continue
|
||||||
|
decltype
|
||||||
|
default
|
||||||
|
delete
|
||||||
|
do
|
||||||
|
double
|
||||||
|
dynamic_cast
|
||||||
|
else
|
||||||
|
enum
|
||||||
|
explicit
|
||||||
|
export
|
||||||
|
extern
|
||||||
|
false
|
||||||
|
final
|
||||||
|
float
|
||||||
|
for
|
||||||
|
friend
|
||||||
|
goto
|
||||||
|
i--
|
||||||
|
i++
|
||||||
|
if
|
||||||
|
import
|
||||||
|
inline
|
||||||
|
int
|
||||||
|
long
|
||||||
|
module
|
||||||
|
mutable
|
||||||
|
namespace
|
||||||
|
new
|
||||||
|
noexcept
|
||||||
|
not
|
||||||
|
not_eq
|
||||||
|
nullptr
|
||||||
|
operator
|
||||||
|
or
|
||||||
|
or_eq
|
||||||
|
override
|
||||||
|
private
|
||||||
|
protected
|
||||||
|
public
|
||||||
|
reflexpr
|
||||||
|
register
|
||||||
|
reinterpret_cast
|
||||||
|
requires
|
||||||
|
return
|
||||||
|
short
|
||||||
|
signed
|
||||||
|
sizeof
|
||||||
|
sizeof(long long)
|
||||||
|
static
|
||||||
|
static_assert
|
||||||
|
static_cast
|
||||||
|
std::cout
|
||||||
|
std::endl
|
||||||
|
std::map
|
||||||
|
std::move
|
||||||
|
std::string
|
||||||
|
std::vector
|
||||||
|
struct
|
||||||
|
switch
|
||||||
|
synchronized
|
||||||
|
template
|
||||||
|
template<class T>
|
||||||
|
template<typename T>
|
||||||
|
this
|
||||||
|
thread_local
|
||||||
|
throw
|
||||||
|
true
|
||||||
|
try
|
||||||
|
typedef
|
||||||
|
typeid
|
||||||
|
typename
|
||||||
|
union
|
||||||
|
unsigned
|
||||||
|
using
|
||||||
|
virtual
|
||||||
|
void
|
||||||
|
volatile
|
||||||
|
wchar_t
|
||||||
|
while
|
||||||
|
xor
|
||||||
|
xor_eq
|
108
resources/runtime/language/csharp
Normal file
108
resources/runtime/language/csharp
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
abstract
|
||||||
|
as
|
||||||
|
base
|
||||||
|
bool
|
||||||
|
break
|
||||||
|
byte
|
||||||
|
case
|
||||||
|
catch
|
||||||
|
char
|
||||||
|
checked
|
||||||
|
class
|
||||||
|
const
|
||||||
|
continue
|
||||||
|
decimal
|
||||||
|
default
|
||||||
|
delegate
|
||||||
|
do
|
||||||
|
double
|
||||||
|
else
|
||||||
|
enum
|
||||||
|
even
|
||||||
|
explicit
|
||||||
|
extern
|
||||||
|
false
|
||||||
|
finally
|
||||||
|
fixed
|
||||||
|
float
|
||||||
|
for
|
||||||
|
foreach
|
||||||
|
goto
|
||||||
|
if
|
||||||
|
implicit
|
||||||
|
in
|
||||||
|
init
|
||||||
|
int
|
||||||
|
interface
|
||||||
|
internal
|
||||||
|
is
|
||||||
|
lock
|
||||||
|
long
|
||||||
|
namespace
|
||||||
|
new
|
||||||
|
null
|
||||||
|
object
|
||||||
|
operator
|
||||||
|
out
|
||||||
|
override
|
||||||
|
params
|
||||||
|
private
|
||||||
|
protected
|
||||||
|
public
|
||||||
|
readonly
|
||||||
|
ref
|
||||||
|
return
|
||||||
|
sbyte
|
||||||
|
sealed
|
||||||
|
short
|
||||||
|
sizeof
|
||||||
|
stackalloc
|
||||||
|
static
|
||||||
|
string
|
||||||
|
struct
|
||||||
|
switch
|
||||||
|
this
|
||||||
|
throw
|
||||||
|
true
|
||||||
|
try
|
||||||
|
typeof
|
||||||
|
uint
|
||||||
|
ulong
|
||||||
|
unchecked
|
||||||
|
unsafe
|
||||||
|
ushort
|
||||||
|
using
|
||||||
|
virtual
|
||||||
|
void
|
||||||
|
volatile
|
||||||
|
add
|
||||||
|
alias
|
||||||
|
ascending
|
||||||
|
async
|
||||||
|
await
|
||||||
|
by
|
||||||
|
descending
|
||||||
|
dynamic
|
||||||
|
equals
|
||||||
|
from
|
||||||
|
get
|
||||||
|
global
|
||||||
|
group
|
||||||
|
into
|
||||||
|
join
|
||||||
|
let
|
||||||
|
nameof
|
||||||
|
notnull
|
||||||
|
on
|
||||||
|
orderby
|
||||||
|
partial
|
||||||
|
remove
|
||||||
|
select
|
||||||
|
set
|
||||||
|
unmanaged
|
||||||
|
value
|
||||||
|
var
|
||||||
|
when
|
||||||
|
where
|
||||||
|
with
|
||||||
|
yield
|
2000
resources/runtime/language/english-advanced
Normal file
2000
resources/runtime/language/english-advanced
Normal file
File diff suppressed because it is too large
Load diff
300
resources/runtime/language/english-ngrams
Normal file
300
resources/runtime/language/english-ngrams
Normal file
|
@ -0,0 +1,300 @@
|
||||||
|
ou
|
||||||
|
te
|
||||||
|
me
|
||||||
|
ve
|
||||||
|
li
|
||||||
|
ed
|
||||||
|
ng
|
||||||
|
ne
|
||||||
|
at
|
||||||
|
ll
|
||||||
|
of
|
||||||
|
ha
|
||||||
|
he
|
||||||
|
in
|
||||||
|
st
|
||||||
|
ce
|
||||||
|
le
|
||||||
|
as
|
||||||
|
ti
|
||||||
|
or
|
||||||
|
se
|
||||||
|
om
|
||||||
|
is
|
||||||
|
al
|
||||||
|
nt
|
||||||
|
co
|
||||||
|
ro
|
||||||
|
io
|
||||||
|
re
|
||||||
|
it
|
||||||
|
on
|
||||||
|
to
|
||||||
|
es
|
||||||
|
th
|
||||||
|
ar
|
||||||
|
en
|
||||||
|
hi
|
||||||
|
er
|
||||||
|
ea
|
||||||
|
si
|
||||||
|
nd
|
||||||
|
ri
|
||||||
|
ma
|
||||||
|
be
|
||||||
|
ic
|
||||||
|
de
|
||||||
|
ra
|
||||||
|
ch
|
||||||
|
ur
|
||||||
|
an
|
||||||
|
con
|
||||||
|
lit
|
||||||
|
pre
|
||||||
|
din
|
||||||
|
tha
|
||||||
|
art
|
||||||
|
ore
|
||||||
|
lan
|
||||||
|
tri
|
||||||
|
der
|
||||||
|
ren
|
||||||
|
orm
|
||||||
|
tho
|
||||||
|
ion
|
||||||
|
eri
|
||||||
|
ran
|
||||||
|
ove
|
||||||
|
ast
|
||||||
|
mor
|
||||||
|
wit
|
||||||
|
can
|
||||||
|
anc
|
||||||
|
rie
|
||||||
|
ies
|
||||||
|
res
|
||||||
|
rou
|
||||||
|
ard
|
||||||
|
nes
|
||||||
|
ime
|
||||||
|
ght
|
||||||
|
age
|
||||||
|
hat
|
||||||
|
one
|
||||||
|
sio
|
||||||
|
hin
|
||||||
|
spe
|
||||||
|
cou
|
||||||
|
ure
|
||||||
|
ntr
|
||||||
|
pla
|
||||||
|
hei
|
||||||
|
omp
|
||||||
|
ers
|
||||||
|
ass
|
||||||
|
ont
|
||||||
|
rec
|
||||||
|
nde
|
||||||
|
ant
|
||||||
|
she
|
||||||
|
ith
|
||||||
|
iti
|
||||||
|
ese
|
||||||
|
out
|
||||||
|
ver
|
||||||
|
tte
|
||||||
|
oth
|
||||||
|
sin
|
||||||
|
and
|
||||||
|
nal
|
||||||
|
men
|
||||||
|
whe
|
||||||
|
rit
|
||||||
|
but
|
||||||
|
ear
|
||||||
|
int
|
||||||
|
nte
|
||||||
|
are
|
||||||
|
cha
|
||||||
|
wor
|
||||||
|
tic
|
||||||
|
ave
|
||||||
|
ter
|
||||||
|
nat
|
||||||
|
sta
|
||||||
|
rat
|
||||||
|
nts
|
||||||
|
cti
|
||||||
|
sen
|
||||||
|
ame
|
||||||
|
ces
|
||||||
|
lar
|
||||||
|
oun
|
||||||
|
rin
|
||||||
|
whi
|
||||||
|
ide
|
||||||
|
ven
|
||||||
|
end
|
||||||
|
ted
|
||||||
|
rea
|
||||||
|
you
|
||||||
|
ive
|
||||||
|
pos
|
||||||
|
oug
|
||||||
|
par
|
||||||
|
uld
|
||||||
|
ati
|
||||||
|
ble
|
||||||
|
les
|
||||||
|
ica
|
||||||
|
era
|
||||||
|
tur
|
||||||
|
ind
|
||||||
|
ons
|
||||||
|
ric
|
||||||
|
ned
|
||||||
|
ate
|
||||||
|
ing
|
||||||
|
ste
|
||||||
|
ust
|
||||||
|
ort
|
||||||
|
ssi
|
||||||
|
all
|
||||||
|
fro
|
||||||
|
the
|
||||||
|
tes
|
||||||
|
ess
|
||||||
|
tiv
|
||||||
|
han
|
||||||
|
hou
|
||||||
|
ent
|
||||||
|
ial
|
||||||
|
ell
|
||||||
|
ome
|
||||||
|
ere
|
||||||
|
ona
|
||||||
|
eat
|
||||||
|
dis
|
||||||
|
tra
|
||||||
|
ugh
|
||||||
|
ich
|
||||||
|
eas
|
||||||
|
str
|
||||||
|
ist
|
||||||
|
ins
|
||||||
|
tio
|
||||||
|
den
|
||||||
|
igh
|
||||||
|
und
|
||||||
|
wer
|
||||||
|
rom
|
||||||
|
use
|
||||||
|
ice
|
||||||
|
thi
|
||||||
|
abl
|
||||||
|
ern
|
||||||
|
man
|
||||||
|
ten
|
||||||
|
ple
|
||||||
|
hav
|
||||||
|
lat
|
||||||
|
red
|
||||||
|
lly
|
||||||
|
enc
|
||||||
|
ite
|
||||||
|
lin
|
||||||
|
our
|
||||||
|
was
|
||||||
|
sti
|
||||||
|
nti
|
||||||
|
tin
|
||||||
|
tor
|
||||||
|
tat
|
||||||
|
hic
|
||||||
|
tan
|
||||||
|
hen
|
||||||
|
not
|
||||||
|
eme
|
||||||
|
had
|
||||||
|
inc
|
||||||
|
oul
|
||||||
|
eve
|
||||||
|
per
|
||||||
|
sed
|
||||||
|
ene
|
||||||
|
app
|
||||||
|
cal
|
||||||
|
pro
|
||||||
|
any
|
||||||
|
for
|
||||||
|
ect
|
||||||
|
een
|
||||||
|
ine
|
||||||
|
ill
|
||||||
|
por
|
||||||
|
ral
|
||||||
|
com
|
||||||
|
hey
|
||||||
|
nce
|
||||||
|
ity
|
||||||
|
ord
|
||||||
|
ose
|
||||||
|
her
|
||||||
|
ndi
|
||||||
|
ain
|
||||||
|
ous
|
||||||
|
tim
|
||||||
|
act
|
||||||
|
min
|
||||||
|
est
|
||||||
|
his
|
||||||
|
form
|
||||||
|
ctio
|
||||||
|
ally
|
||||||
|
hich
|
||||||
|
ring
|
||||||
|
whic
|
||||||
|
that
|
||||||
|
ting
|
||||||
|
nter
|
||||||
|
ions
|
||||||
|
othe
|
||||||
|
thei
|
||||||
|
ould
|
||||||
|
were
|
||||||
|
thin
|
||||||
|
tive
|
||||||
|
ence
|
||||||
|
over
|
||||||
|
sion
|
||||||
|
ever
|
||||||
|
heir
|
||||||
|
ture
|
||||||
|
inte
|
||||||
|
ight
|
||||||
|
some
|
||||||
|
cont
|
||||||
|
this
|
||||||
|
ents
|
||||||
|
ment
|
||||||
|
part
|
||||||
|
ough
|
||||||
|
rati
|
||||||
|
ding
|
||||||
|
ther
|
||||||
|
cons
|
||||||
|
here
|
||||||
|
ning
|
||||||
|
ance
|
||||||
|
ecti
|
||||||
|
comp
|
||||||
|
from
|
||||||
|
tion
|
||||||
|
ated
|
||||||
|
they
|
||||||
|
ical
|
||||||
|
atio
|
||||||
|
with
|
||||||
|
able
|
||||||
|
have
|
||||||
|
pres
|
50
resources/runtime/language/english-pirate
Normal file
50
resources/runtime/language/english-pirate
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
ahoy
|
||||||
|
anchor
|
||||||
|
arrr
|
||||||
|
avast
|
||||||
|
aye
|
||||||
|
beast
|
||||||
|
bilge
|
||||||
|
blackbeard
|
||||||
|
blimey
|
||||||
|
bombard
|
||||||
|
booty
|
||||||
|
bounty
|
||||||
|
buccaneer
|
||||||
|
bumboo
|
||||||
|
capsize
|
||||||
|
cutlass
|
||||||
|
doubloon
|
||||||
|
doughboy
|
||||||
|
eyepatch
|
||||||
|
freebooter
|
||||||
|
grog
|
||||||
|
hardtack
|
||||||
|
hogshead
|
||||||
|
hornswoggle
|
||||||
|
island
|
||||||
|
keel
|
||||||
|
landlubber
|
||||||
|
man-o-war
|
||||||
|
marooned
|
||||||
|
matey
|
||||||
|
mutiny
|
||||||
|
parley
|
||||||
|
peg-leg
|
||||||
|
picaroon
|
||||||
|
pillage
|
||||||
|
plank
|
||||||
|
plunder
|
||||||
|
poop-deck
|
||||||
|
privateer
|
||||||
|
rigging
|
||||||
|
rullock
|
||||||
|
rumfustian
|
||||||
|
sail
|
||||||
|
scallywag
|
||||||
|
scupper
|
||||||
|
scurvy
|
||||||
|
sea
|
||||||
|
seamen
|
||||||
|
swashbuckler
|
||||||
|
treasure
|
151
resources/runtime/language/english-text
Normal file
151
resources/runtime/language/english-text
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
The manager of the fruit stand always sat and only sold vegetables.
|
||||||
|
Always bring cinnamon buns on a deep-sea diving expedition.
|
||||||
|
He spiked his hair green to support his iguana.
|
||||||
|
As he waited for the shower to warm, he noticed that he could hear water change temperature.
|
||||||
|
The door slammed on the watermelon.
|
||||||
|
The two walked down the slot canyon oblivious to the sound of thunder in the distance.
|
||||||
|
We should play with legos at camp.
|
||||||
|
The delicious aroma from the kitchen was ruined by cigarette smoke.
|
||||||
|
Everyone says they love nature until they realize how dangerous she can be.
|
||||||
|
Eating eggs on Thursday for choir practice was recommended.
|
||||||
|
Thigh-high in the water, the fisherman’s hope for dinner soon turned to despair.
|
||||||
|
The beauty of the African sunset disguised the danger lurking nearby.
|
||||||
|
He learned the hardest lesson of his life and had the scars, both physical and mental, to prove it.
|
||||||
|
Seek success, but always be prepared for random cats.
|
||||||
|
She advised him to come back at once.
|
||||||
|
This book is sure to liquefy your brain.
|
||||||
|
He found his art never progressed when he literally used his sweat and tears.
|
||||||
|
He is no James Bond; his name is Roger Moore.
|
||||||
|
He excelled at firing people nicely.
|
||||||
|
There was no ice cream in the freezer, nor did they have money to go to the store.
|
||||||
|
When I cook spaghetti, I like to boil it a few minutes past al dente so the noodles are super slippery.
|
||||||
|
He went back to the video to see what had been recorded and was shocked at what he saw.
|
||||||
|
He strives to keep the best lawn in the neighborhood.
|
||||||
|
Even with the snow falling outside, she felt it appropriate to wear her bikini.
|
||||||
|
He turned in the research paper on Friday; otherwise, he would have not passed the class.
|
||||||
|
8% of 25 is the same as 25% of 8 and one of them is much easier to do in your head.
|
||||||
|
Had he known what was going to happen, he would have never stepped into the shower.
|
||||||
|
Tomatoes make great weapons when water balloons aren’t available.
|
||||||
|
The rain pelted the windshield as the darkness engulfed us.
|
||||||
|
There should have been a time and a place, but this wasn't it.
|
||||||
|
Peter found road kill an excellent way to save money on dinner.
|
||||||
|
Kevin embraced his ability to be at the wrong place at the wrong time.
|
||||||
|
Flesh-colored yoga pants were far worse than even he feared.
|
||||||
|
This made him feel like an old-style rootbeer float smells.
|
||||||
|
She was amazed by the large chunks of ice washing up on the beach.
|
||||||
|
It caught him off guard that space smelled of seared steak.
|
||||||
|
You realize you're not alone as you sit in your bedroom massaging your calves after a long day of playing tug-of-war with Grandpa Joe in the hospital.
|
||||||
|
To the surprise of everyone, the Rapture happened yesterday but it didn't quite go as expected.
|
||||||
|
Pink horses galloped across the sea.
|
||||||
|
The complicated school homework left the parents trying to help their kids quite confused.
|
||||||
|
It was obvious she was hot, sweaty, and tired.
|
||||||
|
I really want to go to work, but I am too sick to drive.
|
||||||
|
While all her friends were positive that Mary had a sixth sense, she knew she actually had a seventh sense.
|
||||||
|
I only enjoy window shopping when the windows are transparent.
|
||||||
|
I want more detailed information.
|
||||||
|
Their argument could be heard across the parking lot.
|
||||||
|
Writing a list of random sentences is harder than I initially thought it would be.
|
||||||
|
He picked up trash in his spare time to dump in his neighbor's yard.
|
||||||
|
He had a vague sense that trees gave birth to dinosaurs.
|
||||||
|
He colored deep space a soft yellow.
|
||||||
|
I'm worried by the fact that my daughter looks to the local carpet seller as a role model.
|
||||||
|
It caught him off guard that space smelled of seared steak.
|
||||||
|
Art doesn't have to be intentional.
|
||||||
|
She found it strange that people use their cellphones to actually talk to one another.
|
||||||
|
He wondered why at 18 he was old enough to go to war, but not old enough to buy cigarettes.
|
||||||
|
The father died during childbirth.
|
||||||
|
Kevin embraced his ability to be at the wrong place at the wrong time.
|
||||||
|
The fact that there's a stairway to heaven and a highway to hell explains life well.
|
||||||
|
You've been eyeing me all day and waiting for your move like a lion stalking a gazelle in a savannah.
|
||||||
|
Twin 4-month-olds slept in the shade of the palm tree while the mother tanned in the sun.
|
||||||
|
The paintbrush was angry at the color the artist chose to use.
|
||||||
|
The fifty mannequin heads floating in the pool kind of freaked them out.
|
||||||
|
He told us a very exciting adventure story.
|
||||||
|
The Great Dane looked more like a horse than a dog.
|
||||||
|
Jeanne wished she has chosen the red button.
|
||||||
|
The sudden rainstorm washed crocodiles into the ocean.
|
||||||
|
He put heat on the wound to see what would grow.
|
||||||
|
The bread dough reminded her of Santa Clause’s belly.
|
||||||
|
He decided to fake his disappearance to avoid jail.
|
||||||
|
Let me help you with your baggage.
|
||||||
|
The stranger officiates the meal.
|
||||||
|
As the rental car rolled to a stop on the dark road, her fear increased by the moment.
|
||||||
|
The sunblock was handed to the girl before practice, but the burned skin was proof she did not apply it.
|
||||||
|
Happiness can be found in the depths of chocolate pudding.
|
||||||
|
Of course, she loves her pink bunny slippers.
|
||||||
|
After fighting off the alligator, Brian still had to face the anaconda.
|
||||||
|
She couldn't decide of the glass was half empty or half full so she drank it.
|
||||||
|
I want more detailed information.
|
||||||
|
Gary didn't understand why Doug went upstairs to get one dollar bills when he invited him to go cow tipping.
|
||||||
|
She works two jobs to make ends meet; at least, that was her reason for not having time to join us.
|
||||||
|
I cheated while playing the darts tournament by using a longbow.
|
||||||
|
All they could see was the blue water surrounding their sailboat.
|
||||||
|
He was the type of guy who liked Christmas lights on his house in the middle of July.
|
||||||
|
It was a slippery slope and he was willing to slide all the way to the deepest depths.
|
||||||
|
The fish listened intently to what the frogs had to say.
|
||||||
|
The book is in front of the table.
|
||||||
|
You have every right to be angry, but that doesn't give you the right to be mean.
|
||||||
|
He waited for the stop sign to turn to a go sign.
|
||||||
|
The gloves protect my feet from excess work.
|
||||||
|
Nancy thought the best way to create a welcoming home was to line it with barbed wire.
|
||||||
|
Best friends are like old tomatoes and shoelaces.
|
||||||
|
He fumbled in the darkness looking for the light switch, but when he finally found it there was someone already there.
|
||||||
|
They say that dogs are man's best friend, but this cat was setting out to sabotage that theory.
|
||||||
|
Some bathing suits just shouldn’t be worn by some people.
|
||||||
|
Dolores wouldn't have eaten the meal if she had known what it actually was.
|
||||||
|
She did her best to help him.
|
||||||
|
Pantyhose and heels are an interesting choice of attire for the beach.
|
||||||
|
Jason didn’t understand why his parents wouldn’t let him sell his little sister at the garage sale.
|
||||||
|
Smoky the Bear secretly started the fires.
|
||||||
|
He wondered if she would appreciate his toenail collection.
|
||||||
|
The thick foliage and intertwined vines made the hike nearly impossible.
|
||||||
|
Purple is the best city in the forest.
|
||||||
|
Nothing seemed out of place except the washing machine in the bar.
|
||||||
|
He didn't understand why the bird wanted to ride the bicycle.
|
||||||
|
Jerry liked to look at paintings while eating garlic ice cream.
|
||||||
|
This book is sure to liquefy your brain.
|
||||||
|
She saw the brake lights, but not in time.
|
||||||
|
I'm worried by the fact that my daughter looks to the local carpet seller as a role model.
|
||||||
|
Whenever he saw a red flag warning at the beach he grabbed his surfboard.
|
||||||
|
She wanted to be rescued, but only if it was Tuesday and raining.
|
||||||
|
Patricia found the meaning of life in a bowl of Cheerios.
|
||||||
|
There was no telling what thoughts would come from the machine.
|
||||||
|
The beach was crowded with snow leopards.
|
||||||
|
He found the chocolate covered roaches quite tasty.
|
||||||
|
She opened up her third bottle of wine of the night.
|
||||||
|
The urgent care center was flooded with patients after the news of a new deadly virus was made public.
|
||||||
|
Lucifer was surprised at the amount of life at Death Valley.
|
||||||
|
When he had to picnic on the beach, he purposely put sand in other people’s food.
|
||||||
|
The book is in front of the table.
|
||||||
|
Gary didn't understand why Doug went upstairs to get one dollar bills when he invited him to go cow tipping.
|
||||||
|
She traveled because it cost the same as therapy and was a lot more enjoyable.
|
||||||
|
Eating eggs on Thursday for choir practice was recommended.
|
||||||
|
Normal activities took extraordinary amounts of concentration at the high altitude.
|
||||||
|
If eating three-egg omelets causes weight-gain, budgie eggs are a good substitute.
|
||||||
|
His mind was blown that there was nothing in space except space itself.
|
||||||
|
The clouds formed beautiful animals in the sky that eventually created a tornado to wreak havoc.
|
||||||
|
That must be the tenth time I've been arrested for selling deep-fried cigars.
|
||||||
|
Never underestimate the willingness of the greedy to throw you under the bus.
|
||||||
|
The team members were hard to tell apart since they all wore their hair in a ponytail.
|
||||||
|
When I cook spaghetti, I like to boil it a few minutes past al dente so the noodles are super slippery.
|
||||||
|
Their argument could be heard across the parking lot.
|
||||||
|
He decided that the time had come to be stronger than any of the excuses he'd used until then.
|
||||||
|
All she wanted was the answer, but she had no idea how much she would hate it.
|
||||||
|
If my calculator had a history, it would be more embarrassing than my browser history.
|
||||||
|
Just go ahead and press that button.
|
||||||
|
I caught my squirrel rustling through my gym bag.
|
||||||
|
On each full moon
|
||||||
|
It was the scarcity that fueled his creativity.
|
||||||
|
Two seats were vacant.
|
||||||
|
The tree fell unexpectedly short.
|
||||||
|
Getting up at dawn is for the birds.
|
||||||
|
Please put on these earmuffs because I can't you hear.
|
||||||
|
Her scream silenced the rowdy teenagers.
|
||||||
|
Traveling became almost extinct during the pandemic.
|
||||||
|
I can't believe this is the eighth time I'm smashing open my piggy bank on the same day!
|
||||||
|
For some unfathomable reason, the response team didn't consider a lack of milk for my cereal as a proper emergency.
|
||||||
|
The beauty of the African sunset disguised the danger lurking nearby.
|
||||||
|
Acres of almond trees lined the interstate highway which complimented the crazy driving nuts.
|
||||||
|
The fish listened intently to what the frogs had to say.
|
||||||
|
She wondered what his eyes were saying beneath his mirrored sunglasses.
|
||||||
|
|
200
resources/runtime/language/english100
Normal file
200
resources/runtime/language/english100
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
the
|
||||||
|
be
|
||||||
|
of
|
||||||
|
and
|
||||||
|
a
|
||||||
|
to
|
||||||
|
in
|
||||||
|
he
|
||||||
|
have
|
||||||
|
it
|
||||||
|
that
|
||||||
|
for
|
||||||
|
they
|
||||||
|
I
|
||||||
|
with
|
||||||
|
as
|
||||||
|
not
|
||||||
|
on
|
||||||
|
she
|
||||||
|
at
|
||||||
|
by
|
||||||
|
this
|
||||||
|
we
|
||||||
|
you
|
||||||
|
do
|
||||||
|
but
|
||||||
|
from
|
||||||
|
or
|
||||||
|
which
|
||||||
|
one
|
||||||
|
would
|
||||||
|
all
|
||||||
|
will
|
||||||
|
there
|
||||||
|
say
|
||||||
|
who
|
||||||
|
make
|
||||||
|
when
|
||||||
|
can
|
||||||
|
more
|
||||||
|
if
|
||||||
|
no
|
||||||
|
man
|
||||||
|
out
|
||||||
|
other
|
||||||
|
so
|
||||||
|
what
|
||||||
|
time
|
||||||
|
up
|
||||||
|
go
|
||||||
|
about
|
||||||
|
than
|
||||||
|
into
|
||||||
|
could
|
||||||
|
state
|
||||||
|
only
|
||||||
|
new
|
||||||
|
year
|
||||||
|
some
|
||||||
|
take
|
||||||
|
come
|
||||||
|
these
|
||||||
|
know
|
||||||
|
see
|
||||||
|
use
|
||||||
|
get
|
||||||
|
like
|
||||||
|
then
|
||||||
|
first
|
||||||
|
any
|
||||||
|
work
|
||||||
|
now
|
||||||
|
may
|
||||||
|
such
|
||||||
|
give
|
||||||
|
over
|
||||||
|
think
|
||||||
|
most
|
||||||
|
even
|
||||||
|
find
|
||||||
|
day
|
||||||
|
also
|
||||||
|
after
|
||||||
|
way
|
||||||
|
many
|
||||||
|
must
|
||||||
|
look
|
||||||
|
before
|
||||||
|
great
|
||||||
|
back
|
||||||
|
through
|
||||||
|
long
|
||||||
|
where
|
||||||
|
much
|
||||||
|
should
|
||||||
|
well
|
||||||
|
people
|
||||||
|
down
|
||||||
|
own
|
||||||
|
just
|
||||||
|
because
|
||||||
|
good
|
||||||
|
each
|
||||||
|
those
|
||||||
|
feel
|
||||||
|
seem
|
||||||
|
how
|
||||||
|
high
|
||||||
|
too
|
||||||
|
place
|
||||||
|
little
|
||||||
|
world
|
||||||
|
very
|
||||||
|
still
|
||||||
|
nation
|
||||||
|
hand
|
||||||
|
old
|
||||||
|
life
|
||||||
|
tell
|
||||||
|
write
|
||||||
|
become
|
||||||
|
here
|
||||||
|
show
|
||||||
|
house
|
||||||
|
both
|
||||||
|
between
|
||||||
|
need
|
||||||
|
mean
|
||||||
|
call
|
||||||
|
develop
|
||||||
|
under
|
||||||
|
last
|
||||||
|
right
|
||||||
|
move
|
||||||
|
thing
|
||||||
|
general
|
||||||
|
school
|
||||||
|
never
|
||||||
|
same
|
||||||
|
another
|
||||||
|
begin
|
||||||
|
while
|
||||||
|
number
|
||||||
|
part
|
||||||
|
turn
|
||||||
|
real
|
||||||
|
leave
|
||||||
|
might
|
||||||
|
want
|
||||||
|
point
|
||||||
|
form
|
||||||
|
off
|
||||||
|
child
|
||||||
|
few
|
||||||
|
small
|
||||||
|
since
|
||||||
|
against
|
||||||
|
ask
|
||||||
|
late
|
||||||
|
home
|
||||||
|
interest
|
||||||
|
large
|
||||||
|
person
|
||||||
|
end
|
||||||
|
open
|
||||||
|
public
|
||||||
|
follow
|
||||||
|
during
|
||||||
|
present
|
||||||
|
without
|
||||||
|
again
|
||||||
|
hold
|
||||||
|
govern
|
||||||
|
around
|
||||||
|
possible
|
||||||
|
head
|
||||||
|
consider
|
||||||
|
word
|
||||||
|
program
|
||||||
|
problem
|
||||||
|
however
|
||||||
|
lead
|
||||||
|
system
|
||||||
|
set
|
||||||
|
order
|
||||||
|
eye
|
||||||
|
plan
|
||||||
|
run
|
||||||
|
keep
|
||||||
|
face
|
||||||
|
fact
|
||||||
|
group
|
||||||
|
play
|
||||||
|
stand
|
||||||
|
increase
|
||||||
|
early
|
||||||
|
course
|
||||||
|
change
|
||||||
|
help
|
||||||
|
line
|
1000
resources/runtime/language/english1000
Normal file
1000
resources/runtime/language/english1000
Normal file
File diff suppressed because it is too large
Load diff
9949
resources/runtime/language/english10000
Normal file
9949
resources/runtime/language/english10000
Normal file
File diff suppressed because it is too large
Load diff
200
resources/runtime/language/english200
Normal file
200
resources/runtime/language/english200
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
the
|
||||||
|
of
|
||||||
|
and
|
||||||
|
to
|
||||||
|
in
|
||||||
|
for
|
||||||
|
is
|
||||||
|
on
|
||||||
|
that
|
||||||
|
by
|
||||||
|
this
|
||||||
|
with
|
||||||
|
you
|
||||||
|
it
|
||||||
|
not
|
||||||
|
or
|
||||||
|
be
|
||||||
|
are
|
||||||
|
from
|
||||||
|
at
|
||||||
|
as
|
||||||
|
your
|
||||||
|
all
|
||||||
|
have
|
||||||
|
new
|
||||||
|
more
|
||||||
|
an
|
||||||
|
was
|
||||||
|
we
|
||||||
|
will
|
||||||
|
home
|
||||||
|
can
|
||||||
|
us
|
||||||
|
about
|
||||||
|
if
|
||||||
|
page
|
||||||
|
my
|
||||||
|
has
|
||||||
|
search
|
||||||
|
free
|
||||||
|
but
|
||||||
|
our
|
||||||
|
one
|
||||||
|
other
|
||||||
|
do
|
||||||
|
no
|
||||||
|
information
|
||||||
|
time
|
||||||
|
they
|
||||||
|
site
|
||||||
|
he
|
||||||
|
up
|
||||||
|
may
|
||||||
|
what
|
||||||
|
which
|
||||||
|
their
|
||||||
|
news
|
||||||
|
out
|
||||||
|
use
|
||||||
|
any
|
||||||
|
there
|
||||||
|
see
|
||||||
|
only
|
||||||
|
so
|
||||||
|
his
|
||||||
|
when
|
||||||
|
contact
|
||||||
|
here
|
||||||
|
business
|
||||||
|
who
|
||||||
|
web
|
||||||
|
also
|
||||||
|
now
|
||||||
|
help
|
||||||
|
get
|
||||||
|
pm
|
||||||
|
view
|
||||||
|
online
|
||||||
|
first
|
||||||
|
am
|
||||||
|
been
|
||||||
|
would
|
||||||
|
how
|
||||||
|
were
|
||||||
|
me
|
||||||
|
services
|
||||||
|
some
|
||||||
|
these
|
||||||
|
click
|
||||||
|
its
|
||||||
|
like
|
||||||
|
service
|
||||||
|
than
|
||||||
|
find
|
||||||
|
price
|
||||||
|
date
|
||||||
|
back
|
||||||
|
top
|
||||||
|
people
|
||||||
|
had
|
||||||
|
list
|
||||||
|
name
|
||||||
|
just
|
||||||
|
over
|
||||||
|
state
|
||||||
|
year
|
||||||
|
day
|
||||||
|
into
|
||||||
|
email
|
||||||
|
two
|
||||||
|
health
|
||||||
|
world
|
||||||
|
re
|
||||||
|
next
|
||||||
|
used
|
||||||
|
go
|
||||||
|
work
|
||||||
|
last
|
||||||
|
most
|
||||||
|
products
|
||||||
|
music
|
||||||
|
buy
|
||||||
|
data
|
||||||
|
make
|
||||||
|
them
|
||||||
|
should
|
||||||
|
product
|
||||||
|
system
|
||||||
|
post
|
||||||
|
her
|
||||||
|
city
|
||||||
|
add
|
||||||
|
policy
|
||||||
|
number
|
||||||
|
such
|
||||||
|
please
|
||||||
|
available
|
||||||
|
copyright
|
||||||
|
support
|
||||||
|
message
|
||||||
|
after
|
||||||
|
best
|
||||||
|
software
|
||||||
|
then
|
||||||
|
jan
|
||||||
|
good
|
||||||
|
video
|
||||||
|
well
|
||||||
|
where
|
||||||
|
info
|
||||||
|
rights
|
||||||
|
public
|
||||||
|
books
|
||||||
|
high
|
||||||
|
school
|
||||||
|
through
|
||||||
|
each
|
||||||
|
links
|
||||||
|
she
|
||||||
|
review
|
||||||
|
years
|
||||||
|
order
|
||||||
|
very
|
||||||
|
privacy
|
||||||
|
book
|
||||||
|
items
|
||||||
|
company
|
||||||
|
read
|
||||||
|
group
|
||||||
|
need
|
||||||
|
many
|
||||||
|
user
|
||||||
|
said
|
||||||
|
de
|
||||||
|
does
|
||||||
|
set
|
||||||
|
under
|
||||||
|
general
|
||||||
|
research
|
||||||
|
university
|
||||||
|
january
|
||||||
|
mail
|
||||||
|
full
|
||||||
|
map
|
||||||
|
reviews
|
||||||
|
program
|
||||||
|
life
|
||||||
|
know
|
||||||
|
games
|
||||||
|
way
|
||||||
|
days
|
||||||
|
management
|
||||||
|
part
|
||||||
|
could
|
||||||
|
great
|
||||||
|
united
|
||||||
|
hotel
|
||||||
|
real
|
||||||
|
item
|
||||||
|
international
|
4958
resources/runtime/language/english5000
Normal file
4958
resources/runtime/language/english5000
Normal file
File diff suppressed because it is too large
Load diff
207
resources/runtime/language/german
Normal file
207
resources/runtime/language/german
Normal file
|
@ -0,0 +1,207 @@
|
||||||
|
die
|
||||||
|
der
|
||||||
|
und
|
||||||
|
in
|
||||||
|
zu
|
||||||
|
den
|
||||||
|
das
|
||||||
|
nicht
|
||||||
|
von
|
||||||
|
sie
|
||||||
|
ist
|
||||||
|
des
|
||||||
|
sich
|
||||||
|
mit
|
||||||
|
dem
|
||||||
|
dass
|
||||||
|
er
|
||||||
|
es
|
||||||
|
ein
|
||||||
|
ich
|
||||||
|
auf
|
||||||
|
so
|
||||||
|
eine
|
||||||
|
auch
|
||||||
|
als
|
||||||
|
an
|
||||||
|
nach
|
||||||
|
wie
|
||||||
|
im
|
||||||
|
für
|
||||||
|
man
|
||||||
|
aber
|
||||||
|
aus
|
||||||
|
durch
|
||||||
|
wenn
|
||||||
|
nur
|
||||||
|
war
|
||||||
|
noch
|
||||||
|
werden
|
||||||
|
bei
|
||||||
|
hat
|
||||||
|
wir
|
||||||
|
was
|
||||||
|
wird
|
||||||
|
sein
|
||||||
|
einen
|
||||||
|
welche
|
||||||
|
sind
|
||||||
|
oder
|
||||||
|
zur
|
||||||
|
um
|
||||||
|
haben
|
||||||
|
einer
|
||||||
|
mir
|
||||||
|
über
|
||||||
|
ihm
|
||||||
|
diese
|
||||||
|
einem
|
||||||
|
ihr
|
||||||
|
uns
|
||||||
|
da
|
||||||
|
zum
|
||||||
|
kann
|
||||||
|
doch
|
||||||
|
vor
|
||||||
|
dieser
|
||||||
|
mich
|
||||||
|
ihn
|
||||||
|
du
|
||||||
|
hatte
|
||||||
|
seine
|
||||||
|
mehr
|
||||||
|
am
|
||||||
|
denn
|
||||||
|
nun
|
||||||
|
unter
|
||||||
|
sehr
|
||||||
|
selbst
|
||||||
|
schon
|
||||||
|
hier
|
||||||
|
bis
|
||||||
|
habe
|
||||||
|
ihre
|
||||||
|
dann
|
||||||
|
ihnen
|
||||||
|
seiner
|
||||||
|
alle
|
||||||
|
wieder
|
||||||
|
meine
|
||||||
|
Zeit
|
||||||
|
gegen
|
||||||
|
vom
|
||||||
|
ganz
|
||||||
|
einzelnen
|
||||||
|
wo
|
||||||
|
muss
|
||||||
|
ohne
|
||||||
|
eines
|
||||||
|
können
|
||||||
|
sei
|
||||||
|
ja
|
||||||
|
wurde
|
||||||
|
jetzt
|
||||||
|
immer
|
||||||
|
seinen
|
||||||
|
wohl
|
||||||
|
dieses
|
||||||
|
ihren
|
||||||
|
würde
|
||||||
|
diesen
|
||||||
|
sondern
|
||||||
|
weil
|
||||||
|
welcher
|
||||||
|
nichts
|
||||||
|
diesem
|
||||||
|
alles
|
||||||
|
waren
|
||||||
|
will
|
||||||
|
Herr
|
||||||
|
viel
|
||||||
|
mein
|
||||||
|
also
|
||||||
|
soll
|
||||||
|
worden
|
||||||
|
lassen
|
||||||
|
dies
|
||||||
|
machen
|
||||||
|
ihrer
|
||||||
|
weiter
|
||||||
|
Leben
|
||||||
|
recht
|
||||||
|
etwas
|
||||||
|
keine
|
||||||
|
seinem
|
||||||
|
ob
|
||||||
|
dir
|
||||||
|
allen
|
||||||
|
großen
|
||||||
|
Jahre
|
||||||
|
Weise
|
||||||
|
müssen
|
||||||
|
welches
|
||||||
|
wäre
|
||||||
|
erst
|
||||||
|
einmal
|
||||||
|
Mann
|
||||||
|
hätte
|
||||||
|
zwei
|
||||||
|
dich
|
||||||
|
allein
|
||||||
|
Herren
|
||||||
|
während
|
||||||
|
Paragraph
|
||||||
|
anders
|
||||||
|
Liebe
|
||||||
|
kein
|
||||||
|
damit
|
||||||
|
gar
|
||||||
|
Hand
|
||||||
|
Herrn
|
||||||
|
euch
|
||||||
|
sollte
|
||||||
|
konnte
|
||||||
|
ersten
|
||||||
|
deren
|
||||||
|
zwischen
|
||||||
|
wollen
|
||||||
|
denen
|
||||||
|
dessen
|
||||||
|
sagen
|
||||||
|
bin
|
||||||
|
Menschen
|
||||||
|
gut
|
||||||
|
darauf
|
||||||
|
wurden
|
||||||
|
weiß
|
||||||
|
gewesen
|
||||||
|
Seite
|
||||||
|
bald
|
||||||
|
weit
|
||||||
|
große
|
||||||
|
solche
|
||||||
|
hatten
|
||||||
|
eben
|
||||||
|
andern
|
||||||
|
beiden
|
||||||
|
macht
|
||||||
|
sehen
|
||||||
|
ganze
|
||||||
|
anderen
|
||||||
|
lange
|
||||||
|
wer
|
||||||
|
ihrem
|
||||||
|
zwar
|
||||||
|
gemacht
|
||||||
|
dort
|
||||||
|
kommen
|
||||||
|
Welt
|
||||||
|
heute
|
||||||
|
Frau
|
||||||
|
werde
|
||||||
|
derselben
|
||||||
|
ganzen
|
||||||
|
deutschen
|
||||||
|
lässt
|
||||||
|
vielleicht
|
||||||
|
meiner
|
1000
resources/runtime/language/german1000
Normal file
1000
resources/runtime/language/german1000
Normal file
File diff suppressed because it is too large
Load diff
10000
resources/runtime/language/german10000
Normal file
10000
resources/runtime/language/german10000
Normal file
File diff suppressed because it is too large
Load diff
25
resources/runtime/language/go
Normal file
25
resources/runtime/language/go
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
break
|
||||||
|
default
|
||||||
|
func
|
||||||
|
interface
|
||||||
|
select
|
||||||
|
case
|
||||||
|
defer
|
||||||
|
go
|
||||||
|
map
|
||||||
|
struct
|
||||||
|
chan
|
||||||
|
else
|
||||||
|
goto
|
||||||
|
package
|
||||||
|
switch
|
||||||
|
const
|
||||||
|
fallthrough
|
||||||
|
if
|
||||||
|
range
|
||||||
|
type
|
||||||
|
continue
|
||||||
|
for
|
||||||
|
import
|
||||||
|
return
|
||||||
|
var
|
329
resources/runtime/language/html
Normal file
329
resources/runtime/language/html
Normal file
|
@ -0,0 +1,329 @@
|
||||||
|
action
|
||||||
|
alt
|
||||||
|
class
|
||||||
|
fill
|
||||||
|
for
|
||||||
|
height
|
||||||
|
href
|
||||||
|
href
|
||||||
|
href
|
||||||
|
href
|
||||||
|
id
|
||||||
|
kind
|
||||||
|
max
|
||||||
|
media
|
||||||
|
method
|
||||||
|
min
|
||||||
|
name
|
||||||
|
src
|
||||||
|
src
|
||||||
|
src
|
||||||
|
srclang
|
||||||
|
srcset
|
||||||
|
stroke
|
||||||
|
stroke-width
|
||||||
|
style
|
||||||
|
stylesheet
|
||||||
|
stylesheet
|
||||||
|
title
|
||||||
|
type
|
||||||
|
type
|
||||||
|
value
|
||||||
|
width
|
||||||
|
<!DOCTYPE
|
||||||
|
<!--
|
||||||
|
-->
|
||||||
|
<a
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
</a>
|
||||||
|
<abbr
|
||||||
|
</abbr>
|
||||||
|
<address>
|
||||||
|
</address>
|
||||||
|
<area
|
||||||
|
<article>
|
||||||
|
</article>
|
||||||
|
<aside>
|
||||||
|
</aside>
|
||||||
|
<audio
|
||||||
|
</audio>
|
||||||
|
<b>
|
||||||
|
</b>
|
||||||
|
<b>
|
||||||
|
</b>
|
||||||
|
<b>
|
||||||
|
</b>
|
||||||
|
<b>
|
||||||
|
</b>
|
||||||
|
<base
|
||||||
|
<bdi>
|
||||||
|
</bdi>
|
||||||
|
<bdo
|
||||||
|
</bdo>
|
||||||
|
<blockquote
|
||||||
|
</blockquote>
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<button
|
||||||
|
</button>
|
||||||
|
<canvas
|
||||||
|
</canvas>
|
||||||
|
<caption>
|
||||||
|
</caption>
|
||||||
|
<cite>
|
||||||
|
</cite>
|
||||||
|
<code>
|
||||||
|
</code>
|
||||||
|
<colgroup>
|
||||||
|
<col
|
||||||
|
</colgroup>
|
||||||
|
<data
|
||||||
|
</data>
|
||||||
|
<datalist
|
||||||
|
</datalist>
|
||||||
|
<dd>
|
||||||
|
</dd>
|
||||||
|
<dl>
|
||||||
|
</dl>
|
||||||
|
<del>
|
||||||
|
</del>
|
||||||
|
<details>
|
||||||
|
</details>
|
||||||
|
<dfn
|
||||||
|
</dfn>
|
||||||
|
<dialog
|
||||||
|
</dialog>
|
||||||
|
<div
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
</div>
|
||||||
|
<dt>
|
||||||
|
</dt>
|
||||||
|
<em>
|
||||||
|
</em>
|
||||||
|
<embed
|
||||||
|
<fieldset>
|
||||||
|
</fieldset>
|
||||||
|
<figcaption>
|
||||||
|
</figcaption>
|
||||||
|
<figure>
|
||||||
|
</figure>
|
||||||
|
<form
|
||||||
|
</form>
|
||||||
|
<footer>
|
||||||
|
</footer>
|
||||||
|
<label
|
||||||
|
</label>
|
||||||
|
<header>
|
||||||
|
</header>
|
||||||
|
<head>
|
||||||
|
</head>
|
||||||
|
<head>
|
||||||
|
</head>
|
||||||
|
<head>
|
||||||
|
</head>
|
||||||
|
<head>
|
||||||
|
</head>
|
||||||
|
<html
|
||||||
|
</html>
|
||||||
|
<html
|
||||||
|
</html>
|
||||||
|
<html
|
||||||
|
</html>
|
||||||
|
<html
|
||||||
|
</html>
|
||||||
|
<hr>
|
||||||
|
<h1>
|
||||||
|
</h1>
|
||||||
|
<h2>
|
||||||
|
</h2>
|
||||||
|
<h3>
|
||||||
|
</h3>
|
||||||
|
<h4>
|
||||||
|
</h4>
|
||||||
|
<h5>
|
||||||
|
</h5>
|
||||||
|
<h6>
|
||||||
|
</h6>
|
||||||
|
<i>
|
||||||
|
</i>
|
||||||
|
<i>
|
||||||
|
</i>
|
||||||
|
<i>
|
||||||
|
</i>
|
||||||
|
<iframe
|
||||||
|
</iframe>
|
||||||
|
<img
|
||||||
|
<img
|
||||||
|
<img
|
||||||
|
<img
|
||||||
|
<input
|
||||||
|
<ins>
|
||||||
|
</ins>
|
||||||
|
<kbd>
|
||||||
|
</kbd>
|
||||||
|
<legend>
|
||||||
|
</legend>
|
||||||
|
<li>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
</li>
|
||||||
|
<link
|
||||||
|
<link
|
||||||
|
<main>
|
||||||
|
</main>
|
||||||
|
<map
|
||||||
|
</map>
|
||||||
|
<mark>
|
||||||
|
</mark>
|
||||||
|
<meta
|
||||||
|
<meter
|
||||||
|
</meter>
|
||||||
|
<nav>
|
||||||
|
</nav>
|
||||||
|
<noscript>
|
||||||
|
</noscript>
|
||||||
|
<object
|
||||||
|
</object>
|
||||||
|
<ol>
|
||||||
|
</ol>
|
||||||
|
<ol>
|
||||||
|
</ol>
|
||||||
|
<ol>
|
||||||
|
</ol>
|
||||||
|
<ol>
|
||||||
|
</ol>
|
||||||
|
<optgroup
|
||||||
|
</optgroup>
|
||||||
|
<option
|
||||||
|
</option>
|
||||||
|
<output
|
||||||
|
</output>
|
||||||
|
<p>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
</p>
|
||||||
|
<param
|
||||||
|
<picture>
|
||||||
|
</picture>
|
||||||
|
<pre>
|
||||||
|
</pre>
|
||||||
|
<progress
|
||||||
|
</progress>
|
||||||
|
<q>
|
||||||
|
</q>
|
||||||
|
<rp>
|
||||||
|
</rp>
|
||||||
|
<ruby>
|
||||||
|
</ruby>
|
||||||
|
<rt>
|
||||||
|
</rt>
|
||||||
|
<s>
|
||||||
|
</s>
|
||||||
|
<samp>
|
||||||
|
</samp>
|
||||||
|
<script>
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
</script>
|
||||||
|
<section>
|
||||||
|
</section>
|
||||||
|
<select
|
||||||
|
</select>
|
||||||
|
<source
|
||||||
|
<span
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
</span>
|
||||||
|
<strong>
|
||||||
|
<strong>
|
||||||
|
<strong>
|
||||||
|
<strong>
|
||||||
|
</strong>
|
||||||
|
<style
|
||||||
|
<style>
|
||||||
|
<style
|
||||||
|
<style>
|
||||||
|
<sub>
|
||||||
|
</sub>
|
||||||
|
<summary>
|
||||||
|
</summary>
|
||||||
|
<sup>
|
||||||
|
</sup>
|
||||||
|
<svg
|
||||||
|
</svg>
|
||||||
|
<table>
|
||||||
|
</table>
|
||||||
|
<tbody>
|
||||||
|
</tbody>
|
||||||
|
<td>
|
||||||
|
</td>
|
||||||
|
<template>
|
||||||
|
</template>
|
||||||
|
<textarea
|
||||||
|
</textarea>
|
||||||
|
<tfoot>
|
||||||
|
</tfoot>
|
||||||
|
<thead>
|
||||||
|
</thead>
|
||||||
|
<th>
|
||||||
|
</th>
|
||||||
|
<time
|
||||||
|
<time>
|
||||||
|
</time>
|
||||||
|
<title>
|
||||||
|
</title>
|
||||||
|
<title>
|
||||||
|
</title>
|
||||||
|
<track
|
||||||
|
<u>
|
||||||
|
</u>
|
||||||
|
<ul>
|
||||||
|
</ul>
|
||||||
|
<ul>
|
||||||
|
</ul>
|
||||||
|
<ul>
|
||||||
|
</ul>
|
||||||
|
<ul>
|
||||||
|
</ul>
|
||||||
|
<var>
|
||||||
|
</var>
|
||||||
|
<video
|
||||||
|
</video>
|
||||||
|
<wbr>
|
||||||
|
</wbr>
|
161745
resources/runtime/language/hungarian-all
Normal file
161745
resources/runtime/language/hungarian-all
Normal file
File diff suppressed because it is too large
Load diff
64
resources/runtime/language/java
Normal file
64
resources/runtime/language/java
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
abstract
|
||||||
|
assert
|
||||||
|
boolean
|
||||||
|
break
|
||||||
|
byte
|
||||||
|
case
|
||||||
|
catch
|
||||||
|
char
|
||||||
|
class
|
||||||
|
continue
|
||||||
|
default
|
||||||
|
do
|
||||||
|
double
|
||||||
|
else
|
||||||
|
enum
|
||||||
|
extends
|
||||||
|
final
|
||||||
|
finally
|
||||||
|
float
|
||||||
|
for
|
||||||
|
if
|
||||||
|
implements
|
||||||
|
import
|
||||||
|
instanceof
|
||||||
|
interface
|
||||||
|
long
|
||||||
|
native
|
||||||
|
new
|
||||||
|
null
|
||||||
|
package
|
||||||
|
private
|
||||||
|
protected
|
||||||
|
public
|
||||||
|
return
|
||||||
|
short
|
||||||
|
static
|
||||||
|
strictfp
|
||||||
|
super
|
||||||
|
switch
|
||||||
|
synchronized
|
||||||
|
this
|
||||||
|
throw
|
||||||
|
throws
|
||||||
|
transient
|
||||||
|
try
|
||||||
|
void
|
||||||
|
volatile
|
||||||
|
while
|
||||||
|
valueOf
|
||||||
|
from
|
||||||
|
parse
|
||||||
|
get
|
||||||
|
contains
|
||||||
|
remove
|
||||||
|
clear
|
||||||
|
put
|
||||||
|
set
|
||||||
|
with
|
||||||
|
throwas
|
||||||
|
build
|
||||||
|
add
|
||||||
|
subtract
|
||||||
|
append
|
||||||
|
length
|
51
resources/runtime/language/javascript
Normal file
51
resources/runtime/language/javascript
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
this
|
||||||
|
function
|
||||||
|
if
|
||||||
|
var
|
||||||
|
return
|
||||||
|
the
|
||||||
|
to
|
||||||
|
value
|
||||||
|
else
|
||||||
|
for
|
||||||
|
true
|
||||||
|
length
|
||||||
|
false
|
||||||
|
null
|
||||||
|
of
|
||||||
|
in
|
||||||
|
element
|
||||||
|
event
|
||||||
|
and
|
||||||
|
object
|
||||||
|
console
|
||||||
|
object
|
||||||
|
jQuery
|
||||||
|
node
|
||||||
|
while
|
||||||
|
do
|
||||||
|
if
|
||||||
|
break
|
||||||
|
continue
|
||||||
|
attributes
|
||||||
|
childNodes
|
||||||
|
firstChild
|
||||||
|
nodeName
|
||||||
|
nodeType
|
||||||
|
onclick
|
||||||
|
ondbclick
|
||||||
|
onmousedown
|
||||||
|
onmouseenter
|
||||||
|
onmouseup
|
||||||
|
onkeyup
|
||||||
|
onkeydown
|
||||||
|
onkeypress
|
||||||
|
oninput
|
||||||
|
oninvalid
|
||||||
|
onreset
|
||||||
|
onselect
|
||||||
|
ondrag
|
||||||
|
try
|
||||||
|
catch
|
||||||
|
throw
|
||||||
|
finally
|
200
resources/runtime/language/norwegian
Normal file
200
resources/runtime/language/norwegian
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
i
|
||||||
|
og
|
||||||
|
det
|
||||||
|
er
|
||||||
|
på
|
||||||
|
til
|
||||||
|
som
|
||||||
|
en
|
||||||
|
å
|
||||||
|
for
|
||||||
|
av
|
||||||
|
at
|
||||||
|
har
|
||||||
|
med
|
||||||
|
de
|
||||||
|
ikke
|
||||||
|
den
|
||||||
|
han
|
||||||
|
om
|
||||||
|
et
|
||||||
|
fra
|
||||||
|
men
|
||||||
|
vi
|
||||||
|
var
|
||||||
|
jeg
|
||||||
|
seg
|
||||||
|
sier
|
||||||
|
vil
|
||||||
|
kan
|
||||||
|
ble
|
||||||
|
skal
|
||||||
|
etter
|
||||||
|
også
|
||||||
|
så
|
||||||
|
ut
|
||||||
|
år
|
||||||
|
nå
|
||||||
|
da
|
||||||
|
dette
|
||||||
|
blir
|
||||||
|
ved
|
||||||
|
mot
|
||||||
|
hadde
|
||||||
|
to
|
||||||
|
hun
|
||||||
|
over
|
||||||
|
være
|
||||||
|
ha
|
||||||
|
må
|
||||||
|
går
|
||||||
|
opp
|
||||||
|
få
|
||||||
|
andre
|
||||||
|
eller
|
||||||
|
bare
|
||||||
|
sin
|
||||||
|
mer
|
||||||
|
inn
|
||||||
|
før
|
||||||
|
bli
|
||||||
|
vært
|
||||||
|
enn
|
||||||
|
alle
|
||||||
|
kroner
|
||||||
|
noe
|
||||||
|
når
|
||||||
|
noen
|
||||||
|
selv
|
||||||
|
denne
|
||||||
|
flere
|
||||||
|
mange
|
||||||
|
får
|
||||||
|
du
|
||||||
|
dag
|
||||||
|
der
|
||||||
|
mener
|
||||||
|
nye
|
||||||
|
fikk
|
||||||
|
norge
|
||||||
|
under
|
||||||
|
første
|
||||||
|
sa
|
||||||
|
siden
|
||||||
|
prosent
|
||||||
|
tre
|
||||||
|
kommer
|
||||||
|
ingen
|
||||||
|
man
|
||||||
|
siste
|
||||||
|
mellom
|
||||||
|
mye
|
||||||
|
oslo
|
||||||
|
hele
|
||||||
|
kunne
|
||||||
|
slik
|
||||||
|
norske
|
||||||
|
hva
|
||||||
|
både
|
||||||
|
millioner
|
||||||
|
oss
|
||||||
|
dem
|
||||||
|
store
|
||||||
|
gang
|
||||||
|
skulle
|
||||||
|
kom
|
||||||
|
hans
|
||||||
|
her
|
||||||
|
helt
|
||||||
|
tidligere
|
||||||
|
ville
|
||||||
|
hvor
|
||||||
|
blant
|
||||||
|
sine
|
||||||
|
politiet
|
||||||
|
ta
|
||||||
|
meg
|
||||||
|
sammen
|
||||||
|
blitt
|
||||||
|
igjen
|
||||||
|
rundt
|
||||||
|
mens
|
||||||
|
nok
|
||||||
|
godt
|
||||||
|
saken
|
||||||
|
fått
|
||||||
|
ned
|
||||||
|
alt
|
||||||
|
uten
|
||||||
|
norsk
|
||||||
|
tid
|
||||||
|
ham
|
||||||
|
ny
|
||||||
|
samme
|
||||||
|
gikk
|
||||||
|
gå
|
||||||
|
sitt
|
||||||
|
tilbake
|
||||||
|
annet
|
||||||
|
foto
|
||||||
|
ser
|
||||||
|
hvis
|
||||||
|
se
|
||||||
|
gjøre
|
||||||
|
ifølge
|
||||||
|
fire
|
||||||
|
tror
|
||||||
|
stor
|
||||||
|
gjennom
|
||||||
|
litt
|
||||||
|
tatt
|
||||||
|
folk
|
||||||
|
fordi
|
||||||
|
gjør
|
||||||
|
står
|
||||||
|
disse
|
||||||
|
derfor
|
||||||
|
langt
|
||||||
|
like
|
||||||
|
neste
|
||||||
|
komme
|
||||||
|
mest
|
||||||
|
fjor
|
||||||
|
god
|
||||||
|
tar
|
||||||
|
bedre
|
||||||
|
fortsatt
|
||||||
|
allerede
|
||||||
|
viser
|
||||||
|
gi
|
||||||
|
si
|
||||||
|
gamle
|
||||||
|
stavanger
|
||||||
|
ønsker
|
||||||
|
måtte
|
||||||
|
vet
|
||||||
|
fram
|
||||||
|
først
|
||||||
|
grunn
|
||||||
|
ligger
|
||||||
|
fem
|
||||||
|
gjort
|
||||||
|
likevel
|
||||||
|
tok
|
||||||
|
gir
|
||||||
|
kveld
|
||||||
|
kanskje
|
||||||
|
del
|
||||||
|
aldri
|
||||||
|
heller
|
||||||
|
satt
|
||||||
|
leder
|
||||||
|
beste
|
||||||
|
hvordan
|
||||||
|
svært
|
||||||
|
fredag
|
||||||
|
jo
|
||||||
|
dermed
|
||||||
|
hatt
|
||||||
|
bør
|
||||||
|
lørdag
|
74
resources/runtime/language/php
Normal file
74
resources/runtime/language/php
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
array_key_exists
|
||||||
|
array_keys
|
||||||
|
array_map
|
||||||
|
array_merge
|
||||||
|
array_pop
|
||||||
|
array_shift
|
||||||
|
array_unshift
|
||||||
|
array_values
|
||||||
|
array_walk
|
||||||
|
basename
|
||||||
|
class_exists
|
||||||
|
compact
|
||||||
|
count
|
||||||
|
date
|
||||||
|
declare
|
||||||
|
defined
|
||||||
|
dirname
|
||||||
|
echo
|
||||||
|
end
|
||||||
|
explode
|
||||||
|
file_exists
|
||||||
|
file_get_contents
|
||||||
|
function_exists
|
||||||
|
get_class
|
||||||
|
implode
|
||||||
|
in_array
|
||||||
|
intval
|
||||||
|
is_array
|
||||||
|
is_bool
|
||||||
|
is_dir
|
||||||
|
is_file
|
||||||
|
is_int
|
||||||
|
is_null
|
||||||
|
is_numeric
|
||||||
|
is_object
|
||||||
|
is_string
|
||||||
|
json_encode
|
||||||
|
max
|
||||||
|
mb_strlen
|
||||||
|
mb_strpos
|
||||||
|
mb_strtolower
|
||||||
|
min
|
||||||
|
preg_match
|
||||||
|
preg_replace
|
||||||
|
print_r
|
||||||
|
realpath
|
||||||
|
reset
|
||||||
|
rtrim
|
||||||
|
sprintf
|
||||||
|
str_repeat
|
||||||
|
str_replace
|
||||||
|
str_starts_with
|
||||||
|
substr
|
||||||
|
time
|
||||||
|
trim
|
||||||
|
uniqid
|
||||||
|
unlink
|
||||||
|
var_dump
|
||||||
|
<?php
|
||||||
|
if
|
||||||
|
else
|
||||||
|
elseif
|
||||||
|
switch
|
||||||
|
case
|
||||||
|
match
|
||||||
|
for
|
||||||
|
foreach
|
||||||
|
while
|
||||||
|
do
|
||||||
|
break
|
||||||
|
continue
|
||||||
|
return
|
||||||
|
=>
|
||||||
|
->
|
100
resources/runtime/language/portuguese
Normal file
100
resources/runtime/language/portuguese
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
pra
|
||||||
|
também
|
||||||
|
vocês
|
||||||
|
quê
|
||||||
|
algo
|
||||||
|
obrigado
|
||||||
|
dia
|
||||||
|
esse
|
||||||
|
lhe
|
||||||
|
este
|
||||||
|
ir
|
||||||
|
deus
|
||||||
|
essa
|
||||||
|
oh
|
||||||
|
melhor
|
||||||
|
ainda
|
||||||
|
temos
|
||||||
|
cara
|
||||||
|
sem
|
||||||
|
pai
|
||||||
|
sempre
|
||||||
|
vida
|
||||||
|
vez
|
||||||
|
homem
|
||||||
|
estamos
|
||||||
|
talvez
|
||||||
|
mãe
|
||||||
|
anos
|
||||||
|
alguém
|
||||||
|
depois
|
||||||
|
verdade
|
||||||
|
claro
|
||||||
|
boa
|
||||||
|
nem
|
||||||
|
pouco
|
||||||
|
ficar
|
||||||
|
coisas
|
||||||
|
aí
|
||||||
|
tinha
|
||||||
|
dois
|
||||||
|
falar
|
||||||
|
deve
|
||||||
|
antes
|
||||||
|
pelo
|
||||||
|
faz
|
||||||
|
parece
|
||||||
|
todo
|
||||||
|
dele
|
||||||
|
pessoas
|
||||||
|
fora
|
||||||
|
lugar
|
||||||
|
apenas
|
||||||
|
ei
|
||||||
|
fazendo
|
||||||
|
ninguém
|
||||||
|
dinheiro
|
||||||
|
acha
|
||||||
|
vá
|
||||||
|
comigo
|
||||||
|
mundo
|
||||||
|
preciso
|
||||||
|
qual
|
||||||
|
grande
|
||||||
|
estar
|
||||||
|
alguma
|
||||||
|
hoje
|
||||||
|
trabalho
|
||||||
|
suas
|
||||||
|
dar
|
||||||
|
seja
|
||||||
|
disso
|
||||||
|
fez
|
||||||
|
nome
|
||||||
|
será
|
||||||
|
novo
|
||||||
|
filho
|
||||||
|
outro
|
||||||
|
qualquer
|
||||||
|
quanto
|
||||||
|
saber
|
||||||
|
vão
|
||||||
|
meus
|
||||||
|
queria
|
||||||
|
ok
|
||||||
|
podemos
|
||||||
|
nossa
|
||||||
|
poderia
|
||||||
|
outra
|
||||||
|
olá
|
||||||
|
precisa
|
||||||
|
venha
|
||||||
|
nosso
|
||||||
|
mulher
|
||||||
|
sinto
|
||||||
|
desculpe
|
||||||
|
toda
|
||||||
|
diga
|
||||||
|
hora
|
||||||
|
daqui
|
||||||
|
amor
|
164
resources/runtime/language/python
Normal file
164
resources/runtime/language/python
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
abs
|
||||||
|
all
|
||||||
|
and
|
||||||
|
any
|
||||||
|
append
|
||||||
|
as
|
||||||
|
ascii
|
||||||
|
assert
|
||||||
|
bin
|
||||||
|
bool
|
||||||
|
break
|
||||||
|
bytearray
|
||||||
|
bytes
|
||||||
|
callable
|
||||||
|
capitalise
|
||||||
|
capitalize
|
||||||
|
casefold
|
||||||
|
ceil
|
||||||
|
center
|
||||||
|
chr
|
||||||
|
class
|
||||||
|
classmethod
|
||||||
|
clear
|
||||||
|
compile
|
||||||
|
complex
|
||||||
|
continue
|
||||||
|
copy
|
||||||
|
count
|
||||||
|
def
|
||||||
|
del
|
||||||
|
delattr
|
||||||
|
dict
|
||||||
|
dir
|
||||||
|
divmod
|
||||||
|
elif
|
||||||
|
else
|
||||||
|
encode
|
||||||
|
endswith
|
||||||
|
enumerate
|
||||||
|
eval
|
||||||
|
except
|
||||||
|
exec
|
||||||
|
exp
|
||||||
|
expandtabs
|
||||||
|
extend
|
||||||
|
fabs
|
||||||
|
factorial
|
||||||
|
False
|
||||||
|
filter
|
||||||
|
finally
|
||||||
|
find
|
||||||
|
float
|
||||||
|
for
|
||||||
|
format
|
||||||
|
from
|
||||||
|
fromkeys
|
||||||
|
frozenset
|
||||||
|
get
|
||||||
|
getattr
|
||||||
|
global
|
||||||
|
globals
|
||||||
|
hasattr
|
||||||
|
hash
|
||||||
|
help
|
||||||
|
hex
|
||||||
|
id
|
||||||
|
if
|
||||||
|
import
|
||||||
|
in
|
||||||
|
index
|
||||||
|
input
|
||||||
|
insert
|
||||||
|
int
|
||||||
|
is
|
||||||
|
isalnum
|
||||||
|
isalpha
|
||||||
|
isdecimal
|
||||||
|
isdigit
|
||||||
|
isidentifie
|
||||||
|
isinstance
|
||||||
|
islower
|
||||||
|
isnumeric
|
||||||
|
isprintable
|
||||||
|
isspace
|
||||||
|
issubclass
|
||||||
|
istitle
|
||||||
|
isupper
|
||||||
|
items
|
||||||
|
iter
|
||||||
|
join
|
||||||
|
keys
|
||||||
|
lambda
|
||||||
|
len
|
||||||
|
list
|
||||||
|
ljust
|
||||||
|
locals
|
||||||
|
lower
|
||||||
|
lstrip
|
||||||
|
maketrans
|
||||||
|
map
|
||||||
|
max
|
||||||
|
memoryview
|
||||||
|
min
|
||||||
|
next
|
||||||
|
None
|
||||||
|
not
|
||||||
|
object
|
||||||
|
oct
|
||||||
|
open
|
||||||
|
or
|
||||||
|
ord
|
||||||
|
partition
|
||||||
|
pass
|
||||||
|
pop
|
||||||
|
popitem
|
||||||
|
pow
|
||||||
|
print
|
||||||
|
property
|
||||||
|
raise
|
||||||
|
range
|
||||||
|
remove
|
||||||
|
replace
|
||||||
|
repr
|
||||||
|
return
|
||||||
|
reverse
|
||||||
|
reversed
|
||||||
|
rfind
|
||||||
|
rindex
|
||||||
|
rjust
|
||||||
|
round
|
||||||
|
rpartition
|
||||||
|
rsplit
|
||||||
|
rstrip
|
||||||
|
set
|
||||||
|
setattr
|
||||||
|
setdefault
|
||||||
|
slice
|
||||||
|
sort
|
||||||
|
sorted
|
||||||
|
split
|
||||||
|
splitlines
|
||||||
|
sqrt
|
||||||
|
startswith
|
||||||
|
staticmethod
|
||||||
|
str
|
||||||
|
strip
|
||||||
|
sum
|
||||||
|
super
|
||||||
|
swapcase
|
||||||
|
title
|
||||||
|
translate
|
||||||
|
True
|
||||||
|
try
|
||||||
|
tuple
|
||||||
|
type
|
||||||
|
update
|
||||||
|
upper
|
||||||
|
values
|
||||||
|
vars
|
||||||
|
while
|
||||||
|
with
|
||||||
|
yield
|
||||||
|
zfill
|
||||||
|
zip
|
67
resources/runtime/language/qt
Normal file
67
resources/runtime/language/qt
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
actionAt
|
||||||
|
actionTriggered
|
||||||
|
addAction
|
||||||
|
allowedAreasChanged
|
||||||
|
animated
|
||||||
|
build
|
||||||
|
centralWidget
|
||||||
|
children
|
||||||
|
connect
|
||||||
|
debug
|
||||||
|
dockNestingEnabled
|
||||||
|
dockOptions
|
||||||
|
documentMode
|
||||||
|
iconSize
|
||||||
|
insertSeperator
|
||||||
|
isMovable
|
||||||
|
make
|
||||||
|
modal
|
||||||
|
movableChanged
|
||||||
|
Orientation
|
||||||
|
paintEvent
|
||||||
|
parent
|
||||||
|
pos
|
||||||
|
private
|
||||||
|
protected
|
||||||
|
public
|
||||||
|
QLabel
|
||||||
|
qmake
|
||||||
|
QPropertyAnimation
|
||||||
|
QPushButton
|
||||||
|
QRect
|
||||||
|
QSize
|
||||||
|
QString
|
||||||
|
QStyleFactory
|
||||||
|
Qt
|
||||||
|
~QToolBar
|
||||||
|
QToolBar
|
||||||
|
release
|
||||||
|
removeToolBarBreak
|
||||||
|
resizeDocks
|
||||||
|
setAnimated
|
||||||
|
setCorner
|
||||||
|
setMenuBar
|
||||||
|
setMovable
|
||||||
|
setTabPosition
|
||||||
|
signals
|
||||||
|
sizeHint
|
||||||
|
slots
|
||||||
|
statusTip
|
||||||
|
styleSheet
|
||||||
|
tabShape
|
||||||
|
toolButtonStyle
|
||||||
|
toolButtonStyleChanged
|
||||||
|
toolTip
|
||||||
|
toolTipDuration
|
||||||
|
topLevelChanged
|
||||||
|
unifiedTitleAndToolBarOnMac
|
||||||
|
updatesEnabled
|
||||||
|
visible
|
||||||
|
whatIsThis
|
||||||
|
width
|
||||||
|
windowFilePath
|
||||||
|
windowFlags
|
||||||
|
windowIcon
|
||||||
|
windowModality
|
||||||
|
windowOpacity
|
||||||
|
windowTitle
|
118
resources/runtime/language/ruby
Normal file
118
resources/runtime/language/ruby
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
BEGIN
|
||||||
|
class
|
||||||
|
ensure
|
||||||
|
nil
|
||||||
|
self
|
||||||
|
when
|
||||||
|
END
|
||||||
|
def
|
||||||
|
false
|
||||||
|
not
|
||||||
|
super
|
||||||
|
while
|
||||||
|
alias
|
||||||
|
defined?
|
||||||
|
for
|
||||||
|
or
|
||||||
|
then
|
||||||
|
yield
|
||||||
|
and
|
||||||
|
do
|
||||||
|
if
|
||||||
|
redo
|
||||||
|
true
|
||||||
|
__LINE__
|
||||||
|
begin
|
||||||
|
else
|
||||||
|
in
|
||||||
|
rescue
|
||||||
|
undef
|
||||||
|
__FILE__
|
||||||
|
break
|
||||||
|
elsif
|
||||||
|
module
|
||||||
|
retry
|
||||||
|
unless
|
||||||
|
__ENCODING__
|
||||||
|
case
|
||||||
|
end
|
||||||
|
next
|
||||||
|
return
|
||||||
|
until
|
||||||
|
BasicObject
|
||||||
|
Object
|
||||||
|
ARGF.class
|
||||||
|
Array
|
||||||
|
Binding
|
||||||
|
Dir
|
||||||
|
Encoding
|
||||||
|
Encoding::Converter
|
||||||
|
Enumerator
|
||||||
|
Enumerator::ArithmeticSequence
|
||||||
|
Enumerator::Chain
|
||||||
|
Enumerator::Lazy
|
||||||
|
Enumerator::Yielder
|
||||||
|
FalseClass
|
||||||
|
Fiber
|
||||||
|
File::Stat
|
||||||
|
Hash
|
||||||
|
IO
|
||||||
|
File
|
||||||
|
MatchData
|
||||||
|
Method
|
||||||
|
Module
|
||||||
|
Class
|
||||||
|
NilClass
|
||||||
|
Numeric
|
||||||
|
Complex
|
||||||
|
Float
|
||||||
|
Integer
|
||||||
|
Rational
|
||||||
|
ObjectSpace::WeakMap
|
||||||
|
Proc
|
||||||
|
Process::Status
|
||||||
|
Random
|
||||||
|
Range
|
||||||
|
Regexp
|
||||||
|
RubyVM
|
||||||
|
RubyVM::AbstractSyntaxTree::Node
|
||||||
|
RubyVM::InstructionSequence
|
||||||
|
String
|
||||||
|
Struct
|
||||||
|
Process::Tms
|
||||||
|
Symbol
|
||||||
|
Thread
|
||||||
|
Thread::Backtrace::Location
|
||||||
|
Thread::ConditionVariable
|
||||||
|
Thread::Mutex
|
||||||
|
Thread::Queue
|
||||||
|
Thread::SizedQueue
|
||||||
|
ThreadGroup
|
||||||
|
Time
|
||||||
|
TracePoint
|
||||||
|
TrueClass
|
||||||
|
UnboundMethod
|
||||||
|
Comparable
|
||||||
|
Enumerable
|
||||||
|
Errno
|
||||||
|
File::Constants
|
||||||
|
FileTest
|
||||||
|
GC
|
||||||
|
GC::Profiler
|
||||||
|
IO::WaitReadable
|
||||||
|
IO::WaitWritable
|
||||||
|
Kernel
|
||||||
|
Marshal
|
||||||
|
Math
|
||||||
|
ObjectSpace
|
||||||
|
Process
|
||||||
|
Process::GID
|
||||||
|
Process::Sys
|
||||||
|
Process::UID
|
||||||
|
RubyVM::AbstractSyntaxTree
|
||||||
|
RubyVM::MJIT
|
||||||
|
Signal
|
||||||
|
Warning
|
||||||
|
ARGF
|
||||||
|
ENV
|
||||||
|
main
|
98
resources/runtime/language/rust
Normal file
98
resources/runtime/language/rust
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
as
|
||||||
|
async
|
||||||
|
await
|
||||||
|
break
|
||||||
|
const
|
||||||
|
continue
|
||||||
|
crate
|
||||||
|
dyn
|
||||||
|
else
|
||||||
|
enum
|
||||||
|
extern
|
||||||
|
false
|
||||||
|
fn
|
||||||
|
for
|
||||||
|
if
|
||||||
|
impl
|
||||||
|
in
|
||||||
|
let
|
||||||
|
loop
|
||||||
|
match
|
||||||
|
=>
|
||||||
|
->
|
||||||
|
Option
|
||||||
|
Result
|
||||||
|
Some
|
||||||
|
None
|
||||||
|
Ok
|
||||||
|
Err
|
||||||
|
mod
|
||||||
|
move
|
||||||
|
mut
|
||||||
|
&mut
|
||||||
|
pub
|
||||||
|
ref
|
||||||
|
return
|
||||||
|
Self
|
||||||
|
self
|
||||||
|
static
|
||||||
|
struct
|
||||||
|
super
|
||||||
|
trait
|
||||||
|
true
|
||||||
|
type
|
||||||
|
union
|
||||||
|
unsafe
|
||||||
|
use
|
||||||
|
where
|
||||||
|
while
|
||||||
|
panic!()
|
||||||
|
println!
|
||||||
|
dbg!
|
||||||
|
u8
|
||||||
|
u16
|
||||||
|
u32
|
||||||
|
u64
|
||||||
|
u128
|
||||||
|
usize
|
||||||
|
i8
|
||||||
|
i16
|
||||||
|
i32
|
||||||
|
i64
|
||||||
|
i128
|
||||||
|
isize
|
||||||
|
bool
|
||||||
|
char
|
||||||
|
&str
|
||||||
|
Vec::new()
|
||||||
|
Vec<_>
|
||||||
|
vec![]
|
||||||
|
HashMap
|
||||||
|
String
|
||||||
|
Vec::new()
|
||||||
|
collect::<Vec<_>>()
|
||||||
|
::<>
|
||||||
|
box
|
||||||
|
lazy_static
|
||||||
|
iter()
|
||||||
|
filter(|x| x.is_ok())
|
||||||
|
<'_>
|
||||||
|
map
|
||||||
|
BTreeMap
|
||||||
|
VecDeque
|
||||||
|
LinkedList
|
||||||
|
HashSet
|
||||||
|
&'static
|
||||||
|
&'_
|
||||||
|
..
|
||||||
|
..=
|
||||||
|
?
|
||||||
|
macro_rules!
|
||||||
|
{:?}
|
||||||
|
format!
|
||||||
|
unwrap()
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
#[test]
|
||||||
|
///
|
||||||
|
//!
|
||||||
|
//
|
104
resources/runtime/language/spanish
Normal file
104
resources/runtime/language/spanish
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
como
|
||||||
|
hola
|
||||||
|
eche
|
||||||
|
no
|
||||||
|
se
|
||||||
|
niño
|
||||||
|
piña
|
||||||
|
monda
|
||||||
|
sopas
|
||||||
|
san
|
||||||
|
yo
|
||||||
|
mondongo
|
||||||
|
eñe
|
||||||
|
peo
|
||||||
|
presente
|
||||||
|
méxico
|
||||||
|
mágico
|
||||||
|
exponente
|
||||||
|
número
|
||||||
|
otra
|
||||||
|
otro
|
||||||
|
ni
|
||||||
|
sur
|
||||||
|
historia
|
||||||
|
maestro
|
||||||
|
negro
|
||||||
|
blanco
|
||||||
|
sopas
|
||||||
|
fácil
|
||||||
|
difícil
|
||||||
|
que
|
||||||
|
muchacho
|
||||||
|
calor
|
||||||
|
agua
|
||||||
|
hielo
|
||||||
|
vapor
|
||||||
|
fuego
|
||||||
|
gas
|
||||||
|
aire
|
||||||
|
atmósfera
|
||||||
|
tierra
|
||||||
|
piso
|
||||||
|
suelo
|
||||||
|
metal
|
||||||
|
metálico
|
||||||
|
hierro
|
||||||
|
oro
|
||||||
|
plata
|
||||||
|
plomo
|
||||||
|
sal
|
||||||
|
barro
|
||||||
|
escritorio
|
||||||
|
silla
|
||||||
|
mesa
|
||||||
|
cama
|
||||||
|
dormitorio
|
||||||
|
habitación
|
||||||
|
cuarto
|
||||||
|
oficina
|
||||||
|
panel
|
||||||
|
puerta
|
||||||
|
ventana
|
||||||
|
entrada
|
||||||
|
hogar
|
||||||
|
casa
|
||||||
|
apartamento
|
||||||
|
departamento
|
||||||
|
edificio
|
||||||
|
construcción
|
||||||
|
elevador
|
||||||
|
ascensor
|
||||||
|
escalera
|
||||||
|
cultura
|
||||||
|
autor
|
||||||
|
actuación
|
||||||
|
espectador
|
||||||
|
espectáculo
|
||||||
|
entretenimiento
|
||||||
|
arte
|
||||||
|
cine
|
||||||
|
dibujo
|
||||||
|
pintura
|
||||||
|
música
|
||||||
|
religión
|
||||||
|
dios
|
||||||
|
artículo
|
||||||
|
educación
|
||||||
|
escuela
|
||||||
|
instituto
|
||||||
|
colegio
|
||||||
|
universidad
|
||||||
|
clase
|
||||||
|
curso
|
||||||
|
estudio
|
||||||
|
formación
|
||||||
|
análisis
|
||||||
|
investigación
|
||||||
|
conocimiento
|
||||||
|
idea
|
||||||
|
información
|
||||||
|
dato
|
||||||
|
forma
|
||||||
|
manera
|
||||||
|
champeta
|
100
resources/runtime/language/ukrainian
Normal file
100
resources/runtime/language/ukrainian
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
або
|
||||||
|
але
|
||||||
|
багато
|
||||||
|
батько
|
||||||
|
без
|
||||||
|
більше
|
||||||
|
будь
|
||||||
|
вибачте
|
||||||
|
вино
|
||||||
|
вівторок
|
||||||
|
він
|
||||||
|
вісім
|
||||||
|
вона
|
||||||
|
вони
|
||||||
|
вчора
|
||||||
|
гарний
|
||||||
|
говорити
|
||||||
|
два
|
||||||
|
де
|
||||||
|
дев'ять
|
||||||
|
день
|
||||||
|
десять
|
||||||
|
дитина
|
||||||
|
дівчина
|
||||||
|
побачення
|
||||||
|
добре
|
||||||
|
друг
|
||||||
|
подруга
|
||||||
|
дружина
|
||||||
|
дуже
|
||||||
|
дякую
|
||||||
|
жінка
|
||||||
|
завтра
|
||||||
|
зараз
|
||||||
|
знову
|
||||||
|
зробити
|
||||||
|
інший
|
||||||
|
кава
|
||||||
|
кафе
|
||||||
|
кінотеатр
|
||||||
|
кішка
|
||||||
|
класно
|
||||||
|
книга
|
||||||
|
коли
|
||||||
|
комп'ютер
|
||||||
|
ласка
|
||||||
|
ліворуч
|
||||||
|
любити
|
||||||
|
магазин
|
||||||
|
мама
|
||||||
|
ми
|
||||||
|
можна
|
||||||
|
музика
|
||||||
|
не
|
||||||
|
неділя
|
||||||
|
ні
|
||||||
|
нічого
|
||||||
|
обід
|
||||||
|
один
|
||||||
|
пиво
|
||||||
|
повтори
|
||||||
|
подобатися
|
||||||
|
понеділок
|
||||||
|
потім
|
||||||
|
праворуч
|
||||||
|
працювати
|
||||||
|
рахунок
|
||||||
|
ресторан
|
||||||
|
ринок
|
||||||
|
розуміти
|
||||||
|
середа
|
||||||
|
сім
|
||||||
|
скільки
|
||||||
|
справи
|
||||||
|
субота
|
||||||
|
сьогодні
|
||||||
|
так
|
||||||
|
також
|
||||||
|
там
|
||||||
|
тільки
|
||||||
|
тому
|
||||||
|
три
|
||||||
|
трохи
|
||||||
|
трошки
|
||||||
|
тут
|
||||||
|
фільм
|
||||||
|
хлопець
|
||||||
|
хотіти
|
||||||
|
хто
|
||||||
|
це
|
||||||
|
цікаво
|
||||||
|
чай
|
||||||
|
четвер
|
||||||
|
чоловік
|
||||||
|
чому
|
||||||
|
чотири
|
||||||
|
чудово
|
||||||
|
шість
|
||||||
|
що
|
||||||
|
я
|
308
src/config.rs
Normal file
308
src/config.rs
Normal file
|
@ -0,0 +1,308 @@
|
||||||
|
use ratatui::style::{Color, Modifier, Style};
|
||||||
|
use serde::{
|
||||||
|
de::{self, IntoDeserializer},
|
||||||
|
Deserialize,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub struct Config {
|
||||||
|
pub default_language: String,
|
||||||
|
pub theme: Theme,
|
||||||
|
pub keyboard: String,
|
||||||
|
|
||||||
|
// server
|
||||||
|
pub server: String,
|
||||||
|
pub bucket: String,
|
||||||
|
pub org: String,
|
||||||
|
pub token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Config {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
default_language: "english1000".into(),
|
||||||
|
theme: Theme::default(),
|
||||||
|
keyboard: "Generic".into(),
|
||||||
|
|
||||||
|
server: "http://localhost:3000".into(),
|
||||||
|
bucket: "dttyper".into(),
|
||||||
|
org: "dttyper".into(),
|
||||||
|
token: "none".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub struct Theme {
|
||||||
|
#[serde(deserialize_with = "deserialize_style")]
|
||||||
|
pub default: Style,
|
||||||
|
#[serde(deserialize_with = "deserialize_style")]
|
||||||
|
pub title: Style,
|
||||||
|
|
||||||
|
// test widget
|
||||||
|
#[serde(deserialize_with = "deserialize_style")]
|
||||||
|
pub input_border: Style,
|
||||||
|
#[serde(deserialize_with = "deserialize_style")]
|
||||||
|
pub prompt_border: Style,
|
||||||
|
|
||||||
|
#[serde(deserialize_with = "deserialize_style")]
|
||||||
|
pub prompt_correct: Style,
|
||||||
|
#[serde(deserialize_with = "deserialize_style")]
|
||||||
|
pub prompt_incorrect: Style,
|
||||||
|
#[serde(deserialize_with = "deserialize_style")]
|
||||||
|
pub prompt_untyped: Style,
|
||||||
|
|
||||||
|
#[serde(deserialize_with = "deserialize_style")]
|
||||||
|
pub prompt_current_correct: Style,
|
||||||
|
#[serde(deserialize_with = "deserialize_style")]
|
||||||
|
pub prompt_current_incorrect: Style,
|
||||||
|
#[serde(deserialize_with = "deserialize_style")]
|
||||||
|
pub prompt_current_untyped: Style,
|
||||||
|
|
||||||
|
#[serde(deserialize_with = "deserialize_style")]
|
||||||
|
pub prompt_cursor: Style,
|
||||||
|
|
||||||
|
// results widget
|
||||||
|
#[serde(deserialize_with = "deserialize_style")]
|
||||||
|
pub results_overview: Style,
|
||||||
|
#[serde(deserialize_with = "deserialize_style")]
|
||||||
|
pub results_overview_border: Style,
|
||||||
|
|
||||||
|
#[serde(deserialize_with = "deserialize_style")]
|
||||||
|
pub results_worst_keys: Style,
|
||||||
|
#[serde(deserialize_with = "deserialize_style")]
|
||||||
|
pub results_worst_keys_border: Style,
|
||||||
|
|
||||||
|
#[serde(deserialize_with = "deserialize_style")]
|
||||||
|
pub results_chart: Style,
|
||||||
|
#[serde(deserialize_with = "deserialize_style")]
|
||||||
|
pub results_chart_x: Style,
|
||||||
|
#[serde(deserialize_with = "deserialize_style")]
|
||||||
|
pub results_chart_y: Style,
|
||||||
|
|
||||||
|
#[serde(deserialize_with = "deserialize_style")]
|
||||||
|
pub results_restart_prompt: Style,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Theme {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
default: Style::default(),
|
||||||
|
|
||||||
|
title: Style::default()
|
||||||
|
.fg(Color::White)
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
|
||||||
|
input_border: Style::default().fg(Color::Cyan),
|
||||||
|
prompt_border: Style::default().fg(Color::Green),
|
||||||
|
|
||||||
|
prompt_correct: Style::default().fg(Color::Green),
|
||||||
|
prompt_incorrect: Style::default().fg(Color::Red),
|
||||||
|
prompt_untyped: Style::default().fg(Color::Gray),
|
||||||
|
|
||||||
|
prompt_current_correct: Style::default()
|
||||||
|
.fg(Color::Green)
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
prompt_current_incorrect: Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
|
||||||
|
prompt_current_untyped: Style::default()
|
||||||
|
.fg(Color::Blue)
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
|
||||||
|
prompt_cursor: Style::default().add_modifier(Modifier::UNDERLINED),
|
||||||
|
|
||||||
|
results_overview: Style::default()
|
||||||
|
.fg(Color::Cyan)
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
results_overview_border: Style::default().fg(Color::Cyan),
|
||||||
|
|
||||||
|
results_worst_keys: Style::default()
|
||||||
|
.fg(Color::Cyan)
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
results_worst_keys_border: Style::default().fg(Color::Cyan),
|
||||||
|
|
||||||
|
results_chart: Style::default().fg(Color::Cyan),
|
||||||
|
results_chart_x: Style::default().fg(Color::Cyan),
|
||||||
|
results_chart_y: Style::default()
|
||||||
|
.fg(Color::Gray)
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
|
||||||
|
results_restart_prompt: Style::default()
|
||||||
|
.fg(Color::Gray)
|
||||||
|
.add_modifier(Modifier::ITALIC),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_style<'de, D>(deserializer: D) -> Result<Style, D::Error>
|
||||||
|
where
|
||||||
|
D: de::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
struct StyleVisitor;
|
||||||
|
impl<'de> de::Visitor<'de> for StyleVisitor {
|
||||||
|
type Value = Style;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
formatter.write_str("a string describing a text style")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
|
||||||
|
let (colors, modifiers) = value.split_once(';').unwrap_or((value, ""));
|
||||||
|
let (fg, bg) = colors.split_once(':').unwrap_or((colors, "none"));
|
||||||
|
|
||||||
|
let mut style = Style {
|
||||||
|
fg: match fg {
|
||||||
|
"none" | "" => None,
|
||||||
|
_ => Some(deserialize_color(fg.into_deserializer())?),
|
||||||
|
},
|
||||||
|
bg: match bg {
|
||||||
|
"none" | "" => None,
|
||||||
|
_ => Some(deserialize_color(bg.into_deserializer())?),
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
for modifier in modifiers.split_terminator(';') {
|
||||||
|
style = style.add_modifier(match modifier {
|
||||||
|
"bold" => Modifier::BOLD,
|
||||||
|
"crossed_out" => Modifier::CROSSED_OUT,
|
||||||
|
"dim" => Modifier::DIM,
|
||||||
|
"hidden" => Modifier::HIDDEN,
|
||||||
|
"italic" => Modifier::ITALIC,
|
||||||
|
"rapid_blink" => Modifier::RAPID_BLINK,
|
||||||
|
"slow_blink" => Modifier::SLOW_BLINK,
|
||||||
|
"reversed" => Modifier::REVERSED,
|
||||||
|
"underlined" => Modifier::UNDERLINED,
|
||||||
|
_ => {
|
||||||
|
return Err(E::invalid_value(
|
||||||
|
de::Unexpected::Str(modifier),
|
||||||
|
&"a style modifier",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(style)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_str(StyleVisitor)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_color<'de, D>(deserializer: D) -> Result<Color, D::Error>
|
||||||
|
where
|
||||||
|
D: de::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
struct ColorVisitor;
|
||||||
|
impl<'de> de::Visitor<'de> for ColorVisitor {
|
||||||
|
type Value = Color;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
formatter.write_str("a color name or hexadecimal color code")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
|
||||||
|
match value {
|
||||||
|
"reset" => Ok(Color::Reset),
|
||||||
|
"black" => Ok(Color::Black),
|
||||||
|
"white" => Ok(Color::White),
|
||||||
|
"red" => Ok(Color::Red),
|
||||||
|
"green" => Ok(Color::Green),
|
||||||
|
"yellow" => Ok(Color::Yellow),
|
||||||
|
"blue" => Ok(Color::Blue),
|
||||||
|
"magenta" => Ok(Color::Magenta),
|
||||||
|
"cyan" => Ok(Color::Cyan),
|
||||||
|
"gray" => Ok(Color::Gray),
|
||||||
|
"darkgray" => Ok(Color::DarkGray),
|
||||||
|
"lightred" => Ok(Color::LightRed),
|
||||||
|
"lightgreen" => Ok(Color::LightGreen),
|
||||||
|
"lightyellow" => Ok(Color::LightYellow),
|
||||||
|
"lightblue" => Ok(Color::LightBlue),
|
||||||
|
"lightmagenta" => Ok(Color::LightMagenta),
|
||||||
|
"lightcyan" => Ok(Color::LightCyan),
|
||||||
|
_ => {
|
||||||
|
if value.len() == 6 {
|
||||||
|
let parse_error = |_| E::custom("color code was not valid hexadecimal");
|
||||||
|
|
||||||
|
Ok(Color::Rgb(
|
||||||
|
u8::from_str_radix(&value[0..2], 16).map_err(parse_error)?,
|
||||||
|
u8::from_str_radix(&value[2..4], 16).map_err(parse_error)?,
|
||||||
|
u8::from_str_radix(&value[4..6], 16).map_err(parse_error)?,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Err(E::invalid_value(
|
||||||
|
de::Unexpected::Str(value),
|
||||||
|
&"a color name or hexadecimal color code",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_str(ColorVisitor)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserializes_basic_colors() {
|
||||||
|
fn color(string: &str) -> Color {
|
||||||
|
deserialize_color(de::IntoDeserializer::<de::value::Error>::into_deserializer(
|
||||||
|
string,
|
||||||
|
))
|
||||||
|
.expect("failed to deserialize color")
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(color("black"), Color::Black);
|
||||||
|
assert_eq!(color("000000"), Color::Rgb(0, 0, 0));
|
||||||
|
assert_eq!(color("ffffff"), Color::Rgb(0xff, 0xff, 0xff));
|
||||||
|
assert_eq!(color("FFFFFF"), Color::Rgb(0xff, 0xff, 0xff));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserializes_styles() {
|
||||||
|
fn style(string: &str) -> Style {
|
||||||
|
deserialize_style(de::IntoDeserializer::<de::value::Error>::into_deserializer(
|
||||||
|
string,
|
||||||
|
))
|
||||||
|
.expect("failed to deserialize style")
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(style("none"), Style::default());
|
||||||
|
assert_eq!(style("none:none"), Style::default());
|
||||||
|
assert_eq!(style("none:none;"), Style::default());
|
||||||
|
|
||||||
|
assert_eq!(style("black"), Style::default().fg(Color::Black));
|
||||||
|
assert_eq!(
|
||||||
|
style("black:white"),
|
||||||
|
Style::default().fg(Color::Black).bg(Color::White)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
style("none;bold"),
|
||||||
|
Style::default().add_modifier(Modifier::BOLD)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
style("none;bold;italic;underlined;"),
|
||||||
|
Style::default()
|
||||||
|
.add_modifier(Modifier::BOLD)
|
||||||
|
.add_modifier(Modifier::ITALIC)
|
||||||
|
.add_modifier(Modifier::UNDERLINED)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
style("00ff00:000000;bold;dim;italic;slow_blink"),
|
||||||
|
Style::default()
|
||||||
|
.fg(Color::Rgb(0, 0xff, 0))
|
||||||
|
.bg(Color::Rgb(0, 0, 0))
|
||||||
|
.add_modifier(Modifier::BOLD)
|
||||||
|
.add_modifier(Modifier::DIM)
|
||||||
|
.add_modifier(Modifier::ITALIC)
|
||||||
|
.add_modifier(Modifier::SLOW_BLINK)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
354
src/main.rs
Normal file
354
src/main.rs
Normal file
|
@ -0,0 +1,354 @@
|
||||||
|
mod config;
|
||||||
|
mod save;
|
||||||
|
mod test;
|
||||||
|
mod ui;
|
||||||
|
|
||||||
|
use config::Config;
|
||||||
|
use save::save_result;
|
||||||
|
use test::{results::Results, Test};
|
||||||
|
|
||||||
|
use crossterm::{
|
||||||
|
self, cursor,
|
||||||
|
event::{self, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers},
|
||||||
|
execute, terminal,
|
||||||
|
};
|
||||||
|
use rand::{seq::SliceRandom, thread_rng};
|
||||||
|
use ratatui::{backend::CrosstermBackend, terminal::Terminal};
|
||||||
|
use rust_embed::RustEmbed;
|
||||||
|
use std::{
|
||||||
|
ffi::OsString,
|
||||||
|
fs,
|
||||||
|
io::{self, BufRead},
|
||||||
|
num,
|
||||||
|
path::PathBuf,
|
||||||
|
str,
|
||||||
|
};
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
#[derive(RustEmbed)]
|
||||||
|
#[folder = "resources/runtime"]
|
||||||
|
struct Resources;
|
||||||
|
|
||||||
|
#[derive(Debug, StructOpt)]
|
||||||
|
#[structopt(name = "dttyper", about = "Terminal-based typing test with exporting")]
|
||||||
|
struct Opt {
|
||||||
|
#[structopt(parse(from_os_str))]
|
||||||
|
contents: Option<PathBuf>,
|
||||||
|
|
||||||
|
#[structopt(short, long)]
|
||||||
|
debug: bool,
|
||||||
|
|
||||||
|
/// Specify word count
|
||||||
|
#[structopt(short, long, default_value = "15")]
|
||||||
|
words: num::NonZeroUsize,
|
||||||
|
|
||||||
|
/// Use config file
|
||||||
|
#[structopt(short, long)]
|
||||||
|
config: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Specify test language in file
|
||||||
|
#[structopt(long, parse(from_os_str))]
|
||||||
|
language_file: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Specify test language
|
||||||
|
#[structopt(short, long)]
|
||||||
|
language: Option<String>,
|
||||||
|
|
||||||
|
/// List installed languages
|
||||||
|
#[structopt(long)]
|
||||||
|
list_languages: bool,
|
||||||
|
|
||||||
|
/// Disable backtracking to completed words
|
||||||
|
#[structopt(long)]
|
||||||
|
no_backtrack: bool,
|
||||||
|
|
||||||
|
/// Enable sudden death mode to restart on first error
|
||||||
|
#[structopt(long)]
|
||||||
|
sudden_death: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Opt {
|
||||||
|
fn gen_contents(&self) -> Option<Vec<String>> {
|
||||||
|
match &self.contents {
|
||||||
|
Some(path) => {
|
||||||
|
let lines: Vec<String> = if path.as_os_str() == "-" {
|
||||||
|
std::io::stdin()
|
||||||
|
.lock()
|
||||||
|
.lines()
|
||||||
|
.map_while(Result::ok)
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
let file = fs::File::open(path).expect("Error reading language file.");
|
||||||
|
io::BufReader::new(file)
|
||||||
|
.lines()
|
||||||
|
.map_while(Result::ok)
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(lines.iter().map(String::from).collect())
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let lang_name = self
|
||||||
|
.language
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| self.config().default_language);
|
||||||
|
|
||||||
|
let bytes: Vec<u8> = self
|
||||||
|
.language_file
|
||||||
|
.as_ref()
|
||||||
|
.map(fs::read)
|
||||||
|
.and_then(Result::ok)
|
||||||
|
.or_else(|| fs::read(self.language_dir().join(&lang_name)).ok())
|
||||||
|
.or_else(|| {
|
||||||
|
Resources::get(&format!("language/{}", &lang_name))
|
||||||
|
.map(|f| f.data.into_owned())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
|
||||||
|
let mut language: Vec<&str> = str::from_utf8(&bytes)
|
||||||
|
.expect("Language file had non-utf8 encoding.")
|
||||||
|
.lines()
|
||||||
|
.collect();
|
||||||
|
language.shuffle(&mut rng);
|
||||||
|
|
||||||
|
let mut contents: Vec<_> = language
|
||||||
|
.into_iter()
|
||||||
|
.cycle()
|
||||||
|
.take(self.words.get())
|
||||||
|
.map(ToOwned::to_owned)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// TODO insert quote logic here :P
|
||||||
|
contents.shuffle(&mut rng);
|
||||||
|
|
||||||
|
Some(contents)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configuration
|
||||||
|
fn config(&self) -> Config {
|
||||||
|
fs::read(
|
||||||
|
self.config
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| self.config_dir().join("config.toml")),
|
||||||
|
)
|
||||||
|
.map(|bytes| {
|
||||||
|
toml::from_str(str::from_utf8(&bytes).unwrap_or_default())
|
||||||
|
.expect("Configuration was ill-formed.")
|
||||||
|
})
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Installed languages under config directory
|
||||||
|
fn languages(&self) -> io::Result<impl Iterator<Item = OsString>> {
|
||||||
|
let builtin = Resources::iter().filter_map(|name| {
|
||||||
|
name.strip_prefix("language/")
|
||||||
|
.map(ToOwned::to_owned)
|
||||||
|
.map(OsString::from)
|
||||||
|
});
|
||||||
|
|
||||||
|
let configured = self
|
||||||
|
.language_dir()
|
||||||
|
.read_dir()
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.map_while(Result::ok)
|
||||||
|
.map(|e| e.file_name());
|
||||||
|
|
||||||
|
Ok(builtin.chain(configured))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Config directory
|
||||||
|
fn config_dir(&self) -> PathBuf {
|
||||||
|
dirs::config_dir()
|
||||||
|
.expect("Failed to find config directory.")
|
||||||
|
.join("dttyper")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Language directory under config directory
|
||||||
|
fn language_dir(&self) -> PathBuf {
|
||||||
|
self.config_dir().join("language")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum State {
|
||||||
|
Test(Test),
|
||||||
|
Results(Results),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
fn render_into<B: ratatui::backend::Backend>(
|
||||||
|
&self,
|
||||||
|
terminal: &mut Terminal<B>,
|
||||||
|
config: &Config,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
match self {
|
||||||
|
State::Test(test) => {
|
||||||
|
terminal.draw(|f| {
|
||||||
|
f.render_widget(config.theme.apply_to(test), f.size());
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
State::Results(results) => {
|
||||||
|
terminal.draw(|f| {
|
||||||
|
f.render_widget(config.theme.apply_to(results), f.size());
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> io::Result<()> {
|
||||||
|
let opt = Opt::from_args();
|
||||||
|
if opt.debug {
|
||||||
|
dbg!(&opt);
|
||||||
|
}
|
||||||
|
|
||||||
|
let config = opt.config();
|
||||||
|
if opt.debug {
|
||||||
|
dbg!(&config);
|
||||||
|
}
|
||||||
|
|
||||||
|
if opt.list_languages {
|
||||||
|
opt.languages()
|
||||||
|
.unwrap()
|
||||||
|
.for_each(|name| println!("{}", name.to_str().expect("Ill-formatted language name.")));
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO add server checking
|
||||||
|
|
||||||
|
let backend = CrosstermBackend::new(io::stdout());
|
||||||
|
let mut terminal = Terminal::new(backend)?;
|
||||||
|
|
||||||
|
terminal::enable_raw_mode()?;
|
||||||
|
execute!(
|
||||||
|
io::stdout(),
|
||||||
|
cursor::Hide,
|
||||||
|
cursor::SavePosition,
|
||||||
|
terminal::EnterAlternateScreen,
|
||||||
|
)?;
|
||||||
|
terminal.clear()?;
|
||||||
|
|
||||||
|
let mut state = State::Test(Test::new(
|
||||||
|
opt.gen_contents().expect(
|
||||||
|
"Couldn't get test contents. Make sure the specified language actually exists.",
|
||||||
|
),
|
||||||
|
!opt.no_backtrack,
|
||||||
|
opt.sudden_death,
|
||||||
|
));
|
||||||
|
|
||||||
|
state.render_into(&mut terminal, &config)?;
|
||||||
|
loop {
|
||||||
|
let event = event::read()?;
|
||||||
|
|
||||||
|
// handle exit controls
|
||||||
|
match event {
|
||||||
|
Event::Key(KeyEvent {
|
||||||
|
code: KeyCode::Char('c'),
|
||||||
|
kind: KeyEventKind::Press,
|
||||||
|
modifiers: KeyModifiers::CONTROL,
|
||||||
|
..
|
||||||
|
}) => break,
|
||||||
|
Event::Key(KeyEvent {
|
||||||
|
code: KeyCode::Esc,
|
||||||
|
kind: KeyEventKind::Press,
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
..
|
||||||
|
}) => match state {
|
||||||
|
State::Test(ref test) => {
|
||||||
|
state = State::Results(Results::from(test));
|
||||||
|
}
|
||||||
|
State::Results(_) => break,
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
match state {
|
||||||
|
State::Test(ref mut test) => {
|
||||||
|
if let Event::Key(key) = event {
|
||||||
|
test.handle_key(key);
|
||||||
|
if test.complete {
|
||||||
|
save_result(
|
||||||
|
&Results::from(&*test),
|
||||||
|
config.server.clone(),
|
||||||
|
config.token.clone(),
|
||||||
|
config.keyboard.clone(),
|
||||||
|
match Opt::from_args().language {
|
||||||
|
None => "english".to_string(),
|
||||||
|
Some(lang) => lang,
|
||||||
|
},
|
||||||
|
config.bucket.clone(),
|
||||||
|
config.org.clone(),
|
||||||
|
test.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
state = State::Results(Results::from(&*test));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
State::Results(ref result) => {
|
||||||
|
match event {
|
||||||
|
Event::Key(KeyEvent {
|
||||||
|
code: KeyCode::Char('r'),
|
||||||
|
kind: KeyEventKind::Press,
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
state = State::Test(Test::new(
|
||||||
|
opt.gen_contents().expect(
|
||||||
|
"Couldn't get test contents. Make sure the specified language actually exists.",
|
||||||
|
),
|
||||||
|
!opt.no_backtrack,
|
||||||
|
opt.sudden_death
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Event::Key(KeyEvent {
|
||||||
|
code: KeyCode::Char('p'),
|
||||||
|
kind: KeyEventKind::Press,
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
if result.missed_words.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// repeat each missed word 5 times
|
||||||
|
let mut practice_words: Vec<String> = (result.missed_words)
|
||||||
|
.iter()
|
||||||
|
.flat_map(|w| vec![w.clone(); 5])
|
||||||
|
.collect();
|
||||||
|
practice_words.shuffle(&mut thread_rng());
|
||||||
|
state = State::Test(Test::new(
|
||||||
|
practice_words,
|
||||||
|
!opt.no_backtrack,
|
||||||
|
opt.sudden_death,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Event::Key(KeyEvent {
|
||||||
|
code: KeyCode::Char('q'),
|
||||||
|
kind: KeyEventKind::Press,
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
..
|
||||||
|
}) => break,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.render_into(&mut terminal, &config)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
terminal::disable_raw_mode()?;
|
||||||
|
execute!(
|
||||||
|
io::stdout(),
|
||||||
|
cursor::RestorePosition,
|
||||||
|
cursor::Show,
|
||||||
|
terminal::LeaveAlternateScreen,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
88
src/save.rs
Normal file
88
src/save.rs
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
use std::{str::FromStr, thread, time::Duration};
|
||||||
|
|
||||||
|
use crossterm::terminal;
|
||||||
|
|
||||||
|
use crate::test::{results::Results, Test, TestWord};
|
||||||
|
|
||||||
|
pub fn save_result(
|
||||||
|
result: &Results,
|
||||||
|
url: String,
|
||||||
|
token: String,
|
||||||
|
keyboard: String,
|
||||||
|
test_type: String,
|
||||||
|
bucket: String,
|
||||||
|
org: String,
|
||||||
|
test: Test,
|
||||||
|
) {
|
||||||
|
// Creating a duplicate of the data
|
||||||
|
let rr = Results {
|
||||||
|
timing: result.timing.clone(),
|
||||||
|
accuracy: result.accuracy.clone(),
|
||||||
|
missed_words: result.missed_words.clone(),
|
||||||
|
};
|
||||||
|
// HTTP client
|
||||||
|
let client = reqwest::blocking::Client::new();
|
||||||
|
|
||||||
|
// parsing language into quotes
|
||||||
|
let language = format!("\"{}\"", test_type);
|
||||||
|
|
||||||
|
// total words typed
|
||||||
|
let words_typed = test.words.len();
|
||||||
|
|
||||||
|
// filtering out incorrectly typed words
|
||||||
|
let words_typed_incorrectly = test
|
||||||
|
.clone()
|
||||||
|
.words
|
||||||
|
.into_iter()
|
||||||
|
.filter(|word| word.events.iter().any(|o| o.correct == Some(false)))
|
||||||
|
.collect::<Vec<TestWord>>();
|
||||||
|
|
||||||
|
// filtering out correctly typed words and checking if they are not already an instance of the incorrectly typed ones
|
||||||
|
// since the user may type the word incorrectly the first time and go back to change it
|
||||||
|
// then turning it into a string
|
||||||
|
let words_typed_correctly = test
|
||||||
|
.clone()
|
||||||
|
.words
|
||||||
|
.into_iter()
|
||||||
|
.filter(|word| {
|
||||||
|
word.events.iter().any(|o| o.correct == Some(true))
|
||||||
|
&& !words_typed_incorrectly.contains(word)
|
||||||
|
})
|
||||||
|
.map(|w| w.text.clone())
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
|
// turning incorrect words into a string
|
||||||
|
let words_typed_incorrectly = words_typed_incorrectly
|
||||||
|
.iter()
|
||||||
|
.map(|w| w.text.clone())
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
|
// building influxdb insert query
|
||||||
|
let data = format!("test,language={},keyboard={} words={},incorrect_types={},correct_types={},wpm={},accuracy={},words_typed_correctly={},words_typed_incorrectly={} {}",
|
||||||
|
language.replace('"', ""),
|
||||||
|
"generic",
|
||||||
|
words_typed,
|
||||||
|
words_typed_incorrectly.len(),
|
||||||
|
words_typed_correctly.len(),
|
||||||
|
rr.timing.overall_cps as f64 * 12.0 * ( rr.accuracy.overall.numerator as f64 / rr.accuracy.overall.denominator as f64), //float
|
||||||
|
( rr.accuracy.overall.numerator as f64 / rr.accuracy.overall.denominator as f64) * 100.0, // accuracy
|
||||||
|
format!("\"{}\"", words_typed_correctly.join(",")),
|
||||||
|
format!("\"{}\"", words_typed_incorrectly.join(",")),
|
||||||
|
chrono::Utc::now().timestamp()
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut server_url = url.clone();
|
||||||
|
server_url.push_str("/api/v2/write");
|
||||||
|
|
||||||
|
let mut url = reqwest::Url::from_str(server_url.as_str()).unwrap();
|
||||||
|
url.set_query(Some(
|
||||||
|
format!("org={}&bucket={}&precision=s", org, bucket).as_str(),
|
||||||
|
));
|
||||||
|
|
||||||
|
// TODO error handle
|
||||||
|
client
|
||||||
|
.post(url)
|
||||||
|
.body(data)
|
||||||
|
.header("Authorization", format!("Token {}", token))
|
||||||
|
.send();
|
||||||
|
}
|
172
src/test/mod.rs
Normal file
172
src/test/mod.rs
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
pub mod results;
|
||||||
|
|
||||||
|
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
|
||||||
|
use std::fmt;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
pub struct TestEvent {
|
||||||
|
pub time: Instant,
|
||||||
|
pub key: KeyEvent,
|
||||||
|
pub correct: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_missed_word_event(event: &TestEvent) -> bool {
|
||||||
|
event.correct != Some(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for TestEvent {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("TestEvent")
|
||||||
|
.field("time", &String::from("Instant { ... }"))
|
||||||
|
.field("key", &self.key)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct TestWord {
|
||||||
|
pub text: String,
|
||||||
|
pub progress: String,
|
||||||
|
pub events: Vec<TestEvent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for TestWord {
|
||||||
|
fn from(string: String) -> Self {
|
||||||
|
TestWord {
|
||||||
|
text: string,
|
||||||
|
progress: String::new(),
|
||||||
|
events: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for TestWord {
|
||||||
|
fn from(string: &str) -> Self {
|
||||||
|
Self::from(string.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Test {
|
||||||
|
pub words: Vec<TestWord>,
|
||||||
|
pub current_word: usize,
|
||||||
|
pub complete: bool,
|
||||||
|
pub backtracking_enabled: bool,
|
||||||
|
pub sudden_death_enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Test {
|
||||||
|
pub fn new(words: Vec<String>, backtracking_enabled: bool, sudden_death_enabled: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
words: words.into_iter().map(TestWord::from).collect(),
|
||||||
|
current_word: 0,
|
||||||
|
complete: false,
|
||||||
|
backtracking_enabled,
|
||||||
|
sudden_death_enabled,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_key(&mut self, key: KeyEvent) {
|
||||||
|
if key.kind != KeyEventKind::Press {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let word = &mut self.words[self.current_word];
|
||||||
|
match key.code {
|
||||||
|
KeyCode::Char(' ') | KeyCode::Enter => {
|
||||||
|
if word.text.chars().nth(word.progress.len()) == Some(' ') {
|
||||||
|
word.progress.push(' ');
|
||||||
|
word.events.push(TestEvent {
|
||||||
|
time: Instant::now(),
|
||||||
|
correct: Some(true),
|
||||||
|
key,
|
||||||
|
})
|
||||||
|
} else if !word.progress.is_empty() || word.text.is_empty() {
|
||||||
|
let correct = word.text == word.progress;
|
||||||
|
if self.sudden_death_enabled && !correct {
|
||||||
|
self.reset();
|
||||||
|
} else {
|
||||||
|
word.events.push(TestEvent {
|
||||||
|
time: Instant::now(),
|
||||||
|
correct: Some(correct),
|
||||||
|
key,
|
||||||
|
});
|
||||||
|
self.next_word();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Backspace => {
|
||||||
|
if word.progress.is_empty() && self.backtracking_enabled {
|
||||||
|
self.last_word();
|
||||||
|
} else {
|
||||||
|
word.events.push(TestEvent {
|
||||||
|
time: Instant::now(),
|
||||||
|
correct: Some(!word.text.starts_with(&word.progress[..])),
|
||||||
|
key,
|
||||||
|
});
|
||||||
|
word.progress.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// CTRL-BackSpace and CTRL-W
|
||||||
|
KeyCode::Char('h') | KeyCode::Char('w')
|
||||||
|
if key.modifiers.contains(KeyModifiers::CONTROL) =>
|
||||||
|
{
|
||||||
|
if self.words[self.current_word].progress.is_empty() {
|
||||||
|
self.last_word();
|
||||||
|
}
|
||||||
|
|
||||||
|
let word = &mut self.words[self.current_word];
|
||||||
|
|
||||||
|
word.events.push(TestEvent {
|
||||||
|
time: Instant::now(),
|
||||||
|
correct: None,
|
||||||
|
key,
|
||||||
|
});
|
||||||
|
word.progress.clear();
|
||||||
|
}
|
||||||
|
KeyCode::Char(c) => {
|
||||||
|
word.progress.push(c);
|
||||||
|
let correct = word.text.starts_with(&word.progress[..]);
|
||||||
|
if self.sudden_death_enabled && !correct {
|
||||||
|
self.reset();
|
||||||
|
} else {
|
||||||
|
word.events.push(TestEvent {
|
||||||
|
time: Instant::now(),
|
||||||
|
correct: Some(correct),
|
||||||
|
key,
|
||||||
|
});
|
||||||
|
if word.progress == word.text && self.current_word == self.words.len() - 1 {
|
||||||
|
self.complete = true;
|
||||||
|
self.current_word = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn last_word(&mut self) {
|
||||||
|
if self.current_word != 0 {
|
||||||
|
self.current_word -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_word(&mut self) {
|
||||||
|
if self.current_word == self.words.len() - 1 {
|
||||||
|
self.complete = true;
|
||||||
|
self.current_word = 0;
|
||||||
|
} else {
|
||||||
|
self.current_word += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.words.iter_mut().for_each(|word: &mut TestWord| {
|
||||||
|
word.progress.clear();
|
||||||
|
word.events.clear();
|
||||||
|
});
|
||||||
|
self.current_word = 0;
|
||||||
|
self.complete = false;
|
||||||
|
}
|
||||||
|
}
|
160
src/test/results.rs
Normal file
160
src/test/results.rs
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
use super::{is_missed_word_event, Test};
|
||||||
|
|
||||||
|
use crossterm::event::KeyEvent;
|
||||||
|
use serde::Serialize;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::{cmp, fmt};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize)]
|
||||||
|
pub struct Fraction {
|
||||||
|
pub numerator: usize,
|
||||||
|
pub denominator: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Fraction {
|
||||||
|
pub const fn new(numerator: usize, denominator: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
numerator,
|
||||||
|
denominator,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Fraction> for f64 {
|
||||||
|
fn from(f: Fraction) -> Self {
|
||||||
|
f.numerator as f64 / f.denominator as f64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl cmp::Ord for Fraction {
|
||||||
|
fn cmp(&self, other: &Self) -> cmp::Ordering {
|
||||||
|
f64::from(*self).partial_cmp(&f64::from(*other)).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for Fraction {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Fraction {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}/{}", self.numerator, self.denominator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait PartialResults {
|
||||||
|
fn progress(&self) -> Fraction;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialResults for Test {
|
||||||
|
fn progress(&self) -> Fraction {
|
||||||
|
Fraction {
|
||||||
|
numerator: self.current_word + 1,
|
||||||
|
denominator: self.words.len(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct TimingData {
|
||||||
|
pub overall_cps: f64,
|
||||||
|
pub per_event: Vec<f64>,
|
||||||
|
pub per_key: HashMap<KeyEvent, f64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AccuracyData {
|
||||||
|
pub overall: Fraction,
|
||||||
|
pub per_key: HashMap<KeyEvent, Fraction>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Results {
|
||||||
|
pub timing: TimingData,
|
||||||
|
pub accuracy: AccuracyData,
|
||||||
|
pub missed_words: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Test> for Results {
|
||||||
|
fn from(test: &Test) -> Self {
|
||||||
|
let events: Vec<&super::TestEvent> =
|
||||||
|
test.words.iter().flat_map(|w| w.events.iter()).collect();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
timing: calc_timing(&events),
|
||||||
|
accuracy: calc_accuracy(&events),
|
||||||
|
missed_words: calc_missed_words(test),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calc_timing(events: &[&super::TestEvent]) -> TimingData {
|
||||||
|
let mut timing = TimingData {
|
||||||
|
overall_cps: -1.0,
|
||||||
|
per_event: Vec::new(),
|
||||||
|
per_key: HashMap::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// map of keys to a two-tuple (total time, clicks) for counting average
|
||||||
|
let mut keys: HashMap<KeyEvent, (f64, usize)> = HashMap::new();
|
||||||
|
|
||||||
|
for win in events.windows(2) {
|
||||||
|
let event_dur = win[1]
|
||||||
|
.time
|
||||||
|
.checked_duration_since(win[0].time)
|
||||||
|
.map(|d| d.as_secs_f64());
|
||||||
|
|
||||||
|
if let Some(event_dur) = event_dur {
|
||||||
|
timing.per_event.push(event_dur);
|
||||||
|
|
||||||
|
let key = keys.entry(win[1].key).or_insert((0.0, 0));
|
||||||
|
key.0 += event_dur;
|
||||||
|
key.1 += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
timing.per_key = keys
|
||||||
|
.into_iter()
|
||||||
|
.map(|(key, (total, count))| (key, total / count as f64))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
timing.overall_cps = timing.per_event.len() as f64 / timing.per_event.iter().sum::<f64>();
|
||||||
|
|
||||||
|
timing
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calc_accuracy(events: &[&super::TestEvent]) -> AccuracyData {
|
||||||
|
let mut acc = AccuracyData {
|
||||||
|
overall: Fraction::new(0, 0),
|
||||||
|
per_key: HashMap::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
events
|
||||||
|
.iter()
|
||||||
|
.filter(|event| event.correct.is_some())
|
||||||
|
.for_each(|event| {
|
||||||
|
let key = acc
|
||||||
|
.per_key
|
||||||
|
.entry(event.key)
|
||||||
|
.or_insert_with(|| Fraction::new(0, 0));
|
||||||
|
|
||||||
|
acc.overall.denominator += 1;
|
||||||
|
key.denominator += 1;
|
||||||
|
|
||||||
|
if event.correct.unwrap() {
|
||||||
|
acc.overall.numerator += 1;
|
||||||
|
key.numerator += 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
acc
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calc_missed_words(test: &Test) -> Vec<String> {
|
||||||
|
test.words
|
||||||
|
.iter()
|
||||||
|
.filter(|word| word.events.iter().any(is_missed_word_event))
|
||||||
|
.map(|word| word.text.clone())
|
||||||
|
.collect()
|
||||||
|
}
|
512
src/ui.rs
Normal file
512
src/ui.rs
Normal file
|
@ -0,0 +1,512 @@
|
||||||
|
use crate::config::Theme;
|
||||||
|
|
||||||
|
use super::test::{results, Test, TestWord};
|
||||||
|
|
||||||
|
use crossterm::event::KeyCode;
|
||||||
|
use crossterm::event::KeyEvent;
|
||||||
|
use ratatui::{
|
||||||
|
buffer::Buffer,
|
||||||
|
layout::{Constraint, Direction, Layout, Rect},
|
||||||
|
symbols::Marker,
|
||||||
|
text::{Line, Span, Text},
|
||||||
|
widgets::{Axis, Block, BorderType, Borders, Chart, Dataset, GraphType, Paragraph, Widget},
|
||||||
|
};
|
||||||
|
use results::Fraction;
|
||||||
|
|
||||||
|
// Convert CPS to WPM (clicks per second)
|
||||||
|
const WPM_PER_CPS: f64 = 12.0;
|
||||||
|
|
||||||
|
// Width of the moving average window for the WPM chart
|
||||||
|
const WPM_SMA_WIDTH: usize = 10;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct SizedBlock<'a> {
|
||||||
|
block: Block<'a>,
|
||||||
|
area: Rect,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SizedBlock<'_> {
|
||||||
|
fn render(self, buf: &mut Buffer) {
|
||||||
|
self.block.render(self.area, buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait UsedWidget: Widget {}
|
||||||
|
impl UsedWidget for Paragraph<'_> {}
|
||||||
|
|
||||||
|
trait DrawInner<T> {
|
||||||
|
fn draw_inner(&self, content: T, buf: &mut Buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DrawInner<&Line<'_>> for SizedBlock<'_> {
|
||||||
|
fn draw_inner(&self, content: &Line, buf: &mut Buffer) {
|
||||||
|
let inner = self.block.inner(self.area);
|
||||||
|
buf.set_line(inner.x, inner.y, content, inner.width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: UsedWidget> DrawInner<T> for SizedBlock<'_> {
|
||||||
|
fn draw_inner(&self, content: T, buf: &mut Buffer) {
|
||||||
|
let inner = self.block.inner(self.area);
|
||||||
|
content.render(inner, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ThemedWidget {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer, theme: &Theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Themed<'t, W: ?Sized> {
|
||||||
|
theme: &'t Theme,
|
||||||
|
widget: W,
|
||||||
|
}
|
||||||
|
impl<'t, W: ThemedWidget> Widget for Themed<'t, W> {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
|
self.widget.render(area, buf, self.theme)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Theme {
|
||||||
|
pub fn apply_to<W>(&self, widget: W) -> Themed<'_, W> {
|
||||||
|
Themed {
|
||||||
|
theme: self,
|
||||||
|
widget,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThemedWidget for &Test {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer, theme: &Theme) {
|
||||||
|
buf.set_style(area, theme.default);
|
||||||
|
|
||||||
|
// Chunks
|
||||||
|
let chunks = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints([Constraint::Length(3), Constraint::Length(6)])
|
||||||
|
.split(area);
|
||||||
|
|
||||||
|
// Sections
|
||||||
|
let input = SizedBlock {
|
||||||
|
block: Block::default()
|
||||||
|
.title(Line::from(vec![Span::styled("Input", theme.title)]))
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.border_type(BorderType::Rounded)
|
||||||
|
.border_style(theme.input_border),
|
||||||
|
area: chunks[0],
|
||||||
|
};
|
||||||
|
input.draw_inner(
|
||||||
|
&Line::from(self.words[self.current_word].progress.clone()),
|
||||||
|
buf,
|
||||||
|
);
|
||||||
|
input.render(buf);
|
||||||
|
|
||||||
|
let target_lines: Vec<Line> = {
|
||||||
|
let words = words_to_spans(&self.words, self.current_word, theme);
|
||||||
|
|
||||||
|
let mut lines: Vec<Line> = Vec::new();
|
||||||
|
let mut current_line: Vec<Span> = Vec::new();
|
||||||
|
let mut current_width = 0;
|
||||||
|
for word in words {
|
||||||
|
let word_width: usize = word.iter().map(|s| s.width()).sum();
|
||||||
|
|
||||||
|
if current_width + word_width > chunks[1].width as usize - 2 {
|
||||||
|
current_line.push(Span::raw("\n"));
|
||||||
|
lines.push(Line::from(current_line.clone()));
|
||||||
|
current_line.clear();
|
||||||
|
current_width = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
current_line.extend(word);
|
||||||
|
current_width += word_width;
|
||||||
|
}
|
||||||
|
lines.push(Line::from(current_line));
|
||||||
|
|
||||||
|
lines
|
||||||
|
};
|
||||||
|
let target = Paragraph::new(target_lines).block(
|
||||||
|
Block::default()
|
||||||
|
.title(Span::styled("Prompt", theme.title))
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.border_type(BorderType::Rounded)
|
||||||
|
.border_style(theme.prompt_border),
|
||||||
|
);
|
||||||
|
target.render(chunks[1], buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn words_to_spans<'a>(
|
||||||
|
words: &'a [TestWord],
|
||||||
|
current_word: usize,
|
||||||
|
theme: &'a Theme,
|
||||||
|
) -> Vec<Vec<Span<'a>>> {
|
||||||
|
let mut spans = Vec::new();
|
||||||
|
|
||||||
|
for word in &words[..current_word] {
|
||||||
|
let parts = split_typed_word(word);
|
||||||
|
spans.push(word_parts_to_spans(parts, theme));
|
||||||
|
}
|
||||||
|
|
||||||
|
let parts_current = split_current_word(&words[current_word]);
|
||||||
|
spans.push(word_parts_to_spans(parts_current, theme));
|
||||||
|
|
||||||
|
for word in &words[current_word + 1..] {
|
||||||
|
let parts = vec![(word.text.clone(), Status::Untyped)];
|
||||||
|
spans.push(word_parts_to_spans(parts, theme));
|
||||||
|
}
|
||||||
|
spans
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Clone, Copy, Debug)]
|
||||||
|
enum Status {
|
||||||
|
Correct,
|
||||||
|
Incorrect,
|
||||||
|
CurrentUntyped,
|
||||||
|
CurrentCorrect,
|
||||||
|
CurrentIncorrect,
|
||||||
|
Cursor,
|
||||||
|
Untyped,
|
||||||
|
Overtyped,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn split_current_word(word: &TestWord) -> Vec<(String, Status)> {
|
||||||
|
let mut parts = Vec::new();
|
||||||
|
let mut cur_string = String::new();
|
||||||
|
let mut cur_status = Status::Untyped;
|
||||||
|
|
||||||
|
let mut progress = word.progress.chars();
|
||||||
|
for tc in word.text.chars() {
|
||||||
|
let p = progress.next();
|
||||||
|
let status = match p {
|
||||||
|
None => Status::CurrentUntyped,
|
||||||
|
Some(c) => match c {
|
||||||
|
c if c == tc => Status::CurrentCorrect,
|
||||||
|
_ => Status::CurrentIncorrect,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if status == cur_status {
|
||||||
|
cur_string.push(tc);
|
||||||
|
} else {
|
||||||
|
if !cur_string.is_empty() {
|
||||||
|
parts.push((cur_string, cur_status));
|
||||||
|
cur_string = String::new();
|
||||||
|
}
|
||||||
|
cur_string.push(tc);
|
||||||
|
cur_status = status;
|
||||||
|
|
||||||
|
// first currentuntyped is cursor
|
||||||
|
if status == Status::CurrentUntyped {
|
||||||
|
parts.push((cur_string, Status::Cursor));
|
||||||
|
cur_string = String::new();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !cur_string.is_empty() {
|
||||||
|
parts.push((cur_string, cur_status));
|
||||||
|
}
|
||||||
|
let overtyped = progress.collect::<String>();
|
||||||
|
if !overtyped.is_empty() {
|
||||||
|
parts.push((overtyped, Status::Overtyped));
|
||||||
|
}
|
||||||
|
parts
|
||||||
|
}
|
||||||
|
|
||||||
|
fn split_typed_word(word: &TestWord) -> Vec<(String, Status)> {
|
||||||
|
let mut parts = Vec::new();
|
||||||
|
let mut cur_string = String::new();
|
||||||
|
let mut cur_status = Status::Untyped;
|
||||||
|
|
||||||
|
let mut progress = word.progress.chars();
|
||||||
|
for tc in word.text.chars() {
|
||||||
|
let p = progress.next();
|
||||||
|
let status = match p {
|
||||||
|
None => Status::Untyped,
|
||||||
|
Some(c) => match c {
|
||||||
|
c if c == tc => Status::Correct,
|
||||||
|
_ => Status::Incorrect,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if status == cur_status {
|
||||||
|
cur_string.push(tc);
|
||||||
|
} else {
|
||||||
|
if !cur_string.is_empty() {
|
||||||
|
parts.push((cur_string, cur_status));
|
||||||
|
cur_string = String::new();
|
||||||
|
}
|
||||||
|
cur_string.push(tc);
|
||||||
|
cur_status = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !cur_string.is_empty() {
|
||||||
|
parts.push((cur_string, cur_status));
|
||||||
|
}
|
||||||
|
|
||||||
|
let overtyped = progress.collect::<String>();
|
||||||
|
if !overtyped.is_empty() {
|
||||||
|
parts.push((overtyped, Status::Overtyped));
|
||||||
|
}
|
||||||
|
parts
|
||||||
|
}
|
||||||
|
|
||||||
|
fn word_parts_to_spans(parts: Vec<(String, Status)>, theme: &Theme) -> Vec<Span<'_>> {
|
||||||
|
let mut spans = Vec::new();
|
||||||
|
for (text, status) in parts {
|
||||||
|
let style = match status {
|
||||||
|
Status::Correct => theme.prompt_correct,
|
||||||
|
Status::Incorrect => theme.prompt_incorrect,
|
||||||
|
Status::Untyped => theme.prompt_untyped,
|
||||||
|
Status::CurrentUntyped => theme.prompt_current_untyped,
|
||||||
|
Status::CurrentCorrect => theme.prompt_current_correct,
|
||||||
|
Status::CurrentIncorrect => theme.prompt_current_incorrect,
|
||||||
|
Status::Cursor => theme.prompt_current_untyped.patch(theme.prompt_cursor),
|
||||||
|
Status::Overtyped => theme.prompt_incorrect,
|
||||||
|
};
|
||||||
|
|
||||||
|
spans.push(Span::styled(text, style));
|
||||||
|
}
|
||||||
|
spans.push(Span::styled(" ", theme.prompt_untyped));
|
||||||
|
spans
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThemedWidget for &results::Results {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer, theme: &Theme) {
|
||||||
|
buf.set_style(area, theme.default);
|
||||||
|
|
||||||
|
// Chunks
|
||||||
|
let chunks = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints([Constraint::Min(1), Constraint::Length(1)])
|
||||||
|
.split(area);
|
||||||
|
let res_chunks = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.margin(1) // Graph looks tremendously better with just a little margin
|
||||||
|
.constraints([Constraint::Ratio(1, 3), Constraint::Ratio(2, 3)])
|
||||||
|
.split(chunks[0]);
|
||||||
|
let info_chunks = Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)])
|
||||||
|
.split(res_chunks[0]);
|
||||||
|
|
||||||
|
let msg = if self.missed_words.is_empty() {
|
||||||
|
"Press 'q' to quit or 'r' for another test"
|
||||||
|
} else {
|
||||||
|
"Press 'q' to quit, 'r' for another test or 'p' to practice missed words"
|
||||||
|
};
|
||||||
|
|
||||||
|
let exit = Span::styled(msg, theme.results_restart_prompt);
|
||||||
|
buf.set_span(chunks[1].x, chunks[1].y, &exit, chunks[1].width);
|
||||||
|
|
||||||
|
// Sections
|
||||||
|
let mut overview_text = Text::styled("", theme.results_overview);
|
||||||
|
overview_text.extend([
|
||||||
|
Line::from(format!(
|
||||||
|
"Adjusted WPM: {:.1}",
|
||||||
|
self.timing.overall_cps * WPM_PER_CPS * f64::from(self.accuracy.overall)
|
||||||
|
)),
|
||||||
|
Line::from(format!(
|
||||||
|
"Accuracy: {:.1}%",
|
||||||
|
f64::from(self.accuracy.overall) * 100f64
|
||||||
|
)),
|
||||||
|
Line::from(format!(
|
||||||
|
"Raw WPM: {:.1}",
|
||||||
|
self.timing.overall_cps * WPM_PER_CPS
|
||||||
|
)),
|
||||||
|
Line::from(format!("Correct Keypresses: {}", self.accuracy.overall)),
|
||||||
|
]);
|
||||||
|
let overview = Paragraph::new(overview_text).block(
|
||||||
|
Block::default()
|
||||||
|
.title(Span::styled("Overview", theme.title))
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.border_type(BorderType::Rounded)
|
||||||
|
.border_style(theme.results_overview_border),
|
||||||
|
);
|
||||||
|
overview.render(info_chunks[0], buf);
|
||||||
|
|
||||||
|
let mut worst_keys: Vec<(&KeyEvent, &Fraction)> = self
|
||||||
|
.accuracy
|
||||||
|
.per_key
|
||||||
|
.iter()
|
||||||
|
.filter(|(key, _)| matches!(key.code, KeyCode::Char(_)))
|
||||||
|
.collect();
|
||||||
|
worst_keys.sort_unstable_by_key(|x| x.1);
|
||||||
|
|
||||||
|
let mut worst_text = Text::styled("", theme.results_worst_keys);
|
||||||
|
worst_text.extend(
|
||||||
|
worst_keys
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(key, acc)| {
|
||||||
|
if let KeyCode::Char(character) = key.code {
|
||||||
|
let key_accuracy = f64::from(**acc) * 100.0;
|
||||||
|
if key_accuracy != 100.0 {
|
||||||
|
Some(format!("- {} at {:.1}% accuracy", character, key_accuracy))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.take(5)
|
||||||
|
.map(Line::from),
|
||||||
|
);
|
||||||
|
let worst = Paragraph::new(worst_text).block(
|
||||||
|
Block::default()
|
||||||
|
.title(Span::styled("Worst Keys", theme.title))
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.border_type(BorderType::Rounded)
|
||||||
|
.border_style(theme.results_worst_keys_border),
|
||||||
|
);
|
||||||
|
worst.render(info_chunks[1], buf);
|
||||||
|
|
||||||
|
let wpm_sma: Vec<(f64, f64)> = self
|
||||||
|
.timing
|
||||||
|
.per_event
|
||||||
|
.windows(WPM_SMA_WIDTH)
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, window)| {
|
||||||
|
(
|
||||||
|
(i + WPM_SMA_WIDTH) as f64,
|
||||||
|
window.len() as f64 / window.iter().copied().sum::<f64>() * WPM_PER_CPS,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Render the chart if possible
|
||||||
|
if !wpm_sma.is_empty() {
|
||||||
|
let wpm_sma_min = wpm_sma
|
||||||
|
.iter()
|
||||||
|
.map(|(_, x)| x)
|
||||||
|
.fold(f64::INFINITY, |a, &b| a.min(b));
|
||||||
|
let wpm_sma_max = wpm_sma
|
||||||
|
.iter()
|
||||||
|
.map(|(_, x)| x)
|
||||||
|
.fold(f64::NEG_INFINITY, |a, &b| a.max(b));
|
||||||
|
|
||||||
|
let wpm_datasets = vec![Dataset::default()
|
||||||
|
.name("WPM")
|
||||||
|
.marker(Marker::Braille)
|
||||||
|
.graph_type(GraphType::Line)
|
||||||
|
.style(theme.results_chart)
|
||||||
|
.data(&wpm_sma)];
|
||||||
|
|
||||||
|
let y_label_min = wpm_sma_min as u16;
|
||||||
|
let y_label_max = (wpm_sma_max as u16).max(y_label_min + 6);
|
||||||
|
|
||||||
|
let wpm_chart = Chart::new(wpm_datasets)
|
||||||
|
.block(Block::default().title(vec![Span::styled("Chart", theme.title)]))
|
||||||
|
.x_axis(
|
||||||
|
Axis::default()
|
||||||
|
.title(Span::styled("Keypresses", theme.results_chart_x))
|
||||||
|
.bounds([0.0, self.timing.per_event.len() as f64]),
|
||||||
|
)
|
||||||
|
.y_axis(
|
||||||
|
Axis::default()
|
||||||
|
.title(Span::styled(
|
||||||
|
"WPM (10-keypress rolling average)",
|
||||||
|
theme.results_chart_y,
|
||||||
|
))
|
||||||
|
.bounds([wpm_sma_min, wpm_sma_max])
|
||||||
|
.labels(
|
||||||
|
(y_label_min..y_label_max)
|
||||||
|
.step_by(5)
|
||||||
|
.map(|n| Span::raw(format!("{}", n)))
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
wpm_chart.render(res_chunks[1], buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
mod split_words {
|
||||||
|
use super::Status::*;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
struct TestCase {
|
||||||
|
word: &'static str,
|
||||||
|
progress: &'static str,
|
||||||
|
expected: Vec<(&'static str, Status)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(test_case: TestCase) -> (TestWord, Vec<(String, Status)>) {
|
||||||
|
let mut word = TestWord::from(test_case.word);
|
||||||
|
word.progress = test_case.progress.to_string();
|
||||||
|
|
||||||
|
let expected = test_case
|
||||||
|
.expected
|
||||||
|
.iter()
|
||||||
|
.map(|(s, v)| (s.to_string(), *v))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
(word, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn typed_words_split() {
|
||||||
|
let cases = vec![
|
||||||
|
TestCase {
|
||||||
|
word: "monkeytype",
|
||||||
|
progress: "monkeytype",
|
||||||
|
expected: vec![("monkeytype", Correct)],
|
||||||
|
},
|
||||||
|
TestCase {
|
||||||
|
word: "monkeytype",
|
||||||
|
progress: "monkeXtype",
|
||||||
|
expected: vec![("monke", Correct), ("y", Incorrect), ("type", Correct)],
|
||||||
|
},
|
||||||
|
TestCase {
|
||||||
|
word: "monkeytype",
|
||||||
|
progress: "monkeas",
|
||||||
|
expected: vec![("monke", Correct), ("yt", Incorrect), ("ype", Untyped)],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for case in cases {
|
||||||
|
let (word, expected) = setup(case);
|
||||||
|
let got = split_typed_word(&word);
|
||||||
|
assert_eq!(got, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn current_word_split() {
|
||||||
|
let cases = vec![
|
||||||
|
TestCase {
|
||||||
|
word: "monkeytype",
|
||||||
|
progress: "monkeytype",
|
||||||
|
expected: vec![("monkeytype", CurrentCorrect)],
|
||||||
|
},
|
||||||
|
TestCase {
|
||||||
|
word: "monkeytype",
|
||||||
|
progress: "monke",
|
||||||
|
expected: vec![
|
||||||
|
("monke", CurrentCorrect),
|
||||||
|
("y", Cursor),
|
||||||
|
("type", CurrentUntyped),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
TestCase {
|
||||||
|
word: "monkeytype",
|
||||||
|
progress: "monkeXt",
|
||||||
|
expected: vec![
|
||||||
|
("monke", CurrentCorrect),
|
||||||
|
("y", CurrentIncorrect),
|
||||||
|
("t", CurrentCorrect),
|
||||||
|
("y", Cursor),
|
||||||
|
("pe", CurrentUntyped),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for case in cases {
|
||||||
|
let (word, expected) = setup(case);
|
||||||
|
let got = split_current_word(&word);
|
||||||
|
assert_eq!(got, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue