twiddler-utils 0.0.3
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.
- data/bin/twid-utils +52 -0
- data/doc/README +2 -0
- data/doc/Specifications +15 -0
- data/doc/coverage/index.html +170 -0
- data/doc/coverage/jquery-1.3.2.min.js +19 -0
- data/doc/coverage/jquery.tablesorter.min.js +15 -0
- data/doc/coverage/lib-twiddler-config_rb.html +2547 -0
- data/doc/coverage/lib-twiddler-lint_rb.html +417 -0
- data/doc/coverage/lib-twiddler-parser_rb.html +645 -0
- data/doc/coverage/lib-twiddler-rendering_rb.html +201 -0
- data/doc/coverage/lib-twiddler-target_builder_rb.html +783 -0
- data/doc/coverage/print.css +12 -0
- data/doc/coverage/rcov.js +42 -0
- data/doc/coverage/screen.css +270 -0
- data/lib/twiddler/config.rb +414 -0
- data/lib/twiddler/lint.rb +59 -0
- data/lib/twiddler/parser.rb +97 -0
- data/lib/twiddler/rendering.rb +23 -0
- data/lib/twiddler/target_builder.rb +120 -0
- data/spec_help/file-sandbox.rb +164 -0
- data/spec_help/fixtures/TWIDDLER.CFG +0 -0
- data/spec_help/gem_test_suite.rb +17 -0
- data/spec_help/spec_helper.rb +4 -0
- data/spec_help/ungemmer.rb +36 -0
- data/templates/cheatsheet.erb +38 -0
- data/templates/lua.erb +51 -0
- metadata +201 -0
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'twiddler/config'
|
2
|
+
|
3
|
+
module Twiddler
|
4
|
+
module Lint
|
5
|
+
def self.check(config)
|
6
|
+
report = []
|
7
|
+
Rule::registered.each do |rule|
|
8
|
+
report << rule.new(config).check
|
9
|
+
end
|
10
|
+
report
|
11
|
+
end
|
12
|
+
|
13
|
+
class Rule
|
14
|
+
class << self
|
15
|
+
def register
|
16
|
+
@@registered ||= []
|
17
|
+
@@registered << self
|
18
|
+
end
|
19
|
+
|
20
|
+
def registered
|
21
|
+
@@registered || []
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(config)
|
26
|
+
@config = config
|
27
|
+
end
|
28
|
+
|
29
|
+
def check()
|
30
|
+
return ""
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class MissingStrokes < Rule
|
35
|
+
register
|
36
|
+
def check()
|
37
|
+
all_strokes = []
|
38
|
+
@config.keytable.each do |key|
|
39
|
+
all_strokes << [key.code, ""]
|
40
|
+
all_strokes << [key.code, "shift"] if key.has_mod?("shift")
|
41
|
+
end
|
42
|
+
|
43
|
+
@config.keyboard.each do |chord|
|
44
|
+
next unless chord.single?
|
45
|
+
all_strokes.delete(@config.keytable.normalized(chord[0]))
|
46
|
+
end
|
47
|
+
|
48
|
+
if all_strokes.empty?
|
49
|
+
return "Missing: all normal keystrokes accounted for"
|
50
|
+
else
|
51
|
+
return "Config lacks strokes: #{all_strokes.map{|str| @config.keytable[*str]}.inspect}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'twiddler/config'
|
2
|
+
|
3
|
+
module Twiddler
|
4
|
+
class Parser
|
5
|
+
def self.parse(data)
|
6
|
+
version = *data.unpack("c")
|
7
|
+
case version
|
8
|
+
when 4
|
9
|
+
V4Parser.new(data, 1).go.parsed
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class V4Parser
|
14
|
+
def initialize(raw, skipped = 0)
|
15
|
+
@data = raw
|
16
|
+
@refs = Hash.new{|h,k| h[k] = [:missing_ref]}
|
17
|
+
bytes_hex
|
18
|
+
@index = 0
|
19
|
+
consume("x" * skipped)
|
20
|
+
bytes_hex
|
21
|
+
@config = Config.new()
|
22
|
+
end
|
23
|
+
|
24
|
+
def parsed
|
25
|
+
return @config
|
26
|
+
end
|
27
|
+
|
28
|
+
def consume(template)
|
29
|
+
old_len = @data.length
|
30
|
+
values = @data.unpack(template + "a*")
|
31
|
+
@data = values.pop
|
32
|
+
@index += old_len - @data.length
|
33
|
+
return values
|
34
|
+
end
|
35
|
+
|
36
|
+
def bytes_hex(count = 8)
|
37
|
+
"0x%04x:#{' %02x'*count}" % ([@index] + @data.unpack("c" * count))
|
38
|
+
end
|
39
|
+
|
40
|
+
def go()
|
41
|
+
total_size = @data.length + 1
|
42
|
+
@keyboard_chord_table, @mouse_chord_table, @multichar_table = *consume("vvv")
|
43
|
+
|
44
|
+
@config.configs[:raw] = *consume("a#{@keyboard_chord_table - @index}")
|
45
|
+
|
46
|
+
until @index >= @mouse_chord_table
|
47
|
+
key1234, mod_or_ff, keyindex = consume("B16B8c")
|
48
|
+
exit if @index > 685
|
49
|
+
if keyindex == 0 and
|
50
|
+
key1234 == "0000000000000000" and
|
51
|
+
mod_or_ff == "00000000"
|
52
|
+
next
|
53
|
+
end
|
54
|
+
|
55
|
+
chord = Config::KeyChord.new
|
56
|
+
chord.keydata = key1234
|
57
|
+
|
58
|
+
if(mod_or_ff == "11111111")
|
59
|
+
@refs[keyindex] = chord
|
60
|
+
else
|
61
|
+
chord.add_keystroke(mod_or_ff, keyindex)
|
62
|
+
end
|
63
|
+
|
64
|
+
@config.keyboard << chord
|
65
|
+
end
|
66
|
+
|
67
|
+
while @index < @multichar_table
|
68
|
+
key1234, data = consume("B16B8")
|
69
|
+
if key1234 == "0000000000000000" and
|
70
|
+
data == "00000000"
|
71
|
+
next
|
72
|
+
end
|
73
|
+
|
74
|
+
chord_record = Config::MouseChord.new
|
75
|
+
chord_record.keydata = key1234
|
76
|
+
chord_record.data = data
|
77
|
+
@config.mouse << chord_record
|
78
|
+
end
|
79
|
+
|
80
|
+
data_idx = 0
|
81
|
+
until @data.size == 0 do
|
82
|
+
data_size = *consume("v")
|
83
|
+
|
84
|
+
chord_data = []
|
85
|
+
((data_size-1)/2).times do
|
86
|
+
mod, idx = *consume("B8c")
|
87
|
+
next if idx == 0 #Not sure this is sufficient
|
88
|
+
@refs[data_idx].add_keystroke(mod, idx)
|
89
|
+
end
|
90
|
+
data_idx += 1
|
91
|
+
end
|
92
|
+
|
93
|
+
return self
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module Twiddler
|
4
|
+
class Rendering
|
5
|
+
def initialize(config)
|
6
|
+
@config = config
|
7
|
+
end
|
8
|
+
|
9
|
+
def embed(name)
|
10
|
+
path = template_path(name)
|
11
|
+
template = File::read(path)
|
12
|
+
erb = ERB.new(template, nil, "%>")
|
13
|
+
erb.filename = path
|
14
|
+
mod = erb.def_module("render")
|
15
|
+
@config.extend(mod)
|
16
|
+
end
|
17
|
+
|
18
|
+
def template_path(name)
|
19
|
+
path = File::expand_path("../../templates/#{name}.erb",
|
20
|
+
File::dirname(__FILE__))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module Twiddler
|
4
|
+
module TargetBuilder
|
5
|
+
def self.build(output_dir, dictionary, config)
|
6
|
+
FileUtils::mkdir_p(output_dir)
|
7
|
+
File::open(dictionary, "r") do |dict|
|
8
|
+
Builder::registered.each do |klass|
|
9
|
+
dict.rewind
|
10
|
+
klass.new(output_dir, dict, config).go
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Builder
|
16
|
+
class << self
|
17
|
+
def register
|
18
|
+
@@registered ||= []
|
19
|
+
@@registered << self
|
20
|
+
end
|
21
|
+
|
22
|
+
def registered
|
23
|
+
@@registered || []
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(dir, dict, config)
|
28
|
+
@dir = dir
|
29
|
+
@dict = dict
|
30
|
+
@config = config
|
31
|
+
end
|
32
|
+
|
33
|
+
def output_to(name)
|
34
|
+
File::open(File::expand_path("#{name}.list", @dir), "w") do |file|
|
35
|
+
yield(file)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def go
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class RegexpBuilder < Builder
|
44
|
+
def match_any_of(list)
|
45
|
+
return "(?:#{list.map{|it| Regexp::escape(it)}.join(")|(?:")})"
|
46
|
+
end
|
47
|
+
def go
|
48
|
+
output_to(filename) do |file|
|
49
|
+
@dict.grep(regexp) do |word|
|
50
|
+
file.puts(word)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class ShortWords < RegexpBuilder
|
57
|
+
register
|
58
|
+
|
59
|
+
def filename
|
60
|
+
"Short Words"
|
61
|
+
end
|
62
|
+
|
63
|
+
def regexp
|
64
|
+
/^.{1,5}$/
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class Macros < RegexpBuilder
|
69
|
+
register
|
70
|
+
|
71
|
+
def filename
|
72
|
+
"Macros"
|
73
|
+
end
|
74
|
+
|
75
|
+
def regexp
|
76
|
+
macros = @config.keyboard.find_all do |chord|
|
77
|
+
chord.keystrokes.length > 1 and chord.keystrokes.all? do |stroke|
|
78
|
+
stroke[1].empty?
|
79
|
+
end
|
80
|
+
end
|
81
|
+
return %r{^.{0,1}(?:#{match_any_of(macros.map{|chord| chord.render_action})}).{0,1}$}
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class Flow < RegexpBuilder
|
86
|
+
register
|
87
|
+
|
88
|
+
def filename
|
89
|
+
"Flow"
|
90
|
+
end
|
91
|
+
|
92
|
+
def regexp
|
93
|
+
flow = []
|
94
|
+
chords = @config.keyboard.find_all do |chord|
|
95
|
+
chord.single? and chord.keystrokes[0][1].empty? and Config.keytable.is_tagged?(chord.keystrokes[0][0], :letters)
|
96
|
+
end
|
97
|
+
|
98
|
+
chords.each do |first|
|
99
|
+
chords.each do |second|
|
100
|
+
next if first == second
|
101
|
+
pairs = first.rows.zip(second.rows).find_all{|one,two| one != two}
|
102
|
+
case pairs.length
|
103
|
+
when 1
|
104
|
+
flow << first.render_action + second.render_action
|
105
|
+
next
|
106
|
+
when 2
|
107
|
+
next if pairs.find{|pair| !pair.include?(:open)}
|
108
|
+
if pairs[0][0] == pairs[1][1] and pairs[0][1] == pairs[1][0]
|
109
|
+
flow << first.render_action + second.render_action
|
110
|
+
next
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
return %r{^.{0,1}(?:#{match_any_of(flow)}).{0,1}$}
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
require 'ftools'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module FileSandbox
|
5
|
+
def self.included(spec)
|
6
|
+
return unless spec.respond_to? :before
|
7
|
+
|
8
|
+
spec.before do
|
9
|
+
setup_sandbox
|
10
|
+
end
|
11
|
+
|
12
|
+
spec.after do
|
13
|
+
teardown_sandbox
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class HaveContents
|
18
|
+
def initialize(contents)
|
19
|
+
@contents = contents
|
20
|
+
end
|
21
|
+
|
22
|
+
def matches?(target)
|
23
|
+
case @contents
|
24
|
+
when Regexp
|
25
|
+
@contents =~ target.contents
|
26
|
+
when String
|
27
|
+
@contents == target.contents
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def have_contents(expected)
|
33
|
+
HaveContents.new(expected)
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_reader :sandbox
|
37
|
+
|
38
|
+
def in_sandbox(&block)
|
39
|
+
raise "I expected to create a sandbox as you passed in a block to me" if !block_given?
|
40
|
+
|
41
|
+
setup_sandbox
|
42
|
+
original_error = nil
|
43
|
+
|
44
|
+
begin
|
45
|
+
yield @sandbox
|
46
|
+
rescue => e
|
47
|
+
original_error = e
|
48
|
+
raise
|
49
|
+
ensure
|
50
|
+
begin
|
51
|
+
teardown_sandbox
|
52
|
+
rescue
|
53
|
+
if original_error
|
54
|
+
STDERR.puts "ALERT: a test raised an error and failed to release some lock(s) in the sandbox directory"
|
55
|
+
raise(original_error)
|
56
|
+
else
|
57
|
+
raise
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def setup_sandbox(path = '__sandbox')
|
64
|
+
unless @sandbox
|
65
|
+
@sandbox = Sandbox.new(path)
|
66
|
+
@__old_path_for_sandbox = Dir.pwd
|
67
|
+
Dir.chdir(@sandbox.root)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def teardown_sandbox
|
72
|
+
if @sandbox
|
73
|
+
Dir.chdir(@__old_path_for_sandbox)
|
74
|
+
@sandbox.clean_up
|
75
|
+
@sandbox = nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class Sandbox
|
80
|
+
attr_reader :root
|
81
|
+
|
82
|
+
def initialize(path = '__sandbox')
|
83
|
+
@root = File.expand_path(path)
|
84
|
+
clean_up
|
85
|
+
FileUtils.mkdir_p @root
|
86
|
+
end
|
87
|
+
|
88
|
+
def [](name)
|
89
|
+
SandboxFile.new(File.join(@root, name), name)
|
90
|
+
end
|
91
|
+
|
92
|
+
# usage new :file=>'my file.rb', :with_contents=>'some stuff'
|
93
|
+
def new(options)
|
94
|
+
if options.has_key? :directory
|
95
|
+
dir = self[options.delete(:directory)]
|
96
|
+
FileUtils.mkdir_p dir.path
|
97
|
+
else
|
98
|
+
file = self[options.delete(:file)]
|
99
|
+
if (binary_content = options.delete(:with_binary_content) || options.delete(:with_binary_contents))
|
100
|
+
file.binary_content = binary_content
|
101
|
+
else
|
102
|
+
file.content = (options.delete(:with_content) || options.delete(:with_contents) || '')
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
raise "unexpected keys '#{options.keys.join(', ')}'" unless options.empty?
|
107
|
+
|
108
|
+
dir || file
|
109
|
+
end
|
110
|
+
|
111
|
+
def remove(options)
|
112
|
+
name = File.join(@root, options[:file])
|
113
|
+
FileUtils.remove_file name
|
114
|
+
end
|
115
|
+
|
116
|
+
def clean_up
|
117
|
+
FileUtils.rm_rf @root
|
118
|
+
if File.exists? @root
|
119
|
+
raise "Could not remove directory #{@root.inspect}, something is probably still holding a lock on it"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
class SandboxFile
|
126
|
+
attr_reader :path
|
127
|
+
|
128
|
+
def initialize(path, sandbox_path)
|
129
|
+
@path = path
|
130
|
+
@sandbox_path = sandbox_path
|
131
|
+
end
|
132
|
+
|
133
|
+
def inspect
|
134
|
+
"SandboxFile: #@sandbox_path"
|
135
|
+
end
|
136
|
+
|
137
|
+
def exist?
|
138
|
+
File.exist? path
|
139
|
+
end
|
140
|
+
|
141
|
+
def content
|
142
|
+
File.read path
|
143
|
+
end
|
144
|
+
|
145
|
+
def content=(content)
|
146
|
+
FileUtils.mkdir_p File.dirname(@path)
|
147
|
+
File.open(@path, "w") {|f| f << content}
|
148
|
+
end
|
149
|
+
|
150
|
+
def binary_content=(content)
|
151
|
+
FileUtils.mkdir_p File.dirname(@path)
|
152
|
+
File.open(@path, "wb") {|f| f << content}
|
153
|
+
end
|
154
|
+
|
155
|
+
def create
|
156
|
+
self.content = ''
|
157
|
+
end
|
158
|
+
|
159
|
+
alias exists? exist?
|
160
|
+
alias contents content
|
161
|
+
alias contents= content=
|
162
|
+
alias binary_contents= binary_content=
|
163
|
+
end
|
164
|
+
end
|