twiddler-utils 0.0.3

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