lexdrill 0.1.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8c02d9aa7303198e675b647958c287d690303a54e62891a52da016a094ab061e
4
- data.tar.gz: 4eb14a5d4f5803b7da36df2f3dc273609f36e61f1477c6bb53aa891959be9d67
3
+ metadata.gz: 694f430036d22f39f5ee347a6220a8e977e726edb54db9f5c99d3f57bfcb8161
4
+ data.tar.gz: 9deb40864be7ded7ed8eb1aff88e25e20a412acf0a678526ff5d3f84f50ad69b
5
5
  SHA512:
6
- metadata.gz: 365127d5d9aa2829ccbb570e200504da60064e188b27327d5a25de815e51581f4714d683d5d7866ac060dbc8e96d60c50b3c27ae5cc5e22e0c62af7a99302d72
7
- data.tar.gz: cf8292e88bce78b4bf4d3db8d5bb5e39643b5a22b255148b3232071022e1274355305ceec5775270b541f394c89ca7544402f81985229f59c95c9ab61c02c8fe
6
+ metadata.gz: 49b05b7ac7dda95a4fd9c34b9de6fbe29486e8a8e84285d8fbb0fe0a211785a9a7870551c3a80566725d4884dee317117c85781a313251c9094fa927a4ff857c
7
+ data.tar.gz: 201a4d5c23a232a4cbb1ca5b2d294e629a9984e1196133511d62262e3c8ae3c238386278735f91d184db4b7214578304861caa70b641c6f0b77988cc2f4ef00f
data/README.md CHANGED
@@ -1,5 +1,109 @@
1
1
  # lexdrill
2
- rubygems_2b77e405d9e5e8b74a8f60a79a57b5a7b3d4a44702507687
3
- mkdir -p ~/.gem
4
- printf -- '---\n:rubygems_api_key: rubygems_2b77e405d9e5e8b74a8f60a79a57b5a7b3d4a44702507687\n' > ~/.gem/credentials
5
- chmod 0600 ~/.gem/credentials
2
+
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.
5
+
6
+ ## Installation
7
+
8
+ ```bash
9
+ gem install lexdrill
10
+ ```
11
+
12
+ ### `drill: command not found` after install?
13
+
14
+ This happens when the gem's executable directory isn't on your `PATH` — common with a
15
+ plain system Ruby (no rbenv/rvm). Fix it in one step:
16
+
17
+ **bash**
18
+ ```bash
19
+ echo "export PATH=\"$(ruby -e 'puts Gem.bindir'):\$PATH\"" >> ~/.bashrc
20
+ source ~/.bashrc
21
+ ```
22
+
23
+ **zsh**
24
+ ```zsh
25
+ echo "export PATH=\"$(ruby -e 'puts Gem.bindir'):\$PATH\"" >> ~/.zshrc
26
+ source ~/.zshrc
27
+ ```
28
+
29
+ Using an rbenv-managed Ruby? Run `rbenv rehash` instead. Using rvm? You shouldn't hit
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. Each
44
+ line is printed as `current/total:` followed by a tab and the word, in a
45
+ randomly-picked color, e.g.:
46
+
47
+ ```
48
+ 1/3: apple
49
+ 2/3: banana
50
+ 3/3: cherry
51
+ 1/3: apple
52
+ ```
53
+
54
+ ### Shell integration (one-time setup)
55
+
56
+ Add one line to your shell's rc file so the current word prints automatically
57
+ before each prompt. This is a **one-time step** — the line stays in your rc file
58
+ and takes effect in every new shell session from then on; you never need to run it
59
+ again by hand.
60
+
61
+ Add it near the **end** of the file, after anything that sets up your `PATH`
62
+ (rvm/rbenv init, Homebrew, etc.) — otherwise the shell may not know where
63
+ `drill` lives yet when this line runs. Guarding it with `command -v` also
64
+ means it never errors, even on a machine/session where `drill` isn't on
65
+ `PATH` for some reason.
66
+
67
+ **zsh** — add to `~/.zshrc`:
68
+ ```zsh
69
+ if command -v drill >/dev/null 2>&1; then
70
+ eval "$(drill hook zsh)"
71
+ fi
72
+ ```
73
+
74
+ **bash** — add to `~/.bashrc`:
75
+ ```bash
76
+ if command -v drill >/dev/null 2>&1; then
77
+ eval "$(drill hook bash)"
78
+ fi
79
+ ```
80
+
81
+ Then open a new shell (or `source ~/.zshrc` / `source ~/.bashrc`) to pick it up.
82
+
83
+ #### Hook not firing in new terminal windows/tabs (rvm users)?
84
+
85
+ Run `drill inspect` first — if it shows `Toggle: enabled` and a valid words
86
+ file, the tool itself is fine and the problem is that the hook function never
87
+ got registered in that session.
88
+
89
+ A common cause with rvm: rvm's installer sometimes puts its actual
90
+ PATH-loading line (`[[ -s "$HOME/.rvm/scripts/rvm" ]] && source
91
+ "$HOME/.rvm/scripts/rvm"`) in `~/.zlogin` (zsh) or `~/.bash_profile` (bash).
92
+ Those files only run for **login shells** — but many terminal emulators
93
+ (e.g. kitty) open new windows/tabs as **non-login** interactive shells, which
94
+ only source `~/.zshrc`/`~/.bashrc`. So rvm's gemset `bin/` directory (where
95
+ `drill` lives) never makes it onto `PATH` in those sessions, even though
96
+ everything looks correctly configured.
97
+
98
+ **Fix:** move that line into `~/.zshrc` / `~/.bashrc` (before the `drill`
99
+ hook block above, so it runs first) instead of relying on it only being in
100
+ `~/.zlogin` / `~/.bash_profile`. It's safe to leave it in both places.
101
+
102
+ ### Commands
103
+
104
+ | Command | What it does |
105
+ |---|---|
106
+ | `drill next` | Print the current word and advance |
107
+ | `drill start` / `drill stop` | Pause/resume the automatic per-prompt hook (doesn't affect manual `next`) |
108
+ | `drill inspect` | Show the active `.drill.txt`/`.drill.counter` paths, word count, counter value, and toggle state |
109
+ | `drill hook zsh\|bash` | Print the shell integration snippet (used above) |
data/lexdrill.gemspec CHANGED
@@ -6,23 +6,23 @@ Gem::Specification.new do |spec|
6
6
  spec.name = "lexdrill"
7
7
  spec.version = Lexdrill::VERSION
8
8
  spec.authors = ["Siarhei Kisliak"]
9
- spec.email = ["siarhei.kisliak@airslate.com"]
9
+ spec.email = ["kislak7@gmail.com"]
10
10
 
11
11
  spec.summary = "Vocabulary drilling in your terminal."
12
12
  spec.description = "lexdrill prints a vocabulary word or phrase on demand, tracking how often " \
13
13
  "each one has been shown."
14
- spec.homepage = "https://github.com/skisliak/lexdrill"
14
+ spec.homepage = "https://github.com/kislak/lexdrill"
15
15
  spec.license = "MIT"
16
16
  spec.required_ruby_version = ">= 3.0"
17
17
 
18
18
  spec.metadata["homepage_uri"] = spec.homepage
19
19
  spec.metadata["source_code_uri"] = spec.homepage
20
- spec.metadata["rubygems_mfa_required"] = "false"
20
+ spec.metadata["rubygems_mfa_required"] = "true"
21
21
 
22
22
  spec.files = Dir.chdir(__dir__) do
23
23
  Dir["lib/**/*.rb"] + Dir["bin/*"] + %w[README.md LICENSE.txt lexdrill.gemspec Gemfile]
24
24
  end
25
25
  spec.bindir = "bin"
26
- spec.executables = ["lexdrill"]
26
+ spec.executables = ["drill"]
27
27
  spec.require_paths = ["lib"]
28
28
  end
data/lib/lexdrill/cli.rb CHANGED
@@ -1,10 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Subcommand dispatcher for the `lexdrill` executable.
4
3
  class Lexdrill::CLI
5
4
  COMMANDS = {
6
5
  print_version: %w[version --version -v],
7
- print_help: %w[help --help -h]
6
+ print_help: %w[help --help -h],
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]
8
12
  }.freeze
9
13
 
10
14
  def self.start(argv = ARGV)
@@ -34,17 +38,70 @@ class Lexdrill::CLI
34
38
 
35
39
  def print_help
36
40
  puts <<~HELP
37
- lexdrill #{Lexdrill::VERSION} — vocabulary drilling in your terminal
41
+ drill #{Lexdrill::VERSION} — vocabulary drilling in your terminal
38
42
 
39
43
  Usage:
40
- lexdrill version Print the gem version
41
- lexdrill help Show this help
44
+ drill version Print the gem version
45
+ drill help Show this help
46
+ drill next Print the current word and advance
47
+ drill hook <zsh|bash> Print the shell integration snippet
48
+ drill start Resume drilling (undoes stop)
49
+ drill stop Pause drilling everywhere until `start`
50
+ drill inspect Show the active config/counter/toggle state
42
51
  HELP
43
52
  0
44
53
  end
45
54
 
55
+ def run_next
56
+ word = Lexdrill::WordList.next
57
+ return print_no_words(Lexdrill::WordList::PATH) unless word
58
+
59
+ puts Lexdrill::Colorizer.paint(Lexdrill::LineFormatter.format(word))
60
+ 0
61
+ end
62
+
63
+ def print_no_words(path)
64
+ warn "drill: no words found in #{path}"
65
+ 1
66
+ end
67
+
68
+ def run_hook
69
+ shell = argv[1]
70
+ return print_hook_usage unless shell
71
+
72
+ print_hook_snippet(shell)
73
+ end
74
+
75
+ def print_hook_usage
76
+ warn "usage: drill hook <zsh|bash>"
77
+ 1
78
+ end
79
+
80
+ def print_hook_snippet(shell)
81
+ puts Lexdrill::ShellSnippet.for(shell)
82
+ 0
83
+ rescue ArgumentError => error
84
+ warn "drill: #{error.message}"
85
+ 1
86
+ end
87
+
88
+ def run_start
89
+ Lexdrill::Toggle.start
90
+ 0
91
+ end
92
+
93
+ def run_stop
94
+ Lexdrill::Toggle.stop
95
+ 0
96
+ end
97
+
98
+ def run_inspect
99
+ puts Lexdrill::Inspector.report
100
+ 0
101
+ end
102
+
46
103
  def print_unknown_command(command)
47
- warn "lexdrill: unknown command #{command.inspect}"
104
+ warn "drill: unknown command #{command.inspect}"
48
105
  print_help
49
106
  1
50
107
  end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Wraps text in a randomly-picked ANSI color code from a fixed palette.
4
+ module Lexdrill::Colorizer
5
+ CODES = [31, 32, 33, 34, 35, 36, 91, 92, 93, 94, 95, 96].freeze
6
+
7
+ def self.paint(text)
8
+ "\e[#{CODES.sample}m#{text}\e[0m"
9
+ end
10
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lexdrill::Config
4
+ FILENAME = ".drill.txt"
5
+ COUNTER_FILENAME = ".drill.counter"
6
+
7
+ def self.dir_path
8
+ cwd = Dir.pwd
9
+ cwd if File.exist?(File.join(cwd, FILENAME))
10
+ end
11
+
12
+ def self.base_path
13
+ ENV.fetch("LEXDRILL_PATH", nil) || dir_path || Dir.home
14
+ end
15
+
16
+ DRILL_PATH = File.join(base_path, FILENAME)
17
+ COUNTER_PATH = File.join(base_path, COUNTER_FILENAME)
18
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Lexdrill::Counter
4
+ attr_reader :path
5
+
6
+ def initialize(path)
7
+ @path = path
8
+ end
9
+
10
+ def value
11
+ return 0 unless File.exist?(path)
12
+
13
+ File.read(path).strip.to_i
14
+ end
15
+
16
+ def increment
17
+ File.write(path, (value + 1).to_s)
18
+ end
19
+
20
+ def reset
21
+ File.write(path, "0")
22
+ end
23
+
24
+ # The current value, unless it's out of bounds for the given size, in
25
+ # which case it resets to 0 first.
26
+ def bounded_value(size)
27
+ current = value
28
+ return current if current < size
29
+
30
+ reset
31
+ 0
32
+ end
33
+ end
@@ -0,0 +1,33 @@
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
+ LEXDRILL_PATH: #{ENV.fetch('LEXDRILL_PATH', '(not set)')}
15
+ REPORT
16
+ end
17
+
18
+ def self.words_summary
19
+ return "missing" unless File.exist?(Lexdrill::WordList::PATH)
20
+
21
+ "#{Lexdrill::WordList.words.size} word(s)"
22
+ end
23
+
24
+ def self.counter_value
25
+ Lexdrill::Counter.new(Lexdrill::WordList::COUNTER_PATH).value
26
+ end
27
+
28
+ def self.toggle_summary
29
+ return "enabled" if Lexdrill::Toggle.enabled?
30
+
31
+ "stopped (#{Lexdrill::Toggle::PATH})"
32
+ end
33
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Formats a shown word as "{line_number}/{total}:\t{word}". The just-advanced
4
+ # counter value equals the 1-based position of the word that was shown.
5
+ module Lexdrill::LineFormatter
6
+ def self.format(word)
7
+ total = Lexdrill::WordList.words.size
8
+ line_number = Lexdrill::Counter.new(Lexdrill::WordList::COUNTER_PATH).value
9
+ "#{line_number}/#{total}:\t#{word}"
10
+ end
11
+ 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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Lexdrill
4
- VERSION = "0.1.0"
4
+ VERSION = "0.3.0"
5
5
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Reads vocabulary words/phrases from a `.drill.txt` file, one per line, and
4
+ # advances a persisted counter to show "the current word" each time.
5
+ class Lexdrill::WordList
6
+ PATH = Lexdrill::Config::DRILL_PATH
7
+ COUNTER_PATH = Lexdrill::Config::COUNTER_PATH
8
+
9
+ def self.words
10
+ @words ||= File.exist?(PATH) && File.readlines(PATH).map(&:strip).reject(&:empty?)
11
+ @words ||= []
12
+ end
13
+
14
+ def self.next
15
+ return nil if words.empty?
16
+
17
+ words[take_index(words.size)]
18
+ end
19
+
20
+ def self.take_index(size)
21
+ counter = Lexdrill::Counter.new(COUNTER_PATH)
22
+ index = counter.bounded_value(size)
23
+ counter.increment
24
+ index
25
+ end
26
+ end
data/lib/lexdrill.rb CHANGED
@@ -1,4 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "lexdrill/version"
4
+ require_relative "lexdrill/config"
5
+ require_relative "lexdrill/counter"
6
+ require_relative "lexdrill/word_list"
7
+ require_relative "lexdrill/toggle"
8
+ require_relative "lexdrill/shell_snippet"
9
+ require_relative "lexdrill/inspector"
10
+ require_relative "lexdrill/colorizer"
11
+ require_relative "lexdrill/line_formatter"
4
12
  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.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Siarhei Kisliak
@@ -13,27 +13,35 @@ dependencies: []
13
13
  description: lexdrill prints a vocabulary word or phrase on demand, tracking how often
14
14
  each one has been shown.
15
15
  email:
16
- - siarhei.kisliak@airslate.com
16
+ - kislak7@gmail.com
17
17
  executables:
18
- - lexdrill
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/lexdrill
25
+ - bin/drill
26
26
  - lexdrill.gemspec
27
27
  - lib/lexdrill.rb
28
28
  - lib/lexdrill/cli.rb
29
+ - lib/lexdrill/colorizer.rb
30
+ - lib/lexdrill/config.rb
31
+ - lib/lexdrill/counter.rb
32
+ - lib/lexdrill/inspector.rb
33
+ - lib/lexdrill/line_formatter.rb
34
+ - lib/lexdrill/shell_snippet.rb
35
+ - lib/lexdrill/toggle.rb
29
36
  - lib/lexdrill/version.rb
30
- homepage: https://github.com/skisliak/lexdrill
37
+ - lib/lexdrill/word_list.rb
38
+ homepage: https://github.com/kislak/lexdrill
31
39
  licenses:
32
40
  - MIT
33
41
  metadata:
34
- homepage_uri: https://github.com/skisliak/lexdrill
35
- source_code_uri: https://github.com/skisliak/lexdrill
36
- rubygems_mfa_required: 'false'
42
+ homepage_uri: https://github.com/kislak/lexdrill
43
+ source_code_uri: https://github.com/kislak/lexdrill
44
+ rubygems_mfa_required: 'true'
37
45
  post_install_message:
38
46
  rdoc_options: []
39
47
  require_paths:
File without changes