vigilem-support 0.0.9

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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/lib/vigilem/ffi.rb +19 -0
  3. data/lib/vigilem/ffi/array_pointer_sync.rb +382 -0
  4. data/lib/vigilem/ffi/struct.rb +54 -0
  5. data/lib/vigilem/ffi/utils.rb +240 -0
  6. data/lib/vigilem/ffi/utils/struct.rb +93 -0
  7. data/lib/vigilem/support.rb +31 -0
  8. data/lib/vigilem/support/core_ext.rb +13 -0
  9. data/lib/vigilem/support/core_ext/debug_puts.rb +5 -0
  10. data/lib/vigilem/support/core_ext/enumerable.rb +25 -0
  11. data/lib/vigilem/support/key_map.rb +323 -0
  12. data/lib/vigilem/support/key_map_info.rb +85 -0
  13. data/lib/vigilem/support/lazy_simple_delegator.rb +81 -0
  14. data/lib/vigilem/support/max_size_error.rb +17 -0
  15. data/lib/vigilem/support/metadata.rb +13 -0
  16. data/lib/vigilem/support/obj_space.rb +28 -0
  17. data/lib/vigilem/support/patch/cucumber/c_lexer.rb +30 -0
  18. data/lib/vigilem/support/patch/ffi/pointer.rb +19 -0
  19. data/lib/vigilem/support/size_error.rb +6 -0
  20. data/lib/vigilem/support/sys.rb +5 -0
  21. data/lib/vigilem/support/system.rb +92 -0
  22. data/lib/vigilem/support/transmutable_hash.rb +206 -0
  23. data/lib/vigilem/support/utils.rb +224 -0
  24. data/lib/vigilem/support/version.rb +5 -0
  25. data/spec/spec_helper.rb +9 -0
  26. data/spec/vigilem/ffi/array_pointer_sync_spec.rb +728 -0
  27. data/spec/vigilem/ffi/struct_spec.rb +22 -0
  28. data/spec/vigilem/ffi/utils/struct_spec.rb +79 -0
  29. data/spec/vigilem/ffi/utils_spec.rb +240 -0
  30. data/spec/vigilem/support/core_ext/enumerable_spec.rb +38 -0
  31. data/spec/vigilem/support/data/cached.kmap +130 -0
  32. data/spec/vigilem/support/data/cached_UTF-8_del.kmap +142 -0
  33. data/spec/vigilem/support/data/cached_short.kmap +38 -0
  34. data/spec/vigilem/support/data/dump_keys_short.kmap +128 -0
  35. data/spec/vigilem/support/key_map_spec.rb +767 -0
  36. data/spec/vigilem/support/lazy_simple_delegator_spec.rb +77 -0
  37. data/spec/vigilem/support/obj_space_spec.rb +47 -0
  38. data/spec/vigilem/support/sys_spec.rb +21 -0
  39. data/spec/vigilem/support/system_spec.rb +31 -0
  40. data/spec/vigilem/support/transmutable_hash_spec.rb +175 -0
  41. data/spec/vigilem/support/utils_spec.rb +214 -0
  42. metadata +240 -0
@@ -0,0 +1,240 @@
1
+ module Vigilem
2
+ module FFI
3
+ # utilities for FFI
4
+ module Utils
5
+
6
+ # converts struct to a Hash
7
+ # @param [#members, #values] struct
8
+ # @param [Integer || NilClass] limit
9
+ # @return [Hash]
10
+ def struct_to_h(struct, limit=nil)
11
+ Utils._struct_to_h(struct, limit)
12
+ end
13
+
14
+ # gets the FFI::Type::Builtin a type
15
+ # @param [Symbol || ::FFI::Type::Builtin] type
16
+ # @return [Symbol] the builtin type name
17
+ def get_builtin_type_name(type)
18
+ builtin = ::FFI.find_type(type) || type
19
+ raise TypeError, "unable to resolve type '#{type}'" unless builtin.is_a? ::FFI::Type::Builtin
20
+ (nt = ::FFI::NativeType).constants.find {|const| nt.const_get(const) == builtin }.downcase
21
+ end
22
+
23
+ alias_method :get_native_type_name, :get_builtin_type_name
24
+
25
+ # @todo this is a problem, like XOpenDisplay, the ptr returned
26
+ # maybe huge, but that;s not the size of the object in the pointer
27
+ # @param [Pointer] pointer
28
+ # @return [Integer]
29
+ def ptr_capacity(pointer)
30
+ pointer.size/pointer.type_size
31
+ end
32
+
33
+ #
34
+ # @param [#type] obj
35
+ # @return [TrueClass || FalseClass]
36
+ def is_a_struct?(obj)
37
+ [::FFI::Struct, ::FFI::StructByValue, ::FFI::StructByReference].any? {|struct_klass| obj.is_a?(struct_klass) or obj.respond(:type).is_a? struct_klass }
38
+ end
39
+
40
+ #
41
+ # @param [::FFI::Pointer] pointer
42
+ # @param [Symbol || ::FFI::Type::Builtin] type
43
+ # @param value
44
+ # @return [::FFI::Pointer] pointer
45
+ def write_typedef(pointer, type, value)
46
+ pointer.__send__(:"write_#{get_builtin_type_name(type) }", value)
47
+ end
48
+
49
+ #
50
+ # @param [::FFI::Pointer] pointer
51
+ # @param [Class] struct_class
52
+ # @param [Integer] num
53
+ # @return [Array]
54
+ def read_array_of_structs(pointer, struct_class, num=nil)
55
+ 1.upto(num||pointer.type_size/struct_class.size).map { struct_class.new(pointer) }
56
+ end
57
+
58
+ #
59
+ # possible refactor `pointer.write_array_of_type(type, :"write_array_of_#{get_builtin_type_name(type) }", [*value])`
60
+ # @param [::FFI::Pointer] pointer
61
+ # @param [Symbol || ::FFI::Type::Builtin] type
62
+ # @param [Array] value
63
+ # @return [::FFI::Pointer] pointer
64
+ def write_array_typedef(pointer, type, value)
65
+ pointer.__send__(:"write_array_of_#{get_builtin_type_name(type) }", [*value])
66
+ end
67
+
68
+ #
69
+ # @param [::FFI::Pointer] pointer
70
+ # @param [Symbol || ::FFI::Type::Builtin] type
71
+ # @return value from pointer
72
+ def read_typedef(pointer, type)
73
+ pointer.__send__(:"read_#{get_builtin_type_name(type) }")
74
+ end
75
+
76
+ #
77
+ # @param [::FFI::Pointer] pointer
78
+ # @param [Symbol || ::FFI::Type::Builtin] type
79
+ # @param [Integer] num
80
+ # @return [Array]
81
+ def read_array_typedef(pointer, type, num=1)
82
+ pointer.__send__(:"read_array_of_#{get_builtin_type_name(type) }", num)
83
+ end
84
+
85
+ #
86
+ # @param [::FFI::Pointer] pointer
87
+ # @param [String] type
88
+ # @param value
89
+ # @param [Integer] offset
90
+ # @return [::FFI::Pointer] pointer
91
+ def put_typedef(pointer, type, value, offset=0)
92
+ pointer.__send__(:"put_#{get_builtin_type_name(type) }", offset, value)
93
+ end
94
+
95
+ #
96
+ # @param [::FFI::Pointer] pointer
97
+ # @param [String] type
98
+ # @param [Array] value
99
+ # @param [Integer] offset
100
+ # @return pointer
101
+ def put_array_typedef(pointer, type, value, offset=0)
102
+ pointer.__send__(:"put_array_of_#{get_builtin_type_name(type) }", offset, [*value])
103
+ end
104
+
105
+ #
106
+ # @param [::FFI::Pointer] pointer
107
+ # @param [Symbol || ::FFI::Type::Builtin] type
108
+ # @param value
109
+ # @return [::FFI::Pointer] pointer
110
+ def replace_typedef(pointer, type, value)
111
+ put_typedef(pointer, type, value)
112
+ end
113
+
114
+ #
115
+ # @param [::FFI::Pointer] pointer
116
+ # @param [Symbol || ::FFI::Type::Builtin] type
117
+ # @param value
118
+ # @return pointer
119
+ def add_int_typedef(pointer, type, value)
120
+ replace_typedef(pointer, type, (value + self.read_typedef(pointer, type)))
121
+ end
122
+
123
+ # @todo needed?
124
+ # @param struct
125
+ # @param [Proc] block
126
+ # @return the type of the fields or the result of the block
127
+ def types(struct, &block)
128
+ struct = Support::Utils.get_class(struct) unless struct.respond_to? :layout
129
+ struct.layout.fields.map do |field|
130
+ if block
131
+ yield field
132
+ else
133
+ field.type
134
+ end
135
+ end
136
+ end
137
+
138
+ # @todo refactor me
139
+ # assigns values to the struct
140
+ # @param [::FFI::Struct] struct
141
+ # @param [Array || Hash] vals
142
+ # @return struct
143
+ def struct_bulk_assign(struct, vals)
144
+ if vals.is_a? Hash
145
+ self.struct_bulk_assign_hash(struct, vals)
146
+ else
147
+ types(struct) do |fld|
148
+ _struct_bulk_assign(struct, fld, fld.name, vals)
149
+ end
150
+ end
151
+ struct
152
+ end
153
+
154
+ alias_method :from_array, :struct_bulk_assign
155
+
156
+ #
157
+ # @param [::FFI::Struct] struct
158
+ # @param [Hash] hsh
159
+ # @return struct
160
+ def struct_bulk_assign_hash(struct, hsh)
161
+ hsh.each do |key, value|
162
+ the_field = struct.layout.fields.find {|fld| fld.name == key }
163
+ raise "on #{struct.class} attr #{key} not found in #{struct.layout.fields.map(&:name)}" unless the_field
164
+ _struct_bulk_assign(struct, the_field, key, [hsh[key]])
165
+ end
166
+ struct
167
+ end
168
+
169
+ alias_method :from_hash, :struct_bulk_assign_hash
170
+
171
+ # @todo unions don't need know the member name, the space taken up is the same
172
+ # @todo refactor me
173
+ # @param [::FFI::Struct] struct
174
+ # @param [::FFI::StructLayout::Field] field
175
+ # @param [Symbol] attr_name
176
+ # @param [Array || Hash] vals
177
+ # @return
178
+ def _struct_bulk_assign(struct, field, attr_name, vals)
179
+ # struct.type.struct_class, why use field?
180
+ #if is_a_struct?(struct.type)
181
+ if is_a_struct?(field)
182
+ if is_a_struct?(frst = vals.first) or frst.is_a? ::FFI::Union
183
+ struct[attr_name] = vals.shift
184
+ else
185
+ ptr_offset = struct.offsets.assoc(attr_name).last
186
+ struct_obj = if (struct_klass = field.type.struct_class) <= VFFIStruct
187
+ struct_klass.new(ptr_offset)
188
+ else
189
+ struct_klass.new
190
+ end
191
+ struct[attr_name] = struct_bulk_assign(struct_obj, vals.shift)
192
+ end
193
+ else
194
+ raise ArgumentError, "Arity mismatch: complex type `#{struct.inspect}' does not match argument `#{vals.inspect}'" unless vals.respond_to? :shift
195
+ struct[attr_name] = vals.shift
196
+ end
197
+ end
198
+
199
+ #
200
+ # @param [Class] struct_class
201
+ # @param [String] str_bytes
202
+ # @return [Array<FFI::Struct>]
203
+ def bytes_to_structs(struct_class, str_bytes)
204
+ ary_of_type(struct_class, struct_class.size, ::FFI::MemoryPointer.from_string(str_bytes))
205
+ end
206
+
207
+ # converts a pointer to an ary of ary_type
208
+ # @param type
209
+ # @param [Fixnum] type_size, this is given because a ::from_string has type_size of 1
210
+ # @param [FFI::Pointer] pointer
211
+ # @return [Array] of type
212
+ def ary_of_type(type, type_size, pointer, len=-1)
213
+ if type.is_a? Class and type <= ::FFI::Struct
214
+ # @todo slice is supposed to return new a pointer, when atype.type_size == pointer.type_size does this return the old pointer?
215
+ 0.upto((pointer.size/type_size) - 1).map {|n| type.new(pointer.slice(n * type_size, type_size).dup) }
216
+ else
217
+ ::Vigilem::FFI::Utils.read_array_typedef(pointer, type, ptr_capacity(pointer))
218
+ end
219
+ end
220
+
221
+ extend self
222
+
223
+ module_function
224
+ #
225
+ # @param [#members, #values] struct
226
+ # @param [Integer || NilClass] limit
227
+ # @return [Hash || Struct]
228
+ def _struct_to_h(struct, current=1, limit=nil)
229
+ if limit.nil? or limit <= current
230
+ Hash[struct.members.zip(struct.values.map {|val| is_a_struct?(val) ? _struct_to_h(val) : val } )]
231
+ else
232
+ struct
233
+ end
234
+ end
235
+
236
+ end
237
+ end
238
+ end
239
+
240
+ require 'vigilem/ffi/utils/struct'
@@ -0,0 +1,93 @@
1
+ module Vigilem
2
+ module FFI
3
+ module Utils
4
+ # Utils specfic to Vigilem::FFI::Struct
5
+ module Struct
6
+
7
+ extend ActiveSupport::Concern
8
+
9
+ # methods to be ::extend'd
10
+ module ClassMethods
11
+
12
+ # converts the members of the struct to methods
13
+ # @return
14
+ def members_to_methods
15
+ members.each do |member_name|
16
+ define_method(member_name, &lambda { self[member_name] })
17
+ define_method(:"#{member_name}=", &lambda {|val| self[member_name] = val })
18
+ end
19
+ end
20
+
21
+ # @see FFI::Struct::layout
22
+ # @param [Array] args
23
+ # @return
24
+ def layout_with_methods(*args)
25
+ raise SizeError, "Odd number of aguments:`#{args}'" if args.size.odd?
26
+ aliases = []
27
+ layout *(args.map do |names_or_value|
28
+ if names_or_value.is_a? Array
29
+ aliases << names_or_value
30
+ names_or_value.first
31
+ else
32
+ names_or_value
33
+ end
34
+ end)
35
+ ret = members_to_methods
36
+ aliases.each {|name_group| name_group.each {|alias_name| alias_method(alias_name, name_group[0]) } }
37
+ ret
38
+ end
39
+
40
+ # @todo update the layout without overwriting, when called after layout
41
+ # acts "like" the union keyword
42
+ # @return
43
+ def union(name, *args, &block)
44
+ # can't be Ruby Struct, because of layout_with_methods
45
+ unyun = Class.new(::FFI::Union) do
46
+ include Vigilem::FFI::Utils::Struct
47
+ layout_with_methods *(args.empty? ? block.call : args)
48
+ end
49
+ const_set(:"#{name[0].upcase}#{name[1..-1]}", unyun)
50
+ [name.to_sym, unyun]
51
+ end
52
+
53
+ #
54
+ # @param [String] str_bytes
55
+ # @return [Array<VFFIStruct>]
56
+ def from_string(str_bytes)
57
+ Utils.bytes_to_structs(self, str_bytes)
58
+ end
59
+ end
60
+
61
+ # @see FFI::Utils#bulk_assign
62
+ # @param [Hash || Array] vals
63
+ # @return
64
+ def bulk_assign(vals)
65
+ ::Vigilem::FFI::Utils.struct_bulk_assign(self, vals)
66
+ end
67
+
68
+ #
69
+ # @param [Symbol] attr_name
70
+ # @return
71
+ def type_of(attr_name)
72
+ field = self.class.layout.fields.find {|fld| fld.name == attr_name }
73
+ raise "no field by the name #{attr_name} for #{self}" unless field
74
+ field.type
75
+ end
76
+
77
+ #
78
+ # @param [Proc] block
79
+ # @return
80
+ def types(&block)
81
+ ::Vigilem::FFI::Utils.types(self, &block)
82
+ end
83
+
84
+ #
85
+ # @see Vigilem::FFI::Utils.struct_to_h
86
+ # @return [Hash]
87
+ def to_h
88
+ ::Vigilem::FFI::Utils.struct_to_h(self)
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,31 @@
1
+ require 'vigilem/support/version'
2
+
3
+ require 'active_support/concern'
4
+
5
+ require 'vigilem/support/utils'
6
+
7
+ require 'vigilem/support/core_ext'
8
+ require 'vigilem/support/system'
9
+
10
+ require 'vigilem/ffi'
11
+
12
+ require 'vigilem/support/metadata'
13
+
14
+ module Vigilem
15
+ #
16
+ module Support
17
+
18
+ # autoload is deprecated
19
+ # @param [Symbol]
20
+ # @return
21
+ def self.const_missing(const)
22
+ if [:System, :TransmutableHash, :LazySimpleDelegator, :MaxSizeError, :SizeError, :Sys, :KeyMap].include? const
23
+ require "vigilem/support/#{const.to_s.snakecase}"
24
+ const_get(const)
25
+ else
26
+ super(const)
27
+ end
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,13 @@
1
+ require 'vigilem/support/core_ext/enumerable'
2
+
3
+ require 'facets/kernel/respond'
4
+
5
+ if defined?(Delegator) and not Delegator.method_defined?(:respond)
6
+ load Delegator.instance_method(:__getobj__).source_location.first
7
+ end
8
+
9
+ require 'facets/hash/autonew' # @todo in use?
10
+ require 'facets/string/snakecase'
11
+ require 'facets/string/titlecase'
12
+
13
+ require 'facets/enumerable/every'
@@ -0,0 +1,5 @@
1
+ module Kernel
2
+ def debug_puts(*args)
3
+ puts "#{caller.first.rpartition(%r{/}).last}>#{args.first}", args[1..-1]
4
+ end
5
+ end
@@ -0,0 +1,25 @@
1
+ require 'facets/enumerable/find_yield'
2
+
3
+ #
4
+ #
5
+ module Enumerable
6
+
7
+ # gets all execpt the parameters listed
8
+ # on a hash this is a key
9
+ # @see #reject
10
+ # @param [Array] objs
11
+ # @return [Enumerable]
12
+ def except(*objs)
13
+ reject {|item| objs.include?(item) }
14
+ end
15
+
16
+ # @todo move to Array?
17
+ # gets all except items listed at specific indexes
18
+ # @param [Array<Integer>] indexes
19
+ # @return [Enumerable]
20
+ def except_at(*indexes)
21
+ reject.with_index {|item, idx| indexes.include?(idx) }
22
+ end
23
+
24
+ alias_method :find_result, :find_yield
25
+ end
@@ -0,0 +1,323 @@
1
+ require 'vigilem/support/transmutable_hash'
2
+
3
+ require 'vigilem/support/utils'
4
+
5
+ require 'vigilem/support/metadata'
6
+
7
+ require 'vigilem/support/key_map_info'
8
+
9
+ module Vigilem
10
+ module Support
11
+ #
12
+ #
13
+ # a TransmutableHash representing a keyboard map
14
+ # keymap = system, KeyMap ruby object
15
+ # @see http://kbd-project.org/manpages/man5/keymaps.5.html
16
+ # @see man dumpkeys -f
17
+ # @todo to_file
18
+ # @todo win32
19
+ # @todo document the expected columns
20
+ # @todo default for each key default_proc point
21
+ # @todo move to a parser object
22
+ # @todo update to fit Win32#LoadKeyboardLayout
23
+ # @todo eject os specific, and make them loadable/injectable
24
+ # @todo some methods return Hash instead of KeyMap
25
+ class KeyMap < TransmutableHash
26
+
27
+ # starts with keycode | spaces or '='
28
+ KEYCODE_SPLIT_REGEX = /(keycode\s+\d{1,3})|\s(=)?\s?/
29
+
30
+ include Metadata
31
+
32
+ attr_writer :mod_weights, :short_inspect, :info
33
+ attr_accessor :charset
34
+
35
+ #
36
+ # @param [Hash] init_hash_or_default the initial hash or default value
37
+ # @param default_value, the default value if not given in init_hash_or_default
38
+ def initialize(init_hash_or_default={}, default_value=nil)
39
+ if init_hash_or_default.is_a? Hash
40
+ super(init_hash_or_default, default_value)
41
+ else
42
+ super({}, init_hash_or_default)
43
+ end
44
+ end
45
+
46
+ #
47
+ # @return [KeyMap]
48
+ def each(&block)
49
+ self.class[super(&block)]
50
+ end
51
+
52
+ #
53
+ # @return [KeyMap]
54
+ def select(&block)
55
+ self.class[super(&block)]
56
+ end
57
+
58
+ # @todo overwrite from info or move to info
59
+ # @return [Hash]
60
+ def mod_weights
61
+ if self.class.mod_weights != @mod_weights
62
+ @mod_weights = self.class.mod_weights.merge(@mod_weights)
63
+ else
64
+ @mod_weights
65
+ end
66
+ end
67
+
68
+ # I.E. keymaps 0-2,4-5,8,12
69
+ # @return [Array<Range || Integer>] the parsed keymap spec
70
+ def spec
71
+ @spec ||= []
72
+ end
73
+
74
+ #
75
+ # @param [String || NilClass] str, defaults to nil
76
+ # @return [NilClass || KeyMapInfo]
77
+ def info(str=nil)
78
+ if str
79
+ @info = KeyMapInfo.load_string(str)
80
+ else
81
+ @info
82
+ end
83
+ end
84
+
85
+ # gets the left_side from the right_side_values
86
+ # @param right_side_values
87
+ # @return right_side_values or w/e the left_side it is mapped to
88
+ def left_side(right_side_values=nil)
89
+ if right_side_values
90
+ if inverted?
91
+ self[right_side_values]
92
+ else
93
+ invert[right_side_values]
94
+ end
95
+ else
96
+ inverted? ? self.values : self.keys
97
+ end
98
+ end
99
+
100
+ #
101
+ # @param [Array<Symbol>] method_names
102
+ # @return [Proc]
103
+ def left_side_aliases(*method_names)
104
+ method_names.each do |method_name|
105
+ define_singleton_method(method_name) {|right_side_value=nil| self.left_side(right_side_value) }
106
+ end
107
+ end
108
+
109
+ alias_method :left_side_alias, :left_side_aliases
110
+
111
+ # keycode keynumber = keysym keysym keysym...
112
+ # modsym modsym modsym keycode keynumber = keysym keysym keysym...
113
+ # get the right_side from the left_side_values
114
+ # @param left_side_values
115
+ # @return
116
+ def right_side(left_side_values=nil)
117
+ if left_side_values
118
+ if inverted?
119
+ invert[left_side_values]
120
+ else
121
+ self[left_side_values]
122
+ end
123
+ else
124
+ inverted? ? self.keys : self.values
125
+ end
126
+ end
127
+
128
+ # essentially an alias_method
129
+ # @param [Symbol] method_names
130
+ # @return [Proc]
131
+ def right_side_aliases(*method_names)
132
+ method_names.each do |method_name|
133
+ define_singleton_method(method_name) {|left_side_values=nil| self.right_side(left_side_values) }
134
+ end
135
+ end
136
+
137
+ alias_method :right_side_alias, :right_side_aliases
138
+
139
+ # @see TransmutableHash#invert
140
+ # @return [KeyMap]
141
+ def invert
142
+ self.class[super]
143
+ end
144
+
145
+ #=== @todo move to parser
146
+
147
+ # takes keycode expresssion blocks and adds them to this KeyMap
148
+ # @param [Array<String>] expresssion_blocks array of ["keycode = vvv", "keycode = vvvv", "alt keycode = vvvv"]
149
+ # @return [nil]
150
+ def parse_keymap_expressions(*expresssion_blocks)
151
+ expresssion_blocks.each {|expr_block| parse_expression_block(expr_block) }.compact
152
+ nil
153
+ end
154
+
155
+ # @todo cleanup
156
+ # takes a keycode expresssion block and adds them to this KeyMap
157
+ # dumpkeys -f || cached.kmap.gz
158
+ # @param [String] expr_blk a String block from keycode to next keycode
159
+ # @return [NilCLass]
160
+ def parse_expression_block(expr_blk)
161
+ expr_blk.split("\n").map do |line|
162
+ # skip comments
163
+ unless line.split('#').first.to_s.empty?
164
+ if line =~ /^keymaps/
165
+ parse_keymap_spec(line)
166
+ else
167
+ line_ary = line.to_s.split(KEYCODE_SPLIT_REGEX).reject(&:empty?)
168
+ if eq_index = line_ary.index('=')
169
+ # ["keycode 2", "=", "U+0031", "U+0021", "U+0031", "U+0031", "VoidSymbol",... ]
170
+ _parse_sides(*Utils.split_on(line_ary, eq_index))
171
+ end #if eq_index
172
+ end #if line
173
+ end #unless
174
+ end #expr
175
+ nil
176
+ end
177
+
178
+ #
179
+ # @param [String] left_side
180
+ # @param [String] right_side
181
+ # @return
182
+ def _parse_sides(left_side, right_side)
183
+ #["alt", "keycode 2"]
184
+ keycode = left_side.pop.gsub(/\s+/, '')
185
+ if left_side.size > 0
186
+ self[Utils.unwrap_ary([*left_side, keycode].flatten)] = Utils.unwrap_ary(right_side)
187
+ else
188
+ build_hash_from_full_table_line(right_side, keycode)
189
+ end
190
+ end
191
+
192
+ private :_parse_sides
193
+
194
+ # takes a list of keysyms configures thier modifiers and adds them to this keymap
195
+ # @see ::build_hash_from_full_table_line
196
+ # @param [Array<String>] right_side an array of keysyms, in a full table this is the right side
197
+ # @param [String] keycode the keycode in that is being mapped
198
+ # @return [TransmutableHash] the results of
199
+ def build_hash_from_full_table_line(right_side, keycode)
200
+ raise 'keymap specification missing' if spec.empty?
201
+ merge!(self.class.build_hash_from_full_table_line(right_side, keycode, spec))
202
+ end
203
+
204
+ # converts keymap spec to ranged array
205
+ # @param [String] expr 'keymaps 0-127' or 'keymaps ?-?,?'
206
+ # @return [Array<Range|| Integer>] the parsed keymap spec that
207
+ def parse_keymap_spec(expr)
208
+ @spec = expr.split(/\s|,/)[1..-1].map {|str| str =~ /\-/ ? Range.new(*str.split('-').map(&:to_i)) : str.to_i }
209
+ end
210
+
211
+ # tests whether or not num is in the spec
212
+ # @param [Numeric] num is this number within 'keymaps 0-127' or 'keymaps ?-?,?'
213
+ # @return [TrueClass || FalseClass]
214
+ def in_spec?(num)
215
+ Utils.in_ranged_array?(spec, num)
216
+ end
217
+
218
+ class << self
219
+
220
+ # weights of the modifiers set by `keymaps`
221
+ def mod_weights
222
+ @mod_weights ||= { 'shift' => 1,
223
+ 'altgr' => 2, 'control' => 4,
224
+ 'alt' => 8, 'shiftl' => 16,
225
+ 'shiftr' => 32, 'ctrll' => 64,
226
+ 'ctrlr' => 128, 'capsshift' => 256 }
227
+ end
228
+
229
+ alias_method :columns, :mod_weights
230
+
231
+ # converts a a keymap file to a ruby object
232
+ # @todo change to stream reading
233
+ # @param [String] path the path of the File
234
+ # @return [KeyMap] the String as a Ruby object
235
+ def load_file(path)
236
+ load_string(File.binread(path))
237
+ end
238
+
239
+ # loads a keymap string and converts it to a Ruby object
240
+ # @param [String] str string to convert to KeyMap
241
+ # @return [KeyMap] the String as a Ruby object
242
+ def load_string(str, str_info=nil)
243
+ exprs = str.split(/^keycode/)
244
+ inst = self.new()
245
+ inst.parse_keymap_expressions(exprs[0], *exprs[1..-1].map {|exp| "keycode#{exp}" })
246
+ inst.info(KeyMapInfo.load_string(str_info)) if str_info
247
+ inst
248
+ end
249
+
250
+ # takes a list of keysyms and converts it to a TransmutableHash
251
+ # @todo name change?
252
+ # @param [Array<String>] right_side an array of keysyms, in a full table this is the right side
253
+ # @param [String] keycode the keycode in that is being mapped
254
+ # @param [Array<Numeric||Range>] columns
255
+ # @return [TransmutableHash] of the keycode combinations mapped to keysyms
256
+ def build_hash_from_full_table_line(right_side, keycode, columns=[0..127])
257
+ raise 'cannot build expresssions for full table with no columns' if columns.empty?
258
+ sze = right_side.size
259
+ right_side.each_with_object(TransmutableHash.new).with_index do |(keysyms, hsh), idx|
260
+ if Utils.in_ranged_array?(columns, idx)
261
+ # not sure why a 'b' is in the mapping when that's not an available range
262
+ (flat = [keysyms].flatten).last.gsub!(/^\+?0x0b/, '0x00')
263
+ hsh[Utils.unwrap_ary([mod_combin(idx), keycode].flatten)] = Utils.unwrap_ary(flat)
264
+ end
265
+ end
266
+ end
267
+
268
+ # finds the Combination (without repeats) of modifiers that fit into n
269
+ # @param [Integer] n the number to fit the modifiers into
270
+ # @return [Array<String>]
271
+ def modifier_combination(n)
272
+ mod_weights.map {|name, weight| name if weight & n != 0 }.compact
273
+ end
274
+
275
+ alias_method :mod_combin, :modifier_combination
276
+
277
+ end
278
+
279
+ #
280
+ # @return [TrueClass || FalseClass]
281
+ def short_inspect?
282
+ !!@short_inspect
283
+ end
284
+
285
+ #
286
+ # @return [TrueClass || FalseClass]
287
+ def short_inspect!
288
+ self.short_inpsect = true
289
+ end
290
+
291
+ #
292
+ # @return [String]
293
+ def short_inspect
294
+ num = (not @short_inspect.is_a?(Integer)) ? 30 : @short_inspect
295
+ "#{Hash[self.to_a.sample(num)].inspect.chomp('}')}...snip...@metadata.keys=#{metadata.keys}}"
296
+ end
297
+
298
+ #
299
+ # @return [String]
300
+ def inspect
301
+ if short_inspect?
302
+ short_inspect
303
+ else
304
+ super
305
+ end
306
+ end
307
+
308
+ #
309
+ # @param [Symbol] method_name
310
+ # @param [Array] args
311
+ # @param [Proc] block
312
+ # @return
313
+ def method_missing(method_name, *args, &block)
314
+ if self.keys.include? method_name
315
+ self[method_name]
316
+ else
317
+ super(method_name, *args, &block)
318
+ end
319
+ end
320
+
321
+ end
322
+ end
323
+ end