Whoops, forgot to commit. All kinds of stuff here.

This commit is contained in:
Mikko Ahlroth 2015-05-17 18:43:33 +03:00
parent a201d44dce
commit 0bed780c14
17 changed files with 320 additions and 55 deletions

View file

@ -24,4 +24,6 @@ use Mix.Config
# import_config "#{Mix.env}.exs"
config :mebe_engine,
data_path: "CONFIGURE" # The path to crawl post and page data from. Relative to this file's path
# The path to crawl post and page data from. No trailing slash, use an absolute path.
data_path: "/path/to/data"

View file

@ -3,6 +3,7 @@ defmodule MebeEngine.Crawler do
The crawler goes through the specified directory, opening and parsing all the matching files
inside concurrently.
"""
require Logger
alias MebeEngine.Parser
@ -17,7 +18,14 @@ defmodule MebeEngine.Crawler do
end
def get_files(path) do
Path.wildcard path <> "/**/*.md"
path = path <> "/**/*.md"
Logger.info "Searching files using '#{path}' with cwd '#{System.cwd}'"
files = Path.wildcard path
Logger.info "Found files:"
for file <- files do
Logger.info file
end
files
end
def parse(file) do

View file

@ -19,21 +19,41 @@ defmodule MebeEngine.DB do
@tag_table :tags
def init() do
:ets.new @meta_table, [:named_table, :set, :protected, read_concurrency: true]
:ets.new @page_table, [:named_table, :set, :protected, read_concurrency: true]
:ets.new @post_table, [:named_table, :ordered_set, :protected, read_concurrency: true]
:ets.new @single_post_table, [:named_table, :set, :protected, read_concurrency: true]
:ets.new @tag_table, [:named_table, :ordered_set, :protected, read_concurrency: true]
# Only create tables if they don't exist already
if (:ets.info @meta_table) == :undefined do
:ets.new @meta_table, [:named_table, :set, :protected, read_concurrency: true]
:ets.new @page_table, [:named_table, :set, :protected, read_concurrency: true]
:ets.new @post_table, [:named_table, :ordered_set, :protected, read_concurrency: true]
:ets.new @single_post_table, [:named_table, :set, :protected, read_concurrency: true]
:ets.new @tag_table, [:named_table, :ordered_set, :protected, read_concurrency: true]
end
end
def destroy() do
:ets.delete_all_objects @meta_table
:ets.delete_all_objects @page_table
:ets.delete_all_objects @post_table
:ets.delete_all_objects @single_post_table
:ets.delete_all_objects @tag_table
end
def insert_count(key, count) do
:ets.insert @meta_table, {key, count}
end
def insert_post(post) do
{year, month, day} = post.date
:ets.insert @post_table, {{year, month, day, post.order}, post}
:ets.insert @single_post_table, {{year, month, day, post.slug}, post}
def insert_posts(posts) do
ordered_posts = Enum.map posts, fn post ->
{year, month, day} = post.date
{{year, month, day, post.order}, post}
end
single_posts = Enum.map posts, fn post ->
{year, month, day} = post.date
{{year, month, day, post.slug}, post}
end
:ets.insert @post_table, ordered_posts
:ets.insert @single_post_table, single_posts
end
def insert_page(page) do
@ -47,20 +67,26 @@ defmodule MebeEngine.DB do
def get_reg_posts(first, last) do
{result, _} = :ets.select_reverse @post_table, [{:"$1", [], [:"$_"]}], first + last
{_, result} = Enum.split result, first
ets_to_data result
get_post_list @post_table, [{:"$1", [], [:"$_"]}], first, last
end
def get_tag_posts(tag, first, last) do
{result, _} = :ets.select_reverse @tag_table, [{{{tag, :_, :_, :_, :_}, :"$1"}, [], [:"$_"]}], first + last
{_, result} = Enum.split result, first
ets_to_data result
get_post_list @tag_table, [{{{tag, :_, :_, :_, :_}, :"$1"}, [], [:"$_"]}], first, last
end
def get_count(type) do
[{_, count}] = :ets.match_object @meta_table, {type, :"$1"}
count
def get_year_posts(year, first, last) do
get_post_list @post_table, [{{{year, :_, :_, :_}, :"$1"}, [], [:"$_"]}], first, last
end
def get_month_posts(year, month, first, last) do
get_post_list @post_table, [{{{year, month, :_, :_}, :"$1"}, [], [:"$_"]}], first, last
end
def get_page(slug) do
case :ets.match_object @page_table, {slug, :"$1"} do
[{_, page}] -> page
_ -> nil
end
end
def get_post(year, month, day, slug) do
@ -70,14 +96,27 @@ defmodule MebeEngine.DB do
end
end
def get_pages() do
:ets.match(@page_table, :"$1")
|> ets_to_data
def get_count(type) do
case :ets.match_object @meta_table, {type, :"$1"} do
[{_, count}] -> count
[] -> 0
end
end
# Combine error handling of different post listing functions
defp get_post_list(table, matchspec, first, last) do
case :ets.select_reverse table, matchspec, first + last do
:"$end_of_table" -> []
{result, _} ->
{_, result} = Enum.split result, first
ets_to_data result
end
end
# Remove key from data returned from ETS
defp ets_to_data(data) do
Enum.map data, fn {_, actual} -> actual end

View file

@ -1,13 +1,15 @@
defmodule MebeEngine do
use Application
def worker_name, do: :mebe_db_worker
# See http://elixir-lang.org/docs/stable/elixir/Application.html
# for more information on OTP Applications
def start(_type, _args) do
import Supervisor.Spec, warn: false
children = [
worker(MebeEngine.Worker, [])
worker(MebeEngine.Worker, [[name: worker_name]])
]
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html

View file

@ -8,6 +8,8 @@ defmodule MebeEngine.Worker do
alias MebeEngine.Crawler
alias MebeEngine.DB
@data_path Application.get_env(:mebe_engine, :data_path)
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, :ok, opts)
end
@ -17,11 +19,24 @@ defmodule MebeEngine.Worker do
{:ok, nil}
end
def handle_call(:refresh, _from, nil) do
refresh_db
{:reply, :ok, nil}
end
def refresh_db() do
Logger.info "Destroying database…"
DB.destroy
Logger.info "Reloading database…"
load_db
Logger.info "Update done!"
end
@doc """
Initialize the database by crawling the configured path and parsing data to the DB.
"""
def load_db() do
Logger.info "Loading post database…"
Logger.info "Loading post database from '#{@data_path}'"
%{
pages: pages,
@ -29,13 +44,13 @@ defmodule MebeEngine.Worker do
tags: tags,
years: years,
months: months,
} = MebeEngine.Crawler.crawl Application.get_env(:mebe_engine, :data_path)
} = Crawler.crawl @data_path
Logger.info "Loaded #{Enum.count pages} pages and #{Enum.count posts} posts."
DB.init
Enum.each posts, fn post -> DB.insert_post post end
DB.insert_posts posts
DB.insert_count :all, Enum.count posts
Enum.each Map.keys(pages), fn page -> DB.insert_page pages[page] end

View file

@ -9,14 +9,35 @@ use Mix.Config
config :mebe_web, MebeWeb.Endpoint,
url: [host: "localhost"],
root: Path.expand("..", __DIR__),
secret_key_base: "CONFIGURE",
debug_errors: false
secret_key_base: "SECRET",
debug_errors: false,
pubsub: [name: MebeWeb.PubSub,
adapter: Phoenix.PubSub.PG2]
config :blog_config,
blog_name: "CONFIGURE",
blog_author: "CONFIGURE",
blog_name: "My awesome blog",
blog_author: "Author McAuthor",
posts_per_page: 10
posts_per_page: 10,
# Disqus comments
disqus_comments: false, # Use Disqus comments
page_commenting: false, # Show comments for pages too
disqus_shortname: "my-awesome-blog",
# Extra HTML that is injected to every page, right before </body>. Useful for analytics scripts.
extra_html: """
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'my-googleanalytics-id', 'auto');
ga('send', 'pageview');
</script>
"""
# Configures Elixir's Logger
config :logger, :console,

View file

@ -34,6 +34,69 @@ defmodule MebeWeb.PageController do
)
end
def year(conn, params) do
%{"year" => year} = params
case Integer.parse year do
{year, _} ->
conn
|> assign(:year, year)
|> render_page(
:year,
[year],
fn first, last -> DB.get_year_posts(year, first, last) end,
DB.get_count(year),
"year.html",
params
)
_ ->
render_404 conn
end
end
def month(conn, params) do
%{"year" => year, "month" => month} = params
case {
Integer.parse(year),
Integer.parse(month)
} do
{{year, _}, {month, _}} ->
conn
|> assign(:year, year)
|> assign(:month, month)
|> render_page(
:month,
[year, month],
fn first, last -> DB.get_month_posts(year, month, first, last) end,
DB.get_count({year, month}),
"month.html",
params
)
_ ->
render_404 conn
end
end
def page(conn, %{"slug" => slug}) do
case DB.get_page slug do
nil ->
render_404 conn
page ->
conn
|> insert_config
|> assign(:page, page)
|> render("page.html")
end
end
def post(conn, %{"year" => year, "month" => month, "day" => day, "slug" => slug}) do
case {
@ -44,20 +107,17 @@ defmodule MebeWeb.PageController do
{{y_int, _}, {m_int, _}, {d_int, _}} ->
case DB.get_post y_int, m_int, d_int, slug do
nil ->
conn
|> put_status(:not_found)
|> render(MebeWeb.ErrorView, :"404")
render_404 conn
post ->
conn
|> insert_config
|> assign(:post, post)
|> render("post.html")
end
_ ->
conn
|> put_status(:not_found)
|> render(MebeWeb.ErrorView, :"404")
render_404 conn
end
end
@ -72,18 +132,16 @@ defmodule MebeWeb.PageController do
case postgetter.(first, last) do
[] ->
conn
|> put_status(:not_found)
|> render(MebeWeb.ErrorView, :"404")
render_404 conn
posts ->
conn
|> insert_config
|> assign(:posts, posts)
|> assign(:total_count, total_count)
|> assign(:page, page)
|> assign(:current_page, page)
|> assign(:page_type, page_type)
|> assign(:page_args, page_args)
|> assign(:posts_per_page, @posts_per_page)
|> render(template)
end
end
@ -104,4 +162,22 @@ defmodule MebeWeb.PageController do
@posts_per_page
}
end
defp render_404(conn) do
conn
|> insert_config
|> put_status(:not_found)
|> render(MebeWeb.ErrorView, :"404")
end
# Insert config variables to the assigns table for the connection
defp insert_config(conn) do
conn
|> assign(:blog_name, Application.get_env(:blog_config, :blog_name))
|> assign(:blog_author, Application.get_env(:blog_config, :blog_author))
|> assign(:posts_per_page, @posts_per_page)
|> assign(:disqus_comments, Application.get_env(:blog_config, :disqus_comments))
|> assign(:page_commenting, Application.get_env(:blog_config, :page_commenting))
|> assign(:disqus_shortname, Application.get_env(:blog_config, :disqus_shortname))
end
end

View file

@ -39,6 +39,13 @@ td, th {
}
#disqus_thread {
border-top: 1px solid #444;
margin-top: 30px;
padding-top: 20px;
}
/* Fix heading */
.navbar-default {
width: 100%;

View file

@ -6,6 +6,6 @@
</p>
<p>
<a href="<%= page_path @conn, :index %>">Go away</a>.
<a href="<%= page_path @conn, :index, 1 %>">Go away</a>.
</p>
</div>

View file

@ -7,7 +7,7 @@
<meta name="description" content="">
<meta name="author" content="">
<title>Hello Phoenix!</title>
<title><%= title @conn %></title>
<link rel="stylesheet" href="<%= static_path(@conn, "/css/app.css") %>">
</head>
@ -47,5 +47,7 @@
</div> <!-- /container -->
<script src="<%= static_path(@conn, "/js/app.js") %>"></script>
<script src="<%= static_path(@conn, "/js/old_hash_redirector.js") %>"></script>
<%= raw extra_html %>
</body>
</html>

View file

@ -0,0 +1,9 @@
<% month = String.rjust(Integer.to_string(@month), 2, ?0) %>
<div class="jumbotron">
<p>
Viewing posts for the month <em><%= @year %>-<%= month %></em>.
</p>
</div>
<%= render "postlist.html", assigns %>

View file

@ -0,0 +1,28 @@
<div class="post page">
<div class="post-header">
<h1><%= @page.title %></h1>
</div>
<div class="post-content">
<%= raw @page.content %>
</div>
<%= if @disqus_comments and @page_commenting do %>
<div id="disqus_thread"></div>
<script type="text/javascript">
/* * * CONFIGURATION VARIABLES * * */
var disqus_shortname = "<%= @disqus_shortname %>";
var disqus_title = "<%= @page.title %>";
/* * * DON'T EDIT BELOW THIS LINE * * */
(function() {
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
</script>
<noscript>
Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a>
</noscript>
<% end %>
</div>

View file

@ -1 +1 @@
<%= render "post_elem.html", conn: @conn, post: @post, short: false %>
<%= render "post_elem.html", Map.put(assigns, :short, false) %>

View file

@ -9,7 +9,6 @@
%>
<div class="post">
<div class="post-header">
<%= if @short do %>
@ -22,7 +21,8 @@
<div class="post-meta">
<span>
Posted on <%= year %>-<%= j_month %>-<%= j_day %>.
Posted on
<a href="<%= page_path @conn, :year, year, 1 %>"><%= year %>-<%= j_month %>-<%= j_day %></a>.
</span>
<%= if @post.tags do %>
@ -41,7 +41,13 @@
<%= if @short do %>
<div class="post-content post-short-content">
<%= raw @post.short_content %>
<%= raw @post.short_content %>
<p>
<a href="<%= page_path @conn, :post, year, j_month, j_day, @post.slug %>">
Read more
</a>
</p>
</div>
<%= else %>
<div class="post-content">
@ -49,4 +55,23 @@
</div>
<% end %>
<%= if not @short and @disqus_comments do %>
<div id="disqus_thread"></div>
<script type="text/javascript">
/* * * CONFIGURATION VARIABLES * * */
var disqus_shortname = "<%= @disqus_shortname %>";
var disqus_title = "<%= @post.title %>";
/* * * DON'T EDIT BELOW THIS LINE * * */
(function() {
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
</script>
<noscript>
Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a>
</noscript>
<% end %>
</div>

View file

@ -1,7 +1,7 @@
<%= for post <- @posts do %>
<div class="row">
<div class="col-xs-10 col-xs-push-1">
<%= render "post_elem.html", conn: @conn, post: post, short: true %>
<%= render "post_elem.html", Map.merge(assigns, %{post: post, short: true}) %>
</div>
</div>
<% end %>
@ -14,20 +14,20 @@
<%= if @total_count > @posts_per_page do %>
<nav>
<ul class="pagination">
<%= if (@page - 1) < 1 do %>
<%= if (@current_page - 1) < 1 do %>
<li class="disabled">
<span>&laquo;</span>
</li>
<%= else %>
<li>
<a href="<%= apply(MebeWeb.Router.Helpers, :page_path, pagination_args ++ [@page - 1]) %>">
<a href="<%= apply(MebeWeb.Router.Helpers, :page_path, pagination_args ++ [@current_page - 1]) %>">
&laquo;
</a>
</li>
<% end %>
<%= for page_no <- 1..total_pages do %>
<%= if page_no === @page do %>
<%= if page_no === @current_page do %>
<li class="active">
<%= else %>
<li>
@ -39,13 +39,13 @@
</li>
<% end %>
<%= if (@page + 1) > total_pages do %>
<%= if (@current_page + 1) > total_pages do %>
<li class="disabled">
<span>&raquo;</span>
</li>
<%= else %>
<li>
<a href="<%= apply(MebeWeb.Router.Helpers, :page_path, pagination_args ++ [@page + 1]) %>">
<a href="<%= apply(MebeWeb.Router.Helpers, :page_path, pagination_args ++ [@current_page + 1]) %>">
&raquo;
</a>
</li>

View file

@ -0,0 +1,7 @@
<div class="jumbotron">
<p>
Viewing posts for the year <em><%= @year %></em>.
</p>
</div>
<%= render "postlist.html", assigns %>

View file

@ -1,3 +1,27 @@
defmodule MebeWeb.LayoutView do
use MebeWeb.Web, :view
@default_title Application.get_env(:blog_config, :blog_name)
@extra_html Application.get_env(:blog_config, :extra_html)
@doc """
Resolve title from the current assigns. Will use the current page's or post's title, if available.
Otherwise will render default title.
"""
def title(conn) do
prefix = cond do
conn.assigns[:post] ->
conn.assigns[:post].title
conn.assigns[:page] ->
conn.assigns[:page].title
true -> @default_title
end
end
@doc """
Return the extra HTML defined in the config.
"""
def extra_html(), do: @extra_html
end