teletype 0.0.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +9 -1
- data/Rakefile +5 -4
- data/bin/teletype +81 -1
- data/lib/teletype/key.rb +64 -0
- data/lib/teletype/page.rb +97 -0
- data/lib/teletype/paginator.rb +57 -0
- data/lib/teletype/practice.rb +27 -0
- data/lib/teletype/screen.rb +67 -0
- data/lib/teletype/stats.rb +101 -0
- data/lib/teletype.rb +8 -0
- metadata +26 -6
- data/LICENSE.txt +0 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 40a3e0c38deacc68555c1440a9fe679afc623e93decc137836a3066aa6710521
|
4
|
+
data.tar.gz: 640e87ce91e95e40b37e5f768e70ae0e355f68f810c51b1862e2fcdf6dd1ddd5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0b885502503555e6d5874eeb897928d37db231e72d6467438ebbeba43d810b3a0e8ad7415c98eb7330017e38fe48465732a495f96ad1f2e3ca521e8184117453
|
7
|
+
data.tar.gz: 5146ce812b50f336daa87cbaa256c1227c49481e32921e9235cc026bc7238dd8e7712cb8ddd5d869b0db64b2cef233d6291cddc9b51d1178926e4d995d7a213e
|
data/README.md
CHANGED
@@ -12,7 +12,15 @@
|
|
12
12
|
### Usage
|
13
13
|
|
14
14
|
Practice numeric keys:
|
15
|
-
`teletype number`
|
15
|
+
`teletype base/number.txt`
|
16
|
+
|
17
|
+
Practice basic keys(numeric and letters):
|
18
|
+
`teletype base`
|
16
19
|
|
17
20
|
Practice with ruby code:
|
18
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/Rakefile
CHANGED
@@ -1,14 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rake/testtask'
|
2
4
|
|
3
5
|
Rake::TestTask.new do |t|
|
4
6
|
t.libs << 'test'
|
5
7
|
end
|
6
8
|
|
7
|
-
desc
|
8
|
-
task :
|
9
|
-
|
9
|
+
desc 'Run tests'
|
10
|
+
task default: :test
|
10
11
|
|
11
|
-
desc
|
12
|
+
desc 'Testing usage for fast iteration'
|
12
13
|
task :usage do
|
13
14
|
puts 'gem uninstall teletype -x'
|
14
15
|
puts `gem uninstall teletype -x`
|
data/bin/teletype
CHANGED
@@ -1,3 +1,83 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
|
-
|
4
|
+
require 'optparse'
|
5
|
+
require_relative '../lib/teletype'
|
6
|
+
|
7
|
+
exercises = File.join(File.dirname(__FILE__), '../', 'exercise')
|
8
|
+
|
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
|
21
|
+
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)
|
52
|
+
|
53
|
+
paths = ARGV
|
54
|
+
paths << 'base' if paths.length.zero?
|
55
|
+
|
56
|
+
text = ''
|
57
|
+
paths.each do |path|
|
58
|
+
path = File.join(exercises, path)
|
59
|
+
|
60
|
+
if File.directory?(path)
|
61
|
+
Dir.glob(File.join(path, '*.*')).each do |file|
|
62
|
+
text += File.read(file)
|
63
|
+
end
|
64
|
+
elsif File.exist?(path)
|
65
|
+
text += File.read(path)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
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/key.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'io/wait'
|
4
|
+
require 'io/console'
|
5
|
+
|
6
|
+
module Teletype
|
7
|
+
# Key implements console reading for multibyte inputs and translates to unicode
|
8
|
+
# characters. The unicode characters will be useful when displaying the inputs back
|
9
|
+
# to console.
|
10
|
+
class Key
|
11
|
+
DICTIONARY = {
|
12
|
+
"\e" => '␛',
|
13
|
+
"\e[1;5A" => '↥', # ctrl-up
|
14
|
+
"\e[1;5B" => '↧', # ctrl-down
|
15
|
+
"\e[1;5C" => '↦', # ctrl-right
|
16
|
+
"\e[1;5D" => '↤', # ctrl-left
|
17
|
+
|
18
|
+
"\e[1~" => '⌂', # home
|
19
|
+
"\e[2~" => '⎀', # insert
|
20
|
+
"\e[3~" => '⌦', # delete
|
21
|
+
"\e[3;2~" => '↱', # shift+delete
|
22
|
+
"\e[3;5~" => '↪', # ctrl+delete
|
23
|
+
"\e[4~" => '↘', # end
|
24
|
+
"\e[5~" => '⇞', # page up
|
25
|
+
"\e[6~" => '⇟', # page down
|
26
|
+
"\e[7~" => '⌂', # home
|
27
|
+
"\e[8~" => '↘', # end
|
28
|
+
|
29
|
+
"\e[A" => '↑',
|
30
|
+
"\e[B" => '↓',
|
31
|
+
"\e[C" => '→',
|
32
|
+
"\e[D" => '←',
|
33
|
+
"\e[E" => '⇲', # clear
|
34
|
+
"\e[H" => '⌂', # home
|
35
|
+
"\e[F" => '↘', # end
|
36
|
+
"\e[Z" => '⇤', # backtab(shift + tab)
|
37
|
+
|
38
|
+
"\u0003" => '⏻',
|
39
|
+
"\u007F" => '⌫', # backspace
|
40
|
+
"\r" => "\n",
|
41
|
+
"\t" => '⇥'
|
42
|
+
}.freeze
|
43
|
+
|
44
|
+
def self.read
|
45
|
+
blocking = true
|
46
|
+
chars = []
|
47
|
+
$stdin.raw do
|
48
|
+
$stdin.noecho do
|
49
|
+
loop do
|
50
|
+
if blocking
|
51
|
+
chars << $stdin.getc
|
52
|
+
blocking = false
|
53
|
+
elsif $stdin.ready?
|
54
|
+
chars << $stdin.getc
|
55
|
+
else
|
56
|
+
key = chars.join
|
57
|
+
return DICTIONARY[key] || key
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Teletype
|
4
|
+
# Prints lines of text to screen and handles key strokes.
|
5
|
+
class Page
|
6
|
+
def initialize(lines, screen, stats)
|
7
|
+
@lines = lines.map { |line| line.gsub("\t", '⇥') }
|
8
|
+
@screen = screen
|
9
|
+
@stats = stats
|
10
|
+
@y = -1
|
11
|
+
end
|
12
|
+
|
13
|
+
def fetch
|
14
|
+
loop do
|
15
|
+
@line = @lines.shift
|
16
|
+
@y += 1
|
17
|
+
break if @line.nil? || @line.strip.length.positive?
|
18
|
+
end
|
19
|
+
|
20
|
+
# skip white spaces at the beginning of a line
|
21
|
+
@x = @line&.match(/[[:graph:]]/)&.pre_match&.length || 0
|
22
|
+
end
|
23
|
+
|
24
|
+
def run
|
25
|
+
@screen.fill(@lines.map { |line| default(line) })
|
26
|
+
|
27
|
+
fetch
|
28
|
+
to(@x, @y)
|
29
|
+
|
30
|
+
loop do
|
31
|
+
break if @lines.empty? && @line.nil?
|
32
|
+
|
33
|
+
char = Key.read
|
34
|
+
|
35
|
+
case char
|
36
|
+
when '⏻'
|
37
|
+
exit
|
38
|
+
when '⌫'
|
39
|
+
erase
|
40
|
+
else
|
41
|
+
advance(char)
|
42
|
+
end
|
43
|
+
|
44
|
+
@screen.log(@stats.rankings)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def advance(char)
|
49
|
+
at = @line[@x]
|
50
|
+
if char == at
|
51
|
+
@stats.hit!(at)
|
52
|
+
print(correct(char))
|
53
|
+
else
|
54
|
+
@stats.miss!(at)
|
55
|
+
print(wrong(visible(char)))
|
56
|
+
end
|
57
|
+
@x += 1
|
58
|
+
fetch if @x == @line.length
|
59
|
+
to(@x, @y)
|
60
|
+
end
|
61
|
+
|
62
|
+
def erase
|
63
|
+
return unless @x.positive?
|
64
|
+
|
65
|
+
@x -= 1
|
66
|
+
to(@x, @y)
|
67
|
+
print default(@line[@x])
|
68
|
+
to(@x, @y)
|
69
|
+
end
|
70
|
+
|
71
|
+
def to(x, y)
|
72
|
+
@screen.to(x, y)
|
73
|
+
end
|
74
|
+
|
75
|
+
def visible(char)
|
76
|
+
case char
|
77
|
+
when ' ' then '␣'
|
78
|
+
when "\n" then '↵'
|
79
|
+
when ("\u0001".."\u001A") then '⌃' # Ctrl[a-z]
|
80
|
+
when /^\e/ then '�'
|
81
|
+
else; char
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def default(str)
|
86
|
+
"\e[94m#{str}\e[0m"
|
87
|
+
end
|
88
|
+
|
89
|
+
def correct(str)
|
90
|
+
"\e[2;32m#{str}\e[0m"
|
91
|
+
end
|
92
|
+
|
93
|
+
def wrong(str)
|
94
|
+
"\e[91m#{str}\e[0m"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -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
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Teletype
|
4
|
+
# Initializes screen size and click stats, then start the practice page by page.
|
5
|
+
class Practice
|
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
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
def start
|
19
|
+
at_exit do
|
20
|
+
@stats.save
|
21
|
+
@paginator.save
|
22
|
+
end
|
23
|
+
|
24
|
+
@paginator.run
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Teletype
|
4
|
+
# Screen abstracts console printing, moving to specific row and column.
|
5
|
+
class Screen
|
6
|
+
attr_accessor :height, :width, :top, :left
|
7
|
+
|
8
|
+
def initialize(height, width, verbose: false)
|
9
|
+
@verbose = verbose
|
10
|
+
@maxh, @maxw = $stdout.winsize
|
11
|
+
|
12
|
+
if @maxh > height
|
13
|
+
@height = height
|
14
|
+
@top = (@maxh - height) / 2
|
15
|
+
else
|
16
|
+
@height = @maxh
|
17
|
+
@top = 0
|
18
|
+
end
|
19
|
+
|
20
|
+
if @maxw > width
|
21
|
+
@width = width
|
22
|
+
@left = (@maxw - width) / 2
|
23
|
+
else
|
24
|
+
@width = @maxw
|
25
|
+
@left = 0
|
26
|
+
end
|
27
|
+
|
28
|
+
at_exit { $stdout.clear_screen }
|
29
|
+
end
|
30
|
+
|
31
|
+
def to(x, y)
|
32
|
+
$stdout.goto(@top + y, @left + x)
|
33
|
+
end
|
34
|
+
|
35
|
+
def fill(lines)
|
36
|
+
invisible do
|
37
|
+
$stdout.clear_screen
|
38
|
+
|
39
|
+
lines.each_with_index do |line, index|
|
40
|
+
$stdout.goto(@top + index, @left)
|
41
|
+
$stdout.puts line
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def log(*lines)
|
47
|
+
return unless @verbose
|
48
|
+
|
49
|
+
original = $stdin.cursor
|
50
|
+
invisible do
|
51
|
+
lines.each_with_index do |line, index|
|
52
|
+
$stdout.goto(@top + @height + 5 + index, @left)
|
53
|
+
$stdout.print "\e[90;104m#{line}\e[0m"
|
54
|
+
$stdout.erase_line(0)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
$stdout.cursor = original if original
|
58
|
+
end
|
59
|
+
|
60
|
+
def invisible
|
61
|
+
$stdout.print "\e[?25l"
|
62
|
+
yield
|
63
|
+
ensure
|
64
|
+
$stdout.print "\e[?25h"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Teletype
|
4
|
+
# Stats keep track of hit/miss rate for pair of keys.
|
5
|
+
# It also has suggestions for keys that need more practice.
|
6
|
+
class Stats
|
7
|
+
def initialize(profile, text)
|
8
|
+
@file = File.join(profile, 'stats')
|
9
|
+
@previous = nil
|
10
|
+
@pairs = {}
|
11
|
+
load(text)
|
12
|
+
end
|
13
|
+
|
14
|
+
def load(text)
|
15
|
+
return unless File.exist?(@file)
|
16
|
+
|
17
|
+
File.readlines(@file).each do |line|
|
18
|
+
keys, hit, miss = line.split("\t")
|
19
|
+
@pairs[keys] = Pair.new(keys,
|
20
|
+
hit: Integer(hit),
|
21
|
+
miss: Integer(miss),
|
22
|
+
available: text.scan(keys).count.positive?)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def save
|
27
|
+
File.write(@file, @pairs.map { |k, p| [k, p.hit, p.miss].join("\t") }.join("\n"))
|
28
|
+
end
|
29
|
+
|
30
|
+
def hit!(key)
|
31
|
+
lookup(key)&.hit!
|
32
|
+
end
|
33
|
+
|
34
|
+
def miss!(key)
|
35
|
+
lookup(key)&.miss!
|
36
|
+
end
|
37
|
+
|
38
|
+
def lookup(key)
|
39
|
+
current = key.downcase
|
40
|
+
keys = "#{@previous}#{current}"
|
41
|
+
@previous = current
|
42
|
+
return if keys.strip.length < 2
|
43
|
+
|
44
|
+
@pairs[keys] ||= Pair.new(keys)
|
45
|
+
end
|
46
|
+
|
47
|
+
def suggestions
|
48
|
+
@pairs.values.select(&:available).select(&:inefficient?).sort.map(&:keys)
|
49
|
+
end
|
50
|
+
|
51
|
+
def rankings
|
52
|
+
@pairs.values.select(&:available).sort.first(10).map(&:to_s).join(' ')
|
53
|
+
end
|
54
|
+
|
55
|
+
# It implements hit/miss rate for second keystroke.
|
56
|
+
# Key miss tends occur when preceded by a certain key.
|
57
|
+
class Pair
|
58
|
+
THRESHOLD = 0.7
|
59
|
+
|
60
|
+
# hit/miss is for the second key
|
61
|
+
attr_accessor :keys, :hit, :miss, :available
|
62
|
+
|
63
|
+
def initialize(keys, hit: 0, miss: 0, available: true)
|
64
|
+
@keys = keys
|
65
|
+
@hit = hit
|
66
|
+
@miss = miss
|
67
|
+
@available = available
|
68
|
+
end
|
69
|
+
|
70
|
+
def inefficient?
|
71
|
+
rate < THRESHOLD
|
72
|
+
end
|
73
|
+
|
74
|
+
def hit!
|
75
|
+
@hit += 1
|
76
|
+
end
|
77
|
+
|
78
|
+
def miss!
|
79
|
+
@miss += 1
|
80
|
+
end
|
81
|
+
|
82
|
+
def rate
|
83
|
+
return 0 if total.zero?
|
84
|
+
|
85
|
+
hit / total.to_f
|
86
|
+
end
|
87
|
+
|
88
|
+
def total
|
89
|
+
hit + miss
|
90
|
+
end
|
91
|
+
|
92
|
+
def <=>(other)
|
93
|
+
[rate, keys] <=> [other.rate, other.keys]
|
94
|
+
end
|
95
|
+
|
96
|
+
def to_s
|
97
|
+
keys.gsub(/[\n\r]/, '↵').gsub(' ', '␣')
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
data/lib/teletype.rb
ADDED
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:
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
- Ochirkhuyag
|
7
|
+
- Ochirkhuyag.L
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
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
|
@@ -39,10 +53,16 @@ executables:
|
|
39
53
|
extensions: []
|
40
54
|
extra_rdoc_files: []
|
41
55
|
files:
|
42
|
-
- LICENSE.txt
|
43
56
|
- README.md
|
44
57
|
- Rakefile
|
45
58
|
- bin/teletype
|
59
|
+
- lib/teletype.rb
|
60
|
+
- lib/teletype/key.rb
|
61
|
+
- lib/teletype/page.rb
|
62
|
+
- lib/teletype/paginator.rb
|
63
|
+
- lib/teletype/practice.rb
|
64
|
+
- lib/teletype/screen.rb
|
65
|
+
- lib/teletype/stats.rb
|
46
66
|
homepage: https://github.com/ochko/teletype
|
47
67
|
licenses:
|
48
68
|
- MIT
|
@@ -62,8 +82,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
62
82
|
- !ruby/object:Gem::Version
|
63
83
|
version: '0'
|
64
84
|
requirements: []
|
65
|
-
rubygems_version: 3.
|
85
|
+
rubygems_version: 3.1.6
|
66
86
|
signing_key:
|
67
87
|
specification_version: 4
|
68
|
-
summary: Typing practice on
|
88
|
+
summary: Typing practice on terminal.
|
69
89
|
test_files: []
|
data/LICENSE.txt
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
The MIT License (MIT)
|
2
|
-
|
3
|
-
Copyright (c) 2021 Ochirkhuyag Lkhagva
|
4
|
-
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
7
|
-
in the Software without restriction, including without limitation the rights
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
10
|
-
furnished to do so, subject to the following conditions:
|
11
|
-
|
12
|
-
The above copyright notice and this permission notice shall be included in
|
13
|
-
all copies or substantial portions of the Software.
|
14
|
-
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
-
THE SOFTWARE.
|