Skip to content

Instantly share code, notes, and snippets.

@teddy-ma
Last active July 19, 2019 00:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save teddy-ma/ee3a62556dd9e74bfead9698dbeceda3 to your computer and use it in GitHub Desktop.
Save teddy-ma/ee3a62556dd9e74bfead9698dbeceda3 to your computer and use it in GitHub Desktop.
tdd org blog parser
class ArticleGeneratorTest < Minitest::Test
BLOG_TEXT = <<EOF
* single post
:PROPERTIES:
:EXPORT_FILE_NAME: demo-article-1
:END:
** Today is Friday
Friday Line _one_
Friday Line _two_
* series post
:PROPERTIES:
:BASE_EXPORT_TITLE: series-post
:BASE_FILE_PATH: series title
:END:
** Today is Friday
:PROPERTIES:
:EXPORT_FILE_NAME: friday
:END:
Friday Line _one_
Friday Line _two_
** Today is Sunday
:PROPERTIES:
:EXPORT_FILE_NAME: sunday
:END:
Sunday Line _one_
Sunday Line _two_
EOF
TEST_BASE_FILE_PATH = "out"
def single_post_items_fixture
[
Item.new(:article_begin),
Item.new(:title_begin),
Item.new("* single post"),
Item.new(:properties_begin),
Item.new(":EXPORT_FILE_NAME: demo-article-1"),
Item.new(:properties_end),
Item.new("** Today is Friday"),
Item.new("Line _one_"),
Item.new("Line _two_")
]
end
def series_posts_items_fixture
[
Item.new(:article_begin),
Item.new(:title_begin),
Item.new("* series post"),
Item.new(:properties_begin),
Item.new(":EXPORT_FILE_NAME: series_page"),
Item.new(:properties_end),
Item.new("** Chapter1"),
Item.new(:properties_begin),
Item.new(":EXPORT_FILE_NAME: chapter1"),
Item.new(:properties_end),
Item.new("Line _one_"),
Item.new("Line _two_"),
Item.new("** Chapter2"),
Item.new(:properties_begin),
Item.new(":EXPORT_FILE_NAME: chapter2"),
Item.new(:properties_end),
Item.new("Line _one_"),
Item.new("Line _two_")
]
end
def single_article_fixture
article = Article.new
article.set_title("hello")
article.set_finished?(true)
article.append_properties(EXPORT_FILE_NAME: "demo-article-1")
article.append_body("** post title")
article.append_body("foobar")
article
end
def series_article_fixture
article = Article.new
article.set_title("welcome")
article.set_finished?(true)
article.append_properties(BASE_EXPORT_TITLE: "series title")
article.append_properties(BASE_FILE_PATH: "series_path")
article.append_body("** post title1")
article.append_body("foobar1")
article.posts.first.append_properties(EXPORT_FILE_NAME: "chapter1")
article.append_body("** post title2")
article.append_body("foobar2")
article.posts.last.append_properties(EXPORT_FILE_NAME: "chapter2")
article
end
def pages_fixture
series_data_array = [
["series/series_post_1", "post 1 title"],
["series/series_post_2", "post 2 title"],
["series/series_post_3", "post 3 title"],
]
pages = series_data_array.map do |data|
Page.new(data[0], data[1], "foobar")
end
[Page.new("series", "series title", series_data_array), Page.new("single_post", "hello", "xyz")] + pages
end
def test_item_parse
finished_title_item = Item.new("* DONE single post")
assert_equal "single post", finished_title_item.to_title
assert finished_title_item.to_finished?
unfinished_title_item = Item.new("* DOING single post")
refute unfinished_title_item.to_finished?
default_title_item = Item.new("* single post")
refute default_title_item.to_finished?
property_item = Item.new(":EXPORT_FILE_NAME: demo-article-1")
except_property = { EXPORT_FILE_NAME: "demo-article-1" }
assert_equal except_property, property_item.to_property
raw_content_item = Item.new("_lol_")
assert_equal "_lol_", raw_content_item.to_raw_content
end
def test_article
article = Article.new
article.set_title("hello")
assert_equal "hello", article.title
article.append_properties({foo: 'bar'})
article.append_properties({abc: 'xyz'})
except_properties = {foo: 'bar', abc: 'xyz'}
assert_equal except_properties, article.properties
end
def test_build_single_post
builder = Builder.new
items = single_post_items_fixture
builder.parse(items)
article = builder.article
assert_equal "single post", article.title
refute article.finished
assert_equal 1, article.posts.count
only_post = article.current_post
except_properties = { EXPORT_FILE_NAME: "demo-article-1" }
assert_equal "Today is Friday", only_post.title
assert_equal except_properties, article.properties
assert_equal ["** Today is Friday", "Line _one_", "Line _two_"], only_post.body
end
def test_build_series_post
builder = Builder.new
items = series_posts_items_fixture
builder.parse(items)
article = builder.article
assert_equal "series post", article.title
refute article.finished
assert_equal 2, article.posts.count
except_article_properties = { EXPORT_FILE_NAME: "series_page" }
except_post1_properties = { EXPORT_FILE_NAME: "chapter1" }
except_post2_properties = { EXPORT_FILE_NAME: "chapter2" }
assert_equal except_article_properties, article.properties
assert_equal except_post1_properties, article.posts.first.properties
assert_equal except_post2_properties, article.posts.last.properties
assert_equal ["** Chapter1", "Line _one_", "Line _two_"], article.posts.first.body
assert_equal ["** Chapter2", "Line _one_", "Line _two_"], article.posts.last.body
assert_equal "Chapter1", article.posts.first.title
assert_equal "Chapter2", article.posts.last.title
end
def test_stream
stream = Stream.new(BLOG_TEXT)
stream.parse
articles = stream.articles
assert_equal 2, articles.count
end
def test_single_post_writer
article = single_article_fixture
writer = WriterFactory.build(article)
refute writer.draft
page = writer.build_pages.first
assert_equal article.posts.first.body.join("\n"), page.content
assert_equal 'post title', page.title
assert_equal 'demo-article-1', page.file_path
end
def test_series_posts_writer
article = series_article_fixture
writer = SeriesWriter.new(article)
refute writer.draft
pages = writer.build_pages
page1 = pages.shift
assert_equal article.posts.first.body.join("\n"), page1.content
assert_equal 'post title1', page1.title
assert_equal 'series_path/chapter1', page1.file_path
page2 = pages.shift
assert_equal article.posts.last.body.join("\n"), page2.content
assert_equal 'post title2', page2.title
assert_equal 'series_path/chapter2', page2.file_path
index_page = pages.shift
assert_equal 'series title', index_page.title
assert_equal 'series_path', index_page.file_path
except_index_page_content = [
['series_path/chapter1', 'post title1'],
['series_path/chapter2', 'post title2']
]
assert_equal except_index_page_content, index_page.content
end
def test_blog_generator
pages = pages_fixture
generator = BlogGenerator.new(TEST_BASE_FILE_PATH, pages)
generator.generator_archive_page
archive_pages = generator.archive_pages
assert_equal 2, archive_pages.count
assert_equal pages[0..1], archive_pages
end
end
class Article
attr_accessor :title, :properties, :finished, :posts
def initialize
@properties = {}
@body = []
@finished = false
@posts = []
end
def set_title(title)
@title = title
end
def set_finished?(boolean)
@finished = boolean
end
def append_properties(kv)
if current_post
current_post.append_properties(kv)
else
@properties.merge!(kv)
end
end
def append_body(line)
posts << Post.new(line) if line.start_with?("** ")
current_post.append_body(line)
end
def current_post
posts.last
end
end
class Post
attr_accessor :title, :body, :properties, :finished
def initialize(title)
parse_title(title)
@body = []
@properties = {}
end
def parse_title(title)
@title = title.gsub("** ", "")
end
def append_properties(kv)
@properties.merge!(kv)
end
def append_body(line)
body << line
end
end
class Stream
attr_accessor :text, :builders, :items, :articles
def initialize(text)
@text = text
@items = []
@builders = []
end
def parse
lines = text.split("\n")
parse_line(lines)
end
def parse_line(lines)
lines.each do |line|
if line.start_with?("* ")
append(:article_end)
append(:title_begin)
append(line)
elsif line.start_with?(":PROPERTIES:")
append(:properties_begin)
elsif line.start_with?(":END:")
append(:properties_end)
else
append(line)
end
end
clean_lines(items)
index_of_begin = items.each_index.select { |index| items[index].article_start?}
index_of_end = items.each_index.select { |index| items[index].article_end?}
pair = index_of_begin.zip(index_of_end)
items_of_article = pair.map { |b,e| items[b..e] }
build_articles(items_of_article)
end
def append(object)
items << Item.new(object)
end
def clean_lines(items)
first_article_end_index = items.index {|item| item.article_end?}
items.shift(first_article_end_index + 1)
end
def build_articles(items_of_article)
@articles = items_of_article.map do |items|
builder = Builder.new
builder.parse(items)
builder.article
end
end
end
class Item
attr_accessor :object
def initialize(object)
@object = object
end
def is_symbol?
object.is_a? Symbol
end
def to_finished?
object.start_with?("* DONE")
end
def to_title
object.gsub("* DONE ", "").gsub("* ", "")
end
def article_start?
is_symbol? && object == :title_begin
end
def article_end?
is_symbol? && object == :article_end
end
def to_property
key, *value = object.split(' ')
key, value = key.gsub(':', '').to_sym, value.join(" ")
{ key => value }
end
def to_raw_content
object.to_s
end
end
class Builder
attr_accessor :article, :current_state
def initialize
@article = Article.new
end
def parse(items)
items.each do |item|
if item.is_symbol?
@current_state = item.object
else
build_content(item)
end
end
end
def build_content(item)
case current_state
when :title_begin
article.set_title(item.to_title)
article.set_finished?(item.to_finished?)
when :properties_begin
article.append_properties(item.to_property)
when :properties_end
article.append_body(item.to_raw_content)
end
end
end
class WriterFactory
def self.build(article)
if article.posts.count > 1
return SeriesWriter.new(article)
else
return SingleWriter.new(article)
end
end
end
class SingleWriter
attr_accessor :file_path, :article, :post, :draft, :content
def initialize(article)
@article = article
if article.finished
@post = article.posts.first
@file_path = article.properties[:EXPORT_FILE_NAME]
@draft = !article.finished
@content = @post.body.join("\n")
end
end
def index_url
file_path + "/" + "index.html"
end
def title
post.title
end
def build_pages
[
Page.new(file_path, title, content)
]
end
end
class SeriesWriter
attr_accessor :base_file_path, :base_export_title, :article, :draft, :content, :posts
def initialize(article)
@articles = article
if article.finished
@posts = article.posts
else
@posts = []
end
@base_file_path = article.properties[:BASE_FILE_PATH]
@base_export_title = article.properties[:BASE_EXPORT_TITLE]
end
def index_url
base_file_path
end
def build_pages
index_page_content = []
post_pages = posts.map do |post|
file_path = post.properties[:EXPORT_FILE_NAME]
file_path = base_file_path + '/' + file_path
content = post.body.join("\n")
title = post.title
index_page_content << [file_path, title]
Page.new(file_path, title, content)
end
index_page = Page.new(base_file_path, base_export_title, index_page_content)
post_pages + [index_page]
end
end
class BlogGenerator
attr_accessor :base_path, :pages, :archive_pages
def initialize(base_path, pages)
@base_path = base_path
@pages = pages
@archive_pages = []
end
def generator_archive_page
pages.each do |page|
archive_pages << page if is_root_page(page)
page.set_base_path(base_path)
end
end
def generate!
# write to file system
end
private
def is_root_page(page)
page.file_path.split("/").count == 1
end
end
class Page
attr_accessor :file_path, :title, :content
def initialize(file_path, title, content)
@file_path = file_path
@title = title
@content = content
end
def set_base_path(base_path)
@file_path = base_path + "/" + file_path
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment