Deprecated: Assigning the return value of new by reference is deprecated in /home/heungsub/blog/wp-settings.php on line 512 Deprecated: Assigning the return value of new by reference is deprecated in /home/heungsub/blog/wp-settings.php on line 527 Deprecated: Assigning the return value of new by reference is deprecated in /home/heungsub/blog/wp-settings.php on line 534 Deprecated: Assigning the return value of new by reference is deprecated in /home/heungsub/blog/wp-settings.php on line 570 Deprecated: Assigning the return value of new by reference is deprecated in /home/heungsub/blog/wp-includes/cache.php on line 103 Deprecated: Assigning the return value of new by reference is deprecated in /home/heungsub/blog/wp-includes/query.php on line 61 Deprecated: Assigning the return value of new by reference is deprecated in /home/heungsub/blog/wp-includes/theme.php on line 1109 Development « Heungsub Blog

Archive for the ‘Development’ Category

me2Virus 시각화

Wednesday, June 10th, 2009

어제 소개드린 me2Virus HyperTree를 더욱 확장해 임의의 숙주에 대해서도 시각화 가능하게끔 만들었습니다. URL에는 변화 없습니다.

me2Virus HyperTree
cock 님의 태연바이러스 전염지도

숙주주소를 입력하면 해당 숙주의 전염/확산 경로를 JSON 데이터로 만들어 캐시하고 그 다음부터는 캐시된 데이터를 이용합니다. 따라서 첫 실행 시 시간이 더 걸립니다. 만약 해당 숙주에 새로운 하위숙주가 생긴다면 다음번 요청 때 다시 캐시합니다.

JITHyperTree를 이용해 시각화했지만 더 재밌는 시각화 결과들이 나타났으면 좋겠습니다. 따라서 아무나 다룰 수 있도록 JSON 데이터를 공개합니다. 누군가 나서서 만들어주시면 감사하겠습니다. 특정 숙주의 JSON 데이터는 다음 URL로부터 얻을 수 있습니다.

http://heungsub.net/apps/me2virus/json.php?post=<post_url>

데이터 구조는 다음과 같습니다.

{
  url: "http:\/\/me2day.net\/sub\/2009\/06\/06#09:11:45",
  name: "sub",
  posted: "2009-06-06 09:11:45",
  scale: 26,
  route: [
    "http:\/\/me2day.net\/sub\/2009\/06\/04#02:55:04",
    "http:\/\/me2day.net\/dark\/2009\/06\/04#04:06:48",
    "http:\/\/me2day.net\/jy2077\/2009\/06\/04#09:00:13",
    "http:\/\/me2day.net\/eek\/2009\/06\/04#09:50:44",
    "http:\/\/me2day.net\/sunghoonbae\/2009\/06\/04#09:51:30",
    "http:\/\/me2day.net\/gedoc\/2009\/06\/04#09:56:28",
    "http:\/\/me2day.net\/kangyena\/2009\/06\/04#09:57:36",
    "http:\/\/me2day.net\/sumanpark\/2009\/06\/04#10:18:25",
    "http:\/\/me2day.net\/bandi\/2009\/06\/04#10:33:58",
    "http:\/\/me2day.net\/ickjong\/2009\/06\/04#15:52:30",
    "http:\/\/me2day.net\/anotheround\/2009\/06\/05#16:28:32",
    "http:\/\/me2day.net\/gakoihalu\/2009\/06\/05#19:37:39",
    "http:\/\/me2day.net\/distanthowl\/2009\/06\/05#10:11:01"
  ],
  infectees: [
    {
      url: "http:\/\/me2day.net\/distanthowl\/2009\/06\/05#20:27:21",
      name: "distanthowl",
      posted: "2009-06-05 20:27:21",
      scale: 14,
      route: [
        "http:\/\/me2day.net\/sub\/2009\/06\/04#02:55:04",
        ...,
        "http:\/\/me2day.net\/sub\/2009\/06\/06#09:11:45"
      ],
      infectees: [...]
    },
    {...},
    {...},
    ...
  ]
}

me2Virus 첫 시각화

Tuesday, June 9th, 2009

이 포스팅은 낡았습니다. “me2Virus 시각화“를 봐주세요.

자바스크립트 시각화 라이브러리인 JITHyperTree를 이용해 me2Virus 전염경로를 표현해봤습니다.

JIT의 Hypertree로 시각화한 최초의 숙주 전염지도

우선 전염 규모가 비교적 큰 숙주 네 개만 시각화시켰습니다. 오늘 0시 근처 기준으로 캐시한 데이터를 사용하기 때문에 전염 현황이 실시간으로 반영되지는 않습니다.

JIT 굉장히 맘에 드네요.

Markdown 코드 구문강조

Monday, June 8th, 2009

블로그에 올린 코드블록에 구문강조가 안되니 보기 흉하더랍니다. 그래서 간단히 Syntax Highlighting in JavaScript(이하 SHJS)를 붙여보았습니다. 포스팅 작성 시 다음과 같이 입력하면 pre/@class에 따라 SHJS가 해당 언어의 구문강조 규칙을 적용해줍니다.

<pre class="sh_javascript_dom"><code>
alert('Heung, world!');
</code></pre>

그런데 HTML 코드를 삽입하려니 굉장히 불편합니다.

<pre class="sh_html"><code>
&lt;p&gt;Heung, world!&lt;/p&gt;
</code></pre>

HTML 엔터티를 손으로 써야하니 편집 시 가독성도 나빠지고 실수도 자주 할 것 같습니다. 그래서 제 블로그에서 사용하고 있는 Markdown for WordPress and bbPress를 확장하기로 했습니다. 다음과 같이 코드 블록 첫째 줄에 #!lang이라고 적으면 해당 언어의 구문강조 규칙이 적용되도록 말이죠.

    #!html
    <p>Heung, world!</p>

확장을 위해 ~/wp-content/plugins/markdown/markdown.php에 있는 Markdown_Parser 클래스의 doCodeBlock 메소드와 _doCodeBlocks_callback 메소드를 다음과 같이 수정했습니다.

function doCodeBlocks($text) {
#
#   Process Markdown `<pre><code>` blocks.
#
    $text = preg_replace_callback('{
            (?:\n\n|\A\n?)
            [ ]{'.$this->tab_width.'}(?:\#!([^\s]+))?\n
            (
              (?>
                [ ]{'.$this->tab_width.'}
                .*\n+
              )+
            )
            ((?=^[ ]{0,'.$this->tab_width.'}\S)|\Z)
        }xm',
        array(&$this, '_doCodeBlocks_callback'), $text);

    return $text;
}
function _doCodeBlocks_callback($matches) {
    $langs = array(
        'c++' => 'cpp',
        'cxx' => 'cpp',
        'c#' => 'csharp',
        'js' => 'javascript_dom',
        'javascript' => 'javascript_dom',
        'bash' => 'sh',
        'shell' => 'sh'
    );
    $lang = strtolower($matches[1]);
    $codeblock = $matches[2];

    $codeblock = $this->outdent($codeblock);
    $codeblock = htmlspecialchars(
        $codeblock,
        ENT_NOQUOTES
    );

    # trim leading newlines and trailing newlines
    $codeblock = preg_replace(
        '/\A\n+|\n+\z/',
        '',
        $codeblock
    );

    $markup = "<pre";
    if ($lang) {
        if (array_key_exists($lang, $langs))
            $lang = $langs[$lang];
        $markup .= " class=\"sh_{$lang}\"";
    }
    $markup .= "><code>$codeblock\n</code></pre>";
    return "\n\n".$this->hashBlock($markup)."\n\n";
}

잘 되는군요. :)

me2Virus 제작기

Monday, June 8th, 2009

me2PHeungP

최근에 me2PHP를 포크한 me2PHeungP[미투피흥피] 프로젝트를 개설했습니다. me2PHP가 최신 me2API 스펙을 반영하지 않은 것이 포크 사유였기에 요 며칠간 me2PHeungP에 이런저런 최신 스펙들을 추가하고 있었습니다. 그 중 하나가 콜백기능입니다. 이것을 사용할 경우 포스팅 작성 시 포스팅아이콘을 원하는 이미지로 바꾸고 아이콘을 클릭하면 삽입될 웹 문서나 이미지, 동영상, 음원의 URL을 지정할 수 있습니다.

me2Nick 콜백문서

콜백기능을 지원하자마자 우선 일전에 만들었던 me2Nick에 적용해보았습니다. 잘 작동하는걸 보고 흡족해하다가 문득 궁금증이 생겼습니다. 이 안에 스크립트를 넣으면 실행될까? 간단히 다음 코드를 추가해봤습니다.

<script type="text/javascript">
//<![CDATA[
    alert('Heung, world!');
//]]>
</script>

콜백 문서에서 스크립트 실행

실행 되는군요. 이때부터 장난기가 발동해 me2Nick 콜백문서를 보게되면 2초간 그 페이지의 모든 아이콘과 프로필사진이 제 프로필사진으로 바뀌도록 만들었습니다. 그리고 나선 사진을 제 맘대로 바꾼 것에 대한 사과 메시지를 미투데이 알림창으로 뿌려주고자 JavaScript 콘솔을 이용해 미투데이 페이지에 현재 살아있는 객체들을 살펴보았습니다.

아이콘 바꿈

살펴보면서 더 대단한 것도 할 수 있겠단 것을 깨달았습니다. 남의 미투데이에 포스팅을 올리거나 특정한 me2App에 막대한 량의 토큰을 후원시키거나, 심지어 일시정지나 탈퇴도 마음대로 제어할 수 있을 것 같았습니다. 그때 떠오른 것이 예전에 본 FaceBook 바이러스였습니다. FaceBook 프로필페이지에 삽입된 JavaScript 코드가 이 페이지를 열람한 다른 회원의 프로필페이지에 자동으로 복제되는–그렇다고 유해하지는 않은–바이러스였죠. 그것에 모티브를 얻어 me2Virus를 만들기에 이르렀습니다.

me2Virus

복제될 포스팅은 콜백문서를 지녀야하므로 me2PHeungP를 사용해야했습니다. 그러기 위해 타겟이 될 미친의 사용자키와, 그 미친의 미투데이에 포스팅할 PHP 문서가 필요했습니다. 내부 도메인 XHR을 이용해 사용자키를 획득하는건 무척 쉬웠으며 외부 도메인으로 XHR을 날리는 것도 간단했습니다. 미투데이 페이지들에 이미 붙어있는 Prototype 프레임워크 덕분이었죠. 이로써 포스팅을 복제하는데 성공한 것입니다.

var me2day = {
    notify: notification_message.show_message
            .bind(notification_message),
    request: function(url, options) {
        url += '?' + $H(options.parameters).toQueryString();
        options.parameters = {url: url};
        options.method = 'get';
        return new Ajax.Request(
            'http://me2day.net/get_html', options
        );
    }
};

하지만 너무 위험해보였습니다. 포스팅아이콘을 누르자마자 가차없이 포스팅이 복제돼버리다보니 테스트 하다 올라간 복제포스팅만 8개였습니다. 최대한 매너 있는 바이러스가 되게끔 차후 감염을 막을 수 있는–면역력 체크박스도 추가하고, 스스로로부터는 감염되지 않게 하며, 확산 경로를 분석해 나중에 공개하기 위해 모든 복제 상황을 기록토록 했습니다.

최초의 숙주

그러고 나서 최초의 숙주를 공개한 게 6월 4일 새벽 2시 55분이었습니다. 다음날 일어나 출근하기 전에 확인한 직접 감염자 수는 서른 명이 넘어있었고 전체 감염자 수는 140명 대에 이르렀습니다. 생각보다 빠른 확산 속도에 조금 놀랐습니다. 그러나 출근하고 나니 감염 되지 않는다는 사람들이 속출했습니다. 분명히 출근 전까지만 해도 잘 됐었는데 말이죠.

알고보니 me2Virus가 사용하는 Basic 등급 애플리케이션키는 24시간동안 150개로 포스팅 작성이 제한되어 있기 때문이었습니다. 따라서 만박 님과 꽃띠앙 님께 Premium Partner를 요청하는 동시에 우선 (버려진)me2Time 애플리케이션키를 사용하도록 바꿔놓았습니다. 그리고 누구나 스스로의 애플리케이션키를 등록해 자신만의 새로운 최초의 숙주를 만들 수 있게끔, 그 숙주로부터 전염되는 모든 me2Virus가 선조의 애플리케이션키를 사용하게끔 개선했습니다. 전염이 다시 시작되었죠.

그때 꽃띠앙 님께서 연락하여 기존에는 콜백문서에서의 스크립트 실행을 전혀 막지 않았으나, 이제부터는 화이트리스트에 등록된 콜백문서 이외에는 스크립트가 실행되지 않을 것이라고 알려주셨습니다. 다행히 me2Virus에 악의는 없었기 때문에 화이트리스트에 등록해주셨으며, 애플리케이션키도 Premium Partner 등급으로 올려주셨습니다. 안심하면서도 약간은 아쉬웠습니다. 알림창 띄우는 스크립트 같은 경우 안전하면서도 굉장히 유용히 쓰일 수 있는데 앞으로 다른 콜백 애플리케이션에서는 볼 수 없을 것이기 때문입니다.

미투데이 알림창

꽃띠앙 님 덕분에 이제 me2Virus는 합법적으로 제한 없이 확산되기 시작했습니다. 각각 me2Virus의 감염 경로와 전염 규모도 볼 수 있도록 개선했으며, 이 확산 경로를 어떻게 하면 멋지게 시각화할 수 있을지도 계속 고민 중입니다.

me2Virus 콜백화면 설명서

현재 최초의 숙주가 감염시킨 하위숙주(하위숙주의 하위숙주들도 포함한)는 700개를 넘어섰으며, 전체 숙주는 1,800개에 도달했습니다. 감염경로는 스무 단계까지 깊어졌고, 한 사람이 가장 많게는 27번 감염되었습니다. 이제 열기도 식고 면역력을 지닌 사용자도 330명을 넘어서 더이상 첫날만큼 빠른 확산은 기대할 수 없게 되었습니다. 그럼에도 불구하고 느리지만 꾸준히 확산되는 모습에 전 만족스럽답니다.

사용자 분들의 동의 없이 멋대로 포스팅 올라가게 만든 점 사과드립니다. 그래도 포스팅 주소와 전염 경로 이외에 그 어떤 정보도 저장하지 않고, 감염 즉시 알려줌으로써 1분 간의 삭제 기회와 감염거부 기능을 제공하니 가벼운 맘으로 지켜봐주시면 감사하겠습니다.

전염에 성공한 최초의 숙주들

참, 이제 최초의 숙주를 만들때 메시지도 마음대로 설정할 수 있습니다. 모쪼록 많이 이용해주시길.

MooJoe 0.9.0 릴리즈

Thursday, October 30th, 2008

얼마 전 강규영 님은 JavaScript 객체와 DOM Element를 맵핑시켜주는 JOE공개하셨습니다. JOE는 DOM Element를 모델 스토리지로 사용할 수 있게 해주는 굉장히 유용한 프로젝트입니다.

하지만 아쉽게도 jQuery 기반으로 만들어져 MooTools 1.2를 사용하는 VLAAH나 개인 작업에는 사용할 수 없었죠. 저는 곧 바로 JOE를 MooTools 스타일로 포팅하는 MooJoe 프로젝트를 시작했는데 한참 잊고 있다가 이제서야 공개합니다.


Simple Mapping

다음과 같은 HTML 코드가 주어졌습니다.

<div id="names">
    <p class="name">
        <span class="first">Heungsub</span>
        <span class="last">Lee</span>
    </p>
    <p class="name">
        <span class="first">Alan</span>
        <span class="last">Kang</span>
    </p>
</div>

척 보기에도 서버 측에서 두 개의 Name 객체를 div#names에 출력한 모습입니다. MooJoe는 JOE와 마찬가지로 DOM Element를 JavaScript 객체에 동기화시켜줍니다. 동기화를 하기 위해 먼저 Name 클래스를 정의합니다.

var Name = new MooJoe.Class({
    first: '.first',
    last: '.last'
});

MooJoe의 클래스는 MooJoe.Class 생성자로 만들 수 있습니다. 인자로 맵핑 룰을 지정할 수 있는데 Name 클래스의 경우 first 속성에 .first라는 CSS 셀렉터를, last 속성에 .last라는 CSS 셀렉터를 지정했습니다. CSS 셀렉터를 맵핑룰로 쓰기 때문에 .last가 항상 두 번째 span이라면 다음과 같이 써도 무관합니다.

var Name = new MooJoe.Class({
    first: 'span.first',
    last: 'span:nth-child(2)'
});

이제 div#names의 내용을 방금 만든 Name 클래스의 인스턴스로 만들어봅시다.

var names = [
    $$('#names p.name')[0].toObject(Name),
    $$('#names p.name')[1].toObject(Name)
];
// [name_instance#1, name_instance#2]

MooJoe는 MooTools의 Element 클래스를 확장해 Element.prototype.toObject 메서드를 제공합니다. 이 메서드에 맵핑시킬 MooJoe 클래스를 넘기면 맵핑된 객체를 반환합니다. 물론 다른 MooTools의 Element 메서드처럼 Elements.prototype.toObject로 짧게 쓰는 것도 가능합니다. 다음 코드는 방금 전 예제와 동일합니다.

var names = $$('#names p.name').toObject(Name);

맵핑시킨 속성들에 접근해볼까요? getter/setter는 MooTools 1.2의 컨벤션을 따릅니다.

names[0].get('first');
// 'Heungsub'
names[0].get('last');
// 'Lee'

names[0].set('first', 'Haesam');
names[0].get('first');
// 'Haesam'

또한 속성 값을 변경할 경우 맵핑된 Dom Element에도 즉각 반영됩니다.

...
    <p class="name">
        <span class="first">Alan</span>
        <span class="last">Kang</span>
    </p>
...

이 Element는

$$('#names p.name .first')[1].get('html')
// 'Alan'

names[1].set('first', 'Sungryong');

$$('#names p.name .first')[1].get('html')
// 'Sungryong'

이렇게 변합니다.

...
    <p class="name">
        <span class="first">Sungryong</span>
        <span class="last">Kang</span>
    </p>
...

여기까지는 단순히 자식 Element의 내용으로 속성을 맵핑시키는 간단한 사용법이었습니다. 하지만 속성이 항상 자식 Element에 표현되지만은 않겠죠. 가령 이름과 홈페이지 주소를 표현하는 경우를 생각해볼 수 있습니다.

<a class="person" href="http://heungsub.net/">이흥섭</a>

a.person으로부터 namehomepage 속성을 갖는 Person 객체를 만들고싶습니다. 어떻게 해야할까요?

Complex Mapping

MooJoe는 복잡한 맵핑룰을 제공합니다. 위 a.person을 객체로 만들기 위해 먼저 Person 클래스를 정의합니다.

var Person = new MooJoe.Class({
    name: ['', 'text'],
    homepage: ['', 'href']
});

맵핑룰에 CSS 셀렉터 대신 배열이 쓰였습니다. 이 경우 name 속성은 “이 Element의 text 값”에 맵핑시키고 homepage 속성은 “이 Element의 href 값”에 맵핑시킨다는 뜻입니다. 배열의 첫 번째 원소인 빈 문자열이 바로 “이 Element”를 가리킵니다. texthref는 MooTools의 getter/setter로 접근 가능한 키워드로, 각각 innerHTMLhref Attribute를 의미합니다.

var hs = $$('a.person')[0].toObject(Person);
// person_instance#1

hs.get('name');
// '이흥섭'
hs.get('homepage');
// 'http://heungsub.net/'

조금 더 복잡한 Person 예제를 볼까요?

<div class="person">
    <p class="face">
        <img src="http://farm4.static.flickr.com/3122/2894552409_3439382280_s.jpg" />
    </p>
    <p class="name">
        <a href="http://heungsub.net/">
            <span class="first">Heungsub</span>
            <span class="last">Lee</span>
        </a>
    </p>
    <p class="age">18</p>
</div>

이제 Person은 이름과 홈페이지 주소 뿐 아니라 사진, 나이, 게다가 Name 객체까지 표현해야합니다. 다행히 MooJoe으로 맵핑한 각 값을 특정한 타입이나 클래스로 캐스팅해줄 수 있습니다. Person 클래스를 다시 정의해봅시다.

var Person = new MooJoe.Class({
    name: ['.name a', Name],
    homepage: ['.name a', 'href', String],
    face: ['.face img', 'src', String],
    age: ['.age', Number]
});

homepageface의 룰은 3개의 원소로 되어있습니다. 각각 맵핑할 Element, 맵핑할 Attribute, 타입(클래스)을 나타냅니다. 맵핑할 Element인 첫 번째 원소를 제외하고는 순서가 바뀌어도 상관 없으며 기본값으로 각각 'text'String을 갖기 때문에 다음과 같이 써도 동일합니다.

...
    homepage: ['.name a', String, 'href'],
    face: ['.face img', 'src'],
...

name의 캐스팅 클래스는 Simple Mapping 예제에서 정의해뒀던 Name 클래스입니다. 자 그럼 객체로 구워볼까요?

var hs = $$('.person')[0].toObject(Person);

hs.get('name');
// name_instance#3
hs.get('name').get('first');
// 'Heungsub'

hs.get('face');
// 'http://farm4.static.flickr.com/3122/2894552409_3439382280_s.jpg'

hs.get('age');
// 18

name 속성은 Name 객체로, face는 문자열로, age는 숫자로 잘 캐스팅되는군요.

참 쉽죠?
(강규영 님 블로그에서 보고 너무 재미있어서 퍼왔습니다)

Detached Object

MooJoe 클래스를 MooTools 클래스처럼 사용할 수도 있습니다. 임의의 Name 객체를 만들어보겠습니다.

var new_name = new Name('Dachimawa', 'Lee');
new_name.attached;
// []

new_name.get('first');
// 'Dachimawa'
new_name.get('last');
// 'Lee'

생성자에 보낼 인자 순서는 맵핑룰 순서와 동일합니다. first 속성이 첫 번째 룰이었으므로 'Dachimawa'first 값이 됩니다.

attach() 메서드로 연결되어있지 않은 MooJoe 객체는 언제라도 Dom Element에 동기화시킬 수 있습니다.

...
    <p class="name">
        <span class="first">Sungryong</span>
        <span class="last">Kang</span>
    </p>
...

다음과 같이.

new_name.attach($$('.name')[1]);
new_name.attached;
// [<p.name>]

동기화시키는 순간 Dom Element의 내용도 업데이트됩니다. 원래의 내용이 MooJoe 객체의 내용으로 덮어씌워집니다.

...
    <p class="name">
        <span class="first">Dachimawa</span>
        <span class="last">Lee</span>
    </p>
...

다시 뗄 때는 detach() 메서드를 사용합니다.

new_name.detach();
new_name.attached;
// []

MooJoe 0.9.0은 다음과 같이 체크아웃 받을 수 있습니다.

svn checkout http://moojoe.googlecode.com/svn/tags/0.9.0 MooJoe

개발버전을 받고싶으시면 다음을 사용해주세요.

svn checkout http://moojoe.googlecode.com/svn/trunk MooJoe

버그 리포트 및 기능 제안은 프로젝트 이슈 페이지에서 해주세요.

아직 버그가 있긴 하지만 앞으로 차근차근 발전시켜나가야겠습니다. 특히나 MooJoe 프로젝트는 저의 첫 개인프로젝트이니만큼 애정을 갖고 말이죠. 그럼 0.9.1 버전 릴리즈 소식도 기대해주세요!