106 lines
3.6 KiB
Python
106 lines
3.6 KiB
Python
#!/usr/bin/python
|
|
import sys
|
|
import json as simplejson
|
|
|
|
def usage():
|
|
return '''
|
|
constructs adjacency_graphs.json from QWERTY and DVORAK keyboard layouts
|
|
|
|
usage:
|
|
%s src/Matchers/adjacency_graphs.json
|
|
''' % sys.argv[0]
|
|
|
|
qwerty = r'''
|
|
`~ 1! 2@ 3# 4$ 5% 6^ 7& 8* 9( 0) -_ =+
|
|
qQ wW eE rR tT yY uU iI oO pP [{ ]} \|
|
|
aA sS dD fF gG hH jJ kK lL ;: '"
|
|
zZ xX cC vV bB nN mM ,< .> /?
|
|
'''
|
|
|
|
dvorak = r'''
|
|
`~ 1! 2@ 3# 4$ 5% 6^ 7& 8* 9( 0) [{ ]}
|
|
'" ,< .> pP yY fF gG cC rR lL /? =+ \|
|
|
aA oO eE uU iI dD hH tT nN sS -_
|
|
;: qQ jJ kK xX bB mM wW vV zZ
|
|
'''
|
|
|
|
keypad = r'''
|
|
/ * -
|
|
7 8 9 +
|
|
4 5 6
|
|
1 2 3
|
|
0 .
|
|
'''
|
|
|
|
mac_keypad = r'''
|
|
= / *
|
|
7 8 9 -
|
|
4 5 6 +
|
|
1 2 3
|
|
0 .
|
|
'''
|
|
|
|
def get_slanted_adjacent_coords(x, y):
|
|
'''
|
|
returns the six adjacent coordinates on a standard keyboard, where each row is slanted to the
|
|
right from the last. adjacencies are clockwise, starting with key to the left, then two keys
|
|
above, then right key, then two keys below. (that is, only near-diagonal keys are adjacent,
|
|
so g's coordinate is adjacent to those of t,y,b,v, but not those of r,u,n,c.)
|
|
'''
|
|
return [(x-1, y), (x, y-1), (x+1, y-1), (x+1, y), (x, y+1), (x-1, y+1)]
|
|
|
|
def get_aligned_adjacent_coords(x, y):
|
|
'''
|
|
returns the nine clockwise adjacent coordinates on a keypad, where each row is vert aligned.
|
|
'''
|
|
return [(x-1, y), (x-1, y-1), (x, y-1), (x+1, y-1), (x+1, y), (x+1, y+1), (x, y+1), (x-1, y+1)]
|
|
|
|
def build_graph(layout_str, slanted):
|
|
'''
|
|
builds an adjacency graph as a dictionary: {character: [adjacent_characters]}.
|
|
adjacent characters occur in a clockwise order.
|
|
for example:
|
|
* on qwerty layout, 'g' maps to ['fF', 'tT', 'yY', 'hH', 'bB', 'vV']
|
|
* on keypad layout, '7' maps to [None, None, None, '=', '8', '5', '4', None]
|
|
'''
|
|
position_table = {} # maps from tuple (x,y) -> characters at that position.
|
|
tokens = layout_str.split()
|
|
token_size = len(tokens[0])
|
|
x_unit = token_size + 1 # x position unit len is token len plus 1 for the following whitespace.
|
|
adjacency_func = get_slanted_adjacent_coords if slanted else get_aligned_adjacent_coords
|
|
assert all(len(token) == token_size for token in tokens), 'token len mismatch:\n ' + layout_str
|
|
for y, line in enumerate(layout_str.split('\n')):
|
|
# the way I illustrated keys above, each qwerty row is indented one space in from the last
|
|
slant = y - 1 if slanted else 0
|
|
for token in line.split():
|
|
x, remainder = divmod(line.index(token) - slant, x_unit)
|
|
assert remainder == 0, 'unexpected x offset for %s in:\n%s' % (token, layout_str)
|
|
position_table[(x,y)] = token
|
|
|
|
adjacency_graph = {}
|
|
for (x,y), chars in position_table.iteritems():
|
|
for char in chars:
|
|
adjacency_graph[char] = []
|
|
for coord in adjacency_func(x, y):
|
|
# position in the list indicates direction
|
|
# (for qwerty, 0 is left, 1 is top, 2 is top right, ...)
|
|
# for edge chars like 1 or m, insert None as a placeholder when needed
|
|
# so that each character in the graph has a same-length adjacency list.
|
|
adjacency_graph[char].append(position_table.get(coord, None))
|
|
return adjacency_graph
|
|
|
|
if __name__ == '__main__':
|
|
if len(sys.argv) != 2:
|
|
print usage()
|
|
sys.exit(0)
|
|
with open(sys.argv[1], 'w') as f:
|
|
data = {
|
|
'qwerty': build_graph(qwerty, True),
|
|
'dvorak': build_graph(dvorak, True),
|
|
'keypad': build_graph(keypad, False),
|
|
'mac_keypad': build_graph(mac_keypad, False),
|
|
}
|
|
simplejson.dump(data, f)
|
|
sys.exit(0)
|
|
|