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.
@@ -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