keyboard_battle 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +4 -0
- data/Gemfile.lock +24 -0
- data/LICENSE +22 -0
- data/README.md +29 -0
- data/Rakefile +2 -0
- data/bin/keyboard_battle +5 -0
- data/keyboard_battle.gemspec +23 -0
- data/lib/keyboard_battle/command_dispatcher.rb +45 -0
- data/lib/keyboard_battle/exercise.rb +64 -0
- data/lib/keyboard_battle/keyboard.rb +58 -0
- data/lib/keyboard_battle/report.rb +20 -0
- data/lib/keyboard_battle/version.rb +3 -0
- data/lib/keyboard_battle.rb +4 -0
- data/spec/keyboard_battle/command_dispatcher_spec.rb +25 -0
- data/spec/keyboard_battle/exercise_spec.rb +56 -0
- data/spec/keyboard_battle/keyboard_spec.rb +8 -0
- data/spec/keyboard_battle/report_spec.rb +20 -0
- data/spec/spec_helper.rb +1 -0
- data/texts/alice_underground.txt +839 -0
- data/texts/declaration_of_independence.txt +63 -0
- data/texts/gullivers.txt +1283 -0
- data/texts/qbf.txt +4 -0
- metadata +95 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
keyboard_battle (0.0.1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
diff-lcs (1.1.3)
|
10
|
+
rspec (2.12.0)
|
11
|
+
rspec-core (~> 2.12.0)
|
12
|
+
rspec-expectations (~> 2.12.0)
|
13
|
+
rspec-mocks (~> 2.12.0)
|
14
|
+
rspec-core (2.12.1)
|
15
|
+
rspec-expectations (2.12.0)
|
16
|
+
diff-lcs (~> 1.1.3)
|
17
|
+
rspec-mocks (2.12.0)
|
18
|
+
|
19
|
+
PLATFORMS
|
20
|
+
ruby
|
21
|
+
|
22
|
+
DEPENDENCIES
|
23
|
+
keyboard_battle!
|
24
|
+
rspec
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Ben Cullen-Kerney
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# KeyboardBattle
|
2
|
+
|
3
|
+
KeyboardBattle is a simple program that compares the performance of keyboard layouts according to two metrics, reach effort (travel from the home row being increasingly effortful) and alternation effort (typing consecutive keys with a single hand being more effortful). For both, a lower value means less effort.
|
4
|
+
|
5
|
+
The program comes with the QWERTY, Dvorak, and Colemak layouts. The format for a keyboard layout description file can be discerned from `lib/keyboards/*.txt`.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
$ gem install keyboard_battle
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
E.g., `keyboard_battle my_text.txt`, or try `keyboard_battle --bundled`
|
14
|
+
|
15
|
+
## Limitations and shortcomings
|
16
|
+
|
17
|
+
Only ASCII characters will be counted (though any character may appear in the source text).
|
18
|
+
|
19
|
+
## Bundled texts
|
20
|
+
|
21
|
+
Except for "the quick brown fox," bundled texts are sourced from archive.org.
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/bin/keyboard_battle
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/keyboard_battle/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Ben Cullen-Kerney"]
|
6
|
+
gem.email = ["ben@beancuke.com"]
|
7
|
+
gem.description = %q{Keyboard Battle compares the performance of keyboard layouts according to reach effort and alternation effort.}
|
8
|
+
gem.summary =<<SUMMARY
|
9
|
+
KeyboardBattle is a simple program that compares the performance of keyboard layouts according to two metrics, reach effort (travel from the home row being increasingly effortful) and alternation effort (typing consecutive keys with a single hand being more effortful). For both, a lower value means less effort.
|
10
|
+
|
11
|
+
The program comes with the QWERTY, Dvorak, and Colemak layouts. The format for a keyboard layout description file can be discerned from lib/keyboards/*.txt.
|
12
|
+
SUMMARY
|
13
|
+
gem.homepage = ""
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($\)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.name = "keyboard_battle"
|
19
|
+
gem.require_paths = ["lib"]
|
20
|
+
gem.version = KeyboardBattle::VERSION
|
21
|
+
|
22
|
+
gem.add_development_dependency "rspec"
|
23
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
class KeyboardBattle::CommandDispatcher
|
2
|
+
|
3
|
+
def initialize(args)
|
4
|
+
if opt = args.first
|
5
|
+
case opt
|
6
|
+
when '--bundled'
|
7
|
+
self.run(Dir.glob('texts/*.txt'))
|
8
|
+
else
|
9
|
+
self.run(args)
|
10
|
+
end
|
11
|
+
else
|
12
|
+
help
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def run(filenames)
|
17
|
+
filenames.each do |filename|
|
18
|
+
exercise = KeyboardBattle::Exercise.new(filename, keyboards)
|
19
|
+
print report(exercise)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def help
|
24
|
+
print <<HELP
|
25
|
+
|
26
|
+
What text(s) would you like to battle upon?
|
27
|
+
You can specify file(s) to exercise, e.g.:
|
28
|
+
|
29
|
+
$ keyboard_battle mytext1.txt mytext2.txt
|
30
|
+
|
31
|
+
Or, use --bundled to try it out with a few bundled texts:
|
32
|
+
|
33
|
+
$ keyboard_battle --bundled
|
34
|
+
|
35
|
+
HELP
|
36
|
+
end
|
37
|
+
|
38
|
+
def report(exercise)
|
39
|
+
KeyboardBattle::Report.new(exercise.filename, exercise.results).to_s
|
40
|
+
end
|
41
|
+
|
42
|
+
def keyboards
|
43
|
+
KeyboardBattle::Keyboard.all
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
class KeyboardBattle::Exercise
|
2
|
+
|
3
|
+
attr_accessor :results, :filename
|
4
|
+
|
5
|
+
def initialize(filename, keyboards)
|
6
|
+
@filename = filename
|
7
|
+
@keyboards = keyboards
|
8
|
+
@results = run
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
attr_accessor :keyboards
|
14
|
+
|
15
|
+
# tests a passage of text for reach effort expended (zero for home row,
|
16
|
+
# increasing for reach), and hand alternation effort. In both values,
|
17
|
+
# lower is better.
|
18
|
+
def run
|
19
|
+
results = { }
|
20
|
+
keyboards.each do |keyboard|
|
21
|
+
# set up container vars
|
22
|
+
prev_hand = nil
|
23
|
+
alternation_effort = 0
|
24
|
+
reach_effort = 0
|
25
|
+
open_and_process(filename,'r') do |file|
|
26
|
+
while line = file.gets
|
27
|
+
line.each_char do |char|
|
28
|
+
if keyboard.combined.include?(char)
|
29
|
+
|
30
|
+
# measure alternation efficiency
|
31
|
+
hand = keyboard.left.include?(char) ? 'l' : 'r'
|
32
|
+
if prev_hand
|
33
|
+
alternation_effort += (hand == prev_hand) ? 1 : 0
|
34
|
+
end
|
35
|
+
prev_hand = hand
|
36
|
+
|
37
|
+
# measure reach efficiency
|
38
|
+
reach_effort += EFFORT[keyboard.combined.find_index(char)]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
results[keyboard.name.to_sym] = {
|
45
|
+
:alternation_effort => alternation_effort,
|
46
|
+
:reach_effort => reach_effort,
|
47
|
+
:raw_score => (alternation_effort + reach_effort)
|
48
|
+
}
|
49
|
+
end
|
50
|
+
results
|
51
|
+
end
|
52
|
+
|
53
|
+
def open_and_process(*args)
|
54
|
+
f = File.open(*args)
|
55
|
+
yield f
|
56
|
+
f.close()
|
57
|
+
end
|
58
|
+
|
59
|
+
EFFORT = ( # left hand + right hand effort values
|
60
|
+
%w(3 2 2 2 2 2 2 1 1 1 1 1 0 0 0 0 1 1 1 1 1 2 3 2 2 2 2 2 2 1 1 1 1 1 0 0 0 0 1 1 1 1 1 2) +
|
61
|
+
%w(2 2 2 2 2 2 1 1 1 1 1 1 2 3 1 0 0 0 0 1 1 1 1 1 1 2 2 2 2 2 2 1 1 1 1 1 1 2 3 1 0 0 0 0 1 1 1 1 1 1)
|
62
|
+
).collect { |el| el.to_i }
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
class KeyboardBattle::Keyboard
|
2
|
+
|
3
|
+
class Qwerty < self
|
4
|
+
def name
|
5
|
+
"qwerty"
|
6
|
+
end
|
7
|
+
|
8
|
+
def left
|
9
|
+
%w(` 1 2 3 4 5 6 q w e r t a s d f g z x c v b ~ ! @ # $ % ^ Q W E R T A S D F G Z X C V B)
|
10
|
+
end
|
11
|
+
|
12
|
+
def right
|
13
|
+
%w(7 8 9 0 - = y u i o p [ ] \\ h j k l ; ' n m , . / & * ( ) _ + Y U I O P { } | H J K L : " N M < > ?)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Dvorak < self
|
18
|
+
def name
|
19
|
+
"dvorak"
|
20
|
+
end
|
21
|
+
|
22
|
+
def left
|
23
|
+
%w(` 1 2 3 4 5 6 ' , . p y a o e u i ; q j k x ~ ! @ # $ % ^ " < > P Y A O E U I : Q J K X)
|
24
|
+
end
|
25
|
+
|
26
|
+
def right
|
27
|
+
%w(7 8 9 0 [ ] f g c r l / = \\ d h t n s - b m w v z & * ( ) { } F G C R L ? + | D H T N S _ B M W V Z)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class Colemak < self
|
32
|
+
def name
|
33
|
+
"colemak"
|
34
|
+
end
|
35
|
+
|
36
|
+
def left
|
37
|
+
%w(` 1 2 3 4 5 6 q w f p g a r s t d z x c v b ~ ! @ # $ % ^ Q W F P G A R S T D Z X C V B)
|
38
|
+
end
|
39
|
+
|
40
|
+
def right
|
41
|
+
%w(7 8 9 0 - = j l u y : [ ] \\ h n e i o ' k m , . / & * ( ) _ + J L U Y ; { } | H N E I O " K M < > ?)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_accessor :left, :right, :name
|
46
|
+
|
47
|
+
def combined
|
48
|
+
left + right
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.all
|
52
|
+
[
|
53
|
+
KeyboardBattle::Keyboard::Qwerty.new,
|
54
|
+
KeyboardBattle::Keyboard::Dvorak.new,
|
55
|
+
KeyboardBattle::Keyboard::Colemak.new
|
56
|
+
]
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class KeyboardBattle::Report
|
2
|
+
|
3
|
+
def initialize(filename, results)
|
4
|
+
@filename = filename
|
5
|
+
@results = results
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_s
|
9
|
+
out = "\n"
|
10
|
+
out << "#{@filename}:\n"
|
11
|
+
@results.each do |keyboard, performance|
|
12
|
+
out << " #{keyboard}:\n"
|
13
|
+
performance.each do |metric, result|
|
14
|
+
out << " #{metric}: #{result}\n"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
out << "\n"
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe KeyboardBattle::CommandDispatcher do
|
4
|
+
|
5
|
+
describe 'an instance with file args' do
|
6
|
+
it 'runs the specified files' do
|
7
|
+
KeyboardBattle::CommandDispatcher.any_instance.should_receive(:run).with(["texts/qbf.txt"])
|
8
|
+
KeyboardBattle::CommandDispatcher.new(["texts/qbf.txt"])
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe 'an instance with --bundled' do
|
13
|
+
it 'runs all bundled files' do
|
14
|
+
KeyboardBattle::CommandDispatcher.any_instance.should_receive(:run).with(Dir.glob("texts/*.txt"))
|
15
|
+
KeyboardBattle::CommandDispatcher.new(["--bundled"])
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe 'an instance without args' do
|
20
|
+
it 'prints the help' do
|
21
|
+
KeyboardBattle::CommandDispatcher.any_instance.should_receive(:help)
|
22
|
+
KeyboardBattle::CommandDispatcher.new([])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe KeyboardBattle::Exercise do
|
4
|
+
|
5
|
+
describe 'an instance' do
|
6
|
+
let(:exercise) { KeyboardBattle::Exercise.new('texts/qbf.txt', KeyboardBattle::Keyboard.all) }
|
7
|
+
|
8
|
+
it "loads the keyboards" do
|
9
|
+
expect(exercise.send(:keyboards).map {|k| k.name}).to eq(%w(qwerty dvorak colemak))
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "when processing texts" do
|
13
|
+
describe "with qwerty" do
|
14
|
+
it "calculates reach effort" do
|
15
|
+
expect(exercise.results[:qwerty][:reach_effort]).to eq(30)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "calculates alternation effort" do
|
19
|
+
expect(exercise.results[:qwerty][:alternation_effort]).to eq(11)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "calculates raw score" do
|
23
|
+
expect(exercise.results[:qwerty][:raw_score]).to eq(41)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "with dvorak" do
|
28
|
+
it "calculates reach effort" do
|
29
|
+
expect(exercise.results[:dvorak][:reach_effort]).to eq(21)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "calculates alternation effort" do
|
33
|
+
expect(exercise.results[:dvorak][:alternation_effort]).to eq(13)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "calculates raw score" do
|
37
|
+
expect(exercise.results[:dvorak][:raw_score]).to eq(34)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "with colemak" do
|
42
|
+
it "calculates reach effort" do
|
43
|
+
expect(exercise.results[:colemak][:reach_effort]).to eq(22)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "calculates alternation effort" do
|
47
|
+
expect(exercise.results[:colemak][:alternation_effort]).to eq(9)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "calculates raw score" do
|
51
|
+
expect(exercise.results[:colemak][:raw_score]).to eq(31)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe KeyboardBattle::Report do
|
4
|
+
|
5
|
+
describe 'an instance' do
|
6
|
+
let(:battle) {
|
7
|
+
KeyboardBattle::Report.new('foo.txt', { dvorak: { a: 1, b: 2, c: 3 }})
|
8
|
+
}
|
9
|
+
|
10
|
+
it "should have an informative to_s" do
|
11
|
+
[
|
12
|
+
/foo(.*)dvorak(.*)a(.*)1/m,
|
13
|
+
/foo(.*)dvorak(.*)b(.*)2/m,
|
14
|
+
/foo(.*)dvorak(.*)c(.*)3/m,
|
15
|
+
].each { |m|
|
16
|
+
battle.to_s.should match(m)
|
17
|
+
}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'keyboard_battle'
|