Skip to content

Instantly share code, notes, and snippets.

@elebow
Created October 22, 2018 03:27
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save elebow/4e0d2bad14f6c21d2c4698e38c543fdc to your computer and use it in GitHub Desktop.
Save elebow/4e0d2bad14f6c21d2c4698e38c543fdc to your computer and use it in GitHub Desktop.
xcompose-to-osx-defaultkeybindingdict.rb
# Please don't even look at this horrible, write-only code
require 'parslet'
require 'pry'
class XComposeParser < Parslet::Parser
rule(:line) do
input.as(:input) >> spaces >>
match(':') >> spaces >>
output.as(:output) >> match['\n'].maybe
end
rule(:input) { str('<Multi_key>') >> spaces >> key.repeat.as(:keys) }
rule(:key) { str('<') >> key_char.as(:key_char) >> str('>') >> spaces }
rule(:key_char) { match['\w'].repeat }
rule(:output) { output_char >> match['^\n'].repeat.as(:char_desc) }
rule(:output_char) { str('"') >> any.as(:output_char) >> str('"') }
rule(:spaces) { match['\s'].repeat }
root :line
end
class SequenceTransform < Parslet::Transform
rule(key_char: simple(:x)) { x.to_s }
rule(output_char: simple(:x)) { x.to_s }
rule(char_desc: simple(:x)) { x.to_s }
end
class Converter
def initialize(filename)
@infile_name = filename
end
def convert
infile = File.new(@infile_name)
sequences = infile.readlines.map do |line|
next if line == "\n" || line.start_with?('#')
tree = parse(line)
SequenceTransform.new.apply(tree)
end.compact
@osx_sequences = {}
sequences.each do |sequence|
h = build_nested_hash(sequence[:input][:keys], sequence[:output][:output_char])
begin
recursive_merge!(@osx_sequences, h)
rescue TypeError
STDERR.puts "string/hash collision with #{h}"
end
end
@osx_sequences = { '§' => @osx_sequences }
print_output
end
def parse(str)
parser = XComposeParser.new
parser.parse(str)
rescue Parslet::ParseFailed => failure
STDERR.puts str
STDERR.puts failure.parse_failure_cause.ascii_tree
end
def print_output
puts '{'
print_hashes_with_indentation(@osx_sequences, 1)
puts '}'
end
def recursive_merge!(h1, h2)
# https://rexchung.com/2007/02/01/hash-recursive-merge-in-ruby/
h1.merge!(h2) do |_key, old, new|
if old.class == Hash
recursive_merge!(old, new)
else
new
end
end
end
def build_nested_hash(keys, output_char)
keys.push(insert_command_for_char(output_char))
.reverse
.reduce { |tail, key| { key => tail } }
end
def insert_command_for_char(char)
"(\"insertText:\", \"#{char}\");"
end
def tr(ch)
{
'ampersand' => '&',
'apostrophe' => '\'',
'asciicircum' => '\136',
'asciitilde' => '~',
'asterisk' => '*',
'at' => '@',
'backslash' => '\\\\',
'bar' => '|',
'braceleft' => '{',
'braceright' => '}',
'bracketleft' => '[',
'bracketright' => ']',
'colon' => ':',
'comma' => ',',
'equal' => '=',
'exclam' => '!',
'grave' => '`',
'greater' => '>',
'less' => '<',
'minus' => '-',
'numbersign' => '#',
'parenleft' => '(',
'parenright' => ')',
'period' => '.',
'plus' => '+',
'question' => '?',
'quotedbl' => '\"',
'slash' => '/',
'space' => ' ',
'underscore' => '_'
}.fetch(ch, ch)
end
def print_hashes_with_indentation(h, level)
h.each_pair do |key, value|
k = tr(key)
if value.instance_of?(Hash)
puts_level(level, "\"#{k}\" = {")
print_hashes_with_indentation(value, level + 1)
puts_level(level, '};')
else
puts_level(level, "\"#{k}\" = #{value}")
end
end
end
def puts_level(level, str)
puts "\t" * level + str
end
end
Converter.new('.XCompose').convert
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment