Emacsからperltidyでバッファを整形するとoverlayやrepositoryが消える、ずれる問題

elispのshell-command-on-regionでREPLACEパラメータを設定してperltidyを呼び出してソースの整形を行った場合、バッファの内容をdelete-regionで削除しているので、設定してあったoverlayが消えてしまう。個人的に利用しているbm.elがoverlayを利用してブックマークを実現しているので、上書き保存のたびにブックマークが消えてしまう(write-file-hookでperltidyを実行しているため)。その他設定してあったrepositoryなどもずれてしまう。

実行前の位置を覚えておいて、整形後に復元するようにすると、多少のずれは出てくるものの、とりあえず両者を併用できる状態になる。

bmの復元は、perltidyの呼び出し前に、ブックマークをpersistentにtoggleし、ファイルに書き出し、呼び出し後に復元し、persistent状態を元に戻す。イメージは下記。

    (bm-toggle-buffer-persistence)
    (bm-buffer-save)
    (shell-command-on-region (point) (mark) "perltidy -i=2  -csc -ce" nil t)
    (setq bm-restore-on-mismatch t)
    (bm-buffer-restore)
    (bm-toggle-buffer-persistence)

bm-restore-on-mismatchはブックマークを復元する場合に元ファイルのサイズが変わっているかチェックするかどうかを判定する変数。サイズが異なっているとずれが発生する可能性が高いのでデフォルトは復元しないようnilになっているが、perltidy実施でサイズは変わっているはずなので、無視してrestoreするようにtに設定している。

registoryの場合は個人的にはpositionしか保存していないので、カレントバッファのmarkerだけ退避しておき、整形後に位置を復元するようにしている。下記をそれぞれ整形の前後に呼び出す。

(setq bkup-marker-register-position '())
(defun bkup-marker-registers ()
  "Backup the marker in the registory which marks the position of the current buffer."
  (interactive)
  (let ((list (copy-sequence register-alist))
        (bname (buffer-name)))
    (setq list (sort list (lambda (a b) (< (car a) (car b)))))
    (dolist (elt list)
      (when (get-register (car elt))
        (let ((val (get-register (car elt)))
              (key-desc (single-key-description (car elt))))
          (cond
           ((markerp val)
            (let ((buf (marker-buffer val)))
              (if (and buf (string= (buffer-name buf) bname))
                  (setq bkup-marker-register-position (append bkup-marker-register-position (list (list key-desc (marker-position val)))))
                )))))))))

(defun restore-marker-registers ()
  (dolist (elt bkup-marker-register-position)
    (goto-char (second elt))
    (point-to-register (string-to-char (car elt))))
  (setq bkup-marker-register-position '()))

BuilderSupportでDSL

GroovyでDSLを作るときに便利なBuilderSupportを使う簡単な例です。

public class MyBuilder extends BuilderSupport{
    protected Object createNode(Object name) {
        def obj = new Expando(name:name,children:,attributes:[:])
        return obj
    }

    protected Object createNode(Object name, Map attributes) {
        def obj = new Expando(name:name,attributes:attributes,children:)
        return obj
    }
    
    protected void setParent(Object parent, Object child) {
        parent.children << child
    }

    protected Object createNode(Object name, Object value) {
        return null
    }
    protected Object createNode(Object name, Map attributes, Object value) {
        return null
    }
   
}

def builder = new MyBuilder()

def ret = builder.aaa{
  target01(prop1:"aaa"){
    child01(name:"name01",class:"child.Child01")
    child02(name:"name02",class:"child.Child02")
  }
  target02(prop1:"bbb"){
    child03(name:"name03",class:"child.Child03")
    child04(name:"name04",class:"child.Child04")
  }

}

println "$ret"

結果

{name=aaa, children=[{name=target01, attributes={prop1=aaa}, children=[{name=child01, attributes={name=name01, class=child.Child01}, children=}, {name=child02, attributes={name=name02, class=child.Child02}, children=}]}, {name=target02, attributes={prop1=bbb}, children=[{name=child03, attributes={name=name03, class=child.Child03}, children=}, {name=child04, attributes={name=name04, class=child.Child04}, children=}]}], attributes={}}

参考URL:
http://groovy.codehaus.org/Make+a+builder

BuilderSupportを継承し、abstructメソッドをオーバーライドします(サンプルに不要なメソッドは空実装にしてnullを返しています)
また、上記参考URLでは、ノードを作成するのにNodeクラスを使ってる例がありますが、値を取り出すのが面倒だったりするので、
Expandoでname,attribute,childrenだけ持ったインスタンスを作って返しています。
ノードの親子関係はsetParent()で親への追加の仕方を記述しておくとBuilderSupportが親子関係を設定してくれています。

Groovyで文字列をeval

その1

import groovy.lang.GroovyShell

GroovyShell shell = new GroovyShell()
def ret = shell.evaluate("1+1")

その2
Evalのme()メソッドを使う。

int i = Eval.me("1+1")

同Evalクラスのx()メソッド。
Eval.x()メソッドの第一パラメータは、評価したい第二パラメータの表現中に変数xで参照できる値を渡す。同じ感じでパラメータを増やしたxy(),xyz()がある模様。

def str = "aaa"
def ret = Eval.x(str,"println x")

参考:
http://groovy.codehaus.org/api/groovy/util/Eval.html

GroovyでArrayListを利用する

def arr = []

Groovyでは上記で、配列でなくArrayListインスタンスになる。

先頭から順に要素を減らしていくような場合tail()が使える。

arr.tail()

空になったかどうかの判定はsize()でなく、インスタンスそのままをifに渡すことができる。

if(arr){
//
}else{
// サイズ0
}

配列からArrayListへの変換はJavaではArrays.asList()などを使うが、Groovyではasを使うことができる。

("aa.bb.cc".split("\\.") as ArrayList) instanceof ArrayList
  => true


逆にArrayListから配列への変換も可能

(["aa","bb","cc"] as String[]) instanceof String[]
  => true

String.split()は配列を返すため、戻り値に対してsize()メソッドを呼び出すとエラーになる。また["aa","bb"]のような値を配列と勘違いしてlengthフィールドを参照するとエラーになってしまう。

Groovyで動的にクラスをロード、再読み込み

基本な仕組みはJavaのクラスローダの場合と同じ(はず)。

基本クラスローダ(ブートストラップ、エクステンション、システム)
システムクラスローダは、クラスパス上のクラスを読むローダ。

クラスパス上に存在するクラスはシステムクラスローダで読まれてしまうので、動的にロード、リロードする場合は、該当クラスをクラスパスに含めないようにし、自前のクラスローダでロードするようにする。

Groovyで用意されているクラスローダは、java.net.URLClassLoaderを継承したGroovyClassLoaderというクラス。

 def loader = new GroovyClassLoader()

動的にロードしたいクラスは後からロードするが、そのクラスが参照するクラスがCLASSPATHに含まれていない場合、Loaderにクラスパスを追加する。

 loader.addClasspath(xxxx)

クラスの読み込んでインスタンスの取得。

  def obj = loader.parseClass(new File("/path/to/Foo.groovy")).newInstance()

parseClassで読まれたクラスはキャッシュされる。
groovyスクリプトを書き換えを行い、GroovyのVMを再起動なしで変更を反映したい場合に再度parseClassを呼んでも、このキャッシュのため、変更が反映されない。

 loader.clearCache()

を呼ぶとキャッシュがクリアされ、その後のparseClass呼び出しで実際に読まれるようになった。

#parseClassの第二パラメータのbool値でキャッシュするかの指定をできるはずだが、手元ではうまく反映されなかたので、clearCache()を使った。

上記は同一のローダで再読み込みする方法で、Javaのようにローダのインスタンスごと使い捨てて新しいローダのインスタンスで読みむ方法でも実現は可能。