바로 이런 느낌의 결과물을 원했습니다.
뉴스레터를 만들어야 한다는 할 일은 제 목록에 너무 오랫동안 남아 있었습니다. 그래서 오늘 드디어 실행에 옮기기로 했습니다. 관심 있는 분은 여기에서 구독하실 수 있습니다.
사실 저는 장문형 뉴스레터는 별로 좋아하지 않습니다. 제가 선호하는 형식은 흥미로운 콘텐츠만 골라 담은 큐레이션 다이제스트입니다. RubyWeekly가 대표적인 예이고, Wistia의 블로그 다이제스트도 마찬가지입니다.
그런데 이런 다이제스트를 매번 손으로 직접 만들자니 시간이 너무 많이 들고, 완전 자동화하자니 너무 비인간적으로 느껴집니다. 그래서 제가 원하는 것은 반자동(semi-automated) 프로세스입니다. 최신 블로그 글들을 자동으로 가져와서, 제가 개인화할 수 있는 HTML 이메일로 출력해 주는 스크립트가 필요합니다.
자, 그럼 만들어 보겠습니다! 딱 한 명, 즉 저 자신만 만족시키면 되는 재미있는 작은 프로젝트입니다.
전체 계획
이 스크립트가 해야 할 일은 다음과 같습니다.
Honeybadger 블로그의 RSS 피드를 가져와서 파싱한다
카테고리를 기준으로 알맞은 아티클을 선택한다
선별된 아티클 목록을 ERB 템플릿으로 렌더링한다
스크립트는 커맨드 라인에서 실행되며, 결과는 STDOUT으로 출력됩니다.
Ruby에서 피드 가져오고 파싱하기
Ruby의 표준 라이브러리에는 RSS와 ATOM 피드를 생성·소비하기 위한 모듈이 기본 포함되어 있다는 사실을 알고 계셨나요? 우리 용도에는 이보다 더 간단할 수 없습니다. 실제 사용법은 다음과 같습니다.
require 'rss'
feed = RSS::Parser.parse('https://www.honeybadger.io/blog/feed/')
feed.items.each do |item|
puts item.title
end
모듈이 피드를 직접 가져오는 것까지 대신 처리해 줍니다. 정말 꼼꼼한 서비스죠!
카테고리 다루기
구독자에게 관심 없는 링크까지 보내고 싶지 않기 때문에, 카테고리별로 아티클을 필터링하려 합니다. Ruby의 RSS 라이브러리에는 categories 메서드가 있지만, 이것은 XML 노드 객체의 배열을 반환합니다. 제게 필요한 것은 카테고리 이름이므로, RSS 아이템을 Article이라는 데코레이터 클래스로 감싸기로 했습니다.
이제 "How To" 카테고리에 속한 아티클만 손쉽게 선택할 수 있습니다.
require 'rss'
require 'delegate'
class Article < SimpleDelegator
def category_names
categories.map &:content
end
end
feed = RSS::Parser.parse('https://www.honeybadger.io/blog/feed/')
articles = feed.items.map { |o| Article.new(o) }.select { |a| a.category_names.include?("How To") }
템플릿 렌더링하기
결과물은 마크업이 많지 않은 이메일이므로, 템플릿 엔진은 ERB 하나면 충분합니다. 아래 코드에서 볼 수 있듯이, 템플릿과 렌더링 코드를 DigestView라는 클래스 안에 함께 넣었습니다. 단일 목적의 아주 작은 템플릿을 굳이 별도 파일로 분리하는 것은 과하다고 판단했기 때문입니다.
최종 출력물은 STDOUT으로 출력됩니다. 덕분에 출력을 macOS의 pbcopy 명령으로 파이프하여 클립보드에 복사한 뒤, 메일 시스템에 바로 붙여넣을 수 있습니다.
require 'rss'
require 'delegate'
require 'erb'
class Article < SimpleDelegator
def category_names
categories.map &:content
end
end
class DigestView
attr_accessor :articles
def initialize(articles)
@articles = articles
end
def render
ERB.new(template, 0, '>').result(binding)
end
def template
%{<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="https://www.w3.org/1999/xhtml">
<head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /></head>
<body>
<h1>Headline: Replace me</h1>
<p>Intro paragraph: Replace me.</p>
<ul>
<% for article in @articles %>
<li>
<a href="<%= article.link %>">
</li>
<% end %>
</ul>
</body>
</html>}
end
end
feed = RSS::Parser.parse('https://www.honeybadger.io/blog/feed/')
articles = feed.items.map { |o| Article.new(o) }.select { |a| a.category_names.include?("How To") }
printf DigestView.new(articles).render
실제 출력 결과는 다음과 같습니다.
블로그 다이제스트 생성기의 출력 화면.
앞으로의 작업
실제 운영 환경에 적용하기 전까지는 조금 더 손볼 부분이 있습니다. 하지만 대부분 Honeybadger에 특화된 커스터마이징이라 일반적으로 큰 의미는 없습니다. 오늘 남은 시간 동안 처리할 체크리스트는 다음과 같습니다.
템플릿 디자인을 다듬고 이메일 발송 서비스로 테스트하기
링크에 Google Analytics 추적 파라미터 추가하기
템플릿에 게시글 요약(description) 추가하기