teletype 1.0.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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