teletype 1.0.1 → 1.2.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: 667cfea43d9fa584b9dab342618c78d850953eddcf78268527d78e471f322b9f
4
- data.tar.gz: 83581a95089f771bb038022c6c91d03f734d5c5712b5d8dfa2fb568047272ec9
3
+ metadata.gz: 40a3e0c38deacc68555c1440a9fe679afc623e93decc137836a3066aa6710521
4
+ data.tar.gz: 640e87ce91e95e40b37e5f768e70ae0e355f68f810c51b1862e2fcdf6dd1ddd5
5
5
  SHA512:
6
- metadata.gz: 14f1e845ee7667b54bf32b11238a3542477161d35d8717d20349c189b1c92cbcb95c6fd071bfcd6d9d05a109f07410f1343e21ba20089b0c03ea54251f8cbe67
7
- data.tar.gz: a863e8fc6da18cca16ad62941f4cbe78e66e5534979a46d59709a133edbe07688fbfd22d88f18db5727f32e815289580be59154abf30860776fb16c4f044c333
6
+ metadata.gz: 0b885502503555e6d5874eeb897928d37db231e72d6467438ebbeba43d810b3a0e8ad7415c98eb7330017e38fe48465732a495f96ad1f2e3ca521e8184117453
7
+ data.tar.gz: 5146ce812b50f336daa87cbaa256c1227c49481e32921e9235cc026bc7238dd8e7712cb8ddd5d869b0db64b2cef233d6291cddc9b51d1178926e4d995d7a213e
data/README.md CHANGED
@@ -19,3 +19,8 @@ Practice basic keys(numeric and letters):
19
19
 
20
20
  Practice with ruby code:
21
21
  `teletype ruby`
22
+
23
+ ### Custom text
24
+
25
+ You can copy text files to **exercise/custom** directory and practice with:
26
+ `teletype custom` or `teletype custom/poem.txt`
data/bin/teletype CHANGED
@@ -1,16 +1,54 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
+ require 'optparse'
4
5
  require_relative '../lib/teletype'
5
6
 
6
7
  exercises = File.join(File.dirname(__FILE__), '../', 'exercise')
7
8
 
8
- if ARGV.first == '--list'
9
- Dir.glob(File.join(exercises, '**', '*.*')).each do |path|
10
- puts path.sub(File.join(exercises, ''), '')
9
+ profile = nil
10
+ height = 5
11
+ width = 120
12
+ suggest = 3
13
+ verbose = false
14
+
15
+ OptionParser.new do |opts|
16
+ opts.banner = 'Usage: teletype [options] [exercise]'
17
+
18
+ opts.on('--help', 'Prints this help') do
19
+ puts opts
20
+ exit
11
21
  end
12
- exit
13
- end
22
+
23
+ opts.on('-l', '--list', 'List exercise files') do |_v|
24
+ Dir.glob(File.join(exercises, '**', '*.*')).each do |path|
25
+ puts path.sub(File.join(exercises, ''), '')
26
+ end
27
+ exit
28
+ end
29
+
30
+ opts.on('-p', '--profile [DIR]', 'Specify profile dir. Defaults to ~/.teletype') do |f|
31
+ profile = f
32
+ end
33
+
34
+ opts.on('-s', '--suggest [LINES]', Integer,
35
+ "Suggested practice lines that needs attention. Defaults to #{suggest}") do |s|
36
+ suggest = s
37
+ end
38
+
39
+ opts.on('-h', '--height [HEIGHT]', Integer, "Height of practice window. Defaults to #{height}") do |h|
40
+ height = h
41
+ end
42
+ opts.on('-w', '--width [WIDTH]', Integer, "Width of practice window. Defaults to #{width}") do |w|
43
+ width = w
44
+ end
45
+ opts.on('-v', '--[no-]verbose', 'Run verbosely') do |v|
46
+ verbose = v
47
+ end
48
+ end.parse!
49
+
50
+ profile ||= File.join(Dir.home, '.teletype')
51
+ Dir.mkdir(profile) unless File.exist?(profile)
14
52
 
15
53
  paths = ARGV
16
54
  paths << 'base' if paths.length.zero?
@@ -28,5 +66,18 @@ paths.each do |path|
28
66
  end
29
67
  end
30
68
 
31
- practice = Teletype::Practice.new(text)
32
- practice.start
69
+ screen = Teletype::Screen.new(height, width, verbose: verbose)
70
+ stats = Teletype::Stats.new(profile, text)
71
+ paginator = Teletype::Paginator.new(
72
+ profile, text,
73
+ suggest: suggest,
74
+ screen: screen,
75
+ stats: stats
76
+ )
77
+
78
+ at_exit do
79
+ stats.save
80
+ paginator.save
81
+ end
82
+
83
+ paginator.run
data/lib/teletype/page.rb CHANGED
@@ -7,24 +7,18 @@ module Teletype
7
7
  @lines = lines.map { |line| line.gsub("\t", '⇥') }
8
8
  @screen = screen
9
9
  @stats = stats
10
+ @y = -1
10
11
  end
11
12
 
12
13
  def fetch
13
14
  loop do
14
15
  @line = @lines.shift
15
- if @y
16
- @y += 1
17
- else
18
- @y = 0
19
- end
16
+ @y += 1
20
17
  break if @line.nil? || @line.strip.length.positive?
21
18
  end
22
19
 
23
- @x = if (spaces = @line&.match(/\A\ +/))
24
- spaces[0].length
25
- else
26
- 0
27
- end
20
+ # skip white spaces at the beginning of a line
21
+ @x = @line&.match(/[[:graph:]]/)&.pre_match&.length || 0
28
22
  end
29
23
 
30
24
  def run
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+
5
+ module Teletype
6
+ # Pager divides lines of text into proper screen size and provides page suggestion
7
+ # based on statistics of click accuracy.
8
+ class Paginator
9
+ def initialize(profile, text, suggest:, screen:, stats:)
10
+ @screen = screen
11
+ @stats = stats
12
+ @suggest = suggest
13
+
14
+ @lines = split(text, @screen.width)
15
+
16
+ @linenum = 0
17
+ @pagefile = File.join(profile, "page-#{Digest::MD5.hexdigest(text)}")
18
+ end
19
+
20
+ def save
21
+ File.write(@pagefile, @linenum) if @linenum.positive?
22
+ end
23
+
24
+ def run
25
+ prev = File.exist?(@pagefile) ? Integer(File.read(@pagefile).strip) : 0
26
+ @lines.each_slice(@screen.height) do |lines|
27
+ @linenum += @screen.height
28
+ next if @linenum < prev
29
+
30
+ Page.new(lines, @screen, @stats).run
31
+
32
+ loop do
33
+ suggestions = @stats.suggestions
34
+ break if suggestions.empty?
35
+
36
+ Page.new(pick(suggestions), @screen, @stats).run
37
+ end
38
+ end
39
+ end
40
+
41
+ def pick(suggestions)
42
+ index = 0.0 # preserve the original order
43
+ matches = ->(line) { suggestions.map { |keys| line.scan(keys).count }.sum - (index += 0.0001) }
44
+ @lines.sort_by { |line| matches.call(line) }.last(@suggest)
45
+ end
46
+
47
+ def split(text, length)
48
+ lines = []
49
+ text.each_line do |line|
50
+ line.chars.each_slice(length) do |slice|
51
+ lines << slice.join
52
+ end
53
+ end
54
+ lines
55
+ end
56
+ end
57
+ end
@@ -3,26 +3,25 @@
3
3
  module Teletype
4
4
  # Initializes screen size and click stats, then start the practice page by page.
5
5
  class Practice
6
- def initialize(text, height: 5, width: 120)
7
- @screen = Screen.new(height: height, width: width)
8
- @stats = Stats.new(text)
9
-
10
- @lines = []
11
- text.each_line do |line|
12
- line.chars.each_slice(@screen.width) do |slice|
13
- @lines << slice.join
14
- end
15
- end
16
-
17
- @pager = Pager.new(@lines, @stats, @screen.height)
6
+ def initialize(profile, text:, height:, width:, suggest:, verbose:)
7
+ @stats = Stats.new(profile, text)
8
+ @paginator = Paginator.new(
9
+ profile, text,
10
+ height: height,
11
+ width: width,
12
+ suggest: suggest,
13
+ verbose: verbose,
14
+ stats: @stats
15
+ )
18
16
  end
19
17
 
20
18
  def start
21
- at_exit { @stats.save }
22
-
23
- @pager.each do |lines|
24
- Page.new(lines, @screen, @stats).run
19
+ at_exit do
20
+ @stats.save
21
+ @paginator.save
25
22
  end
23
+
24
+ @paginator.run
26
25
  end
27
26
  end
28
27
  end
@@ -5,7 +5,8 @@ module Teletype
5
5
  class Screen
6
6
  attr_accessor :height, :width, :top, :left
7
7
 
8
- def initialize(height:, width:)
8
+ def initialize(height, width, verbose: false)
9
+ @verbose = verbose
9
10
  @maxh, @maxw = $stdout.winsize
10
11
 
11
12
  if @maxh > height
@@ -43,6 +44,8 @@ module Teletype
43
44
  end
44
45
 
45
46
  def log(*lines)
47
+ return unless @verbose
48
+
46
49
  original = $stdin.cursor
47
50
  invisible do
48
51
  lines.each_with_index do |line, index|
@@ -1,18 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Teletype
4
- # Stats keep track of hit/miss rate for each key and suggests a key that needs more practice.
4
+ # Stats keep track of hit/miss rate for pair of keys.
5
+ # It also has suggestions for keys that need more practice.
5
6
  class Stats
6
- def initialize(text)
7
+ def initialize(profile, text)
8
+ @file = File.join(profile, 'stats')
7
9
  @previous = nil
8
10
  @pairs = {}
9
11
  load(text)
10
12
  end
11
13
 
12
14
  def load(text)
13
- return unless File.exist?(file)
15
+ return unless File.exist?(@file)
14
16
 
15
- File.readlines(file).each do |line|
17
+ File.readlines(@file).each do |line|
16
18
  keys, hit, miss = line.split("\t")
17
19
  @pairs[keys] = Pair.new(keys,
18
20
  hit: Integer(hit),
@@ -22,11 +24,7 @@ module Teletype
22
24
  end
23
25
 
24
26
  def save
25
- File.write(file, @pairs.map { |k, p| [k, p.hit, p.miss].join("\t") }.join("\n"))
26
- end
27
-
28
- def file
29
- File.join(Dir.home, '.teletype-stats')
27
+ File.write(@file, @pairs.map { |k, p| [k, p.hit, p.miss].join("\t") }.join("\n"))
30
28
  end
31
29
 
32
30
  def hit!(key)
@@ -46,7 +44,7 @@ module Teletype
46
44
  @pairs[keys] ||= Pair.new(keys)
47
45
  end
48
46
 
49
- def suggestions(_lines)
47
+ def suggestions
50
48
  @pairs.values.select(&:available).select(&:inefficient?).sort.map(&:keys)
51
49
  end
52
50
 
data/lib/teletype.rb CHANGED
@@ -4,5 +4,5 @@ require_relative 'teletype/key'
4
4
  require_relative 'teletype/screen'
5
5
  require_relative 'teletype/stats'
6
6
  require_relative 'teletype/page'
7
- require_relative 'teletype/pager'
7
+ require_relative 'teletype/paginator'
8
8
  require_relative 'teletype/practice'
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: teletype
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ochirkhuyag.L
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-06-18 00:00:00.000000000 Z
11
+ date: 2022-06-21 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: minitest
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.16'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.16'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: rake
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -45,7 +59,7 @@ files:
45
59
  - lib/teletype.rb
46
60
  - lib/teletype/key.rb
47
61
  - lib/teletype/page.rb
48
- - lib/teletype/pager.rb
62
+ - lib/teletype/paginator.rb
49
63
  - lib/teletype/practice.rb
50
64
  - lib/teletype/screen.rb
51
65
  - lib/teletype/stats.rb
@@ -71,5 +85,5 @@ requirements: []
71
85
  rubygems_version: 3.1.6
72
86
  signing_key:
73
87
  specification_version: 4
74
- summary: Typing practice on command line.
88
+ summary: Typing practice on terminal.
75
89
  test_files: []
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Teletype
4
- # Pager divides lines of text into proper screen size and provides page suggestion
5
- # based on statistics of click accuracy.
6
- class Pager
7
- SUGGEST = 3
8
-
9
- def initialize(lines, stats, height)
10
- @lines = lines
11
- @stats = stats
12
- @height = height
13
- end
14
-
15
- def each
16
- @lines.each_slice(@height).map do |lines|
17
- yield lines
18
-
19
- loop do
20
- suggestions = @stats.suggestions(@lines)
21
- break if suggestions.empty?
22
-
23
- yield pick(suggestions)
24
- end
25
- end
26
- end
27
-
28
- def pick(suggestions)
29
- index = 0.0
30
- @lines.sort_by { |line| suggestions.map { |keys| -line.scan(keys).count }.sum + (index += 0.0001) }.first(SUGGEST)
31
- end
32
- end
33
- end