Emacsのキーバインドをhtmlで出力させるツール

Emacsキーバインドは複雑で、テキストで一覧表示されてもなかな覚えられなくて困っていました。
そこでEmacsキーバインドを図示するrubyスクリプトを作ってみました。

使い方

  1. M-x help b でキーバインド一覧を表示させます
  2. そのテキストをコピペして、src.txtみたいな名前で保存します
  3. 続きをよむ、にあるソースコードemacs_keybind.rbみたいな名前で保存します。
  4. 下のようにコマンドを実行します。
# asciiキーボード配列で出力
emacs_keybind.rb -k ascii src.txt > keyboard.html

# 日本語キーボード配列で出力
emacs_keybind.rb -k japanese src.txt > keyboard.html

# はてなダイアリーに乗せられる形式で出力
emacs_keybind.rb -k japanese -f hatena src.txt > keyboard.hatena

人のEmacsキーバインドを一度見てみたいなあ、とずっと思っていました。
はてなダイアリー等にのっけてくれたらとてもうれしいです。

ツールの不満

  1. 生成されるキーバインドのhtmlが横に大きすぎて一覧出来ない。もっと横に圧縮して表示できないものか・・。
  2. 本当はこのコマンドの起動から、browse-urlによるブラウザの立ち上げまで、Emacsから実行できるようにしたいのだけど方法が分からない・・。

ソースコード

#! ruby -Ku

require 'optparse'

Version = "0.0.1"

Header = <<EOF
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title></title>
</head>
<body>
<p>
EOF

Footer = <<EOF
</p>
</body>
</html>
EOF

HeaderHatena = <<EOF
**Emacsキーバインド
EOF

KeyboardAscii =
[
 # ESC 〜 DEL
 [
  ['ESC'],
  ['F1'],
  ['F2'],
  ['F3'],
  ['F4'],
  ['F5'],
  ['F6'],
  ['F7'],
  ['F8'],
  ['F9'],
  ['F10'],
  ['F11'],
  ['F12'],
  ['INS'],
  ['DEL'],
 ],
 # 半角/全角 〜 Backspace
 [
  ['`~', %w(` ~)],
  ['1!', %w(1 !)],
  ['2@', %w(2 @)],
  ['3#', %w(3 #)],
  ['4$', %w(4 \$)],
  ['5%', %w(5 %)],
  ['6^', %w(6 ^)],
  ["7&amp;", %w(7 &)],
  ['8*', %w(8 \*)],
  ['9(', %w(9 \\\()],
  ['0)', %w(0 \\\))],
  ['-_', %w(- _)],
  ['= +', %w(= \+)],
  ['BS'],
 ],
 # TAB 〜 RET
 [
  ['TAB', %w(TAB)],
  ['Q', %w(q Q)],
  ['W', %w(w W)],
  ['E', %w(e E)],
  ['R', %w(r R)],
  ['T', %w(t T)],
  ['Y', %w(y Y)],
  ['U', %w(u U)],
  ['I', %w(i I)],
  ['O', %w(o O)],
  ['P', %w(p P)],
  ['[{', %w(\[ \{)],
  [']}', %w(\] \})],
  ['\|', %w(\\\\ \|)],
 ],
 # Caps 〜 RET
 [
  ['Caps'],
  ['A', %w(a A)],
  ['S', %w(s S)],
  ['D', %w(d D)],
  ['F', %w(f F)],
  ['G', %w(g G)],
  ['H', %w(h H)],
  ['J', %w(j J)],
  ['K', %w(k K)],
  ['L', %w(l L)],
  [';:', %w(; :)],
  ["'\"", %w(' ")],
  [''],
  ['RET', %w(RET)],
 ],
 # Shift 〜 Shift
 [
  ['Shift'],
  ['Z', %w(z Z)],
  ['X', %w(x X)],
  ['C', %w(c C)],
  ['V', %w(v V)],
  ['B', %w(b B)],
  ['N', %w(n N)],
  ['M', %w(m M)],
  [',&lt;', %w(, <)],
  ['.&gt;', %w(\. >)],
  ['/?', %w(\/ \?)],
  [''],
  [''],
  ['Shift'],
 ],
 # Ctrl 〜 [right]
 [
  ['Ctrl'],
  ['Alt'],
  [''],
  [''],
  ['SPC', %w(SPC)],
  [''],
  [''],
  [''],
  ['Alt'],
  ['Ctrl'],
  ['[up]'],
  ['[down]'],
  ['[left]'],
  ['[right]'],
 ],
]

KeyboardJapanese =
[
 # ESC 〜 DEL
 [
  ['ESC'],
  ['F1'],
  ['F2'],
  ['F3'],
  ['F4'],
  ['F5'],
  ['F6'],
  ['F7'],
  ['F8'],
  ['F9'],
  ['F10'],
  ['F11'],
  ['F12'],
  ['INS'],
  ['DEL'],
 ],
 # 半角/全角 〜 Backspace
 [
  ['半/全'],
  ['1!', %w(1 !)],
  ['2"', %w(2 ")],
  ['3#', %w(3 #)],
  ['4$', %w(4 \$)],
  ['5%', %w(5 %)],
  ['6&amp;', %w(6 &)],
  ["7'", %w(7 ')],
  ['8(', %w(8 \\\()],
  ['9)', %w(9 \\\))],
  ['0', %w(0)],
  ['-=', %w(- =)],
  ['^~', %w(^ ~)],
  ['\|', %w(\\\\ \|)],
  ['BS'],
 ],
 # TAB 〜 RET
 [
  ['TAB', %w(TAB)],
  ['Q', %w(q Q)],
  ['W', %w(w W)],
  ['E', %w(e E)],
  ['R', %w(r R)],
  ['T', %w(t T)],
  ['Y', %w(y Y)],
  ['U', %w(u U)],
  ['I', %w(i I)],
  ['O', %w(o O)],
  ['P', %w(p P)],
  ['@`', %w(@ `)],
  ['[{', %w(\[ \{)],
  [''],
  ['RET', %w(RET)],
 ],
 # Caps 〜 RET
 [
  ['Caps'],
  ['A', %w(a A)],
  ['S', %w(s S)],
  ['D', %w(d D)],
  ['F', %w(f F)],
  ['G', %w(g G)],
  ['H', %w(h H)],
  ['J', %w(j J)],
  ['K', %w(k K)],
  ['L', %w(l L)],
  [';+', %w(; \+)],
  [':*', %w(: \*)],
  [']}', %w(\] \})],
  [''],
  [''],                         # RETのみ特殊で二列で表示する
 ],
 # Shift 〜 Shift
 [
  ['Shift'],
  ['Z', %w(z Z)],
  ['X', %w(x X)],
  ['C', %w(c C)],
  ['V', %w(v V)],
  ['B', %w(b B)],
  ['N', %w(n N)],
  ['M', %w(m M)],
  [',&lt;', %w(, <)],
  ['.&gt;', %w(\. >)],
  ['/?', %w(\/ \?)],
  ['\_', %w(\\\\ _)],
  [''],
  [''],
  ['Shift'],
 ],
 # Ctrl 〜 [right]
 [
  ['Ctrl'],
  [''],
  ['Alt'],
  [''],
  ['SPC', %w(SPC)],
  [''],
  ['Alt'],
  ['Ctrl'],
  ['[up]'],
  ['[down]'],
  ['[left]'],
  ['[right]'],
 ],
]

class EmacsKeybind
  def initialize(file_name, keyboard, filter)
    # キーバインドデータ
    src = []

    f = open(ARGV[0])

    f.each {|line|
      src << line.chomp.split(/\t+/)
    }

    @keybind = src

    # キーボードデータ
    @keyboard = keyboard

    # フィルター名
    @filter = filter
  end

  def functions(regexp)
    funcs = []
    @keybind.each do |data|
      funcs << data if data[0] =~ regexp
    end
    funcs
  end

  def print_keybind(title, prefix, suffix)
    puts %Q{<h3>#{title}</h3>} if (@filter == 'html')
    puts %Q{*** #{title}} if (@filter == 'hatena')

    puts %Q{<table cellspacing="2" cellpadding="0" border="1">}
    @keyboard.each do |row|
      puts %Q{<tr>}
      row.each do |key|
        funcs = []

        if (key.size == 2)
          key[1].each do |s|
            f = functions(Regexp.new("#{prefix}#{s}#{suffix}"))
            f.each {|a| a[1] += "(#{s.sub(/\\/, '')})" if (a[1] !~ /\(.\)$/) }
            funcs.concat f
          end
        end

        puts %Q{<td #{td_attr(key, funcs)}><p align="center">#{p_value(key, funcs)}</p></td>}
      end
      puts %Q{</tr>}
    end
    puts %Q{</table>}
  end

  def td_attr(key, funcs)
    attr = []
    attr << 'bgcolor="#80ff00"'if (funcs.size > 0)
    attr.join(' ')
   end

  def p_value(key, funcs)
    str = "#{key[0]}"

    funcs.each do |func|
      str += "<br><b><font size=\"-1\" color=\"#7F00FF\">#{func[1]}</font></b>"
    end

    str
  end

  def print_standard_keybind
    print Header if (@filter == 'html')
    print HeaderHatena if (@filter == 'hatena')
    print_keybind("C-?", '^C-', '$')
    print_keybind("M-?", '^M-', '$')
    print_keybind("C-M-?", '^C-M-', '$')
    print_keybind("C-x C-?", '^C-x C-', '$')
    print_keybind("C-x ?", '^C-x ', '$')
    print_keybind("C-c C-?", '^C-c C-', '$')
    print_keybind("C-c ?", '^C-c ', '$')
    print Footer if (@filter == 'html')
  end
end

#################################################
# main
#################################################

keyboard_kind = nil
filter = 'html'

opt = OptionParser.new('Usage: emacs_keybind [options] input_file')
opt.on('-k', '--keyboard (ascii, japanese)') {|v| keyboard_kind = v }
opt.on('-f', '--filter (html, hatena)') {|v| filter = v }
opt.parse!(ARGV)

case keyboard_kind
when 'ascii'
  keyboard = KeyboardAscii
when 'japanese'
  keyboard = KeyboardJapanese
else
  abort "#{keyboard_kind} is not support. please set keyboard kind(ascci or japanese)"
end

case filter
when 'html'
when 'hatena'
else
  abort "#{keyboard_kind} is not support. please set keyboard kind(ascci or japanese)"
end

keybind = EmacsKeybind.new(ARGV[0], keyboard, filter)
keybind.print_standard_keybind

おんがえしのEmacsキーバインド

C-?





































































































ESC

F1

F2

F3

F4

F5

F6

F7

F8

F9

F10

F11

F12

INS

DEL

半/全

1!

2"

3#

4$

5%

6&

7'

8(

9)

0

-=
negative-argument(-)

^~

\|
toggle-input-method(\)

BS

TAB

Q
query-replace(q)

W
kill-region(w)

E
move-end-of-line(e)

R
isearch-backward(r)

T
call-last-kbd-macro(t)

Y
yank(y)

U
universal-argument(u)

I

O
open-line(o)

P
previous-line(p)

@`
set-mark-command(@)

[{

RET

Caps

A
move-beginning-of-line(a)

S
isearch-forward(s)

D
delete-char(d)

F
forward-char(f)

G
keyboard-quit(g)

H
delete-backward-char(h)

J
eval-print-last-sexp(j)

K
kill-line(k)

L
recenter(l)

;+
other-window(;)

:*

]}
lisp-complete-symbol(])

Shift

Z
context-copy(z)

X
Prefix Command(x)
Control-X-prefix(x)
Prefix Command(x)

C
mode-specific-command-prefix(c)

V
scroll-up(v)

B
backward-char(b)

N
next-line(n)

M

,<
anything-at-point(,)

.>
ff-find-other-file(.)

/?
undo(/)

\_
toggle-input-method(\)
undo(_)

Shift

Ctrl

Alt

SPC
set-mark-command(SPC)

Alt

Ctrl

[up]

[down]

[left]

[right]

M-?




































































































ESC

F1

F2

F3

F4

F5

F6

F7

F8

F9

F10

F11

F12

INS

DEL

半/全

1!
line-to-top(!)

2"

3#

4$
ispell-word($)

5%
query-replace(%)

6&

7'
tags-loop-continue(')

8(
insert-parentheses*1

0

-=
negative-argument(-)
count-lines-region(=)

^~
not-modified(~)

\|
delete-horizontal-space(\)
shell-command-on-region(|)

BS

TAB
lisp-complete-symbol(TAB)

Q
query-replace-regexp(q)

W
kill-ring-save(w)

E
forward-sentence(e)

R
move-to-window-line(r)

T
transpose-words(t)

Y
yank-pop(y)

U
yank-push(u)

I
tab-to-tab-stop(i)

O
next-error(o)

P
scroll-previous-10-line(p)

@`
mark-word(@)
tmm-menubar(`)

[{
backward-paragraph({)

RET

Caps

A
backward-sentence(a)

S
anything-isearch-again(s)

D
kill-word(d)

F
forward-word(f)

G
moccur(g)

H
help-for-help(h)

J
indent-new-comment-line(j)

K
upcase-word(k)

L
downcase-word(l)

;+
comment-dwim(;)

:*
eval-expression(:)
pop-tag-mark(*)

]}
forward-paragraph(})

Shift

Z
context-kill(z)

X
execute-extended-command(x)

C
capitalize-word(c)

V
scroll-down(v)

B
backward-word(b)

N
scroll-next-10-line(n)

M
complete-symbol(m)

,<
tags-loop-continue(,)
beginning-of-buffer(<)

.>
find-tag(.)
end-of-buffer(>)

/?
anything-dabbrev-expand(/)

\_
delete-horizontal-space(\)

Shift

Ctrl

Alt

SPC
just-one-space(SPC)

Alt

Ctrl

[up]

[down]

[left]

[right]

C-M-?





































































































ESC

F1

F2

F3

F4

F5

F6

F7

F8

F9

F10

F11

F12

INS

DEL

半/全

1!

2"

3#

4$

5%
query-replace-regexp(%)

6&

7'

8(

9)

0

-=
negative-argument(-)

^~

\|
indent-region(\)

BS

TAB

Q
indent-pp-sexp(q)

W
append-next-kill(w)

E
end-of-defun(e)

R
isearch-backward-regexp(r)

T
transpose-sexps(t)

Y

U
backward-up-list(u)

I

O
split-line(o)

P
backward-list(p)

@`
mark-sexp(@)

[{

RET

Caps

A
beginning-of-defun(a)

S
isearch-forward-regexp(s)

D
down-list(d)

F
forward-sexp(f)

G

H
mark-defun(h)

J
indent-new-comment-line(j)

K
kill-sexp(k)

L
reposition-window(l)

;+

:*

]}

Shift

Z

X
eval-defun(x)

C
exit-recursive-edit(c)

V
scroll-other-window(v)

B
backward-sexp(b)

N
forward-list(n)

M

,<

.>
find-tag-regexp(.)

/?
dabbrev-completion(/)

\_
indent-region(\)

Shift

Ctrl

Alt

SPC
mark-sexp(SPC)

Alt

Ctrl

[up]

[down]

[left]

[right]

C-x C-?





































































































ESC

F1

F2

F3

F4

F5

F6

F7

F8

F9

F10

F11

F12

INS

DEL

半/全

1!

2"

3#

4$

5%

6&

7'

8(

9)

0

-=

^~

\|

BS

TAB

Q
toggle-read-only(q)

W
write-file(w)

E
eval-last-sexp(e)

R
find-file-read-only(r)

T
transpose-lines(t)

Y

U
upcase-region(u)

I

O
delete-blank-lines(o)

P
mark-page(p)

@`
pop-global-mark(@)

[{

RET

Caps

A

S
save-buffer(s)

D
list-directory(d)

F
find-file(f)

G
moccur-custom-grep-at-point(g)

H

J
dired-jump(j)

K
kmacro-keymap(k)

L
downcase-region(l)

;+

:*

]}

Shift

Z
iconify-or-deiconify-frame(z)

X
exchange-point-and-mark(x)

C
save-buffers-kill-emacs(c)

V
find-alternate-file(v)

B
list-buffers(b)

N
set-goal-column(n)

M

,<

.>

/?

\_

Shift

Ctrl

Alt

SPC
pop-global-mark(SPC)

Alt

Ctrl

[up]

[down]

[left]

[right]

C-x ?




































































































ESC

F1

F2

F3

F4

F5

F6

F7

F8

F9

F10

F11

F12

INS

DEL

半/全

1!
delete-other-windows(1)

2"
split-window-vertically(2)

3#
split-window-horizontally(3)

4$
ctl-x-4-prefix(4)
set-selective-display($)

5%
ctl-x-5-prefix(5)

6&
2C-command(6)

7'
expand-abbrev(')

8(
iso-transl-ctl-x-8-map(8)
kmacro-start-macro*2

0
delete-window(0)

-=
shrink-window-if-larger-than-buffer(-)
what-cursor-position(=)

^~

\|

BS

TAB
indent-rigidly(TAB)

Q
kbd-macro-query(q)

W

E
kmacro-end-and-call-macro(e)

R
Prefix Command(r)

T

Y

U
advertised-undo(u)

I
insert-file(i)

O
other-window(o)

P

@`
Prefix Command(@)
next-error(`)

[{
backward-page([)
shrink-window-horizontally({)

RET
Prefix Command(RET)

Caps

A
Prefix Command(a)

S
save-some-buffers(s)

D
dired(d)

F
set-fill-column(f)

G

H
mark-whole-buffer(h)

J

K
kill-buffer(k)

L
count-lines-page(l)

;+
comment-set-column(;)
balance-windows(+)

:*
calc-dispatch(*)

]}
forward-page(])
enlarge-window-horizontally(})

Shift

Z
repeat(z)

X

C

V
vc-prefix-map(v)

B
iswitchb-buffer(b)

N
Prefix Command(n)

M
compose-mail(m)

,<
scroll-left(<)

.>
set-fill-prefix(.)
scroll-right(>)

/?

\_

Shift

Ctrl

Alt

SPC

Alt

Ctrl

[up]

[down]

[left]

[right]

C-c C-?





































































































ESC

F1

F2

F3

F4

F5

F6

F7

F8

F9

F10

F11

F12

INS

DEL

半/全

1!

2"

3#

4$

5%

6&

7'

8(

9)

0

-=

^~

\|

BS

TAB

Q

W

E

R

T

Y

U

I

O

P

@`

[{

RET

Caps

A

S

D

F

G

H

J

K

L

;+

:*

]}

Shift

Z

X

C

V

B

N

M

,<

.>

/?

\_

Shift

Ctrl

Alt

SPC

Alt

Ctrl

[up]

[down]

[left]

[right]

C-c ?





































































































ESC

F1

F2

F3

F4

F5

F6

F7

F8

F9

F10

F11

F12

INS

DEL

半/全

1!

2"

3#

4$

5%

6&

7'

8(

9)

0

-=

^~

\|

BS

TAB

Q

W
sdic-describe-word(w)
sdic-describe-word-at-point(W)

E

R

T

Y

U

I

O

P

@`

[{

RET

Caps

A

S

D

F

G
goto-line(g)

H

J

K

L

;+

:*

]}

Shift

Z

X

C

V

B

N

M

,<

.>

/?

\_

Shift

Ctrl

Alt

SPC

Alt

Ctrl

[up]

[down]

[left]

[right]

*1:)

9)
move-past-close-and-reindent(

*2:)

9)
kmacro-end-macro(