diff --git a/README.md b/README.md
index b8dcf18..6094325 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
[![Latest Version](https://img.shields.io/hexpm/v/simple_blog?color=b5a3be&label=Latest+version)](https://hexdocs.pm/simple_blog)
+
# Simple Blog
-
A blog engine written in elixir to generate static blogs from markdown.
@@ -39,7 +39,7 @@ $ mix deps.get
### Generate new blog post
```console
-$ mix simple_blog.post.new "10 tips for new developers"
+$ mix simple_blog.post "10 tips for new developers"
```
The file will be created at `blog/_posts/yyyy-mm-dd-10-tips-for-new-developers.md`.
@@ -50,7 +50,7 @@ The local http server is designed to local development of your blog. To start it
```console
$ mix clean
-$ mix simple_blog.server.start
+$ mix simple_blog.server
```
The server will be running at `http://localhost:4000`.
diff --git a/lib/flat_files.ex b/lib/flat_files.ex
deleted file mode 100644
index e157eac..0000000
--- a/lib/flat_files.ex
+++ /dev/null
@@ -1,21 +0,0 @@
-defmodule FlatFiles do
- def list_all(filepath) do
- _list_all(filepath)
- end
-
- defp _list_all(filepath) do
- cond do
- String.contains?(filepath, ".git") -> []
- true -> expand(File.ls(filepath), filepath)
- end
- end
-
- defp expand({:ok, files}, path) do
- files
- |> Enum.flat_map(&_list_all("#{path}/#{&1}"))
- end
-
- defp expand({:error, _}, path) do
- [path]
- end
-end
diff --git a/lib/mix/tasks/simple_blog/compile.ex b/lib/mix/tasks/simple_blog/compile.ex
index cefd931..cb3138d 100644
--- a/lib/mix/tasks/simple_blog/compile.ex
+++ b/lib/mix/tasks/simple_blog/compile.ex
@@ -3,25 +3,34 @@ defmodule Mix.Tasks.SimpleBlog.Compile do
require Logger
@moduledoc """
- Module responsible for transpile markdown into html
+ Command responsible for transpile markdown into html
"""
+ @doc """
+ Generates a static blog at output folder
+
+ ## Examples
+
+ iex> Mix.Tasks.SimpleBlog.Compile.run([])
+ """
@impl Mix.Task
- def run([]) do
+ def run([]), do: run(["blog", "output"])
+
+ def run([root_directory, output_directory]) do
posts =
- "blog"
+ root_directory
|> SimpleBlog.Reader.Posts.read_from_dir()
|> SimpleBlog.Converter.Posts.markdown_to_html()
|> Enum.map(&SimpleBlog.Post.parse(&1))
index_html =
- File.read("blog/index.html.eex")
+ File.read(root_directory <> "/index.html.eex")
|> SimpleBlog.Converter.Page.exx_to_html(posts)
|> rewrite_stylesheets()
|> rewrite_images()
- File.mkdir("output")
- {:ok, file} = File.open("output/index.html", [:write])
+ File.mkdir(output_directory)
+ {:ok, file} = File.open(output_directory <> "/index.html", [:write])
index_html
|> String.split("\n")
@@ -29,10 +38,10 @@ defmodule Mix.Tasks.SimpleBlog.Compile do
File.close(file)
- File.cp_r("blog/css", "output/css")
- File.cp_r("blog/images", "output/images")
+ File.cp_r(root_directory <> "/css", output_directory <> "/css")
+ File.cp_r(root_directory <> "/images", output_directory <> "/images")
- write_html_posts(posts)
+ write_html_posts(root_directory, output_directory, posts)
end
defp rewrite_stylesheets(html) do
@@ -104,34 +113,33 @@ defmodule Mix.Tasks.SimpleBlog.Compile do
end
end
- defp write_html_posts(posts) do
+ defp write_html_posts(root_directory, output_directory, posts) do
posts
- |> Enum.map(&create_folders/1)
- |> IO.inspect()
+ |> Enum.map(&create_folders(&1, output_directory))
posts
- |> Enum.map(&create_posts_html/1)
+ |> Enum.map(&create_posts_html(&1, root_directory, output_directory))
end
- defp create_folders(post) do
+ defp create_folders(post, output_directory) do
post
- |> SimpleBlog.Post.generate_html_dir("output/posts/")
+ |> SimpleBlog.Post.generate_html_dir(output_directory <> "/posts/")
|> File.mkdir_p()
end
- def create_posts_html(post) do
- dir = SimpleBlog.Post.generate_html_dir(post, "output/posts/")
+ def create_posts_html(post, root_directory, output_directory) do
+ dir = SimpleBlog.Post.generate_html_dir(post, output_directory <> "/posts/")
filename = SimpleBlog.Post.generate_html_filename(post)
postname = SimpleBlog.Post.generate_filename(post)
post =
- "blog"
+ root_directory
|> SimpleBlog.Reader.Posts.read_post(postname)
|> SimpleBlog.Converter.Posts.markdown_to_html()
|> SimpleBlog.Post.parse()
result =
- File.read("blog/post.html.eex")
+ File.read(root_directory <> "/post.html.eex")
|> SimpleBlog.Converter.Page.exx_to_html(post)
|> rewrite_stylesheets_post()
|> rewrite_images_post()
diff --git a/lib/mix/tasks/simple_blog/post/new.ex b/lib/mix/tasks/simple_blog/post.ex
similarity index 72%
rename from lib/mix/tasks/simple_blog/post/new.ex
rename to lib/mix/tasks/simple_blog/post.ex
index 582b7f0..9ce36a5 100644
--- a/lib/mix/tasks/simple_blog/post/new.ex
+++ b/lib/mix/tasks/simple_blog/post.ex
@@ -1,17 +1,17 @@
-defmodule Mix.Tasks.SimpleBlog.Post.New do
+defmodule Mix.Tasks.SimpleBlog.Post do
use Mix.Task
@moduledoc """
- Module responsible for generate a new blog post.
+ Command responsible for generate a new blog post.
"""
@doc """
- It generates a new blog post
+ Generates a new blog post
## Examples
- # iex> Mix.Tasks.SimpleBlog.Post.New.run(["My first blog post"])
- # :ok
+ iex> Mix.Tasks.SimpleBlog.Post.run(["My first blog post"])
+ "Blog post created at blog_test/_posts/yyyy-mm-dd-my-first-blog-post.md"
"""
@impl Mix.Task
def run([]), do: Mix.shell().info(usage())
@@ -35,6 +35,10 @@ defmodule Mix.Tasks.SimpleBlog.Post.New do
IO.binwrite(file, "--->" <> "\n")
File.close(file)
+ Mix.shell().info("""
+ Blog post created at #{full_file_path}
+ """)
+
{:error, :enoent} ->
Mix.shell().info("""
The directory #{root_directory} was not found
@@ -42,6 +46,9 @@ defmodule Mix.Tasks.SimpleBlog.Post.New do
end
end
+ @doc """
+ Returns instructions about command usage
+ """
def usage() do
"""
To generate a new blog post you should pass a title as string:
diff --git a/lib/mix/tasks/simple_blog/server/start.ex b/lib/mix/tasks/simple_blog/server.ex
similarity index 57%
rename from lib/mix/tasks/simple_blog/server/start.ex
rename to lib/mix/tasks/simple_blog/server.ex
index f7d6d23..0518f10 100644
--- a/lib/mix/tasks/simple_blog/server/start.ex
+++ b/lib/mix/tasks/simple_blog/server.ex
@@ -1,11 +1,19 @@
-defmodule Mix.Tasks.SimpleBlog.Server.Start do
+defmodule Mix.Tasks.SimpleBlog.Server do
use Mix.Task
require Logger
@moduledoc """
- Module responsible for a simple http server to allow local working on blog
+ Command responsible for a simple http server to allow local working on blog
"""
+ @doc """
+ Starts a local HTTP server in the port 4000
+
+ ## Examples
+
+ iex> Mix.Tasks.SimpleBlog.Server.run([])
+ "Server running on localhost:4000"
+ """
@impl Mix.Task
def run([]) do
webserver = [
diff --git a/lib/simple_blog.ex b/lib/simple_blog.ex
deleted file mode 100644
index 0537502..0000000
--- a/lib/simple_blog.ex
+++ /dev/null
@@ -1,18 +0,0 @@
-defmodule SimpleBlog do
- @moduledoc """
- Documentation for `SimpleBlog`.
- """
-
- @doc """
- Hello world.
-
- ## Examples
-
- iex> SimpleBlog.hello()
- :world
-
- """
- def hello do
- :world
- end
-end
diff --git a/lib/simple_blog/converter/page.ex b/lib/simple_blog/converter/page.ex
index dd8a10d..ce2a11f 100644
--- a/lib/simple_blog/converter/page.ex
+++ b/lib/simple_blog/converter/page.ex
@@ -1,4 +1,21 @@
defmodule SimpleBlog.Converter.Page do
+ @moduledoc """
+ Module responsible for convert pages from eex to html
+ """
+
+ @doc """
+ Convert eex file to html
+
+ ## Examples
+
+ iex> posts = [%SimpleBlog.Post{title: "post 1"}, %SimpleBlog.Post{title: "post 2"}]
+ iex> SimpleBlog.Converter.Page.exx_to_html({:ok, "<%= for post <- posts do %><%= post.title %><% end %>"}, posts)
+ "post 1post 2"
+
+ iex> post = %SimpleBlog.Post{title: "post 1"}
+ iex> SimpleBlog.Converter.Page.exx_to_html({:ok, "<%= post.title %>"}, post)
+ "post 1"
+ """
def exx_to_html({:ok, body}, posts) when is_list(posts) do
quoted = EEx.compile_string(body)
diff --git a/lib/simple_blog/converter/posts.ex b/lib/simple_blog/converter/posts.ex
index 70c893f..645e8f6 100644
--- a/lib/simple_blog/converter/posts.ex
+++ b/lib/simple_blog/converter/posts.ex
@@ -1,6 +1,24 @@
defmodule SimpleBlog.Converter.Posts do
require Earmark
+ @moduledoc """
+ Module responsible for convert posts from markdown to html
+ """
+
+ @doc """
+ Convert markdown file to html
+
+ ## Examples
+
+ iex> SimpleBlog.Converter.Posts.markdown_to_html(["# post1", "# post2"])
+ ["
\\npost1
\\n", "\\npost2
\\n"]
+
+ iex> SimpleBlog.Converter.Posts.markdown_to_html("# post1")
+ "\\npost1
\\n"
+
+ iex> SimpleBlog.Converter.Posts.markdown_to_html([])
+ []
+ """
def markdown_to_html([]), do: []
def markdown_to_html(files) when is_list(files) do
diff --git a/lib/simple_blog/post.ex b/lib/simple_blog/post.ex
index bab981e..040cb53 100644
--- a/lib/simple_blog/post.ex
+++ b/lib/simple_blog/post.ex
@@ -5,15 +5,15 @@ defmodule SimpleBlog.Post do
@extension "md"
- defstruct title: "", tags: [], body: "", date: "", filename: ""
+ defstruct title: "", body: "", date: "", filename: ""
@doc """
- Generate filename for blog post
+ Generate directory name for blog post
## Examples
- iex> SimpleBlog.Post.generate_filename(%SimpleBlog.Post{ title: "My first blog post", date: ~D[2023-10-04] })
- "2023-10-04-my-first-blog-post.md"
+ iex> SimpleBlog.Post.generate_filename(%SimpleBlog.Post{ title: "My first blog post", date: ~D[2023-10-04]})
+ "2023-10-04-my-first-blog-post.md"
"""
def generate_filename(%SimpleBlog.Post{title: title, date: date}) do
normalized_title =
@@ -24,6 +24,18 @@ defmodule SimpleBlog.Post do
"#{date}-#{normalized_title}.#{@extension}"
end
+ @doc """
+ Parse comment with data into %SimpleBlog.Post struct
+
+ ## Examples
+ iex> body = ""
+ iex> SimpleBlog.Post.parse(body)
+ %SimpleBlog.Post{body: body, title: "Dev onboarding", date: "2023-10-25", filename: "2023-10-25-dev-onboarding.md"}
+ """
def parse(body) do
[_, filename_line, title_line, date_line | _] = String.split(body, "\n")
@@ -34,14 +46,31 @@ defmodule SimpleBlog.Post do
%SimpleBlog.Post{body: body, title: title, date: date, filename: filename}
end
+ @doc """
+ Generate filename for blog post
+
+ ## Examples
+
+ iex> SimpleBlog.Post.generate_html_dir(%SimpleBlog.Post{date: "2023-10-04"}, "output")
+ "output/2023/10/04/"
+ """
def generate_html_dir(%SimpleBlog.Post{date: date}, base_dir) do
[year, month, day] = String.split(date, "-")
base_dir <> "/" <> year <> "/" <> month <> "/" <> day <> "/"
end
+ @doc """
+ Generate html filename for blog post
+
+ ## Examples
+
+ iex> SimpleBlog.Post.generate_html_filename(%SimpleBlog.Post{title: "doctests with elixir"})
+ "doctests-with-elixir.html"
+ """
def generate_html_filename(%SimpleBlog.Post{title: title}) do
title
|> String.replace(" ", "-")
+ |> String.downcase()
|> Kernel.<>(".html")
end
end
diff --git a/lib/simple_blog/reader/posts.ex b/lib/simple_blog/reader/posts.ex
index fed5457..0181dca 100644
--- a/lib/simple_blog/reader/posts.ex
+++ b/lib/simple_blog/reader/posts.ex
@@ -1,6 +1,16 @@
defmodule SimpleBlog.Reader.Posts do
- require Logger
+ @moduledoc """
+ Module responsible for read blog posts
+ """
+ @doc """
+ Reads content from markdown files located in _posts
+
+ ## Examples
+
+ iex> SimpleBlog.Reader.Posts.read_from_dir("blog")
+ ["## post title 1", "## post title 2"]
+ """
def read_from_dir(root_directory) do
posts_directory = root_directory <> "/_posts/"
@@ -10,6 +20,14 @@ defmodule SimpleBlog.Reader.Posts do
end
end
+ @doc """
+ Reads content from markdown file for specific post
+
+ ## Examples
+
+ iex> SimpleBlog.Reader.Posts.read_post("blog", "2023-10-25-metaprogramming-in-ruby.md")
+ "### Metaprogramming in ruby"
+ """
def read_post(root_directory, post) do
posts_directory = root_directory <> "/_posts/"
post_path = posts_directory <> post
diff --git a/lib/simple_blog/server.ex b/lib/simple_blog/server.ex
index 8d0ace5..4ff43ee 100644
--- a/lib/simple_blog/server.ex
+++ b/lib/simple_blog/server.ex
@@ -7,10 +7,20 @@ defmodule SimpleBlog.Server do
require Logger
require Earmark
+ @moduledoc """
+ Module responsible for handle HTTP requests
+ """
+
+ @doc """
+ Initializes server passing initial params
+ """
def init(_options) do
Logger.info("Initializing server ...")
end
+ @doc """
+ Handle HTTP requests.
+ """
def call(%Plug.Conn{request_path: "/posts/", query_string: query_string} = conn, _opts) do
postname =
query_string
diff --git a/mix.exs b/mix.exs
index 4fd77da..a4ea9bb 100644
--- a/mix.exs
+++ b/mix.exs
@@ -34,7 +34,7 @@ defmodule SimpleBlog.MixProject do
[
{:plug_cowboy, "~> 2.0"},
{:earmark, "~> 1.4"},
- {:ex_doc, "~> 0.27", only: :dev, runtime: false},
+ {:ex_doc, "~> 0.27", only: :dev, runtime: false}
# {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
]
diff --git a/test/mix/tasks/simple_blog/compile_test.exs b/test/mix/tasks/simple_blog/compile_test.exs
new file mode 100644
index 0000000..9768da3
--- /dev/null
+++ b/test/mix/tasks/simple_blog/compile_test.exs
@@ -0,0 +1,43 @@
+defmodule Mix.Tasks.SimpleBlog.CompileTest do
+ use ExUnit.Case
+
+ describe "run/1" do
+ setup do
+ on_exit(fn -> File.rm_rf("output_test") end)
+ end
+
+ test "create a directory to static blog" do
+ Mix.Tasks.SimpleBlog.Compile.run(["blog_test", "output_test"])
+ assert File.exists?("output_test")
+ end
+
+ test "converts posts to html" do
+ Mix.Tasks.SimpleBlog.Post.run(["My First Blog Post", "blog_test"])
+ Mix.Tasks.SimpleBlog.Compile.run(["blog_test", "output_test"])
+
+ today = Date.utc_today() |> Date.to_string()
+ dir = SimpleBlog.Post.generate_html_dir(%SimpleBlog.Post{date: today}, "output_test/posts")
+
+ assert File.exists?(dir <> "my-first-blog-post.html")
+
+ on_exit(fn -> File.rm("blog_test/_posts/#{today}-my-first-blog-post.md") end)
+ end
+
+ test "creates css files" do
+ Mix.Tasks.SimpleBlog.Compile.run(["blog_test", "output_test"])
+ css_dir = "output_test/css/"
+
+ assert File.exists?(css_dir <> "_solarized-light.css")
+ assert File.exists?(css_dir <> "plain.css")
+ assert File.exists?(css_dir <> "reset.css")
+ assert File.exists?(css_dir <> "style.css")
+ end
+
+ test "creates images files" do
+ Mix.Tasks.SimpleBlog.Compile.run(["blog_test", "output_test"])
+ images_dir = "output_test/images/"
+
+ assert File.exists?(images_dir <> "avatar.png")
+ end
+ end
+end
diff --git a/test/mix/tasks/simple_blog/post/new_test.exs b/test/mix/tasks/simple_blog/post_test.exs
similarity index 50%
rename from test/mix/tasks/simple_blog/post/new_test.exs
rename to test/mix/tasks/simple_blog/post_test.exs
index ffffe1e..a04ff34 100644
--- a/test/mix/tasks/simple_blog/post/new_test.exs
+++ b/test/mix/tasks/simple_blog/post_test.exs
@@ -1,7 +1,7 @@
-defmodule Mix.Tasks.SimpleBlog.Post.NewTest do
+defmodule Mix.Tasks.SimpleBlog.PostTest do
use ExUnit.Case
import ExUnit.CaptureIO
- doctest Mix.Tasks.SimpleBlog.Post.New
+ # doctest Mix.Tasks.SimpleBlog.Post.New
@instructions """
To generate a new blog post you should pass a title as string:
@@ -11,13 +11,25 @@ defmodule Mix.Tasks.SimpleBlog.Post.NewTest do
describe "run" do
test "returns instructions for no arguments" do
- message = capture_io(fn -> Mix.Tasks.SimpleBlog.Post.New.run([]) end)
+ message = capture_io(fn -> Mix.Tasks.SimpleBlog.Post.run([]) end)
assert message == "#{@instructions}\n"
end
test "show success message for created blog post" do
- Mix.Tasks.SimpleBlog.Post.New.run(["My First Blog Post", "blog_test"])
+ message =
+ capture_io(fn ->
+ Mix.Tasks.SimpleBlog.Post.run(["My First Blog Post", "blog_test"])
+ end)
+
+ today = Date.utc_today() |> Date.to_string()
+
+ assert message == "Blog post created at blog_test/_posts/#{today}-my-first-blog-post.md\n\n"
+ on_exit(fn -> File.rm("blog_test/_posts/#{today}-my-first-blog-post.md") end)
+ end
+
+ test "creates a new markdown file for blog post" do
+ Mix.Tasks.SimpleBlog.Post.run(["My First Blog Post", "blog_test"])
today = Date.utc_today() |> Date.to_string()
@@ -27,7 +39,7 @@ defmodule Mix.Tasks.SimpleBlog.Post.NewTest do
test "show error message when blog does not exist" do
message =
- capture_io(fn -> Mix.Tasks.SimpleBlog.Post.New.run(["My First Blog Post", "invalid"]) end)
+ capture_io(fn -> Mix.Tasks.SimpleBlog.Post.run(["My First Blog Post", "invalid"]) end)
assert message == "The directory invalid was not found\n\n"
end
@@ -35,7 +47,7 @@ defmodule Mix.Tasks.SimpleBlog.Post.NewTest do
describe "usage" do
test "returns instructions" do
- assert Mix.Tasks.SimpleBlog.Post.New.usage() == @instructions
+ assert Mix.Tasks.SimpleBlog.Post.usage() == @instructions
end
end
end
diff --git a/test/simple_blog/reader/posts_test.exs b/test/simple_blog/reader/posts_test.exs
index 880c001..d4800a6 100644
--- a/test/simple_blog/reader/posts_test.exs
+++ b/test/simple_blog/reader/posts_test.exs
@@ -2,8 +2,8 @@ defmodule SimpleBlog.Reader.PostsTest do
use ExUnit.Case
setup do
- Mix.Tasks.SimpleBlog.Post.New.run(["my first job day", "blog_test"])
- Mix.Tasks.SimpleBlog.Post.New.run(["10 tips for a junior develop", "blog_test"])
+ Mix.Tasks.SimpleBlog.Post.run(["my first job day", "blog_test"])
+ Mix.Tasks.SimpleBlog.Post.run(["10 tips for a junior develop", "blog_test"])
on_exit(fn ->
{:ok, files} = File.ls("blog_test/_posts/")
diff --git a/test/simple_blog_test.exs b/test/simple_blog_test.exs
deleted file mode 100644
index b0920bc..0000000
--- a/test/simple_blog_test.exs
+++ /dev/null
@@ -1,8 +0,0 @@
-defmodule SimpleBlogTest do
- use ExUnit.Case
- doctest SimpleBlog
-
- test "greets the world" do
- assert SimpleBlog.hello() == :world
- end
-end