lexdrill 0.17.0 → 0.19.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 +31 -37
- data/lib/lexdrill/beat.rb +6 -7
- data/lib/lexdrill/cli.rb +15 -17
- data/lib/lexdrill/color.rb +27 -0
- data/lib/lexdrill/colorizer.rb +7 -6
- data/lib/lexdrill/config.rb +11 -17
- data/lib/lexdrill/default_words.rb +1 -2
- data/lib/lexdrill/google_auth.rb +1 -1
- data/lib/lexdrill/inspector.rb +2 -2
- data/lib/lexdrill/line_formatter.rb +11 -21
- data/lib/lexdrill/oauth_remote.rb +4 -4
- data/lib/lexdrill/rand.rb +3 -3
- data/lib/lexdrill/remote.rb +4 -4
- data/lib/lexdrill/service_account_auth.rb +1 -1
- data/lib/lexdrill/stats.rb +7 -8
- data/lib/lexdrill/toggle.rb +3 -4
- data/lib/lexdrill/version.rb +1 -1
- data/lib/lexdrill/word_list.rb +10 -12
- data/lib/lexdrill.rb +1 -1
- metadata +3 -3
- data/lib/lexdrill/format.rb +0 -26
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2c04d80068319a1be565d64c8325ce808f242d7a71609f34796b348cc04b0571
|
|
4
|
+
data.tar.gz: 2b06d6b6d48cc3556c77524bce72c77e18404aceadd802cb3ae40134a29cfc3d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c15dac65d6521acd98fb4a81f016466882d4397587a9de255544364cbcb0a9bc535e399c91dd7979ea4fadc5206ca86c4c2f04db62898baa298d2a82a24f908d
|
|
7
|
+
data.tar.gz: b8f7dd159de9b89f81cafd9a73d50d2e7db6541ed3fcd0028f4dbee6b344301f085e3be7f7c78a669bb1494845afc65eaba886f17a164355dd99521a9a6d4c6b
|
data/README.md
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
The `lexdrill` gem installs a `drill` command that prints a vocabulary word or
|
|
4
4
|
phrase on demand, tracking how often each one has been shown.
|
|
5
5
|
|
|
6
|
+

|
|
7
|
+
|
|
6
8
|
## Installation
|
|
7
9
|
|
|
8
10
|
```bash
|
|
@@ -31,8 +33,9 @@ this — rvm keeps gem executable directories on `PATH` automatically.
|
|
|
31
33
|
|
|
32
34
|
## Usage
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
All of lexdrill's state lives in one place: `~/.drill/` (mode `0700`). It's a
|
|
37
|
+
single global config — no per-project lists, no environment variable
|
|
38
|
+
overrides. Create `~/.drill/words` yourself — one word or phrase per line:
|
|
36
39
|
|
|
37
40
|
```
|
|
38
41
|
apple
|
|
@@ -40,27 +43,18 @@ banana
|
|
|
40
43
|
cherry
|
|
41
44
|
```
|
|
42
45
|
|
|
43
|
-
If you don't create one, `drill next` seeds `~/.drill
|
|
46
|
+
If you don't create one, `drill next` seeds `~/.drill/words` with a default
|
|
44
47
|
starter list (a set of NLP presuppositions) the first time it runs, so
|
|
45
48
|
there's always something to drill.
|
|
46
49
|
|
|
47
50
|
Run `drill next` to print the current word and advance to the next one.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
**simple** — the drill sign (always blue), a space, then the word (colored
|
|
52
|
-
by its show count — see "Mastery" below), all on one line:
|
|
51
|
+
Output is `counter/total` (the word's own 1-based position in the list)
|
|
52
|
+
and the drill sign, together in yellow, a space, then the word (colored by
|
|
53
|
+
its show count — see "Mastery" below), all on one line:
|
|
53
54
|
```
|
|
54
|
-
⟳ apple
|
|
55
|
+
1/6⟳ apple
|
|
55
56
|
```
|
|
56
|
-
|
|
57
|
-
**full** — `counter/total⟳[loop_start-loop_end]` on one line, the word on
|
|
58
|
-
the next, colored by the word's show count:
|
|
59
|
-
```
|
|
60
|
-
1/6⟳[1-3]
|
|
61
|
-
apple
|
|
62
|
-
```
|
|
63
|
-
(word 1 of 6 total; currently in the loop spanning words 1-3)
|
|
57
|
+
(word 1 of 6 total)
|
|
64
58
|
|
|
65
59
|
### Shell integration (one-time setup)
|
|
66
60
|
|
|
@@ -131,9 +125,7 @@ before moving on:
|
|
|
131
125
|
drill beat 3 2 # loop size 3, repeat each loop 2 times
|
|
132
126
|
```
|
|
133
127
|
|
|
134
|
-
`drill beat none` turns it back off (plain word-by-word again).
|
|
135
|
-
**global** setting — it applies everywhere, independent of which project's
|
|
136
|
-
`.drill.txt` is currently active.
|
|
128
|
+
`drill beat none` turns it back off (plain word-by-word again).
|
|
137
129
|
|
|
138
130
|
There are also named shortcuts for common loop sizes, one word/phrase apart:
|
|
139
131
|
|
|
@@ -169,16 +161,18 @@ exits 0.
|
|
|
169
161
|
|
|
170
162
|
### Mastery (color by show count)
|
|
171
163
|
|
|
172
|
-
Every time `next` shows a word it's tracked in
|
|
173
|
-
[`drill stats`](#commands) below).
|
|
174
|
-
color follows a blue → red gradient, one step per
|
|
175
|
-
glance, blue words are fresh and red words are heavily drilled.
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
164
|
+
Every time `next` shows a word it's tracked in `~/.drill/stats` (see
|
|
165
|
+
[`drill stats`](#commands) below). By default (`drill color default`), the
|
|
166
|
+
word's color follows a blue → red gradient, one step per 10 shows — so at
|
|
167
|
+
a glance, blue words are fresh and red words are heavily drilled. Once a
|
|
168
|
+
word hits 120 shows it's considered mastered and `next` stops selecting it
|
|
169
|
+
(it still appears in `drill list`/`drill stats`, just no longer comes up
|
|
170
|
+
automatically). If every word in the list has been mastered, `next`
|
|
171
|
+
reports that on stderr and exits 1 instead of showing anything.
|
|
172
|
+
|
|
173
|
+
Prefer a vivid random color every time instead of the gradient? Run
|
|
174
|
+
`drill color random` (and `drill color default` to switch back — this is
|
|
175
|
+
the default, so it's also the way to undo `random`).
|
|
182
176
|
|
|
183
177
|
### Google Sheets export
|
|
184
178
|
|
|
@@ -202,7 +196,7 @@ Either way, `export` always **overwrites** the named tab (creating it
|
|
|
202
196
|
first if it doesn't exist yet) with the current word list, one phrase per
|
|
203
197
|
row, so it stays an exact mirror even if the list shrinks. `import` is the
|
|
204
198
|
reverse: it reads column A of the tab (ignoring any other columns, like an
|
|
205
|
-
old export's show counts) and **replaces** your local
|
|
199
|
+
old export's show counts) and **replaces** your local `~/.drill/words` —
|
|
206
200
|
useful for editing the list in Sheets and pulling changes back down, or
|
|
207
201
|
seeding a fresh machine from an existing sheet.
|
|
208
202
|
|
|
@@ -228,9 +222,9 @@ a real secret you must keep local.
|
|
|
228
222
|
field in the downloaded JSON).
|
|
229
223
|
5. Open your target Google Sheet → **Share** → paste that email → grant
|
|
230
224
|
**Editor** access → uncheck "Notify people" → Share.
|
|
231
|
-
6. Save the downloaded key file to `~/.drill
|
|
232
|
-
your machine (`drill` reads it from that fixed path
|
|
233
|
-
|
|
225
|
+
6. Save the downloaded key file to `~/.drill/gcp-service-account.json` on
|
|
226
|
+
your machine (`drill` reads it from that fixed path; it's never embedded
|
|
227
|
+
in the gem or committed anywhere).
|
|
234
228
|
|
|
235
229
|
```bash
|
|
236
230
|
drill remote 'https://docs.google.com/spreadsheets/d/1opBP4APL5SUvepm9qwjIYRNtDZdoY1Ee87F5PWdxaMg/edit?usp=sharing'
|
|
@@ -245,7 +239,7 @@ short-lived JWT with the local key file.
|
|
|
245
239
|
Uses the OAuth 2.0 **Device Authorization Grant** ("visit this URL, enter
|
|
246
240
|
this code," the same style of flow `gcloud auth login` uses) — you approve
|
|
247
241
|
access to your own account once, and the resulting token is cached locally
|
|
248
|
-
at `~/.drill
|
|
242
|
+
at `~/.drill/gcp-token.json` (mode `0600`), never published or shared.
|
|
249
243
|
|
|
250
244
|
The OAuth client id/secret embedded in `lib/lexdrill/google_auth.rb` are
|
|
251
245
|
**not** secret for this use — Google's own docs say client credentials for
|
|
@@ -286,11 +280,11 @@ itself automatically).
|
|
|
286
280
|
|---|---|
|
|
287
281
|
| `drill next` | Print the current word and advance |
|
|
288
282
|
| `drill start` / `drill stop` | Pause/resume the automatic per-prompt hook (doesn't affect manual `next`) |
|
|
289
|
-
| `drill inspect` | Show the active
|
|
283
|
+
| `drill inspect` | Show the active config directory, word count, counter value, toggle, beat, rand, and color state |
|
|
290
284
|
| `drill hook zsh\|bash` | Print the shell integration snippet (used above) |
|
|
291
285
|
| `drill beat <2-8> <repetitions>` / `drill beat none` | Set or disable the rhythm |
|
|
292
286
|
| `drill polka\|waltz\|rock\|jazz\|jiga\|balkan\|samba <repetitions>` | Shorthand for a fixed loop size (see table above) |
|
|
293
|
-
| `drill
|
|
287
|
+
| `drill color random\|default` | Color each shown word randomly, or by its show count (`default`) |
|
|
294
288
|
| `drill add <text>` | Append a new item to the end of the list |
|
|
295
289
|
| `drill list` | Show how many times each item has been shown, numbered |
|
|
296
290
|
| `drill open` | Open the list file in `$EDITOR`/`$VISUAL` (falls back to `vi`) |
|
data/lib/lexdrill/beat.rb
CHANGED
|
@@ -3,14 +3,13 @@
|
|
|
3
3
|
require "fileutils"
|
|
4
4
|
|
|
5
5
|
# Global rhythm config: repeats loops of `loop_size` consecutive words
|
|
6
|
-
# `repetitions` times before advancing to the next loop.
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# a uniformly random word each time instead.
|
|
6
|
+
# `repetitions` times before advancing to the next loop. Disabled (plain
|
|
7
|
+
# word-by-word advance) unless explicitly configured. `drill beat rand`
|
|
8
|
+
# selects a third mode (marked by the literal contents "rand") where
|
|
9
|
+
# WordList.next ignores the counter/rhythm entirely and picks a uniformly
|
|
10
|
+
# random word each time instead.
|
|
12
11
|
module Lexdrill::Beat
|
|
13
|
-
PATH =
|
|
12
|
+
PATH = Lexdrill::Config.path("beat")
|
|
14
13
|
MIN_LOOP_SIZE = 2
|
|
15
14
|
MAX_LOOP_SIZE = 8
|
|
16
15
|
DEFAULT_REPETITIONS = 8
|
data/lib/lexdrill/cli.rb
CHANGED
|
@@ -11,7 +11,7 @@ class Lexdrill::CLI
|
|
|
11
11
|
run_inspect: %w[inspect],
|
|
12
12
|
run_beat: %w[beat],
|
|
13
13
|
run_beat_alias: %w[polka waltz rock jazz jiga balkan samba],
|
|
14
|
-
|
|
14
|
+
run_color: %w[color],
|
|
15
15
|
run_add: %w[add],
|
|
16
16
|
run_list: %w[list],
|
|
17
17
|
run_open: %w[open],
|
|
@@ -67,7 +67,7 @@ class Lexdrill::CLI
|
|
|
67
67
|
drill beat rand Ignore the counter/rhythm; show a random item each time
|
|
68
68
|
drill polka|waltz|rock|jazz|jiga|balkan|samba [repetitions]
|
|
69
69
|
Shorthand for a fixed loop size (2 through 8, in order)
|
|
70
|
-
drill
|
|
70
|
+
drill color random|default Color each word randomly, or by its show count (default)
|
|
71
71
|
drill add <text> Append a new item to the end of the list
|
|
72
72
|
drill list Show how many times each item has been shown
|
|
73
73
|
drill open Open the list file in $EDITOR/$VISUAL (falls back to vi)
|
|
@@ -75,7 +75,7 @@ class Lexdrill::CLI
|
|
|
75
75
|
drill rand <n> drill next shows a word ~1-in-n times (n=1 is every time)
|
|
76
76
|
drill go <number> Jump so the next `next` shows item <number> (1-based, see drill list)
|
|
77
77
|
drill remote <url> Set the Google Sheet used by a local service account key
|
|
78
|
-
(~/.drill
|
|
78
|
+
(~/.drill/gcp-service-account.json) — no interactive sign-in
|
|
79
79
|
drill oauth <url> Set the Google Sheet used by the OAuth (personal-login) flow
|
|
80
80
|
drill sheet Print a link to the currently active spreadsheet
|
|
81
81
|
drill export <sheet-name> Export the word list text to the given tab (overwrites its
|
|
@@ -92,7 +92,7 @@ class Lexdrill::CLI
|
|
|
92
92
|
word = Lexdrill::WordList.next
|
|
93
93
|
return print_next_failure unless word
|
|
94
94
|
|
|
95
|
-
puts
|
|
95
|
+
puts Lexdrill::LineFormatter.format(word)
|
|
96
96
|
0
|
|
97
97
|
end
|
|
98
98
|
|
|
@@ -107,14 +107,6 @@ class Lexdrill::CLI
|
|
|
107
107
|
1
|
|
108
108
|
end
|
|
109
109
|
|
|
110
|
-
def colored_line(word)
|
|
111
|
-
text = Lexdrill::LineFormatter.format(word)
|
|
112
|
-
return text if Lexdrill::Format.simple?
|
|
113
|
-
|
|
114
|
-
count = Lexdrill::Stats.counts.fetch(word, 0)
|
|
115
|
-
Lexdrill::Colorizer.paint_by_count(text, count)
|
|
116
|
-
end
|
|
117
|
-
|
|
118
110
|
def print_no_words(path)
|
|
119
111
|
warn "drill: no words found in #{path}"
|
|
120
112
|
1
|
|
@@ -203,16 +195,22 @@ class Lexdrill::CLI
|
|
|
203
195
|
1
|
|
204
196
|
end
|
|
205
197
|
|
|
206
|
-
def
|
|
198
|
+
def run_color
|
|
207
199
|
mode = argv[1]
|
|
208
|
-
return
|
|
200
|
+
return print_color_usage unless mode
|
|
201
|
+
return print_invalid_color(mode) unless Lexdrill::Color::VALID.include?(mode)
|
|
209
202
|
|
|
210
|
-
Lexdrill::
|
|
203
|
+
Lexdrill::Color.set(mode)
|
|
211
204
|
0
|
|
212
205
|
end
|
|
213
206
|
|
|
214
|
-
def
|
|
215
|
-
warn "usage: drill
|
|
207
|
+
def print_color_usage
|
|
208
|
+
warn "usage: drill color <#{Lexdrill::Color::VALID.join('|')}>"
|
|
209
|
+
1
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def print_invalid_color(mode)
|
|
213
|
+
warn "drill: unknown color mode #{mode.inspect} (expected #{Lexdrill::Color::VALID.join(' or ')})"
|
|
216
214
|
1
|
|
217
215
|
end
|
|
218
216
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Global "how to color a shown word" setting: "default" colors by show
|
|
4
|
+
# count (see Lexdrill::Colorizer's blue -> red gradient); "random" picks a
|
|
5
|
+
# vivid random color every time instead. An absent file (or any
|
|
6
|
+
# unrecognized value) means "default".
|
|
7
|
+
module Lexdrill::Color
|
|
8
|
+
PATH = Lexdrill::Config.path("color")
|
|
9
|
+
RANDOM = "random"
|
|
10
|
+
DEFAULT = "default"
|
|
11
|
+
VALID = [RANDOM, DEFAULT].freeze
|
|
12
|
+
|
|
13
|
+
def self.set(mode)
|
|
14
|
+
File.write(PATH, mode)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.current
|
|
18
|
+
return DEFAULT unless File.exist?(PATH)
|
|
19
|
+
|
|
20
|
+
value = File.read(PATH).strip
|
|
21
|
+
VALID.include?(value) ? value : DEFAULT
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.random?
|
|
25
|
+
current == RANDOM
|
|
26
|
+
end
|
|
27
|
+
end
|
data/lib/lexdrill/colorizer.rb
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
# Wraps text in an ANSI color code. `paint_by_count` maps a word's show
|
|
4
4
|
# count onto a blue -> red truecolor gradient, one step per
|
|
5
5
|
# Lexdrill::Stats::BUCKET_SIZE shows, so a word's color reflects how many
|
|
6
|
-
# times it's been drilled —
|
|
7
|
-
#
|
|
6
|
+
# times it's been drilled — unless `drill color random` is set (see
|
|
7
|
+
# Lexdrill::Color), in which case a vivid random color is used every time
|
|
8
|
+
# instead.
|
|
8
9
|
module Lexdrill::Colorizer
|
|
9
|
-
|
|
10
|
+
YELLOW = 33
|
|
10
11
|
GRADIENT_STEPS = Lexdrill::Stats::GRADUATION_THRESHOLD / Lexdrill::Stats::BUCKET_SIZE
|
|
11
12
|
HUE_DEGREES = 360
|
|
12
13
|
# Which of {peak, trough, 0} each RGB channel takes in each 60-degree hue
|
|
@@ -20,12 +21,12 @@ module Lexdrill::Colorizer
|
|
|
20
21
|
%i[peak zero trough]
|
|
21
22
|
].freeze
|
|
22
23
|
|
|
23
|
-
def self.
|
|
24
|
-
wrap(text,
|
|
24
|
+
def self.paint_yellow(text)
|
|
25
|
+
wrap(text, YELLOW)
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
def self.paint_by_count(text, count)
|
|
28
|
-
code =
|
|
29
|
+
code = Lexdrill::Color.random? ? random_code : gradient_code(count)
|
|
29
30
|
wrap(text, code)
|
|
30
31
|
end
|
|
31
32
|
|
data/lib/lexdrill/config.rb
CHANGED
|
@@ -1,24 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
# All of lexdrill's persisted state lives under this one directory in the
|
|
6
|
+
# user's home folder — a single global config, with no per-project or
|
|
7
|
+
# per-directory overrides.
|
|
3
8
|
module Lexdrill::Config
|
|
4
|
-
|
|
5
|
-
COUNTER_FILENAME = ".drill.counter"
|
|
6
|
-
STATS_FILENAME = ".drill.stats"
|
|
9
|
+
DIR = File.join(Dir.home, ".drill")
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
end
|
|
11
|
+
# 0700: this directory can hold real secrets (OAuth refresh token, service
|
|
12
|
+
# account key), same reasoning as their own individual 0600 chmods.
|
|
13
|
+
FileUtils.mkdir_p(DIR, mode: 0o700)
|
|
12
14
|
|
|
13
|
-
def self.
|
|
14
|
-
|
|
15
|
+
def self.path(filename)
|
|
16
|
+
File.join(DIR, filename)
|
|
15
17
|
end
|
|
16
|
-
|
|
17
|
-
def self.home_drill_path
|
|
18
|
-
File.join(Dir.home, FILENAME)
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
DRILL_PATH = File.join(base_path, FILENAME)
|
|
22
|
-
COUNTER_PATH = File.join(base_path, COUNTER_FILENAME)
|
|
23
|
-
STATS_PATH = File.join(base_path, STATS_FILENAME)
|
|
24
18
|
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# The starter list seeded into
|
|
4
|
-
# LEXDRILL_PATH override nor a project-local .drill.txt exists yet.
|
|
3
|
+
# The starter list seeded into the words file on first run.
|
|
5
4
|
module Lexdrill::DefaultWords
|
|
6
5
|
WORDS = [
|
|
7
6
|
"The map is not the territory",
|
data/lib/lexdrill/google_auth.rb
CHANGED
|
@@ -14,7 +14,7 @@ require "fileutils"
|
|
|
14
14
|
# PATH below, obtained only after this specific user's own interactive
|
|
15
15
|
# consent, which never leaves this machine and is never published.
|
|
16
16
|
module Lexdrill::GoogleAuth
|
|
17
|
-
PATH =
|
|
17
|
+
PATH = Lexdrill::Config.path("gcp-token.json")
|
|
18
18
|
|
|
19
19
|
CLIENT_ID = "608056429593-ncdgh4bnfpuf9486l2rlt7g763h1gl6g.apps.googleusercontent.com"
|
|
20
20
|
CLIENT_SECRET = "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"
|
data/lib/lexdrill/inspector.rb
CHANGED
|
@@ -14,8 +14,8 @@ module Lexdrill::Inspector
|
|
|
14
14
|
Toggle: #{toggle_summary}
|
|
15
15
|
Beat: #{beat_summary}
|
|
16
16
|
Rand: #{rand_summary}
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
Color: #{Lexdrill::Color.current}
|
|
18
|
+
Config dir: #{Lexdrill::Config::DIR}
|
|
19
19
|
REPORT
|
|
20
20
|
end
|
|
21
21
|
|
|
@@ -1,34 +1,24 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Formats a shown word for display
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
3
|
+
# Formats a shown word for display, on one line:
|
|
4
|
+
# "{index}/{total}{SEPARATOR} {word}" — index is the word's own 1-based
|
|
5
|
+
# position in the list, re-derived through Lexdrill::Beat so it stays
|
|
6
|
+
# meaningful even when a rhythm repeats steps. The counter/total and the
|
|
7
|
+
# separator are painted yellow, as one unit; the word is colored by its
|
|
8
|
+
# show count (see Lexdrill::Colorizer).
|
|
9
9
|
module Lexdrill::LineFormatter
|
|
10
10
|
SEPARATOR = "⟳"
|
|
11
11
|
|
|
12
12
|
def self.format(word)
|
|
13
|
-
return simple(word) if Lexdrill::Format.simple?
|
|
14
|
-
|
|
15
|
-
full(word)
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def self.simple(word)
|
|
19
|
-
count = Lexdrill::Stats.counts.fetch(word, 0)
|
|
20
|
-
"#{Lexdrill::Colorizer.paint_blue(SEPARATOR)} #{Lexdrill::Colorizer.paint_by_count(word, count)}"
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def self.full(word)
|
|
24
13
|
total = Lexdrill::WordList.words.size
|
|
25
|
-
|
|
26
|
-
"#{
|
|
14
|
+
count = Lexdrill::Stats.counts.fetch(word, 0)
|
|
15
|
+
prefix = Lexdrill::Colorizer.paint_yellow("#{loop_index(total) + 1}/#{total}#{SEPARATOR}")
|
|
16
|
+
"#{prefix} #{Lexdrill::Colorizer.paint_by_count(word, count)}"
|
|
27
17
|
end
|
|
28
18
|
|
|
29
|
-
def self.
|
|
19
|
+
def self.loop_index(total)
|
|
30
20
|
counter = Lexdrill::Counter.new(Lexdrill::WordList::COUNTER_PATH)
|
|
31
21
|
step_used = counter.value - 1
|
|
32
|
-
Lexdrill::Beat.loop_info(total, step_used)
|
|
22
|
+
Lexdrill::Beat.loop_info(total, step_used).index
|
|
33
23
|
end
|
|
34
24
|
end
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Global "which Google Sheet to export to via the OAuth device-flow login"
|
|
4
|
-
# setting.
|
|
5
|
-
#
|
|
6
|
-
#
|
|
4
|
+
# setting. Stored as a plain spreadsheet id, parsed out of a normal
|
|
5
|
+
# Google Sheets share URL. Set via `drill oauth <url>`. See
|
|
6
|
+
# Lexdrill::Remote for the separate, service-account-based flow.
|
|
7
7
|
module Lexdrill::OauthRemote
|
|
8
|
-
PATH =
|
|
8
|
+
PATH = Lexdrill::Config.path("oauth-remote")
|
|
9
9
|
URL_PATTERN = %r{/spreadsheets/d/([a-zA-Z0-9_-]+)}
|
|
10
10
|
|
|
11
11
|
def self.set(url)
|
data/lib/lexdrill/rand.rb
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
# Global "how often to actually show a word" setting, applied by
|
|
4
4
|
# `drill next` itself (both automatic hook-triggered calls and manual
|
|
5
|
-
# invocations respect it equally).
|
|
6
|
-
#
|
|
5
|
+
# invocations respect it equally). Stored as a plain integer; defaults to
|
|
6
|
+
# 1 (show every time).
|
|
7
7
|
module Lexdrill::Rand
|
|
8
|
-
PATH =
|
|
8
|
+
PATH = Lexdrill::Config.path("rand")
|
|
9
9
|
DEFAULT = 1
|
|
10
10
|
|
|
11
11
|
def self.value
|
data/lib/lexdrill/remote.rb
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Global "which Google Sheet to export to via a service account" setting.
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
4
|
+
# Stored as a plain spreadsheet id, parsed out of a normal Google Sheets
|
|
5
|
+
# share URL. Set via `drill remote <url>` — the user shares that
|
|
6
|
+
# spreadsheet with their service account's email directly in Google
|
|
7
7
|
# Sheets, so no interactive Google sign-in is needed at export/import time.
|
|
8
8
|
# See Lexdrill::OauthRemote for the separate, personal-login-based flow.
|
|
9
9
|
module Lexdrill::Remote
|
|
10
|
-
PATH =
|
|
10
|
+
PATH = Lexdrill::Config.path("remote")
|
|
11
11
|
URL_PATTERN = %r{/spreadsheets/d/([a-zA-Z0-9_-]+)}
|
|
12
12
|
|
|
13
13
|
def self.set(url)
|
|
@@ -13,7 +13,7 @@ require "base64"
|
|
|
13
13
|
# published, and lexdrill never writes or transmits it anywhere except in
|
|
14
14
|
# the signed JWT sent directly to Google's own token endpoint.
|
|
15
15
|
module Lexdrill::ServiceAccountAuth
|
|
16
|
-
PATH =
|
|
16
|
+
PATH = Lexdrill::Config.path("gcp-service-account.json")
|
|
17
17
|
TOKEN_URL = "https://oauth2.googleapis.com/token"
|
|
18
18
|
SCOPE = "https://www.googleapis.com/auth/spreadsheets"
|
|
19
19
|
GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer"
|
data/lib/lexdrill/stats.rb
CHANGED
|
@@ -3,15 +3,14 @@
|
|
|
3
3
|
require "json"
|
|
4
4
|
|
|
5
5
|
# Tracks how many times each word/phrase has been shown by `next`, persisted
|
|
6
|
-
# as a word => count JSON object
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# stops selecting it.
|
|
6
|
+
# as a word => count JSON object in the stats file. Every BUCKET_SIZE shows
|
|
7
|
+
# moves a word into the next display-color bucket (see Lexdrill::Colorizer);
|
|
8
|
+
# once a word reaches GRADUATION_THRESHOLD shows it's considered mastered
|
|
9
|
+
# and `next` stops selecting it.
|
|
11
10
|
module Lexdrill::Stats
|
|
12
|
-
PATH = Lexdrill::Config
|
|
13
|
-
BUCKET_SIZE =
|
|
14
|
-
GRADUATION_THRESHOLD =
|
|
11
|
+
PATH = Lexdrill::Config.path("stats")
|
|
12
|
+
BUCKET_SIZE = 10
|
|
13
|
+
GRADUATION_THRESHOLD = 120
|
|
15
14
|
|
|
16
15
|
def self.record(word)
|
|
17
16
|
data = load
|
data/lib/lexdrill/toggle.rb
CHANGED
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
require "fileutils"
|
|
4
4
|
|
|
5
|
-
# Global pause/resume switch for the shell hook
|
|
6
|
-
#
|
|
7
|
-
# presence means stopped.
|
|
5
|
+
# Global pause/resume switch for the shell hook. Enabled by default; the
|
|
6
|
+
# marker file's presence means stopped.
|
|
8
7
|
module Lexdrill::Toggle
|
|
9
|
-
PATH =
|
|
8
|
+
PATH = Lexdrill::Config.path("disabled")
|
|
10
9
|
|
|
11
10
|
def self.enabled?
|
|
12
11
|
!File.exist?(PATH)
|
data/lib/lexdrill/version.rb
CHANGED
data/lib/lexdrill/word_list.rb
CHANGED
|
@@ -1,28 +1,26 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Reads vocabulary words/phrases from
|
|
3
|
+
# Reads vocabulary words/phrases from the words file, one per line, and
|
|
4
4
|
# advances a persisted counter to show "the current word" each time. On a
|
|
5
|
-
# fresh install with
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# remain in `.words` for `list`/`stats` to report.
|
|
5
|
+
# fresh install, seeds the file with a default starter list instead of
|
|
6
|
+
# coming up empty. Words that have graduated (see Lexdrill::Stats) are
|
|
7
|
+
# excluded from `.next` but remain in `.words` for `list`/`stats` to report.
|
|
9
8
|
class Lexdrill::WordList
|
|
10
|
-
PATH = Lexdrill::Config
|
|
11
|
-
COUNTER_PATH = Lexdrill::Config
|
|
12
|
-
HOME_PATH = Lexdrill::Config.home_drill_path
|
|
9
|
+
PATH = Lexdrill::Config.path("words")
|
|
10
|
+
COUNTER_PATH = Lexdrill::Config.path("counter")
|
|
13
11
|
|
|
14
12
|
def self.words
|
|
15
|
-
|
|
13
|
+
seed_default
|
|
16
14
|
@words ||= File.exist?(PATH) && File.readlines(PATH, encoding: "UTF-8").map(&:strip).reject(&:empty?)
|
|
17
15
|
@words ||= []
|
|
18
16
|
end
|
|
19
17
|
|
|
20
|
-
def self.
|
|
21
|
-
return if
|
|
18
|
+
def self.seed_default
|
|
19
|
+
return if File.exist?(PATH)
|
|
22
20
|
|
|
23
21
|
File.write(PATH, Lexdrill::DefaultWords::TEXT, encoding: "UTF-8")
|
|
24
22
|
end
|
|
25
|
-
private_class_method :
|
|
23
|
+
private_class_method :seed_default
|
|
26
24
|
|
|
27
25
|
def self.next
|
|
28
26
|
active = active_words
|
data/lib/lexdrill.rb
CHANGED
|
@@ -17,7 +17,7 @@ require_relative "lexdrill/service_account_auth"
|
|
|
17
17
|
require_relative "lexdrill/sheets_client"
|
|
18
18
|
require_relative "lexdrill/shell_snippet"
|
|
19
19
|
require_relative "lexdrill/inspector"
|
|
20
|
+
require_relative "lexdrill/color"
|
|
20
21
|
require_relative "lexdrill/colorizer"
|
|
21
|
-
require_relative "lexdrill/format"
|
|
22
22
|
require_relative "lexdrill/line_formatter"
|
|
23
23
|
require_relative "lexdrill/cli"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lexdrill
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.19.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Siarhei Kisliak
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-14 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: lexdrill prints a vocabulary word or phrase on demand, tracking how often
|
|
14
14
|
each one has been shown.
|
|
@@ -27,11 +27,11 @@ files:
|
|
|
27
27
|
- lib/lexdrill.rb
|
|
28
28
|
- lib/lexdrill/beat.rb
|
|
29
29
|
- lib/lexdrill/cli.rb
|
|
30
|
+
- lib/lexdrill/color.rb
|
|
30
31
|
- lib/lexdrill/colorizer.rb
|
|
31
32
|
- lib/lexdrill/config.rb
|
|
32
33
|
- lib/lexdrill/counter.rb
|
|
33
34
|
- lib/lexdrill/default_words.rb
|
|
34
|
-
- lib/lexdrill/format.rb
|
|
35
35
|
- lib/lexdrill/google_auth.rb
|
|
36
36
|
- lib/lexdrill/http_client.rb
|
|
37
37
|
- lib/lexdrill/inspector.rb
|
data/lib/lexdrill/format.rb
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Global display mode for `next`'s output: "simple" (the drill sign + word)
|
|
4
|
-
# or "full" (position + loop info). Lives at ~/.drill.format; defaults to
|
|
5
|
-
# "simple".
|
|
6
|
-
module Lexdrill::Format
|
|
7
|
-
PATH = File.join(Dir.home, ".drill.format")
|
|
8
|
-
SIMPLE = "simple"
|
|
9
|
-
FULL = "full"
|
|
10
|
-
VALID = [SIMPLE, FULL].freeze
|
|
11
|
-
|
|
12
|
-
def self.current
|
|
13
|
-
return SIMPLE unless File.exist?(PATH)
|
|
14
|
-
|
|
15
|
-
value = File.read(PATH).strip
|
|
16
|
-
VALID.include?(value) ? value : SIMPLE
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def self.set(mode)
|
|
20
|
-
File.write(PATH, mode)
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def self.simple?
|
|
24
|
-
current == SIMPLE
|
|
25
|
-
end
|
|
26
|
-
end
|