lexdrill 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +123 -2
- data/lexdrill.gemspec +1 -1
- data/lib/lexdrill/beat.rb +121 -0
- data/lib/lexdrill/cli.rb +121 -8
- data/lib/lexdrill/colorizer.rb +20 -0
- data/lib/lexdrill/format.rb +25 -0
- data/lib/lexdrill/inspector.rb +41 -0
- data/lib/lexdrill/line_formatter.rb +34 -0
- data/lib/lexdrill/shell_snippet.rb +34 -0
- data/lib/lexdrill/toggle.rb +22 -0
- data/lib/lexdrill/version.rb +1 -1
- data/lib/lexdrill/word_list.rb +3 -2
- data/lib/lexdrill.rb +7 -0
- metadata +10 -3
- /data/bin/{lexdrill → drill} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0510a355aeb50c560e27a73fd9ab70350c8e5c326d5dbd0ad43138e849b0bd2f
|
|
4
|
+
data.tar.gz: 529f2a86af2ec0b735c3147bd412fe82d276f6d434e6a074deefb650270818eb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d9168c8d3218c457b6cf08662254d7b69f6adaef33cb1962a1dbcf4a0243c653bffbd9a74580e5cd7dbdcce9118fe0406b221d72f42b7347525a3a0adc93ee20
|
|
7
|
+
data.tar.gz: 552834f01e378765ddf7549410ad964dc254c8c286bc34605f765661f68fb232100003dedad70c18fe025e8215cdba250d0c37f0a60338f235647f3335bb1eee
|
data/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# lexdrill
|
|
2
2
|
|
|
3
|
-
lexdrill
|
|
3
|
+
The `lexdrill` gem installs a `drill` command that prints a vocabulary word or
|
|
4
|
+
phrase on demand, tracking how often each one has been shown.
|
|
4
5
|
|
|
5
6
|
## Installation
|
|
6
7
|
|
|
@@ -8,7 +9,7 @@ lexdrill prints a vocabulary word or phrase on demand, tracking how often each o
|
|
|
8
9
|
gem install lexdrill
|
|
9
10
|
```
|
|
10
11
|
|
|
11
|
-
### `
|
|
12
|
+
### `drill: command not found` after install?
|
|
12
13
|
|
|
13
14
|
This happens when the gem's executable directory isn't on your `PATH` — common with a
|
|
14
15
|
plain system Ruby (no rbenv/rvm). Fix it in one step:
|
|
@@ -27,3 +28,123 @@ source ~/.zshrc
|
|
|
27
28
|
|
|
28
29
|
Using an rbenv-managed Ruby? Run `rbenv rehash` instead. Using rvm? You shouldn't hit
|
|
29
30
|
this — rvm keeps gem executable directories on `PATH` automatically.
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
Create a `.drill.txt` file — one word or phrase per line — in your home directory,
|
|
35
|
+
or in a specific project directory to give that project its own list:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
apple
|
|
39
|
+
banana
|
|
40
|
+
cherry
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Run `drill next` to print the current word and advance to the next one.
|
|
44
|
+
There are two output styles (`drill format simple|full`, `full` is the
|
|
45
|
+
default):
|
|
46
|
+
|
|
47
|
+
**full** — `counter/total⟳[loop_start-loop_end]` on one line, the word on
|
|
48
|
+
the next, in a randomly-picked color:
|
|
49
|
+
```
|
|
50
|
+
1/6⟳[1-3]
|
|
51
|
+
apple
|
|
52
|
+
```
|
|
53
|
+
(word 1 of 6 total; currently in the loop spanning words 1-3)
|
|
54
|
+
|
|
55
|
+
**simple** — three drill signs (always blue), then the word on its own
|
|
56
|
+
line in a separately-picked random color:
|
|
57
|
+
```
|
|
58
|
+
⟳⟳⟳
|
|
59
|
+
apple
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Shell integration (one-time setup)
|
|
63
|
+
|
|
64
|
+
Add one line to your shell's rc file so the current word prints automatically
|
|
65
|
+
before each prompt. This is a **one-time step** — the line stays in your rc file
|
|
66
|
+
and takes effect in every new shell session from then on; you never need to run it
|
|
67
|
+
again by hand.
|
|
68
|
+
|
|
69
|
+
Add it near the **end** of the file, after anything that sets up your `PATH`
|
|
70
|
+
(rvm/rbenv init, Homebrew, etc.) — otherwise the shell may not know where
|
|
71
|
+
`drill` lives yet when this line runs. Guarding it with `command -v` also
|
|
72
|
+
means it never errors, even on a machine/session where `drill` isn't on
|
|
73
|
+
`PATH` for some reason.
|
|
74
|
+
|
|
75
|
+
**zsh** — add to `~/.zshrc`:
|
|
76
|
+
```zsh
|
|
77
|
+
if command -v drill >/dev/null 2>&1; then
|
|
78
|
+
eval "$(drill hook zsh)"
|
|
79
|
+
fi
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**bash** — add to `~/.bashrc`:
|
|
83
|
+
```bash
|
|
84
|
+
if command -v drill >/dev/null 2>&1; then
|
|
85
|
+
eval "$(drill hook bash)"
|
|
86
|
+
fi
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Then open a new shell (or `source ~/.zshrc` / `source ~/.bashrc`) to pick it up.
|
|
90
|
+
|
|
91
|
+
#### Hook not firing in new terminal windows/tabs (rvm users)?
|
|
92
|
+
|
|
93
|
+
Run `drill inspect` first — if it shows `Toggle: enabled` and a valid words
|
|
94
|
+
file, the tool itself is fine and the problem is that the hook function never
|
|
95
|
+
got registered in that session.
|
|
96
|
+
|
|
97
|
+
A common cause with rvm: rvm's installer sometimes puts its actual
|
|
98
|
+
PATH-loading line (`[[ -s "$HOME/.rvm/scripts/rvm" ]] && source
|
|
99
|
+
"$HOME/.rvm/scripts/rvm"`) in `~/.zlogin` (zsh) or `~/.bash_profile` (bash).
|
|
100
|
+
Those files only run for **login shells** — but many terminal emulators
|
|
101
|
+
(e.g. kitty) open new windows/tabs as **non-login** interactive shells, which
|
|
102
|
+
only source `~/.zshrc`/`~/.bashrc`. So rvm's gemset `bin/` directory (where
|
|
103
|
+
`drill` lives) never makes it onto `PATH` in those sessions, even though
|
|
104
|
+
everything looks correctly configured.
|
|
105
|
+
|
|
106
|
+
**Fix:** move that line into `~/.zshrc` / `~/.bashrc` (before the `drill`
|
|
107
|
+
hook block above, so it runs first) instead of relying on it only being in
|
|
108
|
+
`~/.zlogin` / `~/.bash_profile`. It's safe to leave it in both places.
|
|
109
|
+
|
|
110
|
+
### Rhythm (`beat`)
|
|
111
|
+
|
|
112
|
+
By default `next` just advances one word at a time. You can instead have it
|
|
113
|
+
repeat loops of consecutive words — e.g. a loop of 3 words shown twice each
|
|
114
|
+
before moving on:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
drill beat 3 2 # loop size 3, repeat each loop 2 times
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`drill beat none` turns it back off (plain word-by-word again). This is a
|
|
121
|
+
**global** setting — it applies everywhere, independent of which project's
|
|
122
|
+
`.drill.txt` is currently active.
|
|
123
|
+
|
|
124
|
+
There are also named shortcuts for common loop sizes, one word/phrase apart:
|
|
125
|
+
|
|
126
|
+
| Loop size | Alias |
|
|
127
|
+
|-----------|----------|
|
|
128
|
+
| 2 | `polka` |
|
|
129
|
+
| 3 | `waltz` |
|
|
130
|
+
| 4 | `rock` |
|
|
131
|
+
| 5 | `jazz` |
|
|
132
|
+
| 6 | `jiga` |
|
|
133
|
+
| 7 | `balkan` |
|
|
134
|
+
| 8 | `samba` |
|
|
135
|
+
|
|
136
|
+
`drill waltz 16` is shorthand for `drill beat 3 16`. Repetitions default to
|
|
137
|
+
`8` if you leave them off — `drill jazz` alone is `drill beat 5 8`, and
|
|
138
|
+
`drill beat 4` alone is `drill beat 4 8`.
|
|
139
|
+
|
|
140
|
+
### Commands
|
|
141
|
+
|
|
142
|
+
| Command | What it does |
|
|
143
|
+
|---|---|
|
|
144
|
+
| `drill next` | Print the current word and advance |
|
|
145
|
+
| `drill start` / `drill stop` | Pause/resume the automatic per-prompt hook (doesn't affect manual `next`) |
|
|
146
|
+
| `drill inspect` | Show the active `.drill.txt`/`.drill.counter` paths, word count, counter value, toggle, and beat state |
|
|
147
|
+
| `drill hook zsh\|bash` | Print the shell integration snippet (used above) |
|
|
148
|
+
| `drill beat <2-8> <repetitions>` / `drill beat none` | Set or disable the rhythm |
|
|
149
|
+
| `drill polka\|waltz\|rock\|jazz\|jiga\|balkan\|samba <repetitions>` | Shorthand for a fixed loop size (see table above) |
|
|
150
|
+
| `drill format simple\|full` | Set the output style (`full` is the default) |
|
data/lexdrill.gemspec
CHANGED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
# Global rhythm config: repeats loops of `loop_size` consecutive words
|
|
6
|
+
# `repetitions` times before advancing to the next loop. Lives at
|
|
7
|
+
# ~/.drill.beat, independent of whichever project's .drill.txt is active.
|
|
8
|
+
# Disabled (plain word-by-word advance) unless explicitly configured.
|
|
9
|
+
module Lexdrill::Beat
|
|
10
|
+
PATH = File.join(Dir.home, ".drill.beat")
|
|
11
|
+
MIN_LOOP_SIZE = 2
|
|
12
|
+
MAX_LOOP_SIZE = 8
|
|
13
|
+
DEFAULT_REPETITIONS = 8
|
|
14
|
+
|
|
15
|
+
ALIASES = {
|
|
16
|
+
"polka" => 2,
|
|
17
|
+
"waltz" => 3,
|
|
18
|
+
"rock" => 4,
|
|
19
|
+
"jazz" => 5,
|
|
20
|
+
"jiga" => 6,
|
|
21
|
+
"balkan" => 7,
|
|
22
|
+
"samba" => 8
|
|
23
|
+
}.freeze
|
|
24
|
+
|
|
25
|
+
# index: 0-based word index. chunk_start/chunk_end: 1-based word positions
|
|
26
|
+
# spanned by the current loop. loop_number: which repeat pass (1-based)
|
|
27
|
+
# through the current loop. total_loops: the configured repetitions.
|
|
28
|
+
LoopInfo = Struct.new(:index, :chunk_start, :chunk_end, :loop_number, :total_loops, keyword_init: true)
|
|
29
|
+
|
|
30
|
+
def self.configured?
|
|
31
|
+
File.exist?(PATH)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.set(loop_size, repetitions)
|
|
35
|
+
File.write(PATH, "#{loop_size} #{repetitions}")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.clear
|
|
39
|
+
FileUtils.rm_f(PATH)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.valid_loop_size?(value)
|
|
43
|
+
(MIN_LOOP_SIZE..MAX_LOOP_SIZE).cover?(value)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.repetitions_or_default(value)
|
|
47
|
+
value ? value.to_i : DEFAULT_REPETITIONS
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.loop_size
|
|
51
|
+
settings[0]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.repetitions
|
|
55
|
+
settings[1]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Total steps in one full cycle through the word list at the current
|
|
59
|
+
# rhythm, or plain `word_count` when no beat is configured.
|
|
60
|
+
def self.cycle_length(word_count)
|
|
61
|
+
return word_count unless configured?
|
|
62
|
+
|
|
63
|
+
chunk_sizes(word_count).sum { |size| size * repetitions }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Maps a step (already bounded within cycle_length) to a word index.
|
|
67
|
+
def self.index_for(word_count, step)
|
|
68
|
+
loop_info(word_count, step).index
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Full detail on where `step` falls: word index, the current loop's word
|
|
72
|
+
# range, which repeat pass it is, and how many total. A single "loop"
|
|
73
|
+
# spanning the whole list (shown once) when no beat is configured.
|
|
74
|
+
def self.loop_info(word_count, step)
|
|
75
|
+
unless configured?
|
|
76
|
+
return LoopInfo.new(index: step, chunk_start: 1, chunk_end: word_count, loop_number: 1, total_loops: 1)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
locate(word_count, step)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def self.locate(word_count, step)
|
|
83
|
+
offset = 0
|
|
84
|
+
chunk_sizes(word_count).each do |size|
|
|
85
|
+
span = size * repetitions
|
|
86
|
+
return locate_within(offset, size, step) if step < span
|
|
87
|
+
|
|
88
|
+
step -= span
|
|
89
|
+
offset += size
|
|
90
|
+
end
|
|
91
|
+
LoopInfo.new(index: 0, chunk_start: 1, chunk_end: word_count, loop_number: 1, total_loops: 1)
|
|
92
|
+
end
|
|
93
|
+
private_class_method :locate
|
|
94
|
+
|
|
95
|
+
def self.locate_within(offset, size, step)
|
|
96
|
+
LoopInfo.new(
|
|
97
|
+
index: offset + (step % size),
|
|
98
|
+
chunk_start: offset + 1,
|
|
99
|
+
chunk_end: offset + size,
|
|
100
|
+
loop_number: (step / size) + 1,
|
|
101
|
+
total_loops: repetitions
|
|
102
|
+
)
|
|
103
|
+
end
|
|
104
|
+
private_class_method :locate_within
|
|
105
|
+
|
|
106
|
+
def self.settings
|
|
107
|
+
return [nil, nil] unless configured?
|
|
108
|
+
|
|
109
|
+
parts = File.read(PATH).strip.split
|
|
110
|
+
[parts[0].to_i, parts[1].to_i]
|
|
111
|
+
end
|
|
112
|
+
private_class_method :settings
|
|
113
|
+
|
|
114
|
+
def self.chunk_sizes(word_count)
|
|
115
|
+
full, remainder = word_count.divmod(loop_size)
|
|
116
|
+
sizes = Array.new(full, loop_size)
|
|
117
|
+
sizes << remainder if remainder.positive?
|
|
118
|
+
sizes
|
|
119
|
+
end
|
|
120
|
+
private_class_method :chunk_sizes
|
|
121
|
+
end
|
data/lib/lexdrill/cli.rb
CHANGED
|
@@ -4,7 +4,14 @@ class Lexdrill::CLI
|
|
|
4
4
|
COMMANDS = {
|
|
5
5
|
print_version: %w[version --version -v],
|
|
6
6
|
print_help: %w[help --help -h],
|
|
7
|
-
run_next: %w[next]
|
|
7
|
+
run_next: %w[next],
|
|
8
|
+
run_hook: %w[hook],
|
|
9
|
+
run_start: %w[start],
|
|
10
|
+
run_stop: %w[stop],
|
|
11
|
+
run_inspect: %w[inspect],
|
|
12
|
+
run_beat: %w[beat],
|
|
13
|
+
run_beat_alias: %w[polka waltz rock jazz jiga balkan samba],
|
|
14
|
+
run_format: %w[format]
|
|
8
15
|
}.freeze
|
|
9
16
|
|
|
10
17
|
def self.start(argv = ARGV)
|
|
@@ -34,12 +41,21 @@ class Lexdrill::CLI
|
|
|
34
41
|
|
|
35
42
|
def print_help
|
|
36
43
|
puts <<~HELP
|
|
37
|
-
|
|
44
|
+
drill #{Lexdrill::VERSION} — vocabulary drilling in your terminal
|
|
38
45
|
|
|
39
46
|
Usage:
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
47
|
+
drill version Print the gem version
|
|
48
|
+
drill help Show this help
|
|
49
|
+
drill next Print the current word and advance
|
|
50
|
+
drill hook <zsh|bash> Print the shell integration snippet
|
|
51
|
+
drill start Resume drilling (undoes stop)
|
|
52
|
+
drill stop Pause drilling everywhere until `start`
|
|
53
|
+
drill inspect Show the active config/counter/toggle state
|
|
54
|
+
drill beat <2-8> [repetitions] Set the rhythm (repetitions defaults to 8)
|
|
55
|
+
drill beat none Disable the rhythm
|
|
56
|
+
drill polka|waltz|rock|jazz|jiga|balkan|samba [repetitions]
|
|
57
|
+
Shorthand for a fixed loop size (2 through 8, in order)
|
|
58
|
+
drill format simple|full Set the output style (full is the default)
|
|
43
59
|
HELP
|
|
44
60
|
0
|
|
45
61
|
end
|
|
@@ -48,17 +64,114 @@ class Lexdrill::CLI
|
|
|
48
64
|
word = Lexdrill::WordList.next
|
|
49
65
|
return print_no_words(Lexdrill::WordList::PATH) unless word
|
|
50
66
|
|
|
51
|
-
puts word
|
|
67
|
+
puts colored_line(word)
|
|
52
68
|
0
|
|
53
69
|
end
|
|
54
70
|
|
|
71
|
+
def colored_line(word)
|
|
72
|
+
text = Lexdrill::LineFormatter.format(word)
|
|
73
|
+
return text if Lexdrill::Format.simple?
|
|
74
|
+
|
|
75
|
+
Lexdrill::Colorizer.paint(text)
|
|
76
|
+
end
|
|
77
|
+
|
|
55
78
|
def print_no_words(path)
|
|
56
|
-
warn "
|
|
79
|
+
warn "drill: no words found in #{path}"
|
|
80
|
+
1
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def run_hook
|
|
84
|
+
shell = argv[1]
|
|
85
|
+
return print_hook_usage unless shell
|
|
86
|
+
|
|
87
|
+
print_hook_snippet(shell)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def print_hook_usage
|
|
91
|
+
warn "usage: drill hook <zsh|bash>"
|
|
92
|
+
1
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def print_hook_snippet(shell)
|
|
96
|
+
puts Lexdrill::ShellSnippet.for(shell)
|
|
97
|
+
0
|
|
98
|
+
rescue ArgumentError => error
|
|
99
|
+
warn "drill: #{error.message}"
|
|
100
|
+
1
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def run_start
|
|
104
|
+
Lexdrill::Toggle.start
|
|
105
|
+
0
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def run_stop
|
|
109
|
+
Lexdrill::Toggle.stop
|
|
110
|
+
0
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def run_inspect
|
|
114
|
+
puts Lexdrill::Inspector.report
|
|
115
|
+
0
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def run_beat
|
|
119
|
+
arg = argv[1]
|
|
120
|
+
return print_beat_usage unless arg
|
|
121
|
+
return clear_beat if arg == "none"
|
|
122
|
+
|
|
123
|
+
set_beat(arg.to_i, Lexdrill::Beat.repetitions_or_default(argv[2]))
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def run_beat_alias
|
|
127
|
+
loop_size = Lexdrill::Beat::ALIASES.fetch(argv.first)
|
|
128
|
+
set_beat(loop_size, Lexdrill::Beat.repetitions_or_default(argv[1]))
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def set_beat(loop_size, repetitions)
|
|
132
|
+
return print_invalid_loop_size unless Lexdrill::Beat.valid_loop_size?(loop_size)
|
|
133
|
+
return print_invalid_repetitions unless repetitions.positive?
|
|
134
|
+
|
|
135
|
+
Lexdrill::Beat.set(loop_size, repetitions)
|
|
136
|
+
0
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def clear_beat
|
|
140
|
+
Lexdrill::Beat.clear
|
|
141
|
+
0
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def print_beat_usage
|
|
145
|
+
warn "usage: drill beat <#{Lexdrill::Beat::MIN_LOOP_SIZE}-#{Lexdrill::Beat::MAX_LOOP_SIZE}> " \
|
|
146
|
+
"<repetitions> | drill beat none"
|
|
147
|
+
1
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def print_invalid_loop_size
|
|
151
|
+
warn "drill: loop size must be between #{Lexdrill::Beat::MIN_LOOP_SIZE} and #{Lexdrill::Beat::MAX_LOOP_SIZE}"
|
|
152
|
+
1
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def print_invalid_repetitions
|
|
156
|
+
warn "drill: repetitions must be a positive number"
|
|
157
|
+
1
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def run_format
|
|
161
|
+
mode = argv[1]
|
|
162
|
+
return print_format_usage unless Lexdrill::Format::VALID.include?(mode)
|
|
163
|
+
|
|
164
|
+
Lexdrill::Format.set(mode)
|
|
165
|
+
0
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def print_format_usage
|
|
169
|
+
warn "usage: drill format <#{Lexdrill::Format::VALID.join('|')}>"
|
|
57
170
|
1
|
|
58
171
|
end
|
|
59
172
|
|
|
60
173
|
def print_unknown_command(command)
|
|
61
|
-
warn "
|
|
174
|
+
warn "drill: unknown command #{command.inspect}"
|
|
62
175
|
print_help
|
|
63
176
|
1
|
|
64
177
|
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Wraps text in an ANSI color code.
|
|
4
|
+
module Lexdrill::Colorizer
|
|
5
|
+
CODES = [31, 32, 33, 34, 35, 36, 91, 92, 93, 94, 95, 96].freeze
|
|
6
|
+
BLUE = 34
|
|
7
|
+
|
|
8
|
+
def self.paint(text)
|
|
9
|
+
wrap(text, CODES.sample)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.paint_blue(text)
|
|
13
|
+
wrap(text, BLUE)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.wrap(text, code)
|
|
17
|
+
"\e[#{code}m#{text}\e[0m"
|
|
18
|
+
end
|
|
19
|
+
private_class_method :wrap
|
|
20
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Global display mode for `next`'s output: "full" (position + loop info) or
|
|
4
|
+
# "simple" (just the word). Lives at ~/.drill.format; defaults to "full".
|
|
5
|
+
module Lexdrill::Format
|
|
6
|
+
PATH = File.join(Dir.home, ".drill.format")
|
|
7
|
+
SIMPLE = "simple"
|
|
8
|
+
FULL = "full"
|
|
9
|
+
VALID = [SIMPLE, FULL].freeze
|
|
10
|
+
|
|
11
|
+
def self.current
|
|
12
|
+
return FULL unless File.exist?(PATH)
|
|
13
|
+
|
|
14
|
+
value = File.read(PATH).strip
|
|
15
|
+
VALID.include?(value) ? value : FULL
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.set(mode)
|
|
19
|
+
File.write(PATH, mode)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.simple?
|
|
23
|
+
current == SIMPLE
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Builds a human-readable snapshot of lexdrill's current config/state, for
|
|
4
|
+
# `drill inspect` — which files are active, how many words, the counter
|
|
5
|
+
# value, and whether drilling is currently stopped.
|
|
6
|
+
module Lexdrill::Inspector
|
|
7
|
+
def self.report
|
|
8
|
+
<<~REPORT
|
|
9
|
+
drill #{Lexdrill::VERSION}
|
|
10
|
+
|
|
11
|
+
Words file: #{Lexdrill::WordList::PATH} (#{words_summary})
|
|
12
|
+
Counter file: #{Lexdrill::WordList::COUNTER_PATH} (value: #{counter_value})
|
|
13
|
+
Toggle: #{toggle_summary}
|
|
14
|
+
Beat: #{beat_summary}
|
|
15
|
+
Format: #{Lexdrill::Format.current}
|
|
16
|
+
LEXDRILL_PATH: #{ENV.fetch('LEXDRILL_PATH', '(not set)')}
|
|
17
|
+
REPORT
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.words_summary
|
|
21
|
+
return "missing" unless File.exist?(Lexdrill::WordList::PATH)
|
|
22
|
+
|
|
23
|
+
"#{Lexdrill::WordList.words.size} word(s)"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.counter_value
|
|
27
|
+
Lexdrill::Counter.new(Lexdrill::WordList::COUNTER_PATH).value
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.toggle_summary
|
|
31
|
+
return "enabled" if Lexdrill::Toggle.enabled?
|
|
32
|
+
|
|
33
|
+
"stopped (#{Lexdrill::Toggle::PATH})"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.beat_summary
|
|
37
|
+
return "not set (plain word-by-word)" unless Lexdrill::Beat.configured?
|
|
38
|
+
|
|
39
|
+
"loop #{Lexdrill::Beat.loop_size}, repeat #{Lexdrill::Beat.repetitions}"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Formats a shown word for display. In "simple" mode: three drill signs in
|
|
4
|
+
# blue, then the word on its own line in a separately-picked random color.
|
|
5
|
+
# In "full" mode (the default):
|
|
6
|
+
# "{counter}/{total}{SEPARATOR}[{loop_start}-{loop_end}]\n{word}" — counter
|
|
7
|
+
# is the word's own 1-based position in the list, re-derived through
|
|
8
|
+
# Lexdrill::Beat so it stays meaningful even when a rhythm repeats steps.
|
|
9
|
+
module Lexdrill::LineFormatter
|
|
10
|
+
SEPARATOR = "⟳"
|
|
11
|
+
SIMPLE_HEADER = SEPARATOR * 3
|
|
12
|
+
|
|
13
|
+
def self.format(word)
|
|
14
|
+
return simple(word) if Lexdrill::Format.simple?
|
|
15
|
+
|
|
16
|
+
full(word)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.simple(word)
|
|
20
|
+
"#{Lexdrill::Colorizer.paint_blue(SIMPLE_HEADER)}\n#{Lexdrill::Colorizer.paint(word)}"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.full(word)
|
|
24
|
+
total = Lexdrill::WordList.words.size
|
|
25
|
+
info = loop_info(total)
|
|
26
|
+
"#{info.index + 1}/#{total}#{SEPARATOR}[#{info.chunk_start}-#{info.chunk_end}]\n#{word}"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.loop_info(total)
|
|
30
|
+
counter = Lexdrill::Counter.new(Lexdrill::WordList::COUNTER_PATH)
|
|
31
|
+
step_used = counter.value - 1
|
|
32
|
+
Lexdrill::Beat.loop_info(total, step_used)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Generates the zsh/bash shell integration snippet for `drill hook`.
|
|
4
|
+
module Lexdrill::ShellSnippet
|
|
5
|
+
ZSH = <<~SNIPPET
|
|
6
|
+
drill_precmd() {
|
|
7
|
+
[[ -f "$HOME/.drill.disabled" ]] && return
|
|
8
|
+
command drill next 2>/dev/null
|
|
9
|
+
}
|
|
10
|
+
if [[ -z "${precmd_functions[(r)drill_precmd]}" ]]; then
|
|
11
|
+
precmd_functions+=(drill_precmd)
|
|
12
|
+
fi
|
|
13
|
+
SNIPPET
|
|
14
|
+
|
|
15
|
+
BASH = <<~SNIPPET
|
|
16
|
+
drill_precmd() {
|
|
17
|
+
[ -f "$HOME/.drill.disabled" ] && return
|
|
18
|
+
command drill next 2>/dev/null
|
|
19
|
+
}
|
|
20
|
+
case ";${PROMPT_COMMAND:-};" in
|
|
21
|
+
*";drill_precmd;"*) ;;
|
|
22
|
+
*) PROMPT_COMMAND="drill_precmd;${PROMPT_COMMAND}" ;;
|
|
23
|
+
esac
|
|
24
|
+
SNIPPET
|
|
25
|
+
|
|
26
|
+
def self.for(shell_name)
|
|
27
|
+
case shell_name.to_s
|
|
28
|
+
when "zsh" then ZSH
|
|
29
|
+
when "bash" then BASH
|
|
30
|
+
else
|
|
31
|
+
raise ArgumentError, "unsupported shell #{shell_name.inspect} (expected \"zsh\" or \"bash\")"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
# Global pause/resume switch for the shell hook, independent of which
|
|
6
|
+
# project's .drill.txt is active. Enabled by default; the marker file's
|
|
7
|
+
# presence means stopped.
|
|
8
|
+
module Lexdrill::Toggle
|
|
9
|
+
PATH = File.join(Dir.home, ".drill.disabled")
|
|
10
|
+
|
|
11
|
+
def self.enabled?
|
|
12
|
+
!File.exist?(PATH)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.start
|
|
16
|
+
FileUtils.rm_f(PATH)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.stop
|
|
20
|
+
File.write(PATH, "")
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/lexdrill/version.rb
CHANGED
data/lib/lexdrill/word_list.rb
CHANGED
|
@@ -19,8 +19,9 @@ class Lexdrill::WordList
|
|
|
19
19
|
|
|
20
20
|
def self.take_index(size)
|
|
21
21
|
counter = Lexdrill::Counter.new(COUNTER_PATH)
|
|
22
|
-
|
|
22
|
+
cycle_length = Lexdrill::Beat.cycle_length(size)
|
|
23
|
+
step = counter.bounded_value(cycle_length)
|
|
23
24
|
counter.increment
|
|
24
|
-
|
|
25
|
+
Lexdrill::Beat.index_for(size, step)
|
|
25
26
|
end
|
|
26
27
|
end
|
data/lib/lexdrill.rb
CHANGED
|
@@ -3,5 +3,12 @@
|
|
|
3
3
|
require_relative "lexdrill/version"
|
|
4
4
|
require_relative "lexdrill/config"
|
|
5
5
|
require_relative "lexdrill/counter"
|
|
6
|
+
require_relative "lexdrill/beat"
|
|
6
7
|
require_relative "lexdrill/word_list"
|
|
8
|
+
require_relative "lexdrill/toggle"
|
|
9
|
+
require_relative "lexdrill/shell_snippet"
|
|
10
|
+
require_relative "lexdrill/inspector"
|
|
11
|
+
require_relative "lexdrill/colorizer"
|
|
12
|
+
require_relative "lexdrill/format"
|
|
13
|
+
require_relative "lexdrill/line_formatter"
|
|
7
14
|
require_relative "lexdrill/cli"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lexdrill
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Siarhei Kisliak
|
|
@@ -15,19 +15,26 @@ description: lexdrill prints a vocabulary word or phrase on demand, tracking how
|
|
|
15
15
|
email:
|
|
16
16
|
- kislak7@gmail.com
|
|
17
17
|
executables:
|
|
18
|
-
-
|
|
18
|
+
- drill
|
|
19
19
|
extensions: []
|
|
20
20
|
extra_rdoc_files: []
|
|
21
21
|
files:
|
|
22
22
|
- Gemfile
|
|
23
23
|
- LICENSE.txt
|
|
24
24
|
- README.md
|
|
25
|
-
- bin/
|
|
25
|
+
- bin/drill
|
|
26
26
|
- lexdrill.gemspec
|
|
27
27
|
- lib/lexdrill.rb
|
|
28
|
+
- lib/lexdrill/beat.rb
|
|
28
29
|
- lib/lexdrill/cli.rb
|
|
30
|
+
- lib/lexdrill/colorizer.rb
|
|
29
31
|
- lib/lexdrill/config.rb
|
|
30
32
|
- lib/lexdrill/counter.rb
|
|
33
|
+
- lib/lexdrill/format.rb
|
|
34
|
+
- lib/lexdrill/inspector.rb
|
|
35
|
+
- lib/lexdrill/line_formatter.rb
|
|
36
|
+
- lib/lexdrill/shell_snippet.rb
|
|
37
|
+
- lib/lexdrill/toggle.rb
|
|
31
38
|
- lib/lexdrill/version.rb
|
|
32
39
|
- lib/lexdrill/word_list.rb
|
|
33
40
|
homepage: https://github.com/kislak/lexdrill
|
/data/bin/{lexdrill → drill}
RENAMED
|
File without changes
|