Rewrite tasks to use new macro style, rip library to separate repository

This commit is contained in:
Mikko Ahlroth 2017-03-21 07:50:09 +02:00
parent 5af3350fc0
commit 1c15eda4fa
18 changed files with 116 additions and 555 deletions

View file

@ -1,72 +0,0 @@
defmodule AppRunner do
def wait_for_eof() do
case IO.getn("", 1024) do
:eof -> nil
_ -> wait_for_eof()
end
end
def exec(program, args) do
Port.open(
{:spawn_executable, program},
[
:exit_status,
:stderr_to_stdout, # Redirect stderr to stdout to log properly
args: args,
line: 1024
]
)
end
def get_pid(port) do
case Port.info(port) do
nil ->
nil
info when is_list(info) ->
case Keyword.get(info, :os_pid) do
nil -> nil
pid -> Integer.to_string(pid)
end
end
end
def kill(pid) do
System.find_executable("kill") |> System.cmd([pid])
end
def wait_loop(port) do
receive do
{_, {:data, {:eol, msg}}} ->
msg |> :unicode.characters_to_binary(:unicode) |> IO.puts()
{_, {:data, {:noeol, msg}}} ->
msg |> :unicode.characters_to_binary(:unicode) |> IO.write()
{_, :eof_received} ->
get_pid(port) |> kill()
:erlang.halt(0)
{_, :closed} ->
:erlang.halt(0)
{_, {:exit_status, status}} ->
:erlang.halt(status)
{:EXIT, _, _} ->
:erlang.halt(1)
end
wait_loop(port)
end
end
[program | args] = System.argv()
port = AppRunner.exec(program, args)
Task.async(fn ->
AppRunner.wait_for_eof()
:eof_received
end)
AppRunner.wait_loop(port)

View file

@ -1,14 +1,16 @@
defmodule Mix.Tasks.Frontend.Build.Assets do defmodule Mix.Tasks.Frontend.Build.Assets do
use Mix.Task use FBU.BuildTask
import MebeWeb.{FrontendConfs} import MebeWeb.FrontendConfs
@shortdoc "Copy other assets to target dir" @shortdoc "Copy other assets to target dir"
def run(_) do task _ do
in_path = Path.join([node_path(), "bootstrap-sass", "assets", "fonts", "bootstrap"])
# Ensure target path exists # Ensure target path exists
out_path = "#{dist_path()}/fonts" out_path = Path.join([dist_path(), "fonts"])
File.mkdir_p!(out_path) File.mkdir_p!(out_path)
File.cp_r!("#{node_path()}/bootstrap-sass/assets/fonts/bootstrap", out_path) File.cp_r!(in_path, out_path)
end end
end end

View file

@ -1,12 +1,13 @@
defmodule Mix.Tasks.Frontend.Build.Css.Compile do defmodule Mix.Tasks.Frontend.Build.Css.Compile do
use Mix.Task use FBU.BuildTask
import MebeWeb.{TaskUtils, FrontendConfs} import MebeWeb.FrontendConfs
import FBU.TaskUtils
@shortdoc "Build the SCSS sources" @shortdoc "Build the SCSS sources"
def bin(), do: node_bin("node-sass") def bin(), do: node_bin("node-sass")
def out_path(), do: "#{tmp_path()}/compiled/css" def out_path(), do: Path.join([tmp_path(), "compiled", "css"])
def args(), do: [ def args(), do: [
"-o", "-o",
@ -14,14 +15,14 @@ defmodule Mix.Tasks.Frontend.Build.Css.Compile do
"--source-map", "--source-map",
"true", "true",
"--include-path", "--include-path",
"#{node_path()}/bootstrap-sass/assets/stylesheets", Path.join([node_path(), "boostrap-sass", "assets", "stylesheets"]),
"--precision", "--precision",
"8" "8"
] ]
def scss_file(), do: "#{src_path()}/css/app.scss" def scss_file(), do: Path.join([src_path(), "css", "app.scss"])
def run(_) do task _ do
bin() |> exec(args() ++ [scss_file()]) |> listen() bin() |> exec(args() ++ [scss_file()]) |> listen()
end end
end end

View file

@ -1,20 +1,18 @@
defmodule Mix.Tasks.Frontend.Build.Css.Copy do defmodule Mix.Tasks.Frontend.Build.Css.Copy do
use Mix.Task use FBU.BuildTask
import MebeWeb.{TaskUtils, FrontendConfs} import MebeWeb.FrontendConfs
@shortdoc "Copy compiled CSS to target dir" @shortdoc "Copy compiled CSS to target dir"
def do_copy() do @deps [
"frontend.build.css.compile"
]
task _ do
# Ensure target path exists # Ensure target path exists
out_path = "#{dist_path()}/css" out_path = Path.join([dist_path(), "css"])
File.mkdir_p!(out_path) File.mkdir_p!(out_path)
File.cp_r!(Mix.Tasks.Frontend.Build.Css.Compile.out_path(), out_path) File.cp_r!(Mix.Tasks.Frontend.Build.Css.Compile.out_path(), out_path)
end end
def run(_) do
run_task("frontend.build.css.compile")
do_copy()
end
end end

View file

@ -1,15 +1,15 @@
defmodule Mix.Tasks.Frontend.Build.Css do defmodule Mix.Tasks.Frontend.Build.Css do
use Mix.Task use FBU.BuildTask
import MebeWeb.TaskUtils import FBU.TaskUtils
@shortdoc "Build the frontend CSS" @shortdoc "Build the frontend CSS"
def run(_) do task _ do
task = case Mix.env() do todo = case Mix.env() do
:prod -> "frontend.build.css.minify" :prod -> "frontend.build.css.minify"
_ -> "frontend.build.css.copy" _ -> "frontend.build.css.copy"
end end
run_task(task) run_task(todo)
end end
end end

View file

@ -1,17 +1,20 @@
defmodule Mix.Tasks.Frontend.Build.Css.Minify do defmodule Mix.Tasks.Frontend.Build.Css.Minify do
use Mix.Task use FBU.BuildTask
import MebeWeb.{TaskUtils, FrontendConfs} import MebeWeb.FrontendConfs
import FBU.TaskUtils
@shortdoc "Minify built CSS files" @shortdoc "Minify built CSS files"
@preferred_cli_env :prod @preferred_cli_env :prod
def run(_) do @deps [
run_task("frontend.build.css.compile") "frontend.build.css.compile"
]
task _ do
in_path = Mix.Tasks.Frontend.Build.Css.Compile.out_path() in_path = Mix.Tasks.Frontend.Build.Css.Compile.out_path()
in_file = "#{in_path}/app.css" in_file = Path.join([in_path, "app.css"])
out_path = "#{dist_path()}/css" out_path = Path.join([dist_path(), "css"])
out_file = "#{out_path}/app.css" out_file = Path.join(out_path, "app.css")
File.mkdir_p!(out_path) File.mkdir_p!(out_path)
@ -21,7 +24,7 @@ defmodule Mix.Tasks.Frontend.Build.Css.Minify do
in_file, in_file,
out_file, out_file,
"--sourcemap", "--sourcemap",
"#{out_path}/app.css.map" Path.join([out_path, "app.css.map"])
] ]
) |> listen() ) |> listen()

View file

@ -1,12 +1,14 @@
defmodule Mix.Tasks.Frontend.Build do defmodule Mix.Tasks.Frontend.Build do
use Mix.Task use FBU.BuildTask
import MebeWeb.TaskUtils import FBU.TaskUtils
@shortdoc "Build the frontend" @shortdoc "Build the frontend"
def run(_) do @deps [
run_task("frontend.clean") "frontend.clean"
]
task _ do
run_tasks([ run_tasks([
"frontend.build.js", "frontend.build.js",
"frontend.build.css", "frontend.build.css",

View file

@ -1,12 +1,17 @@
defmodule Mix.Tasks.Frontend.Build.Js.Bundle do defmodule Mix.Tasks.Frontend.Build.Js.Bundle do
use Mix.Task use FBU.BuildTask
import MebeWeb.{TaskUtils, FrontendConfs} import MebeWeb.FrontendConfs
import FBU.TaskUtils
@shortdoc "Bundle the JavaScript sources into app.js" @shortdoc "Bundle the JavaScript sources into app.js"
@deps [
"frontend.build.js.transpile"
]
def bin(), do: node_bin("rollup") def bin(), do: node_bin("rollup")
def out_path(), do: "#{tmp_path()}/bundled/js" def out_path(), do: Path.join([tmp_path(), "bundled", "js"])
def args() do def args() do
op = out_path() op = out_path()
@ -15,23 +20,17 @@ defmodule Mix.Tasks.Frontend.Build.Js.Bundle do
"--config", "--config",
"rollup.config.js", "rollup.config.js",
"--input", "--input",
"#{Mix.Tasks.Frontend.Build.Js.Transpile.out_path()}/app.js", Path.join([Mix.Tasks.Frontend.Build.Js.Transpile.out_path(), "app.js"]),
"--output", "--output",
"#{op}/app.js", Path.join([op, "app.js"]),
"--format", "--format",
"cjs", "cjs",
"--sourcemap", "--sourcemap",
"#{op}/app.js.map" Path.join([op, "app.js.map"])
] ]
end end
def do_bundle() do task _ do
bin() |> exec(args()) |> listen() bin() |> exec(args()) |> listen()
end end
def run(_) do
run_task("frontend.build.js.transpile")
do_bundle()
end
end end

View file

@ -1,20 +1,18 @@
defmodule Mix.Tasks.Frontend.Build.Js.Copy do defmodule Mix.Tasks.Frontend.Build.Js.Copy do
use Mix.Task use FBU.BuildTask
import MebeWeb.{TaskUtils, FrontendConfs} import MebeWeb.FrontendConfs
@shortdoc "Copy bundled JS to target dir" @shortdoc "Copy bundled JS to target dir"
def do_copy() do @deps [
"frontend.build.js.bundle"
]
task _ do
# Ensure target path exists # Ensure target path exists
out_path = "#{dist_path()}/js" out_path = Path.join([dist_path(), "js"])
File.mkdir_p!(out_path) File.mkdir_p!(out_path)
File.cp_r!(Mix.Tasks.Frontend.Build.Js.Bundle.out_path(), out_path) File.cp_r!(Mix.Tasks.Frontend.Build.Js.Bundle.out_path(), out_path)
end end
def run(_) do
run_task("frontend.build.js.bundle")
do_copy()
end
end end

View file

@ -1,15 +1,15 @@
defmodule Mix.Tasks.Frontend.Build.Js do defmodule Mix.Tasks.Frontend.Build.Js do
use Mix.Task use FBU.BuildTask
import MebeWeb.TaskUtils import FBU.TaskUtils
@shortdoc "Build the frontend JavaScript" @shortdoc "Build the frontend JavaScript"
def run(_) do task _ do
task = case Mix.env() do todo = case Mix.env() do
:prod -> "frontend.build.js.minify" :prod -> "frontend.build.js.minify"
_ -> "frontend.build.js.copy" _ -> "frontend.build.js.copy"
end end
run_task(task) run_task(todo)
end end
end end

View file

@ -1,17 +1,20 @@
defmodule Mix.Tasks.Frontend.Build.Js.Minify do defmodule Mix.Tasks.Frontend.Build.Js.Minify do
use Mix.Task use FBU.BuildTask
import MebeWeb.{TaskUtils, FrontendConfs} import MebeWeb.FrontendConfs
import FBU.TaskUtils
@shortdoc "Minify built JS files" @shortdoc "Minify built JS files"
@preferred_cli_env :prod @preferred_cli_env :prod
def run(_) do @deps [
run_task("frontend.build.js.bundle") "frontend.build.js.bundle"
]
task _ do
in_path = Mix.Tasks.Frontend.Build.Js.Bundle.out_path() in_path = Mix.Tasks.Frontend.Build.Js.Bundle.out_path()
in_file = "#{in_path}/app.js" in_file = Path.join([in_path, "app.js"])
out_path = "#{dist_path()}/js" out_path = Path.join([dist_path(), "js"])
out_file = "#{out_path}/app.js" out_file = Path.join([out_path, "app.js"])
File.mkdir_p!(out_path) File.mkdir_p!(out_path)
@ -19,9 +22,9 @@ defmodule Mix.Tasks.Frontend.Build.Js.Minify do
node_bin("uglifyjs"), node_bin("uglifyjs"),
[ [
"--in-source-map", "--in-source-map",
"#{in_path}/app.js.map", Path.join([in_path, "app.js.map"]),
"--source-map", "--source-map",
"#{out_path}/app.js.map", Path.join([out_path, "app.js.map"]),
"--source-map-url", "--source-map-url",
"app.js.map", "app.js.map",
"--screw-ie8", "--screw-ie8",

View file

@ -1,22 +1,23 @@
defmodule Mix.Tasks.Frontend.Build.Js.Transpile do defmodule Mix.Tasks.Frontend.Build.Js.Transpile do
use Mix.Task use FBU.BuildTask
import MebeWeb.{TaskUtils, FrontendConfs} import MebeWeb.FrontendConfs
import FBU.TaskUtils
@shortdoc "Transpile JS sources to ES5" @shortdoc "Transpile JS sources to ES5"
def bin(), do: node_bin("babel") def bin(), do: node_bin("babel")
def out_path(), do: "#{tmp_path()}/transpiled/js" def out_path(), do: Path.join([tmp_path(), "transpiled", "js"])
def args(), do: [ def args(), do: [
"#{src_path()}/js", Path.join([src_path(), "js"]),
"--out-dir", "--out-dir",
out_path(), out_path(),
"--source-maps", "--source-maps",
"inline" "inline"
] ]
def run(_) do task _ do
bin() |> exec(args()) |> listen() bin() |> exec(args()) |> listen()
end end
end end

View file

@ -1,10 +1,10 @@
defmodule Mix.Tasks.Frontend.Clean do defmodule Mix.Tasks.Frontend.Clean do
use Mix.Task use FBU.BuildTask
import MebeWeb.{FrontendConfs} import MebeWeb.FrontendConfs
@shortdoc "Clean build artifacts" @shortdoc "Clean build artifacts"
def run(_) do task _ do
File.rm_rf!(tmp_path()) File.rm_rf!(tmp_path())
File.rm_rf!(dist_path()) File.rm_rf!(dist_path())
end end

View file

@ -1,6 +1,6 @@
defmodule Mix.Tasks.Frontend.Watch do defmodule Mix.Tasks.Frontend.Watch do
use Mix.Task use FBU.BuildTask
import MebeWeb.{TaskUtils} import FBU.TaskUtils
alias Mix.Tasks.Frontend.Build.Js.Transpile, as: TranspileJS alias Mix.Tasks.Frontend.Build.Js.Transpile, as: TranspileJS
alias Mix.Tasks.Frontend.Build.Js.Bundle, as: BundleJS alias Mix.Tasks.Frontend.Build.Js.Bundle, as: BundleJS
alias Mix.Tasks.Frontend.Build.Js.Copy, as: CopyJS alias Mix.Tasks.Frontend.Build.Js.Copy, as: CopyJS
@ -9,9 +9,11 @@ defmodule Mix.Tasks.Frontend.Watch do
@shortdoc "Watch frontend and rebuild when necessary" @shortdoc "Watch frontend and rebuild when necessary"
def run(_) do @deps [
run_task("frontend.build") "frontend.build"
]
task _ do
[ [
exec( exec(
TranspileJS.bin(), TranspileJS.bin(),
@ -21,13 +23,13 @@ defmodule Mix.Tasks.Frontend.Watch do
watch( watch(
"JSBundle", "JSBundle",
TranspileJS.out_path(), TranspileJS.out_path(),
fn _, _ -> Task.start_link(BundleJS, :do_bundle, []) end fn _, _ -> run_task(BundleJS, deps: false) end
), ),
watch( watch(
"JSCopy", "JSCopy",
BundleJS.out_path(), BundleJS.out_path(),
fn _, _ -> CopyJS.do_copy() end fn _, _ -> run_task(CopyJS, deps: false) end
), ),
exec( exec(
@ -41,8 +43,9 @@ defmodule Mix.Tasks.Frontend.Watch do
watch( watch(
"CSSCopy", "CSSCopy",
CompileCSS.out_path(), CompileCSS.out_path(),
fn _, _ -> CopyCSS.do_copy() end fn _, _ -> run_task(CopyCSS, deps: false) end
) )
] |> listen(watch: true) ]
|> listen(watch: true)
end end
end end

View file

@ -1,34 +1,39 @@
defmodule MebeWeb.FrontendConfs do defmodule MebeWeb.FrontendConfs do
import MebeWeb.TaskUtils
@moduledoc """ @moduledoc """
Project specific paths and other stuff for Mebe frontend. Project specific paths and other stuff for Mebe frontend.
""" """
@doc """
Get absolute path to root directory of project.
"""
def proj_path() do
Path.expand("../../../", __DIR__)
end
@doc """ @doc """
Get absolute path to node_modules. Get absolute path to node_modules.
""" """
def node_path() do def node_path() do
"#{proj_path()}/node_modules" Path.join(proj_path(), "node_modules")
end end
@doc """ @doc """
Get absolute path to binary installed with npm. Get absolute path to binary installed with npm.
""" """
def node_bin(executable), do: "#{node_path()}/.bin/#{executable}" def node_bin(executable), do: Path.join([node_path(), ".bin", executable])
@doc """ @doc """
Get absolute path to source directory for frontend build. Get absolute path to source directory for frontend build.
""" """
def src_path(), do: "#{proj_path()}/web/static" def src_path(), do: Path.join([proj_path(), "web", "static"])
@doc """ @doc """
Get absolute path to temp directory for build artifacts. Get absolute path to temp directory for build artifacts.
""" """
def tmp_path(), do: "#{proj_path()}/.tmp" def tmp_path(), do: Path.join([proj_path(), ".tmp"])
@doc """ @doc """
Get absolute path to target directory for frontend build. Get absolute path to target directory for frontend build.
""" """
def dist_path(), do: "#{proj_path()}/priv/static" def dist_path(), do: Path.join([proj_path(), "priv", "static"])
end end

View file

@ -1,382 +0,0 @@
defmodule MebeWeb.TaskUtils do
@moduledoc """
Utilities for project build tasks.
"""
require Logger
@elixir System.find_executable("elixir")
@default_task_timeout 60000
defmodule ProgramSpec do
@moduledoc """
Program that is executed with arguments. Name is used for prefixing logs.
"""
defstruct [
name: "",
port: nil,
pending_output: ""
]
end
defmodule WatchSpec do
@moduledoc """
Watch specification, target to watch and callback to execute on events.
Name is used for prefixing logs.
Callback must have arity 2, gets filename/path and list of events as arguments.
"""
defstruct [
name: "",
path: "",
callback: nil,
pid: nil,
name_atom: nil
]
end
@doc """
Get configuration value.
"""
def conf(key) when is_atom(key) do
Application.get_env(:code_stats, key)
end
@doc """
Get path to project root directory.
"""
def proj_path() do
Path.expand("../../..", __DIR__)
end
@doc """
Get absolute path to a program in $PATH.
"""
def exec_path(program) do
System.find_executable(program)
end
@doc """
Run the given Mix task and wait for it to stop before returning.
See run_tasks/2 for the argument description.
"""
def run_task(task, timeout \\ @default_task_timeout) do
run_tasks([task], timeout)
end
@doc """
Run the given Mix tasks in parallel and wait for them all to stop
before returning.
Tasks should be tuples {task_name, args} or binaries (no args).
"""
def run_tasks(tasks, timeout \\ @default_task_timeout) do
tasks
|> Enum.map(fn
task when is_binary(task) ->
fn -> Mix.Task.run(task, []) end
{task, args} ->
fn -> Mix.Task.run(task, args) end
end)
|> run_funs(timeout)
end
@doc """
Run the given functions in parallel and wait for them all to stop
before returning.
Functions can either be anonymous functions or tuples of
{module, fun, args}.
"""
def run_funs(funs, timeout \\ @default_task_timeout) when is_list(funs) do
funs
|> Enum.map(fn
fun when is_function(fun) ->
Task.async(fun)
{module, fun, args} ->
Task.async(module, fun, args)
end)
|> Enum.map(fn task ->
Task.await(task, timeout)
end)
end
@doc """
Start an external program with apprunner.
Apprunner handles killing the program if BEAM is abruptly shut down.
Name is used as a prefix for logging output.
Options that can be given:
- name: Use as name for logging, otherwise name of binary is used.
- cd: Directory to change to before executing.
Returns ProgramSpec for the started program.
"""
def exec(executable, args, opts \\ []) do
name = Keyword.get(
opts,
:name,
(executable |> Path.rootname() |> Path.basename())
)
options = [
:exit_status, # Send msg with status when command stops
args: ["#{proj_path()}/lib/mix/tasks/apprunner.exs" | [executable | args]],
line: 1024 # Send command output as lines of 1k length
]
options = case Keyword.get(opts, :cd) do
nil -> options
cd -> Keyword.put(options, :cd, cd)
end
Logger.debug("[Spawned] Program #{name}")
%ProgramSpec{
name: name,
pending_output: "",
port: Port.open(
{:spawn_executable, @elixir},
options
)
}
end
@doc """
Start watching a path. Name is used for prefixing logs.
Path can point to a file or a directory in which case all subdirs will be watched.
Returns a WatchSpec.
"""
def watch(name, path, fun) do
name_atom = String.to_atom(name)
{:ok, pid} = :fs.start_link(name_atom, String.to_charlist(path))
:fs.subscribe(name_atom)
Logger.debug("[Spawned] Watch #{name}")
%WatchSpec{
name: name,
path: path,
callback: fun,
pid: pid,
name_atom: name_atom
}
end
@doc """
Listen to messages from specs and print them to the screen.
If watch: true is given in the options, will listen for user's enter key and
kill programs if enter is pressed.
"""
def listen(specs, opts \\ [])
# If there are no specs, stop running
def listen([], _), do: :ok
def listen(spec, opts) when not is_list(spec) do
listen([spec], opts)
end
def listen(specs, opts) when is_list(specs) do
# Start another task to ask for user input if we are in watch mode
task = with \
true <- Keyword.get(opts, :watch, false),
nil <- Keyword.get(opts, :task),
{:ok, task} <- Task.start_link(__MODULE__, :wait_for_input, [self()])
do
Logger.info("Programs/watches started, press ENTER to exit.")
task
end
specs = receive do
# User pressed enter
:user_input_received ->
Logger.info("ENTER received, killing tasks.")
Enum.each(specs, &kill/1)
[]
# Program sent output with end of line
{port, {:data, {:eol, msg}}} ->
program = Enum.find(specs, program_checker(port))
msg = :unicode.characters_to_binary(msg, :unicode)
prefix = "[#{program.name}] #{program.pending_output}"
Logger.debug(prefix <> msg)
specs
|> Enum.reject(program_checker(port))
|> Enum.concat([
%{program | pending_output: ""}
])
# Program sent output without end of line
{port, {:data, {:noeol, msg}}} ->
program = Enum.find(specs, program_checker(port))
msg = :unicode.characters_to_binary(msg, :unicode)
specs
|> Enum.reject(program_checker(port))
|> Enum.concat([
%{program | pending_output: "#{program.pending_output}#{msg}"}
])
# Port was closed normally after being told to close
{port, :closed} ->
handle_closed(specs, port)
# Port closed because the program closed by itself
{port, {:exit_status, 0}} ->
handle_closed(specs, port)
# Program closed with error status
{port, {:exit_status, status}} ->
program = Enum.find(specs, program_checker(port))
Logger.error("Program #{program.name} returned status #{status}.")
raise "Failed status #{status} from #{program.name}!"
# Port crashed
{:EXIT, port, _} ->
handle_closed(specs, port)
# FS watch sent file event
{_, {:fs, :file_event}, {file, events}} ->
handle_events(specs, file, events)
end
listen(specs, Keyword.put(opts, :task, task))
end
@doc """
Kill a running program returned by exec().
"""
def kill(%ProgramSpec{name: name, port: port}) do
if name != nil do
Logger.debug("[Killing] #{name}")
end
send(port, {self(), :close})
end
@doc """
Kill a running watch.
"""
def kill(%WatchSpec{name: name, pid: pid}) do
Logger.debug("[Killing] #{name}")
Process.exit(pid, :kill)
end
@doc """
Print file size of given file in human readable form.
If old file is given as second argument, print the old file's size
and the diff also.
"""
def print_size(new_file, old_file \\ nil) do
new_size = get_size(new_file)
{prefix, postfix} = if old_file != nil do
old_size = get_size(old_file)
{
"#{human_size(old_size)} -> ",
" Diff: #{human_size(old_size - new_size)}"
}
else
{"", ""}
end
Logger.debug("#{Path.basename(new_file)}: #{prefix}#{human_size(new_size)}.#{postfix}")
end
def wait_for_input(target) do
IO.gets("")
send(target, :user_input_received)
end
defp get_size(file) do
%File.Stat{size: size} = File.stat!(file)
size
end
defp human_size(size) do
size_units = ["B", "kiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"] # You never know
human_size(size, size_units)
end
defp human_size(size, [unit | []]), do: size_with_unit(size, unit)
defp human_size(size, [unit | rest]) do
if size > 1024 do
human_size(size / 1024, rest)
else
size_with_unit(size, unit)
end
end
defp size_with_unit(size, unit) when is_float(size), do: "#{Float.round(size, 2)} #{unit}"
defp size_with_unit(size, unit), do: "#{size} #{unit}"
# Utility to find program in spec list based on port
defp program_checker(port) do
fn
%ProgramSpec{port: program_port} ->
program_port == port
%WatchSpec{} ->
false
end
end
# Utility to find watch in spec list based on path
defp watch_checker(path) do
fn
%ProgramSpec{} ->
false
%WatchSpec{path: watch_path} ->
# If given path is relative to (under) the watch path or is the same
# path completely, it's a match.
path != watch_path and Path.relative_to(path, watch_path) != path
end
end
defp handle_closed(specs, port) do
case Enum.find(specs, program_checker(port)) do
%ProgramSpec{} = program ->
Logger.debug("[Stopped] #{program.name}")
specs
|> Enum.reject(program_checker(port))
nil ->
# Program was already removed
specs
end
end
defp handle_events(specs, file, events) do
file = to_string(file)
case Enum.find(specs, watch_checker(file)) do
%WatchSpec{name: name, callback: callback} ->
Logger.debug("[#{name}] Changed #{inspect(events)}: #{file}")
callback.(file, events)
nil ->
# Watch was maybe removed for some reason
Logger.debug("[Error] Watch sent event but path was not in specs list: #{inspect(events)} #{file}")
end
specs
end
end

View file

@ -40,7 +40,7 @@ defmodule MebeWeb.Mixfile do
{:earmark, "~> 1.0.3"}, {:earmark, "~> 1.0.3"},
{:slugger, github: "h4cc/slugger", ref: "ef864669cdaae18d475600589c19c74e92ef67b4"}, {:slugger, github: "h4cc/slugger", ref: "ef864669cdaae18d475600589c19c74e92ef67b4"},
{:calendar, "~> 0.17.1"}, {:calendar, "~> 0.17.1"},
{:fs, "~> 2.12.0", override: true, only: :dev} {:fbu, github: "Nicd/fbu", only: :dev}
] ]
end end
end end

View file

@ -3,8 +3,8 @@
"cowboy": {:hex, :cowboy, "1.0.4", "a324a8df9f2316c833a470d918aaf73ae894278b8aa6226ce7a9bf699388f878", [:make, :rebar], [{:cowlib, "~> 1.0.0", [hex: :cowlib, optional: false]}, {:ranch, "~> 1.0", [hex: :ranch, optional: false]}]}, "cowboy": {:hex, :cowboy, "1.0.4", "a324a8df9f2316c833a470d918aaf73ae894278b8aa6226ce7a9bf699388f878", [:make, :rebar], [{:cowlib, "~> 1.0.0", [hex: :cowlib, optional: false]}, {:ranch, "~> 1.0", [hex: :ranch, optional: false]}]},
"cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], []}, "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], []},
"earmark": {:hex, :earmark, "1.0.3", "89bdbaf2aca8bbb5c97d8b3b55c5dd0cff517ecc78d417e87f1d0982e514557b", [:mix], []}, "earmark": {:hex, :earmark, "1.0.3", "89bdbaf2aca8bbb5c97d8b3b55c5dd0cff517ecc78d417e87f1d0982e514557b", [:mix], []},
"fbu": {:git, "https://github.com/Nicd/fbu.git", "6b1729f4a7c8607fcd115be5a48fbba9865d0eed", []},
"fs": {:hex, :fs, "2.12.0", "ad631efacc9a5683c8eaa1b274e24fa64a1b8eb30747e9595b93bec7e492e25e", [:rebar3], []}, "fs": {:hex, :fs, "2.12.0", "ad631efacc9a5683c8eaa1b274e24fa64a1b8eb30747e9595b93bec7e492e25e", [:rebar3], []},
"fwatch": {:hex, :fwatch, "0.5.0", "c61d9c1653ef5958770165315552e560c01202edd21ec05be9ba737ed44166e0", [:mix], [{:earmark, ">= 0.0.0", [hex: :earmark, optional: false]}, {:fs, "~> 0.9.1", [hex: :fs, optional: false]}]},
"hackney": {:hex, :hackney, "1.7.1", "e238c52c5df3c3b16ce613d3a51c7220a784d734879b1e231c9babd433ac1cb4", [:rebar3], [{:certifi, "1.0.0", [hex: :certifi, optional: false]}, {:idna, "4.0.0", [hex: :idna, optional: false]}, {:metrics, "1.0.1", [hex: :metrics, optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, optional: false]}]}, "hackney": {:hex, :hackney, "1.7.1", "e238c52c5df3c3b16ce613d3a51c7220a784d734879b1e231c9babd433ac1cb4", [:rebar3], [{:certifi, "1.0.0", [hex: :certifi, optional: false]}, {:idna, "4.0.0", [hex: :idna, optional: false]}, {:metrics, "1.0.1", [hex: :metrics, optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, optional: false]}]},
"idna": {:hex, :idna, "4.0.0", "10aaa9f79d0b12cf0def53038547855b91144f1bfcc0ec73494f38bb7b9c4961", [:rebar3], []}, "idna": {:hex, :idna, "4.0.0", "10aaa9f79d0b12cf0def53038547855b91144f1bfcc0ec73494f38bb7b9c4961", [:rebar3], []},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []},
@ -12,7 +12,7 @@
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []}, "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []},
"phoenix": {:hex, :phoenix, "1.2.3", "b68dd6a7e6ff3eef38ad59771007d2f3f344988ea6e658e9b2c6ffb2ef494810", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, optional: false]}, {:plug, "~> 1.4 or ~> 1.3.3 or ~> 1.2.4 or ~> 1.1.8 or ~> 1.0.5", [hex: :plug, optional: false]}, {:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: false]}]}, "phoenix": {:hex, :phoenix, "1.2.3", "b68dd6a7e6ff3eef38ad59771007d2f3f344988ea6e658e9b2c6ffb2ef494810", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, optional: false]}, {:plug, "~> 1.4 or ~> 1.3.3 or ~> 1.2.4 or ~> 1.1.8 or ~> 1.0.5", [hex: :plug, optional: false]}, {:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: false]}]},
"phoenix_html": {:hex, :phoenix_html, "2.9.3", "1b5a2122cbf743aa242f54dced8a4f1cc778b8bd304f4b4c0043a6250c58e258", [:mix], [{:plug, "~> 1.0", [hex: :plug, optional: false]}]}, "phoenix_html": {:hex, :phoenix_html, "2.9.3", "1b5a2122cbf743aa242f54dced8a4f1cc778b8bd304f4b4c0043a6250c58e258", [:mix], [{:plug, "~> 1.0", [hex: :plug, optional: false]}]},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.0.8", "4333f9c74190f485a74866beff2f9304f069d53f047f5fbb0fb8d1ee4c495f73", [:mix], [{:fs, "~> 0.9.1", [hex: :fs, optional: false]}, {:phoenix, "~> 1.0 or ~> 1.2-rc", [hex: :phoenix, optional: false]}]}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.0.7", "167ab0942e88d1d4a597996cf7dd8d2b014cc14d3f9472b58858cde8dd9ac2e4", [:mix], [{:fs, "~> 2.12.0", [hex: :fs, optional: false]}, {:phoenix, "~> 1.0 or ~> 1.2-rc", [hex: :phoenix, optional: false]}]},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.1", "c10ddf6237007c804bf2b8f3c4d5b99009b42eca3a0dfac04ea2d8001186056a", [:mix], []}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.1", "c10ddf6237007c804bf2b8f3c4d5b99009b42eca3a0dfac04ea2d8001186056a", [:mix], []},
"plug": {:hex, :plug, "1.3.4", "b4ef3a383f991bfa594552ded44934f2a9853407899d47ecc0481777fb1906f6", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1", [hex: :cowboy, optional: true]}, {:mime, "~> 1.0", [hex: :mime, optional: false]}]}, "plug": {:hex, :plug, "1.3.4", "b4ef3a383f991bfa594552ded44934f2a9853407899d47ecc0481777fb1906f6", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1", [hex: :cowboy, optional: true]}, {:mime, "~> 1.0", [hex: :mime, optional: false]}]},
"poison": {:hex, :poison, "2.2.0", "4763b69a8a77bd77d26f477d196428b741261a761257ff1cf92753a0d4d24a63", [:mix], []}, "poison": {:hex, :poison, "2.2.0", "4763b69a8a77bd77d26f477d196428b741261a761257ff1cf92753a0d4d24a63", [:mix], []},