둘째 날: Perl 원라이너(one-liner)로 Octopress 따라잡기

저자

@pung96 - Perl 원라이너의 귀재, 블랙홀이 있을 것으로 추정되는 스위스 제네바와 핀란드를 오가며 중이온 연구에 매진 중인 물리학자, 하지만 Perl과 C++, 심지어 Python까지 자유자재로 구사하는 물리학자를 가장한 프로그래머, 훈훈한 외모로 인해 유부남임에도 불구하고 훈중년이라는 별칭을 획득.

시작하며

한 줄의 Perl 코드로 필요한 알고리즘을 작성하는 Perl 원라이너(one-liner)는 매우 유용합니다. 간단한 코드를 작성할 때라던가, 쉘과 결합해서 사용해야 할 때라면 더더욱 그러하지요. 이미 세상에는 훌륭한 원라이너 문서가 많은데 이를 반복한다면 무척 지루한 일이겠죠. 대신 Ruby로 만든 GitHub 블로그 툴인 Octropress를 Perl 원라이너로 살짝 흉내어 Perl 원라이너를 어떻게 쓸 수 있는지 살펴보겠습니다.

원라이너의 철학

원라이너란 명령줄에서 엔터키를 누르기 직전까지의 한 줄안에 원하는 동작을 실행시킬 수 있는 만큼 필요한 명령을 사용해서 코드를 작성하는 것을 말합니다. 그래서 원라이너는 간단하게 쓰기 위한 도구인 만큼 최적화라던가 미학 따윈 소나 줘버려!와 같은 마음가짐으로 내키는 대로 작성하면 됩니다. 사실 이 문서의 코드들도 이런 마음가짐대로 마구 작성한 것들입니다. ;-) 또한 코드 안팎에서 쉘의 기능을 충실히 이용해야 한다는 점을 잊지 마세요. system이라던가 역따옴표, while 구문, 환경 변수 등 가리지 않도록 합니다.

준비물

원라이너로 Octopress를 따라잡으려면 다음 항목들을 준비해야 합니다.

미리 둘러보기

이미 만들어놓은 예제 블로그를 방문해보면, Octopress에서 훔쳐온 테마를 기반으로 한 아주 간단한 블로그를 볼 수 있습니다. 물론 Octropress에 비하면 아주 사소한 기능만을 구현했지만, 이 이상의 것을 구현하려 한다면 Perl 원라이너는를 사용하는 것은 적절하지 않습니다. 편집기를 열어서 모듈을 만드는 것이 좋겠지요. :-)

Octopress 뺨치는 Perl 원라이너 블로그

Octropress는 GitHub 저장소를 블로그로 만들기 위해 gh-pagessource 두 개의 가지를 사용합니다. gh-pages 가지에서는 HTML 파일을 발행하며, source 가지는 실제 블로그 컨텐츠와 기타 여러 파일을 저장합니다.

디렉토리 구조

-- blog                     # source 브랜치
  |-- _scratch              # 작업용 임시 디렉토리
  |-- _deploy               # html 디렉토리. ph-graphs 브렌치
  |-- .gitignore
  |-- init.sh               # 초기화 스크립트
  |-- build.sh              # 빌드 스크립트
  `-- source
      |-- _post             # 블로그 포스트 디렉토리
      |  |-- 2011-11-30-3rd-post.markdown
      |  |-- 2011-11-30-4th-post.markdown
      |  `-- 2011-11-30-first-post.markdown
      |-- _layout
      |  |-- default.html   # 기본 템플릿
      |  |-- index.html     # 첫 페이지용 블로그 포스트 템플릿(default.html에 include 됨)
      |  `-- post.html      # 각 페이지를 위한 블로그 포스트 템플릿(default.html 에 include 됨)
      `-- favicon.png, stylesheets, javascripts

.gitignore를 보면 _scratch, _deploy 폴더는 source 가지에 추가되지 않습니다. Octropress의 구현을 살펴보기 전 까지는 _deploy 폴더처럼 발행을 위한 디렉토리를 어떻게 관리할지에 대해 많은 고민을 했었는데, 이 방법은 생각도 못했네요. 단순히 소스용 가지와 발행용 가지 두 개를 만들어 다른 디렉토리로 관리하면 끝입니다. 천재...

출발!!

이미 만들어놓은 예제 블로그를 이용하는 방법을 기준으로 설명하겠습니다. 예제 블로그를 방문해 가벼운 마음으로 Fork한 후 저장소를 클론합니다.

git clone git@git.github.com:<username>/blog.git -b source
cd blog
./init.sh

init.sh 파일의 내용은 다음과 같습니다.

git clone -b gh-pages git@git.github.com:<username>/blog.git _deploy
mkdir -p _scratch

_deploy 디렉토리와 _scratch 디렉토리를 만들어주는 간단한 스크립트입니다. <username>은 당연히 바꾸어 주어야 겠지요?

build.sh 파일은 작성한 마크다운 문서를 이용해 HTML 파일을 만들고 다시 GitHub로 밀어넣는 Perl 원라이너의 집합입니다. 지금부터 build.sh 파일을 분석해볼까요?

초기화

먼저 _deploy, _scratch 디렉토리를 깨끗하게 비우고 작업할 파일을 _scratch 디렉토리로 복사합니다.

rm -rf _deploy/* _scratch/*
cp -a source/_posts/* _scratch/

블로그 포스트 파일 구성

블로그 포스트 파일을 메타 정보와 컨텐츠로 분리해야 합니다. 자, 이제 마크다운 파일을 살펴보죠. 파일의 형식은 Octopress의 블로그 포스트 형식을 그대로 사용합니다.

---
layout: post
title: "4th-post"
date: 2011-11-30 18:19
comments: true
categories: 
---

# 4th post
이곳이 블로그 내용을 적는 곳이죠.

안타깝게도 이 형식은 완전한 YAML 형식이 아니라서 약간의 지저분한 코드가 필요합니다. 먼저 제목 등의 정보가 저장된 부분을 추출해서 YAML 파일로 저장하고 나머지 부분을 남겨보겠습니다.

perl -i -0 -mYAML=Load -ne'BEGIN{open$y, ">_scratch/posts.yml"}/^---\s*$(.*?)^---\s*$(.*)/sm;print$y "---\nfile: $ARGV", $1;print $2' _scratch/*.markdown

추출된 정보들은 파일 정보를 추가해 하나의 YAML 파일에 통합해 저장합니다. 이런 정보를 버클리 DB와 같은 본격적인 데이터베이스에 저장한 후 사용하는 것도 좋은 방법입니다. 하지만 YAML 역시 거의 완전한 텍스트 기반 데이터베이스로 사용할만하며, 파일 자체를 사람이 바로 읽을 수 있다는 큰 장점이 있습니다.

메타 정보 가공하기

우선 날짜에 따라 정렬해보겠습니다. DumpFile처럼 LoadFile("posts.yml")을 사용할 수도 있지만 앞에서 무심코 -0을 적어버렸기 때문에 그냥 파일을 읽어들여서 Load에 파일 내용을 통째로 넣었습니다.

CPAN의 DateTimeX::Easy 모듈을 사용해 날짜 및 시간 정보(2011-11-30 18:19)를 파싱해서 정확한 값을 사용할 수도 있지만 여기서는 그냥 문자열 비교를 사용해 정렬하는 것으로도 충분했습니다.

perl -0 -MYAML=Load,Dump -ne'print Dump(sort{$b->{date}cmp$a->{date}}Load($_))' _scratch/posts.yml > _scratch/posts2.yml

순서에 따른 아이다와 포스트의 URL(예: /blog/blog/2011/11/30/4th-post)을 YAML DB에 추가합니다. /blog/blog 이렇게 중복이 생겼네요. 그냥 넘어갑시다.

perl -0 -MYAML=Load,Dump -ne'print Dump(map {$_->{file}=~/(\d+)-(\d+)-(\d+)-(.*?)\./;$_->{url}="blog/$1/$2/$3/$4";$_->{iid}=$id++;$_}Load($_))' _scratch/posts2.yml > _scratch/posts.yml

잘 추출되어 정렬되었는지, 필요한 정보가 추가되고 있는지 CPAN의 Data::Dumper 모듈을 이용해 확인해봅시다. Data::Dumper 모듈은 원라이너 코딩시 가장 많이 사용하는 모듈 중의 하나입니다.

perl -0 -MYAML=Load -MData::Dumper -ne'print Dumper Load($_);' posts.yml

두개의 YAML 파일을 관리하는 것은 귀찮으니 앞으로 쓰일 YAML 파일들을 하나로 통합해 버립니다. cat으로 합치면 간단하게 끝납니다.

cat _config.yml _scratch/posts.yml > _scratch/all.yml

이번에는 마크다운으로 저장한 페이지를 HTML로 바꿉니다. Text::Markdown 모듈이 알아서 다 해 줍니다. -i 옵션을 사용해서 한방에 파일들을 변환해 버렸습니다. 사실 -i 는 굉장히 위험한 옵션입니다. 사용하기 전에 파일을 백업하거나 -i.bak 같은 방법을 쓰는 등 각별한 관리가 필요합니다. 저 역시 숱하게 파일을 날려버리고 여러번 울었었죠! 특히 -p 옵션 대신 무심코 -n 옵션을 쓴다면 깨끗해진 파일들을 보게 됩니다. 이런!;;;

perl -i -0 -mText::Markdown=markdown -ne'print markdown($_)' _scratch/*.markdown

Text::Xslate 모듈을 이용해 미리 작성한 템플릿에 만들어진 HTML 파일을 집어넣어서 실제 보여줄 페이지를 만듭니다. 제목이나 앞뒤 페이지 네비게이션을 위해서 메타정보도 함께 전달해야 합니다.

perl -mYAML=LoadFile -MText::Xslate -e'($s,@p)=LoadFile("_scratch/all.yml");map{open$f,">$_->{file}";print$f Text::Xslate->new->render("source/_layouts/default.html",  { site=>$s,  page=>$_, type=>"post.html", posts=>\@p})}@p'

최종 작성된 파일들을 _deploy 폴더의 blog/년도/월/날짜/제목/index.html 형식으로 보내줍니다.

ls _scratch/*.html | perl -nle'/(\d+)-(\d+)-(\d+)-(.*?)\./;$d="_deploy/blog/$1/$2/$3/$4";system("mkdir -p $d && cp $_ $d/index.html");'

이제 메인 페이지를 만들겠습니다. 첫 페이지에 보여줄 포스트의 개수를 정하고 페이지 네비게이션을 만들면 좋겠지만, 귀찮아서 그냥 모든 포스트를 한 화면에 넣었습니다. 똑같은 템플릿을 사용하면서 템플릿 내에서 for 반복문을 이용해 모든 포스트를 출력합니다.

perl -mYAML=LoadFile -MText::Xslate -e'($s,@p)=LoadFile("_scratch/all.yml");open$f,">_deploy/index.html";print$f Text::Xslate->new->render("source/_layouts/default.html",  { site=>$s,  pages=>\@p,  type=>"index.html", posts=>\@p})'

와우! 이제 다 끝났습니다. Octopress 에서 훔쳐온 정체를 알 수 없는 각종 CSS와 자바스크립트 파일들을 _deploy 폴더로 복사합시다.

rsync -a source/favicon.png source/images source/javascripts source/stylesheets _deploy/

_deploy 폴더로 이동해 추가, 변경, 삭제된 파일들을 업데이트 한 후 원격 저장소의 gh-pages 브랜치로 밀어 넣습니다.

cd _deploy
git add .
git add -u
git commit -m "Site updated at `date`"
git push origin gh-pages --force

놀랍게도 끝

source 가지를 push하는 것 있지 마세요!

git add .
git commit -a -m <log>
git push origin source

이제 https://<username>.github.com/blog 주소의 여러분 만의 블로그가 생성되었습니다.

참 쉽죠잉~ ;-)

보너스

처음 계획할 때 생각했던 것보다 다양한(?) 코드가 나오지 않은 것 같아 아쉬움을 달래기 위해 간단한 원라이너 라이프핵 두 가지를 더 소개합니다.

Perl 코드 내에 쉘 환경 변수를 넣기

환경 변수를 넣으려면 $ENV{VarName} 해시 변수를 사용할 수도 있지만, 쉘의 문자열 병합 기능을 이용할 수도 있습니다.

perl -e'print "Current Term is '$TERM'\n"'

여러 개의 파일 열기

속도에 아주 민감한 코드가 아니라면, 여러 파일을 처리할 때 원라이너 Perl 코드 내에서 파일을 여는 대신 쉘의 while 루프를 사용할 수도 있습니다.

ls *.root | while read x;do perl -nle'do something' $x > $x.new ;done

생각해보면 당연한 거죠? 하지만 저는 Perl 원라이너 안에서 이중 반복문을 사용할 때가 더 많답니다. :-)

정리하며

Perl 원라이너는 정말 강력합니다. 몇 가지 옵션과 CPAN의 모듈, 그리고 심지어 유닉스 유틸리티와 파이프를 연결하면 한 줄의 원라이너 코드로 할 수 없는 일을 찾는 것이 더 어려울 것입니다. 물론 지금까지의 모든 작업을 모듈로 리팩터링 해서 우아하게(?) 만들 수도 있지만 불과 몇 줄 코드의 집합을 단 두 개의 파일로 정리해서 간단히 끝내버렸는데, 오히려 이 편이 더 멋있지 않나요?

이 문서에서는 마크다운 문서만 다루었지만 POD 등의 다른 형식을 추가하는 것도 어렵지 않겠죠. 이미 모든 것은 CPAN님께서 알고 계십니다. 비슷한 방식으로 ikiwiki를 GitHub에서 서비스 하는 것도 간단하고 재밌겠네요. _deploy 디렉토리는 단순한 정적 파일들만을 가지고 있기 때문에 GitHub로 밀어넣는 것 뿐만 아니라, 다른 웹서버나 dotcloud, Amazon S3 같은 클라우드 서비스를 이용할 수도 있겠죠. 숙제로 남겨두기로 할까요?

Octopress도 매우 훌륭한 도구입니다. Perl 사용자는 Perl 도구만 써야한다는 강박을 가진 것은 아니지만 Perl로 만들어진 것이 아니면 수정할 수가 없어서 재미가 없고 답답한 느낌이 들어요. 누가 Perl로 Octopress 에 버금가는 도구를 만들어주면 좋겠는데...

여기까지. 더 이상은 무리! ;-)

Perl 원라이너 참고문서

blog comments powered by Disqus