Skip to content

Instantly share code, notes, and snippets.

@VinceGuidry
Last active April 26, 2025 20:05
Show Gist options
  • Save VinceGuidry/e85fa37e3e60f2dcb63c051cee71060f to your computer and use it in GitHub Desktop.
Save VinceGuidry/e85fa37e3e60f2dcb63c051cee71060f to your computer and use it in GitHub Desktop.
Ruby DSL
require 'tempfile'
# The DSL class. The render method simply takes a block, then executes the block in the scope of the DSL class. You can see
# a use of it in the next file.
module Keyd
class DSL
def self.render(&block)
d = new
d.instance_exec(&block)
d.render
end
def initialize = @output = ''
def render = @output
def build(s) = @build << s << "\n"
def push(string) = @output << string
def build_block(name, &block)
@build = "[#{name}]\n\n"
block.call
push @build << "\n"
end
def block(name, contents) = push("[#{name}]\n\n#{contents}\n\n")
def id_block(ids) = block('ids', ids.join("\n"))
def global = block('global', 'layer_indicator = 1')
def empty_block(name) = push("[#{name}]\n\n")
def bind(key, bindng) = build("#{key} = #{bindng}")
def overload(key, layer, tap) = build("#{key} = overload(#{layer}, #{tap})")
def overload_layer(key) = build("#{key} = overload(#{key}, #{key})")
def clear(key) = build("#{key} = clear()")
def clear_c(key) = build("#{key} = clearm(C-#{key})")
def clear_a(key) = build("#{key} = clearm(A-#{key})")
def layer(key, layer) = build("#{key} = layer(#{layer})")
def lm(k) = "lettermod(#{lm_hash[k]}, #{k}, 150, 200)"
def lettermod(key, layer) = build("#{key} = #{lm(key)}")
def comment_menu_macro(key, b, comment) = build("# #{comment}\n#{key} = macro(cyclewindows+#{b})")
def cm(key) = build("#{key} = C-M-#{key}")
def lettermods = lm_hash.each {|k, l| lettermod k, l }
def lm_hash
{
'f' => 'focusright',
'd' => 'meta',
'a' => 'shift',
's' => 'alt',
'r' => 'workspace',
}
end
def layer_blocks
build_block('workspace') do
comment_menu_macro 'j', 'q', 'swap-etbws'
comment_menu_macro 'k', 'r', 'add-editor-row'
comment_menu_macro 'l', 't', 'restore'
comment_menu_macro 'semicolon', 'u', 'save'
end
build_block('focusright') do
comment_menu_macro 'j', 'w', 'focus-work'
comment_menu_macro 'k', 'b', 'focus-browser'
comment_menu_macro 'l', 'c', 'focus-comm'
comment_menu_macro 'u', 's', 'scratchpad-show'
comment_menu_macro 'i', 'y', 'rotate-editors-backward'
comment_menu_macro 'o', 'e', 'rotate-editors-forward'
end
end
def meta_cm_0_9(name)
build_block(name) do
cm '1'
cm '2'
cm '3'
cm '4'
cm '5'
cm '6'
cm '7'
cm '8'
cm '9'
cm '0'
end
end
def re_letter(key, b: 'noop', s: nil, ec: nil, esc: nil)
bind key, b
bind "edit.#{key}", key
bind "lineedit.#{key}", key
if s
bind "shift.#{key}", s
bind "edit+shift.#{key}", "S-#{key}"
bind "lineedit+shift.#{key}", "S-#{key}"
end
bind "meta.#{key}", "M-#{key}"
bind "meta+shift.#{key}", "S-M-#{key}"
bind "edit+compose.#{key}", ec if ec
bind "workspace+#{key}", key
if esc
bind "compose+edit+shift.#{key}", esc
bind "compose+lineedit+shift.#{key}", esc
end
build ''
end
def e_letter(key, b: 'noop', s: nil, a: nil, ec: nil)
bind key, b
bind "edit.#{key}", key
if s
bind "shift.#{key}", s
bind "edit+shift.#{key}", "S-#{key}"
end
bind "edit+compose.#{key}", ec if ec
yield if block_given?
a.call if a
build ''
end
def e_letters
@sub = ('a'..'z').to_a.zip(Array.new(26, nil)).to_h
lm_hash.each {|k, l| ltr(k, b: lm(k)) }
yield if block_given?
@sub.each do |k, h|
h.nil? ? e_letter(k.to_s) : e_letter(k.to_s, **h)
end
end
def re_letters
@sub = ('a'..'z').to_a.zip(Array.new(26, nil)).to_h
lm_hash.each {|k, l| ltr(k, b: lm(k)) }
yield if block_given?
@sub.each do |k, h|
h.nil? ? re_letter(k.to_s) : re_letter(k, **h)
end
end
def ltr(k, h = nil, &block)
@sub[k] = {} if @sub[k].nil?
@sub[k].merge! h if h
if block
@sub[k][:a] = block
end
end
def bare(k, b)
@sub[k] = {} if @sub[k].nil?
@sub[k][:b] = b
end
def shift(k, b)
@sub[k] = {} if @sub[k].nil?
@sub[k][:s] = b
end
def add(k, &block)
@sub[k] = {} if @sub[k].nil?
@sub[k][:a] = block
end
end
end
# I think the only reason why the output of the render call was saved to a variable is so I can troubleshoot it just with a
# REPL call at the bottom of the file. Leaving this as Ruby and not, say, `IO.read`ing in a file allows full access to ruby.
ids = ["3297:c6cf"]
t = DSL.render do
id_block ids
global
build_block('main') do
clear 'z+x'
overload 'capslock', 'capslock', 'esc'
clear_c 'scrolllock'
overload 'compose', 'compose', 'C-compose'
overload 'tab', 'shift', 'tab'
overload 'grave', 'tilde', 'grave'
lettermods
end
layer_blocks
build_block('compose') do
bind 'j', 'left'
bind 'k', 'down'
bind 'l', 'up'
bind 'semicolon', 'right'
end
meta_cm_0_9 'meta:M'
empty_block 'capslock:C'
build_block('nav') do
clear 'z'
end
empty_block 'nav+control'
build_block 'nav+shift' do
bind 'm', 'M-left'
bind 'comma', 'M-down'
bind 'dot', 'M-up'
bind 'slash', 'M-right'
end
build_block 'edit' do
clear 'z+x'
end
empty_block 'edit+shift'
build_block 'lineedit' do
clear 'z+x'
end
empty_block 'lineedit+shift'
end
# The output. If you're wondering why this seems like a lot of work just to manage this, it's because it's the simplest one.
# The one generating the main keyd config clocks in at 369 LOC, and manually editing it was causing intense psychic pain.
[ids]
3297:c6cf
[global]
layer_indicator = 1
[main]
z+x = clear()
capslock = overload(capslock, esc)
scrolllock = clearm(C-scrolllock)
compose = overload(compose, C-compose)
tab = overload(shift, tab)
grave = overload(tilde, grave)
f = lettermod(focusright, f, 150, 200)
d = lettermod(meta, d, 150, 200)
a = lettermod(shift, a, 150, 200)
s = lettermod(alt, s, 150, 200)
r = lettermod(workspace, r, 150, 200)
# What it spits out.
[workspace]
# swap-etbws
j = macro(cyclewindows+q)
# add-editor-row
k = macro(cyclewindows+r)
# restore
l = macro(cyclewindows+t)
# save
semicolon = macro(cyclewindows+u)
[focusright]
# focus-work
j = macro(cyclewindows+w)
# focus-browser
k = macro(cyclewindows+b)
# focus-comm
l = macro(cyclewindows+c)
# scratchpad-show
u = macro(cyclewindows+s)
# rotate-editors-backward
i = macro(cyclewindows+y)
# rotate-editors-forward
o = macro(cyclewindows+e)
[compose]
j = left
k = down
l = up
semicolon = right
[meta:M]
1 = C-M-1
2 = C-M-2
3 = C-M-3
4 = C-M-4
5 = C-M-5
6 = C-M-6
7 = C-M-7
8 = C-M-8
9 = C-M-9
0 = C-M-0
[capslock:C]
[nav]
z = clear()
[nav+control]
[nav+shift]
m = M-left
comma = M-down
dot = M-up
slash = M-right
[edit]
z+x = clear()
[edit+shift]
[lineedit]
z+x = clear()
[lineedit+shift]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment