• 2011-06-18

    clienpad.appspot.com

    http://boxcatstudio.files.wordpress.com/2011/06/clienpad.png

    와이프가 아이패드에서 클리앙을 보시는게 너무 불편해 보여서, jQuery Mobile 공부 삼아 간단히 웹앱을 만들어봤습니다. 소스 코드는 GitHub 에 공개해두었습니다.

    Google App Engine 으로 프록시 서버를 만들었고, jQuery Mobile을 이용해서 모바일 브라우저에서 JSON 으로 받아서 보여주는 방식입니다. 가능하면 프록시 서버 없이 하고 싶었는데 iOS 에서 돌아가도록 할려다 보니 방법이 없더군요.

    구현에 관련해서 간단히 정리해보자면,

    • template 의 최신 기능 때문에 django 1.2 를 이용했는데, 코드 레벨에서는 거의 webapp 만 사용했습니다;;;
    • memcache 에 각 URL 을 파싱한 dict를 저장하는데, 60초 정도만 살아남도록 했습니다. 그래서 그런지 처음 접속하는 사람은 제법 느린 편입니다. 반응 속도가 4-5초 정도가 나오는 바람에 와이프가 많이 실망하네요. ㅠㅠ 다시 생각해보니 그냥 JSON 이나 렌더링된 문자열 자체를 저장하는게 나을 수 있겠네요.
    • Beautiful Soup 으로 HTML DOM 파싱을 했습니다. 클리앙의 HTML 구조가 old-school 같아서 (ㅎㅎ) 시간이 좀 걸렸습니다;;;

    조만간 jQuery Mobile 팁에 관한 글을 좀 써보겠습니다.

  • 2011-04-14

    Bulkloader GData Connector

    구글 앱엔진은 데이터스토어의 내용을 로컬 파일로 내려받거나 올리는 벌크로딩을 지원한다. 그런데 막상 기획 데이터와 실시간 연동하려면, 스프레드시트를 편집한 후 CSV로 '하나씩' 다운받아서 다시 콘솔창에서 appcfg.py 로 '한 종류씩' 올려야 하는, 상당히 피곤한 과정을 거쳐야 한다.

    만약 구글 스프레드시트의 데이터를 바로 데이터스토어로 올리고 내릴 수 있다면 얼마나 편할까? 해서 스프레드시트 API 를 써서 직접 만들어봤는데, Model 당 import/export/create 코드를 각각 작성해야 하는 단점이 있었다. 그런데, 이미 bulkloader.yaml 를 만들어서 관리중이라면, 아래 커넥터를 써서 간단하게 구현할 수 있다.

    http://code.google.com/p/bulkloader-gdata-connector/

    기본적인 사항들은 가이드 문서를 읽어보면 되고, 아래 다음 사항들을 참고하면 된다.

    • django 1.2 를 사용할 경우, 코드 맨 위에 use_library('django', '1.2')를 추가할 것. 그렇지 않으면 무서운 0.96.4 is already in use 에러를 맛보리라.
    • 구글앱스 도메인을 사용할 경우 GenerateAuthSubURL(..., domain='yourapps.com') 을 추가할 것.
    • 컬럼 이름에는 공백, underscore(_), 대문자를 사용하면 안된다.
    • 내보내기는 제법 오랜 시간이 걸리지만, 불러오는 건 꽤 빠르다. 즉 최초에 한번 내보내고 그 다음부터는 금방 금방 불러올 수 있다는 뜻이다. :)
    • 단점이라면, 데이터를 불러오려면 콘솔 즉 프로그래머의 개입(==패스워드 입력)이 필요하다는 점이다. 웹 기반 UI 에서 Connector를 어찌어찌 잘 부를 수 있으면 좋겠는데, 워낙 코드가 복잡해서;;
  • 2011-04-14

    Ant Cookbook

    Property vs. Var

    상수인 Property은 하위 Target 에게 보이지 않지만, 변수인 Var 는 보인다.

    특정 확장자를 가진 파일만 삭제하기(recursive)

    <delete>
        <fileset dir="${dir}/.." includes="**/*.js"/>
    </delete>
    

    특정 폴더 이하 모든 하위 폴더를 삭제하기(빈 폴더 포함)

    <delete includeemptydirs="yes">
        <fileset dir="${dir}" includes="**/*"/>
    </delete>
    

    정규식을 이용해서 파일 이름 바꾸기

    apply task 보다는 move task + regexpmapper 를 이용하면 간단하다. move 는 대상 폴더가 없으면 무조건 만들기 때문에 편하다!

    <!--
    <apply executable="mv">
        <fileset dir="${dir}" includes="*.png"/>
        -v"/>
        <srcfile/>
        <targetfile/>
        <regexpmapper from="^_\d\d\d\d_(.*)\.png$" to="${dir}/\1.png"/>
    </apply>
    -->
    
    <move todir="${dir}">
        <fileset dir="${dir}" includes="*.png"/>
        <regexpmapper from="_\d\d\d\d_(.*)\.png" to="\1.png"/>
    </move>
    

    경로에서 부모 폴더(dirname)와 파일명(basename)으로 분리하기

    basename 의 suffix 를 지정하면 확장자 없는 순수 파일명만 저장할 수 있다.

    <dirname property="parentdir" file="${file}"/>
    <basename property="basename" file="${file}" suffix=".png"/>
    

    파일명에 정규식을 적용해서 속성에 저장하기

    propertyregex 를 이용하면 된다. 이때 디폴트값을 정해줄 수도 있다.

    <propertyregex property="output"
        input="${file}"
        regexp="(.+?)_(.+)"
        select="\2"
        defaultValue="${default}"/>
    

    하위 폴더 각각에 대해서 특정 타겟을 실행하기

    foreach task 를 이용하면 된다. 이때 dirset 은 항상 부모 폴더 자신(./)을 포함하는데, excludes=./ 등으로 자신을 제거할 수 없으므로, 아래처럼 includes=/* 으로 제외할 수 있다.

    <foreach param="dir" target="xxx">
        <path>
            <dirset dir="${parentdir}" includes="*/**"/>
        </path>
    </foreach>
    

    Exec vs. Apply

    exec 는 커맨드라인 명령을 1회 실행하지만, apply 는 특정 집합에 대해서 for 루프처럼 실행이 가능하다.
    이때 arg value 는 -v 나 -f 같은 단일 파라미터이고, arg line 은 --output xxx 처럼 공백이 들어가는 긴 파라미터에 사용한다.
    또한 regexpmapper 를 이용해서 targetfile 에 대해 정규식을 적용할 수도 있다.

    <exec executable="${file.python}">
        <arg value="trim.py"/>
        <arg value="--verbose"/>
        json ${trim.dir}/anchor.json"/>
        <arg value="${trim.dir}"/>
    </exec>
    <apply executable="${file.python}">
        <fileset dir="${flip.dir}" includes="*.png" excludes="*.r.png"/>
        <arg value="flip.py"/>
        <arg value="--verbose"/>
        <arg value="-o"/>
        <targetfile/>
        <srcfile/>
        <regexpmapper from="(.*)\.png$" to="${flip.dir}/${flip.default}.png"/>
    </apply>
    

    파일 출력

    echo task를 이용하면 화면 출력 뿐만 아니라, 파일에 임의의 문자열을 append를 할 수 있다. 이걸 이용하면 concat task 를 쓰지 않고도 header 나 footer 를 간단하게 넣을 수 있다.

    <echo file="${script}" append="yes">
        sprites = sprites || {};
        sprites.data = sprites.data || {};
    </echo>
    

    파일 이어붙일 때 필터링하기

    filterchain 을 이용해서 특정 문자열을 포함한 라인을 제거하거나, tokenfilter + replaceregex 를 이용해서 문자열을 바꿀 수도 있다.

    <concat destfile="${script}" append="yes">
        <path>
            <filelist dir="${dir}" files="${json}" />
        </path>
        <filterchain>
            <linecontains negate="true">
                <contains value="trimmed"/>
            </linecontains>
            <tokenfilter>
                <!--
                <replaceregex pattern="\{"frames"\:" replace='sprites["${spritename}"] =' flags="g"/>
                -->
                <!-- remove .<span class="hiddenSpellError" pre="remove ">png</span> -->
                <replaceregex pattern="\.png" replace='' flags="g"/>
            </tokenfilter>
        </filterchain>
    </concat>
    

    파일을 복사하면서 이름 바꾸기

    copy task 와 globmapper 를 쓰면 파일을 복사하면서 단순하게 이름을 바꿀 수 있다.

    <copy todir="${destdir}">
        <fileset dir="${srcdir}" includes="*.png"/>
    </copy>
    <copy todir="${destdir}">
        <filelist dir="${srcdir}" files="${src}.js"/>
        <mapper type="glob" from="*.js" to="sprite.*.js"/>
    </copy>
    

    명령 실행시 입력 리다이렉트 적용하기

    잘 알려지진 않았지만, 구글 앱엔진의 패스워드를 파일에 저장한 후, appcfg.py --passin < file 을 이용하면 자동화가 가능하다. ant 에서는 exec 의 inputstring 으로 대체할 수 있다.

    <exec executable="appcfg.py" inputstring="${bulkload.pass}">
      <arg value="${bulkload.cmd}"/>
      <arg line="--application=${bulkload.appid}"/>
      <arg value="--config_file=${bulkload.config}"/>
      <arg value="--email=${bulkload.email}"/>
      <arg value="--url=${bulkload.url}"/>
      <arg value="--kind=${bulkload.kind}"/>
      <arg value="--filename=${bulkload.csv}"/>
      <arg value="--no_cookies"/>
      passin"/>
      <arg value="."/>
      </exec>
    
  • 2011-03-01

    TextMate Tips

    깔끔하게 지우기

    rm ~/Library/Preferences/com.macromates.*
    rm -rf /Library/Application Support/TextMate/
    rm -rf ~/Library/Application Support/TextMate/
    

    한글 폰트 설치

    2바이트 문자들이 모두 깨지는 관계로 전용 한글 폰트를 구해야 한다. 누가 나눔코딩글꼴의 TextMate 버전을 안 만들어줄까?

    http://www.appleforum.com/application/45362-textmate에서-한글-글꼴폭-문제.html

    유용한 단축키

    ⌘(cmd, window) ⌥ (option, alt) ^(control) ⇧(shift)

    • ⌘T : 빠른 파일 찾아가기
    • ⇧⌘T : 현재 파일의 심볼 찾기
    • ^⇧V : 현재 파일의 정적 분석 및 검증(Validation). 파이썬일 경우 pylint, 자바스크립트일 경우 jslint 가 실행된다.
    • ⌃⌘R : 현재 편집중인 파일을 Project+ 사이드바로 찾기

    더 많은 팁은 http://stackoverflow.com/questions/99807/what-are-some-useful-textmate-shortcuts 에서 찾아볼 수 있다.

    추천 번들 & 플러그인

    zen coding

    via http://code.google.com/p/zen-coding/

    TextMate 도 강력한 툴이지만 이놈도 만만치않다. 마법같은 ⌘R 의 힘을 느껴보시라.

    Project+

    via http://ciaranwal.sh/projectplus

    GetBundles

    via solutions.treypiepmeier.com

    
    mkdir -p ~/Library/Application\ Support/TextMate/Bundles
    cd !$
    svn co http://svn.textmate.org/trunk/Review/Bundles/GetBundles.tmbundle/
    osascript -e 'tell app "TextMate" to reload bundles'
    

    YUI compressor textmate bundle

    via http://www.experienceinternet.co.uk/software/yui-compressor-textmate-bundle/

    PyLint

    파이썬 소스 파일에 대해서 ^⇧V 로 정적 분석 및 검증을 할 수 있다. 설치한 후 TextMate PATH 에 /usr/local/bin 을 추가해야 한다. 참고로 easy_install 으로 모듈을 설치할 때 파이썬 버전을 지정하려면 아래와 같이 하면 된다.

    sudo easy_install-2.5 pylint
    

    각종 경고 목록은 http://www.logilab.org/card/pylintfeatures 에 있다. 
    현재 파일에서 경고를 끄려면,

    # pylint: disable=W0232,R0902,C0103,C0301
    

    정규식

    Basic

    캡처한 문자열은 $1, $2 .. 를 이용한다. $0 은 전체를 의미한다.

    no trailing space after comma

    pylint 를 돌려보면 콤마(,) 다음에 공백을 무조건 넣으라고 한다. negative look  ahead  를 이용하면 간단하다.

    Find: ",(?![\s])"
    Replace: ", "
    

    참고 자료

  • 2011-01-12

    have_app_server vs. on_production_server

    사실 하고 싶은 건, (app id, on production server) 조합으로 적합한 facebook setting 을 로딩하는 건데.. 웬지 잘 안되었던 것 같은 느낌 :)

    from google.appengine.api import apiproxy_stub_map
    import os
    have_appserver = bool(apiproxy_stub_map.apiproxy.GetStub('datastore_v3'))
    on_production_server = have_appserver and \
      not os.environ.get('SERVER_SOFTWARE', '').lower().startswith('devel')