Code Metaphor

Programming, Writing, Reading, Thoughts…

이 블로그는 더이상 운영되고 있지 않습니다. 단지 예전 URL을 유지하는 용도로만 남아 있습니다. 새 블로그의 주소는 blog.dahlia.kr입니다.

Archive for June, 2008

Mascara: ECMAScript 4 미리 써보기

Sunday, June 22nd, 2008

1주일 전쯤에 Ajaxian에 흥미로운 포스팅이 올라왔다. Mascara라는 프로젝트 소개였는데, ECMAScript 4 코드를 클래식 JavaScript 코드로 변환해주는 프로그램이었다. Python으로 구현되어 있었고,

You can already download an early release. It is however of alpha-qualiy, and only suited for the adventurous.

라고 되어 있길래 급히 흥미가 생겨 뒷문장은 한 귀로 흘려버리고; 프로그램을 받아보았다. 몇달 전에 스펙 조금만 읽어서 기억이 가물가물한 ECMAScript 문법을 겨우겨우 떠올리며 몇가지 코드들을 작성하고 변환하며 재밌게 놀았다. 그러다가 이걸 써먹을만한 곳이 없을까 하고 생각해보니, 혼자서 Google App Engine으로 끄적거리고 있는 이르니아 연대기 홈페이지 리뉴얼 작업에 적용해보기로 했다. (이르니아 연대기 홈페이지 리뉴얼은 거의 4년 넘게 말만 하고 있는 만년 프로젝트;)

Mascara 사이트에서 파일을 받아서 풀어보면 굉장히 너저분하게 구성되어 있다. 나는 일단 이것을 GAE 프로젝트 폴더 안쪽에 mascara라는 이름의 폴더를 생성하여 집어넣었다.

dahlia$ touch ecmascript4.py
dahlia$ tree
.
|-- app.yaml
|-- ecmascript4.py
|-- mascara
|   |-- cgitranslate.py
|   |-- cgitranslate.pyc
|   |-- config.py
|   |-- config.pyc
|   |-- django.py
|   |-- es4translator
|   |   |-- __init__.pyc
|   |   |-- ast.pyc
|   (...omitted)
`-- static
    `-- Person.es4

2 directories, 89 files

그리고 위처럼 ecmascript4.py라는 이름으로 파일을 만들었다. 이 파일은 static 폴더 안의 *.es4 파일을 요청할 경우 그것을 JavaScript로 번역해줄 것이다. 하지만 일단은 아무 것도 하지 않고 냅두자. 우선 그렇게 하려면 app.yaml을 수정해야 한다.

아래처럼 핸들러 하나를 더 추가한다.

application: teamernia
version: 1
runtime: python
api_version: 1

handlers:
- url: /static/.*\.es4
  script: ecmascript4.py
- url: /static
  static_dir: static
  expiration: 1d

app.yaml 파일에 대한 자세한 설명은 GAE 사이트의 해당 문서에서 볼 수 있으니 참고하시라. 이렇게 해두면 원한대로 /static/*.es4 패턴의 요청에 대해 ecmascript4.py 파일을 라우팅하게 된다. 그러면 이제 ecmascript4.py 파일을 작성해보자. GAE에서는 Django를 사용할 수도 있고, 또 대부분 그렇게들 하는 것 같지만, 나는 기본적으로 제공되는 최소주의적인 webapp 프레임워크에 더 호감이 가서 그것을 사용했다.

일단 Mascara를 사용해야 하는데, 사용가능한 Python 모듈 형태는 아니다. 그래서 import mascara라고 써봤자 없는 모듈이라면서 ImportError 예외가 난다. 사용하려는 모듈은 Mascara 폴더 안에 있는 cgitranslate.py다. 그래서 그냥 sys.pathmascara 폴더를 추가해서 cgitranslate 모듈을 임포트 하기로 했다.

from os.path import dirname
import sys, re
sys.path.append(dirname(__file__) + '/mascara')

그 다음 webapp.RequestHandler를 서브클래싱했다.

import re
from google.appengine.ext import webapp

class ECMAScript4(webapp.RequestHandler):
    def get(self):
        import cgitranslate
        self.response.headers['Content-Type'] = 'text/javascript'
        cgitranslate.httpTranslate(
            re.sub('^/', '../../', self.request.path, 1),
            self.response.out
        )

별 내용은 없고, Mascara에서 제공된 기능을 가져다 쓰기만 했다. Content-Type은 당연히 text/javascript로 지정했다. JavaScript로 변환되어 출력될 것이기 때문이다.

cgitranslate.HttpTranslate 함수는 변환할 ECMAScript 4 파일 경로와 그것을 출력할 유사 파일 객체(file-like object)를 인자로 받는다.

유사 파일 객체란 C++의 std::ostream과 비슷한 것으로 보면 된다. 다만 Python에서는 따로 무엇을 서브클래싱할 필요 없이 write 메서드를 구현하면 된다. (물론 입력 받기 위해서는 read 메서드 등도 구현해야 하지만, 여기서는 출력만 필요하므로…) 덕 타이핑(duck typing)에 기반한 Python다운 인터페이스라고 할 수 있다. 참고로 이런 유사 파일 객체는 굉장히 다양한 곳에서 쓰일 수 있다. 이를테면, print>>f, of.write(str(o))와 동일한 작동을 한다.

마침 webapp 프레임워크의 Responseout이라는 속성으로 유사 파일 객체를 가지고 있다. 따로 StringIO.StringIO를 쓴다거나 직접 유사 파일 객체를 만들 필요 없이, self.response.out을 전달하기만 하면 된다.

경로 문자열을 전달하기 전에 self.request.path를 약간 가공하고 있는데, cgitranslate.HttpTranslate 함수에 절대 경로를 전달할 경우 보안상 허용되지 않는다고 오류를 내기 때문이다. 그래서 맨 앞 슬래시를 ../../로 치환해준다. 왜 ../../냐면… 중요도에 비해 설명하기 귀찮으므로 생략.;;

아무튼 저렇게만 해줘도 잘 작동한다. static 폴더에 Person.es4라는 이름으로 아래 예제 코드를 저장해봤다.

class Person {
    var name: !String, birthday: !Date;

    function Person(n: !String, dob: !Date): name = n, birthday = dob {}

    function get age(): int {
        var now: Date = new Date;

        var year: int = now.getFullYear() - this.birthday.getFullYear(),
            nmon: int = now.getMonth(), bmon: int = this.birthday.getMonth(),
            ndate: int = now.getDate(), bdate: int = this.birthday.getDate();

        if(nmon < bmon || ndate < bdate)
            --year;

        return year;
    }
}

var dahlia: Person = new Person('Hong, MinHee', new Date(1988, 8, 4));

보다시피 getter도 한번 써보고 타입 명시도 해보고 언어에 포함된 클래스도 써봤다. 브라우저에서 http://localhost:8080/static/Person.es4로 접근해보니 아래와 같이 다소 흉칙하게 변환된 JavaScript 코드가 출력된다.

function $class($SuperInstanceCtor, $ClassCtor) {
var $InstanceCtor = function() { if ($init_instance) this.$init.apply(this, arguments); };
if ($SuperInstanceCtor) { $SuperInstanceCtor = $SuperInstanceCtor.$prototype;
$ClassCtor.prototype = $SuperInstanceCtor; }
var $tmpl = new $ClassCtor($SuperInstanceCtor, $InstanceCtor);
$InstanceCtor.prototype = $tmpl; $init_instance = false;
$instancePrototype = new $InstanceCtor(); $InstanceCtor.$prototype = $instancePrototype;
$init_instance = true; return $InstanceCtor; }
function $coerceTo(x, type) { if (x instanceof type) return x;
throw new Error("coercion error"); }

var Person=$class(null, function($super, $static) {
    this.name;
    this.birthday;
    this.Person=function(n,dob){
        this.name=n;
        this.birthday=dob;
    };
    this.get_age=function(){
        var now=new Date;
        var year=now.getFullYear()-this.birthday.getFullYear();
        var nmon=now.getMonth();
        var bmon=this.birthday.getMonth();
        var ndate=now.getDate();
        var bdate=this.birthday.getDate();
        if(nmon<bmon||ndate<bdate)--year;
        return year;
    };
    this.$init = this.Person;
});this.Person=Person;
var dahlia=new Person('Hong, MinHee',new Date(1988,8,4));
/* TRANSLATION DONE: ../../static/Person.es4 */

이제 이걸 HTML에서 <script> 태그로 불러와 사용하면 된다.

<script type="text/javascript" src="/static/Person.es4"></script>

type 속성을 application/ecmascript; version=4가 아닌 text/javascript로 지정한 것에 주의하자. 뭐, 당연한 소리지만…….

이렇게 하고 나서 혼자 짝짝짝 박수치며 좋아하다가 변환이 꽤 비용도 크고 속도도 느릴 것이라는 생각이 들었다. GAE에서 지원하는 Memcache로 캐시도 되게끔 코드를 수정했다.

from google.appengine.api import memcache

class ECMAScript4(webapp.RequestHandler):
    half_day = 3600 * 12

    def get(self):
        key = 'ecmascript4\t' + self.request.path
        js = memcache.get(key)
        if not js:
            from cgitranslate import httpTranslate
            from StringIO import StringIO
            buffer = StringIO()
            httpTranslate(re.sub('^/', '../../', self.request.path, 1), buffer)
            js = buffer.getvalue()
            memcache.set(key, js, self.half_day)
        self.response.headers['Content-Type'] = 'text/javascript'
        self.response.out.write(js)

한번 생성한 캐시는 한나절 동안 가지고 있는다. 사실 해당 소스 파일의 저장 시각을 기준으로 비교하면 더 좋겠지만, GAE에서는 파일 접근을 허용하지 않아서 어쩔 수 없었다. 아무튼 이렇게 해놓고 브라우저로 접근하니, 첫번째 접속할 때는 여전히 느렸다. 하지만 그 이후로 새로고침을 하면 JavaScript 코드가 매우 빨리 보이는 것을 확인할 수 있었다. 대신 Person.es4 파일을 아무리 수정해도 브라우저에서는 반영되지 않았다. 흑흑.

Mascara를 가지고 놀다가 삼항연산자를 타입 명시로 해석해버리는 버그도 하나 발견했다. 아까 한귀로 흘렸던 alpha-qualiy라던가 only suited for the adventurous라는 말들이 생각나는 순간이었다. (;;)


2008년 6월 27일 추가: 위와 같이 할 경우 스크립트의 컴파일 에러가 날 경우, 에러 메세지 자체를 캐시하게 된다. 에러 메세지에 대해서는 캐시하지 않고, 성공했을 때만 캐시하도록 하려면 아래처럼 작성해야 한다.

class ECMAScript4(webapp.RequestHandler):
    half_day = 3600 * 12

    def get(self):
        key = 'ecmascript4\t' + self.request.path
        js = memcache.get(key)
        if not js:
            from cgitranslate import *
            try:
                path = re.sub('^/', '../../', self.request.path, 1)
                output = translateInCgi(path)
                if output.succeeded():
                    js = output.resultcode
                    memcache.set(key, js, self.half_day)
                else:
                    js = errorResponse(
                        "Compilation failed",
                        "".join(output.getMessages())
                    )
            except:
                js = errorResponse("Translation failed", formatException())
        self.response.headers['Content-Type'] = 'text/javascript'
        self.response.out.write(js)

최근 사태들에 대한 내 생각

Sunday, June 1st, 2008

웹에는 하이퍼링크라는 훌륭한 인용 시스템이 있다. 그걸 잘 활용해보려 한다.

요약하자면 이렇다. 나는 이명박 정권에 큰 반발감을 가지고 있지만, 촛불문화제에는 반대한다. 탄핵이 되었으면 좋겠지만, 법적 구실이 없는 상태에서의 탄핵에는 반대한다. (별 생각이 없었을 때 이명박 탄핵 서명에도 동참했었는데, 지금은 취소하고 싶다. 취소하는 게 있다고 하던데, 찾지를 못하겠다.) 미국산 쇠고기 수입에 반대한다. 몇년 전부터 계속 한미FTA 자체를 반대했었다. 그렇지만 미국산 쇠고기의 광우병 괴담에는 전혀 동의하지 않으며, 그것이 수입의 반대 이유가 될 수는 없다고 생각한다. 마지막으로, 조금만 자신들 생각에 빗나가는 글을 써도 떼로 몰려들어 물어뜯는—어찌보면 그네들이 증오하는 사람의 자세와 흡사하기도 한—행동들에도 반대한다. 주장에 대한 이유, 근거는 링크한 글들에 모두 들어있다.

댓글은 닫는다. 이견이 있는 분들은 여기 말고 링크한 글들에 해주시라.

Powered by WordPress. Styled by Hong, MinHee. XML Feed, Comments XML Feed.