Remove tests since utf-8 names on env variables are poorly supported depending on the OS and ERTS

Code.require_file "../test_helper.exs", __FILE__

require :os, as: OS

defmodule SystemTest do
  use ExUnit.Case, async: true
  import PathHelpers

  test :build_info do
    assert not nil?(System.build_info[:version])
    assert not nil?(System.build_info[:tag])
    assert not nil?(System.build_info[:date])
  end

  test :argv do
    list = elixir('-e "IO.inspect System.argv" -- -o opt arg1 arg2 --long-opt 10')
    { args, _ } = Code.eval list, []
    assert args == ["-o", "opt", "arg1", "arg2", "--long-opt", "10"]
  end

  test :at_exit do
    output = elixir('-e "System.at_exit(fn x -> IO.inspect x end)"')
    assert output == '0\n'
  end

  test :env do
    assert System.get_env("SYSTEM_ELIXIR_ENV_TEST_VAR") == nil
    System.put_env('SYSTEM_ELIXIR_ENV_TEST_VAR', 'SAMPLE')
    assert System.get_env("SYSTEM_ELIXIR_ENV_TEST_VAR") == "SAMPLE"
  end

  test :cmd do
    assert is_binary(System.cmd "binary")
    assert is_list(System.cmd 'binary')
  end

  test :find_executable_with_binary do
    assert System.find_executable("erl")
    assert is_binary System.find_executable("erl")
    assert !System.find_executable("does-not-really-exist-from-elixir")
  end

  test :find_executable_with_list do
    assert System.find_executable('erl')
    assert is_list System.find_executable('erl')
    assert !System.find_executable('does-not-really-exist-from-elixir')
  end
end

Code.require_file "../test_helper.exs", __FILE__

require :os, as: OS

defmodule SystemTest do
  use ExUnit.Case, async: true
  import PathHelpers

  test :build_info do
    assert not nil?(System.build_info[:version])
    assert not nil?(System.build_info[:tag])
    assert not nil?(System.build_info[:date])
  end

  test :argv do
    list = elixir('-e "IO.inspect System.argv" -- -o opt arg1 arg2 --long-opt 10')
    { args, _ } = Code.eval list, []
    assert args == ["-o", "opt", "arg1", "arg2", "--long-opt", "10"]
  end

  test :at_exit do
    output = elixir('-e "System.at_exit(fn x -> IO.inspect x end)"')
    assert output == '0\n'
  end

  test :env do
    assert System.get_env("SYSTEM_ELIXIR_ENV_TEST_VAR") == nil
    System.put_env('SYSTEM_ELIXIR_ENV_TEST_VAR', 'SAMPLE')
    assert System.get_env("SYSTEM_ELIXIR_ENV_TEST_VAR") == "SAMPLE"
  end

  test :env_utf8 do
    assert System.get_env("SYSTEM_ELIXIR_UTF_TEST_VAR") == nil
    System.put_env("SYSTEM_ELIXIR_UTF_TEST_VAR", "olé")
    assert length(:os.getenv("SYSTEM_ELIXIR_UTF_TEST_VAR")) == 3
    assert System.get_env("SYSTEM_ELIXIR_UTF_TEST_VAR") == "olé"
  end

  test :cmd do
    assert is_binary(System.cmd "binary")
    assert is_list(System.cmd 'binary')
  end

  test :find_executable_with_binary do
    assert System.find_executable("erl")
    assert is_binary System.find_executable("erl")
    assert !System.find_executable("does-not-really-exist-from-elixir")
  end

  test :find_executable_with_list do
    assert System.find_executable('erl')
    assert is_list System.find_executable('erl')
    assert !System.find_executable('does-not-really-exist-from-elixir')
  end
end


Simplify code

Simplify code

defmodule AdventOfCode2018.Day0301 do
  @regexp ~r/^#(?<id>\d+) @ (?<x>\d+),(?<y>\d+): (?<w>\d+)x(?<h>\d+)$/

  @doc ~S"""
  iex> AdventOfCode2018.Day0301.interpret("#1 @ 1,3: 4x4")
  %{id: 1, x: 1, y: 3, w: 4, h: 4}
  iex> AdventOfCode2018.Day0301.interpret("#123 @ 2,10: 6x7")
  %{id: 123, x: 2, y: 10, w: 6, h: 7}
  """
  def interpret(input) do
    Regex.named_captures(@regexp, input)
    |> Enum.map(fn {k,v}->{String.to_atom(k),String.to_integer(v)} end)
    |> Map.new
  end

  def build_pixels(%{id: id, x: x, y: y, w: w, h: h}) do
    for i <- (0..w-1), j <- (0..h-1),
      do: %{x: x + i, y: y + j}
  end

  # We store all the "pixels" in a map to count them later
  def mark(box = %{id: id}, map) do
    build_pixels(box)
    |> Enum.reduce(map, fn(p, acc) ->
      Map.put(acc, p, Map.get(acc, p, []) ++ [id])
    end)
  end

  # Input is analysed line by line, then we build a map of all the pixels
  # drawn by each box
  def build_map(input) do
    input
    |> String.splitter("\n", trim: true)
    |> Stream.map(&String.trim/1)
    |> Stream.map(&(interpret(&1)))
  end

  def mark_map(map) do
    Enum.reduce(map, Map.new, &mark/2)
  end

  # here we finally we count all the pixels drawn more than once.
  def count_overlap(input) do
    input
    |> build_map
    |> mark_map
    |> Enum.filter(fn({_, ids}) -> Enum.count(ids) > 1 end)
    |> Enum.count
  end

  def find_non_overlapping(input) do
    map = input
    |> build_map

    all_ids = Enum.reduce(map, MapSet.new, fn(p, acc) ->
      MapSet.put(acc, p.id)
    end)

    overlapping_ids = map
    |> mark_map
    |> Enum.filter(fn({_coords, ids}) -> Enum.count(ids) > 1 end)
    |> Enum.map((fn({_coords, ids}) -> ids end))
    |> List.flatten
    |> Enum.uniq

    MapSet.difference(all_ids, MapSet.new(overlapping_ids))
    |> Enum.to_list
  end
end

defmodule AdventOfCode2018.Day0301 do
  @regexp ~r/^#(?<id>\d+) @ (?<x>\d+),(?<y>\d+): (?<w>\d+)x(?<h>\d+)$/

  @doc ~S"""
  iex> AdventOfCode2018.Day0301.interpret("#1 @ 1,3: 4x4")
  %{id: 1, x: 1, y: 3, w: 4, h: 4}
  iex> AdventOfCode2018.Day0301.interpret("#123 @ 2,10: 6x7")
  %{id: 123, x: 2, y: 10, w: 6, h: 7}
  """
  def interpret(input) do
    Regex.named_captures(@regexp, input)
    |> Enum.map(fn {k,v}->{String.to_atom(k),String.to_integer(v)} end)
    |> Map.new
  end

  def build_pixels(%{id: id, x: x, y: y, w: w, h: h}) do
    for i <- (0..w-1), j <- (0..h-1),
      do: %{x: x + i, y: y + j}
  end

  # We store all the "pixels" in a map to count them later
  def mark(box = %{id: id}, map) do
    build_pixels(box)
    |> Enum.reduce(map, fn(p, acc) ->
      Map.put(acc, p, Map.get(acc, p, []) ++ [id])
    end)
  end

  # Input is analysed line by line, then we build a map of all the pixels
  # drawn by each box
  def build_map(input) do
    input
    |> String.splitter("\n", trim: true)
    |> Stream.map(&String.trim/1)
    |> Stream.map(&(interpret(&1)))
  end

  def mark_map(map) do
    Enum.reduce(map, Map.new, &mark/2)
  end

  # here we finally we count all the pixels drawn more than once.
  def count_overlap(input) do
    input
    |> build_map
    |> mark_map
    |> Enum.reduce(0, fn({_, ids}, acc) ->
      if Enum.count(ids) > 1 do acc + 1 else acc end
    end)
  end

  def find_non_overlapping(input) do
    map = input
    |> build_map

    all_ids = Enum.reduce(map, MapSet.new, fn(p, acc) ->
      MapSet.put(acc, p.id)
    end)

    overlapping_ids = map
    |> mark_map
    |> Enum.filter(fn({_coords, ids}) -> Enum.count(ids) > 1 end)
    |> Enum.map((fn({_coords, ids}) -> ids end))
    |> List.flatten
    |> Enum.uniq

    MapSet.difference(all_ids, MapSet.new(overlapping_ids))
    |> Enum.to_list
  end
end


Applied `mix format` to the Mixfile.

Applied `mix format` to the Mixfile.

defmodule Foobar.Mixfile do
  use Mix.Project

  @name "Foobar"
  @version File.read!("VERSION") |> String.strip()
  @github "https://github.com/bendiken/foobar"
  @bitbucket "https://bitbucket.org/bendiken/foobar"
  @homepage @github

  def project do
    [
      app: :foobar,
      version: @version,
      elixir: "~> 1.4",
      compilers: Mix.compilers(),
      build_embedded: Mix.env() == :prod,
      start_permanent: Mix.env() == :prod,
      name: @name,
      source_url: @github,
      homepage_url: @homepage,
      description: description(),
      aliases: aliases(),
      deps: deps(),
      package: package(),
      docs: [source_ref: @version, main: "readme", extras: ["README.md"]],
      test_coverage: [tool: ExCoveralls],
      preferred_cli_env: [
        coveralls: :test,
        "coveralls.detail": :test,
        "coveralls.post": :test,
        "coveralls.html": :test
      ]
    ]
  end

  def application do
    # mod: {Foobar, []},
    [extra_applications: [:logger]]
  end

  defp package do
    [
      files: ~w(lib priv src mix.exs CHANGES.md README.md UNLICENSE VERSION),
      maintainers: ["Arto Bendiken"],
      licenses: ["Public Domain"],
      links: %{"GitHub" => @github, "Bitbucket" => @bitbucket}
    ]
  end

  defp description do
    """
    Foobar is a template for Elixir projects.
    """
  end

  defp deps do
    [
      {:credo, ">= 0.0.0", only: [:dev, :test]},
      {:dialyxir, ">= 0.0.0", only: [:dev, :test]},
      {:earmark, ">= 0.0.0", only: :dev},
      {:ex_doc, ">= 0.0.0", only: :dev},
      {:excoveralls, ">= 0.0.0", only: :test}
    ]
  end

  defp aliases do
    []
  end
end

defmodule Foobar.Mixfile do
  use Mix.Project

  @name      "Foobar"
  @version   File.read!("VERSION") |> String.strip
  @github    "https://github.com/bendiken/foobar"
  @bitbucket "https://bitbucket.org/bendiken/foobar"
  @homepage  @github

  def project do
    [app: :foobar,
     version: @version,
     elixir: "~> 1.4",
     compilers: Mix.compilers,
     build_embedded: Mix.env == :prod,
     start_permanent: Mix.env == :prod,
     name: @name,
     source_url: @github,
     homepage_url: @homepage,
     description: description(),
     aliases: aliases(),
     deps: deps(),
     package: package(),
     docs: [source_ref: @version, main: "readme", extras: ["README.md"]],
     test_coverage: [tool: ExCoveralls],
     preferred_cli_env: [
       "coveralls": :test,
       "coveralls.detail": :test,
       "coveralls.post": :test,
       "coveralls.html": :test,
     ]]
  end

  def application do
    [#mod: {Foobar, []},
     extra_applications: [:logger]]
  end

  defp package do
    [files: ~w(lib priv src mix.exs CHANGES.md README.md UNLICENSE VERSION),
     maintainers: ["Arto Bendiken"],
     licenses: ["Public Domain"],
     links: %{"GitHub" => @github, "Bitbucket" => @bitbucket}]
  end

  defp description do
    """
    Foobar is a template for Elixir projects.
    """
  end

  defp deps do
    [{:credo,       ">= 0.0.0", only: [:dev, :test]},
     {:dialyxir,    ">= 0.0.0", only: [:dev, :test]},
     {:earmark,     ">= 0.0.0", only: :dev},
     {:ex_doc,      ">= 0.0.0", only: :dev},
     {:excoveralls, ">= 0.0.0", only: :test}]
  end

  defp aliases do
    []
  end
end


Expand includes after compiling EEx pages

defmodule Serum.Build.FileProcessor.Page do
  @moduledoc false

  import Serum.IOProxy, only: [put_msg: 2]
  alias Serum.Markdown
  alias Serum.Page
  alias Serum.Plugin
  alias Serum.Project
  alias Serum.Renderer
  alias Serum.Result
  alias Serum.Template
  alias Serum.Template.Compiler, as: TC

  @spec preprocess_pages([Serum.File.t()], Project.t()) :: Result.t({[Page.t()], [map()]})
  def preprocess_pages(files, proj) do
    put_msg(:info, "Processing page files...")

    result =
      files
      |> Task.async_stream(&preprocess_page(&1, proj))
      |> Enum.map(&elem(&1, 1))
      |> Result.aggregate_values(:file_processor)

    case result do
      {:ok, pages} ->
        sorted_pages = Enum.sort(pages, &(&1.order < &2.order))

        {:ok, {sorted_pages, Enum.map(sorted_pages, &Page.compact/1)}}

      {:error, _} = error ->
        error
    end
  end

  @spec preprocess_page(Serum.File.t(), Project.t()) :: Result.t(Page.t())
  defp preprocess_page(file, proj) do
    import Serum.HeaderParser

    opts = [
      title: :string,
      label: :string,
      group: :string,
      order: :integer,
      template: :string
    ]

    required = [:title]

    with {:ok, %{in_data: data} = file2} <- Plugin.processing_page(file),
         {:ok, {header, extras, rest}} <- parse_header(data, opts, required) do
      header = Map.put(header, :label, header[:label] || header.title)
      page = Page.new(file2.src, {header, extras}, rest, proj)

      {:ok, page}
    else
      {:invalid, message} -> {:error, {message, file.src, 0}}
      {:error, _} = plugin_error -> plugin_error
    end
  end

  @spec process_pages([Page.t()], Project.t()) :: Result.t([Page.t()])
  def process_pages(pages, proj) do
    pages
    |> Task.async_stream(&process_page(&1, proj))
    |> Enum.map(&elem(&1, 1))
    |> Result.aggregate_values(:file_processor)
    |> case do
      {:ok, pages} -> Plugin.processed_pages(pages)
      {:error, _} = error -> error
    end
  end

  @spec process_page(Page.t(), Project.t()) :: Result.t(Page.t())
  defp process_page(page, proj) do
    case do_process_page(page, proj) do
      {:ok, page} -> Plugin.processed_page(page)
      {:error, _} = error -> error
    end
  end

  @spec do_process_page(Page.t(), Project.t()) :: Result.t(Page.t())
  defp do_process_page(page, proj)

  defp do_process_page(%Page{type: ".md"} = page, proj) do
    {:ok, %Page{page | data: Markdown.to_html(page.data, proj)}}
  end

  defp do_process_page(%Page{type: ".html"} = page, _proj) do
    {:ok, page}
  end

  defp do_process_page(%Page{type: ".html.eex"} = page, _proj) do
    with {:ok, ast} <- TC.compile_string(page.data),
         template = Template.new(ast, page.file, :template, page.file),
         {:ok, new_template} <- TC.Include.expand(template),
         {:ok, html} <- Renderer.render_fragment(new_template, []) do
      {:ok, %Page{page | data: html}}
    else
      {:ct_error, msg, line} -> {:error, {msg, page.file, line}}
      {:error, _} = error -> error
    end
  end
end

defmodule Serum.Build.FileProcessor.Page do
  @moduledoc false

  import Serum.IOProxy, only: [put_msg: 2]
  alias Serum.Markdown
  alias Serum.Page
  alias Serum.Plugin
  alias Serum.Project
  alias Serum.Renderer
  alias Serum.Result
  alias Serum.Template
  alias Serum.Template.Compiler, as: TC

  @spec preprocess_pages([Serum.File.t()], Project.t()) :: Result.t({[Page.t()], [map()]})
  def preprocess_pages(files, proj) do
    put_msg(:info, "Processing page files...")

    result =
      files
      |> Task.async_stream(&preprocess_page(&1, proj))
      |> Enum.map(&elem(&1, 1))
      |> Result.aggregate_values(:file_processor)

    case result do
      {:ok, pages} ->
        sorted_pages = Enum.sort(pages, &(&1.order < &2.order))

        {:ok, {sorted_pages, Enum.map(sorted_pages, &Page.compact/1)}}

      {:error, _} = error ->
        error
    end
  end

  @spec preprocess_page(Serum.File.t(), Project.t()) :: Result.t(Page.t())
  defp preprocess_page(file, proj) do
    import Serum.HeaderParser

    opts = [
      title: :string,
      label: :string,
      group: :string,
      order: :integer,
      template: :string
    ]

    required = [:title]

    with {:ok, %{in_data: data} = file2} <- Plugin.processing_page(file),
         {:ok, {header, extras, rest}} <- parse_header(data, opts, required) do
      header = Map.put(header, :label, header[:label] || header.title)
      page = Page.new(file2.src, {header, extras}, rest, proj)

      {:ok, page}
    else
      {:invalid, message} -> {:error, {message, file.src, 0}}
      {:error, _} = plugin_error -> plugin_error
    end
  end

  @spec process_pages([Page.t()], Project.t()) :: Result.t([Page.t()])
  def process_pages(pages, proj) do
    pages
    |> Task.async_stream(&process_page(&1, proj))
    |> Enum.map(&elem(&1, 1))
    |> Result.aggregate_values(:file_processor)
    |> case do
      {:ok, pages} -> Plugin.processed_pages(pages)
      {:error, _} = error -> error
    end
  end

  @spec process_page(Page.t(), Project.t()) :: Result.t(Page.t())
  defp process_page(page, proj) do
    case do_process_page(page, proj) do
      {:ok, page} -> Plugin.processed_page(page)
      {:error, _} = error -> error
    end
  end

  @spec do_process_page(Page.t(), Project.t()) :: Result.t(Page.t())
  defp do_process_page(page, proj)

  defp do_process_page(%Page{type: ".md"} = page, proj) do
    {:ok, %Page{page | data: Markdown.to_html(page.data, proj)}}
  end

  defp do_process_page(%Page{type: ".html"} = page, _proj) do
    {:ok, page}
  end

  defp do_process_page(%Page{type: ".html.eex"} = page, _proj) do
    with {:ok, ast} <- TC.compile_string(page.data),
         template = Template.new(ast, page.file, :template, page.file),
         {:ok, html} <- Renderer.render_fragment(template, []) do
      {:ok, %Page{page | data: html}}
    else
      {:ct_error, msg, line} -> {:error, {msg, page.file, line}}
      {:error, _} = error -> error
    end
  end
end


Remove unused file

Remove unused file

defmodule Serum.BuildPass1.IndexBuilder do
  alias Serum.Build
  alias Serum.Error
  alias Serum.Tag

  @type state :: Build.state

  @spec run(Build.mode, state) :: Error.result(Tag.t)

  def run(_mode, state) do
    tags =
      state.post_info
      |> Enum.reduce(MapSet.new(), fn info, acc ->
        MapSet.union acc, MapSet.new(info.tags)
      end)
      |> MapSet.to_list
    {:ok, tags}
  end
end


Remove log

Remove log

defmodule Weber.Localization.LocalizationManager do

  use GenServer.Behaviour

  defrecord LocalizationConfig,
    config: nil,
    default_locale: nil

  def start_link(config) do
    :gen_server.start_link({:local, :localization_manager}, __MODULE__, [config], [])
  end

  def init([config]) do
    :gen_server.cast(:erlang.self(), :load_localization_files)
    { :ok, LocalizationConfig.new config: config}
  end

  def handle_cast(:load_localization_files, state) do
    case :lists.keyfind(:localization, 1, state.config) do
      false ->
        {:stop, :normal, state}

      {:localization, localization_config} ->
        {:ok, project_path} = File.cwd()
        {_, default_locale}  = :lists.keyfind(:default_locale, 1, localization_config)
        {_, use_locales}  = :lists.keyfind(:use_locales, 1, localization_config)

        use_locales = Enum.map(use_locales, fn(l) -> atom_to_binary(l) <> ".json" end)

        case File.ls(project_path <> "/deps/weber/lib/weber/i18n/localization/locale") do
          {:ok, localization_files} ->
            Enum.each(localization_files, fn (file) ->
              case :lists.member(file, use_locales) do
                true ->
                  case File.read(project_path <> "/deps/weber/lib/weber/i18n/localization/locale/" <> file) do
                    {:ok, data} -> Weber.Localization.Locale.start_link(binary_to_atom(file), data)
                    _ -> :ok
                  end
                false ->
                    :ok
              end
            end)
          _ -> :ok
        end

        case File.ls(project_path <> "/lang") do
          {:ok, translation_files} ->
            Enum.each(translation_files, fn (file) ->
              {:ok, translation_file_data} = File.read(project_path <> "/lang/" <> file)
              case translation_file_data do
                <<>> -> :ok
                _ -> Weber.Translation.Translate.start_link(binary_to_atom(file), translation_file_data)
              end
            end)
          _ -> :ok
        end

        {:noreply, LocalizationConfig.new config: state.config, default_locale: default_locale}
    end
  end

  def handle_cast({:set_current_locale, locale}, state) do
    {:noreply, LocalizationConfig.new config: state.config, default_locale: locale <> ".json"}
  end

  def handle_call(:get_current_locale, _from, state) do
    {:reply, :lists.nth(1, String.split(state.default_locale, ".")), state}
  end

end

defmodule Weber.Localization.LocalizationManager do

  use GenServer.Behaviour

  defrecord LocalizationConfig,
    config: nil,
    default_locale: nil

  def start_link(config) do
    :gen_server.start_link({:local, :localization_manager}, __MODULE__, [config], [])
  end

  def init([config]) do
    :gen_server.cast(:erlang.self(), :load_localization_files)
    { :ok, LocalizationConfig.new config: config}
  end

  def handle_cast(:load_localization_files, state) do
    case :lists.keyfind(:localization, 1, state.config) do
      false ->
        {:stop, :normal, state}

      {:localization, localization_config} ->
        {:ok, project_path} = File.cwd()
        {_, default_locale}  = :lists.keyfind(:default_locale, 1, localization_config)
        {_, use_locales}  = :lists.keyfind(:use_locales, 1, localization_config)

        use_locales = Enum.map(use_locales, fn(l) -> atom_to_binary(l) <> ".json" end)

        case File.ls(project_path <> "/deps/weber/lib/weber/i18n/localization/locale") do
          {:ok, localization_files} ->
            Enum.each(localization_files, fn (file) ->
              case :lists.member(file, use_locales) do
                true ->
                  :io.format("~p~n", [File.read(project_path <> "/deps/weber/lib/weber/i18n/localization/locale/" <> file)])
                  case File.read(project_path <> "/deps/weber/lib/weber/i18n/localization/locale/" <> file) do
                    {:ok, data} -> Weber.Localization.Locale.start_link(binary_to_atom(file), data)
                    _ -> :ok
                  end
                false ->
                    :ok
              end
            end)
          _ -> :ok
        end

        case File.ls(project_path <> "/lang") do
          {:ok, translation_files} ->
            Enum.each(translation_files, fn (file) ->
              {:ok, translation_file_data} = File.read(project_path <> "/lang/" <> file)
              case translation_file_data do
                <<>> -> :ok
                _ -> Weber.Translation.Translate.start_link(binary_to_atom(file), translation_file_data)
              end
            end)
          _ -> :ok
        end

        {:noreply, LocalizationConfig.new config: state.config, default_locale: default_locale}
    end
  end

  def handle_cast({:set_current_locale, locale}, state) do
    {:noreply, LocalizationConfig.new config: state.config, default_locale: locale <> ".json"}
  end

  def handle_call(:get_current_locale, _from, state) do
    {:reply, :lists.nth(1, String.split(state.default_locale, ".")), state}
  end

end


Remove command option in favor of shell scripts

defmodule Rprel.CLI do
  @invalid_repo_name_msg "You must provide a full repo name."
  def invalid_repo_name_msg, do: @invalid_repo_name_msg

  @invalid_commit_msg "You must provide a commit sha."
  def invalid_commit_msg, do: @invalid_commit_msg

  @invalid_version_msg "You must provide a version number."
  def invalid_version_msg, do: @invalid_version_msg

  @invalid_files_msg "You must provide at least one valid file."
  def invalid_files_msg, do: @invalid_files_msg

  @invalid_token_msg "You must provide a valid GitHub authentication token."
  def invalid_token_msg, do: @invalid_token_msg

  @release_already_exists_msg "A release for that version already exists. Please use a different version."
  def release_already_exists_msg, do: @release_already_exists_msg

  @unspecified_error_msg "An unknown error has occurred."
  def unspecified_error_msg, do: @unspecified_error_msg

  def main(argv) do
    {_result, message} = do_main(argv)
    if message, do: IO.puts(message)
  end

  def do_main(argv) do
    {opts, args, _invalid_opts} = OptionParser.parse_head(argv, strict: [help: :boolean, version: :boolean], aliases: [h: :help, v: :version])
    case args do
      ["help" | cmd] -> help(cmd)
      ["build" | build_argv] -> build(build_argv)
      ["release" | release_argv] -> release(release_argv)
      _ -> handle_other_commands(opts)
     end
  end

  def help(cmd) do
    case cmd do
      ["build"] -> {:ok, build_help_text}
      ["release"] -> {:ok, release_help_text}
      _ -> {:error, "No help topic for '#{cmd}'"}
    end
  end

  def build(build_argv) do
    {build_opts, _build_args, _invalid_opts} = OptionParser.parse(build_argv, strict: [help: :boolean, archive_command: :string, build_number: :string, commit: :string, path: :string], aliases: [h: :help, a: :archive_command])
    cond do
      build_opts[:help] -> {:ok, build_help_text}
      true -> Rprel.Build.create(build_opts)
    end
  end

  def release(release_argv) do
    {release_opts, release_args, _invalid_opts} = OptionParser.parse(release_argv, strict: [help: :boolean, token: :string, commit: :string, repo: :string, version: :string], aliases: [h: :help, t: :token, c: :commit, r: :repo, v: :version])
    cond do
      release_opts[:help] -> {:ok, release_help_text}
      true -> release_opts |> update_with_release_env_vars |> do_release(release_args)
    end
  end

  def handle_other_commands(opts) do
    cond do
      opts[:help] -> {:ok, help_text}
      opts[:version] -> {:ok, Rprel.version}
      true -> {:ok, help_text}
    end
  end

  defp do_release(opts, args) do
    release = %Rprel.GithubRelease{name: opts[:repo], version: opts[:version], commit: opts[:commit]}
    case Rprel.ReleaseCreator.create(release, args, [token: opts[:token]]) do
      {:error, :invalid_auth_token} -> {:error, @invalid_token_msg}
      {:error, :invalid_repo_name} -> {:error, @invalid_repo_name_msg}
      {:error, :missing_commit} -> {:error, @invalid_commit_msg}
      {:error, :missing_version} -> {:error, @invalid_version_msg}
      {:error, :missing_files} -> {:error, @invalid_files_msg}
      {:error, :release_already_exists} -> {:error, @release_already_exists_msg}
      {:error, :unspecified_error} -> {:error, @unspecified_error_msg}
      {:ok, _} -> {:ok, ""}
    end
  end

  defp update_with_release_env_vars(opts) do
    opts
    |> Keyword.put_new(:token, System.get_env("GITHUB_AUTH_TOKEN"))
    |> Keyword.put_new(:commit, System.get_env("RELEASE_COMMIT"))
    |> Keyword.put_new(:repo, System.get_env("RELEASE_REPO"))
    |> Keyword.put_new(:version, System.get_env("RELEASE_VERSION"))
  end

  def help_text do
    ~s"""
    NAME:
       rprel - Build and create releases
    USAGE:
       rprel [global options] command [command options] [arguments...]
    VERSION:
      #{Rprel.version}
    AUTHOR(S):
      Tyler Long
      Colin Rymer
    COMMANDS:
      build
      help
      release
    GLOBAL OPTIONS:
      --help, -h           show help
      --version, -v        print the version
    COPYRIGHT:
      2016
    """
  end

  def build_help_text do
    ~s"""
    NAME:
       rprel build - Builds a release artifact

    USAGE:
       rprel build [command options] [arguments...]

    OPTIONS:
       --archive-command ARCHIVE_CMD, -a ARCHIVE_CMD
           The ARCHIVE_CMD to run during the artifact packaging phase. If no command
           is provided and a Makefile exists with an `archive` target, `make archive`
           will be run, otherwise, the source provided will be packaged into a
           gzipped tarball.
       --build-number NUMBER
           The NUMBER used by the CI service to identify the build` [$BUILD_NUMBER]
       --commit SHA
           The SHA of the build (default: `git rev-parse --verify HEAD`) [$GIT_COMMIT]
       --path PATH
           The path to tar and gzip (default: current working directory )
    """
  end

  def release_help_text do
    ~s"""
    NAME:
       rprel release - Creates GitHub release and upload artifacts

    USAGE:
       rprel release [command options] [arguments...]

    OPTIONS:
       --token TOKEN, -t TOKEN
           The GitHub authentication TOKEN [$GITHUB_AUTH_TOKEN]
       --commit SHA, -c SHA
           The commit SHA that will be used to create the release [$RELEASE_COMMIT]
       --repo OWNER/REPO, -r OWNER/REPO
           The full repo name, OWNER/REPO, where the release will be created [$RELEASE_REPO]
       --version VERSION, -v VERSION
           The release VERSION [$RELEASE_VERSION]
    """
  end
end

defmodule Rprel.CLI do
  @invalid_repo_name_msg "You must provide a full repo name."
  def invalid_repo_name_msg, do: @invalid_repo_name_msg

  @invalid_commit_msg "You must provide a commit sha."
  def invalid_commit_msg, do: @invalid_commit_msg

  @invalid_version_msg "You must provide a version number."
  def invalid_version_msg, do: @invalid_version_msg

  @invalid_files_msg "You must provide at least one valid file."
  def invalid_files_msg, do: @invalid_files_msg

  @invalid_token_msg "You must provide a valid GitHub authentication token."
  def invalid_token_msg, do: @invalid_token_msg

  @release_already_exists_msg "A release for that version already exists. Please use a different version."
  def release_already_exists_msg, do: @release_already_exists_msg

  @unspecified_error_msg "An unknown error has occurred."
  def unspecified_error_msg, do: @unspecified_error_msg

  def main(argv) do
    {_result, message} = do_main(argv)
    if message, do: IO.puts(message)
  end

  def do_main(argv) do
    {opts, args, _invalid_opts} = OptionParser.parse_head(argv, strict: [help: :boolean, version: :boolean], aliases: [h: :help, v: :version])
    case args do
      ["help" | cmd] -> help(cmd)
      ["build" | build_argv] -> build(build_argv)
      ["release" | release_argv] -> release(release_argv)
      _ -> handle_other_commands(opts)
     end
  end

  def help(cmd) do
    case cmd do
      ["build"] -> {:ok, build_help_text}
      ["release"] -> {:ok, release_help_text}
      _ -> {:error, "No help topic for '#{cmd}'"}
    end
  end

  def build(build_argv) do
    {build_opts, _build_args, _invalid_opts} = OptionParser.parse(build_argv, strict: [help: :boolean, command: :string, archive_command: :string, build_number: :string, commit: :string, path: :string], aliases: [h: :help, c: :command, a: :archive_command])
    cond do
      build_opts[:help] -> {:ok, build_help_text}
      true -> Rprel.Build.create(build_opts)
    end
  end

  def release(release_argv) do
    {release_opts, release_args, _invalid_opts} = OptionParser.parse(release_argv, strict: [help: :boolean, token: :string, commit: :string, repo: :string, version: :string], aliases: [h: :help, t: :token, c: :commit, r: :repo, v: :version])
    cond do
      release_opts[:help] -> {:ok, release_help_text}
      true -> release_opts |> update_with_release_env_vars |> do_release(release_args)
    end
  end

  def handle_other_commands(opts) do
    cond do
      opts[:help] -> {:ok, help_text}
      opts[:version] -> {:ok, Rprel.version}
      true -> {:ok, help_text}
    end
  end

  defp do_release(opts, args) do
    release = %Rprel.GithubRelease{name: opts[:repo], version: opts[:version], commit: opts[:commit]}
    case Rprel.ReleaseCreator.create(release, args, [token: opts[:token]]) do
      {:error, :invalid_auth_token} -> {:error, @invalid_token_msg}
      {:error, :invalid_repo_name} -> {:error, @invalid_repo_name_msg}
      {:error, :missing_commit} -> {:error, @invalid_commit_msg}
      {:error, :missing_version} -> {:error, @invalid_version_msg}
      {:error, :missing_files} -> {:error, @invalid_files_msg}
      {:error, :release_already_exists} -> {:error, @release_already_exists_msg}
      {:error, :unspecified_error} -> {:error, @unspecified_error_msg}
      {:ok, _} -> {:ok, ""}
    end
  end

  defp update_with_release_env_vars(opts) do
    opts
    |> Keyword.put_new(:token, System.get_env("GITHUB_AUTH_TOKEN"))
    |> Keyword.put_new(:commit, System.get_env("RELEASE_COMMIT"))
    |> Keyword.put_new(:repo, System.get_env("RELEASE_REPO"))
    |> Keyword.put_new(:version, System.get_env("RELEASE_VERSION"))
  end

  def help_text do
    ~s"""
    NAME:
       rprel - Build and create releases
    USAGE:
       rprel [global options] command [command options] [arguments...]
    VERSION:
      #{Rprel.version}
    AUTHOR(S):
      Tyler Long
      Colin Rymer
    COMMANDS:
      build
      help
      release
    GLOBAL OPTIONS:
      --help, -h           show help
      --version, -v        print the version
    COPYRIGHT:
      2016
    """
  end

  def build_help_text do
    ~s"""
    NAME:
       rprel build - Builds a release artifact

    USAGE:
       rprel build [command options] [arguments...]

    OPTIONS:
       --command CMD, -c CMD
           The CMD to run during the building of the artifact. If no command is
           provided and a Makefile is present, rprel will run `make build` if `build`
           is a valid Make target, otherwise falling back to just `make`. If no `CMD`
           is provided and there is no Makefile, nothing will be done during the
           build phase.
       --archive-command ARCHIVE_CMD, -a ARCHIVE_CMD
           The ARCHIVE_CMD to run during the artifact packaging phase. If no command
           is provided and a Makefile exists with an `archive` target, `make archive`
           will be run, otherwise, the source provided will be packaged into a
           gzipped tarball.
       --build-number NUMBER
           The NUMBER used by the CI service to identify the build` [$BUILD_NUMBER]
       --commit SHA
           The SHA of the build (default: `git rev-parse --verify HEAD`) [$GIT_COMMIT]
       --path PATH
           The path to tar and gzip (default: current working directory )
    """
  end

  def release_help_text do
    ~s"""
    NAME:
       rprel release - Creates GitHub release and upload artifacts

    USAGE:
       rprel release [command options] [arguments...]

    OPTIONS:
       --token TOKEN, -t TOKEN
           The GitHub authentication TOKEN [$GITHUB_AUTH_TOKEN]
       --commit SHA, -c SHA
           The commit SHA that will be used to create the release [$RELEASE_COMMIT]
       --repo OWNER/REPO, -r OWNER/REPO
           The full repo name, OWNER/REPO, where the release will be created [$RELEASE_REPO]
       --version VERSION, -v VERSION
           The release VERSION [$RELEASE_VERSION]
    """
  end
end


fix warnings

fix warnings

defmodule Ello.V2.PostController do
  use Ello.V2.Web, :controller
  alias Ello.Core.{Content, Content.Post}
  alias Ello.Events
  alias Ello.Events.CountPostView

  def show(conn, params) do
    with %Post{} = post <- load_post(conn, params),
         true           <- owned_by_user(post, params) do
      track_post_view(conn, post)
      render(conn, post: post)
    else
      _ -> send_resp(conn, 404, "")
    end
  end

  defp load_post(conn, %{"id" => id_or_slug}) do
    Content.post(id_or_slug,
      conn.assigns[:current_user],
      conn.assigns[:allow_nsfw],
      conn.assigns[:allow_nudity]
    )
  end

  defp track_post_view(%{assigns: assigns}, post) do
    current_user_id = case assigns[:current_user] do
      %{id: id} -> id
      _ -> nil
    end

    event = %CountPostView{
      post_ids: [post.id],
      current_user_id: current_user_id,
      stream_kind: "post",
    }
    Events.publish(event)
  end

  defp owned_by_user(post, %{"user_id" => "~" <> username}),
    do: post.author.username == username
  defp owned_by_user(post, %{"user_id" => user_id}),
    do: "#{post.author.id}" == user_id
  defp owned_by_user(_, _), do: true

end

defmodule Ello.V2.PostController do
  use Ello.V2.Web, :controller
  alias Ello.Core.{Content, Content.Post}
  alias Ello.Events
  alias Ello.Events.CountPostView

  def show(conn, params) do
    with %Post{} = post <- load_post(conn, params),
         true           <- owned_by_user(post, params) do
      track_post_view(conn, post)
      render(conn, post: post)
    else
      _ -> send_resp(conn, 404, "")
    end
  end

  defp load_post(conn, %{"id" => id_or_slug}) do
    Content.post(id_or_slug,
      conn.assigns[:current_user],
      conn.assigns[:allow_nsfw],
      conn.assigns[:allow_nudity]
    )
  end

  defp track_post_view(%{assigns: assigns} = conn, post) do
    current_user_id = case assigns[:current_user] do
      %{id: id} -> id
      _ -> nil
    end

    event = %CountPostView{
      post_ids: [post.id],
      current_user_id: current_user_id,
      stream_kind: "post",
    }
    Ello.Events.publish(event)
  end

  defp owned_by_user(post, %{"user_id" => "~" <> username}),
    do: post.author.username == username
  defp owned_by_user(post, %{"user_id" => user_id}),
    do: "#{post.author.id}" == user_id
  defp owned_by_user(_, _), do: true

end


Error fix for themes directory resolve

Error fix for themes directory resolve

defmodule Dnevnik.Theme do
   alias Dnevnik.{Config, Errors, Utils.Git, Utils.IO}

  def init do
   File.mkdir "#{Config.content_directory}/themes"
   File.cp_r IO.resolve("themes"), "#{Config.content_directory}/themes"
  end

  def ensure, do: Config.data |> Map.get(:theme) |> String.split("/") |> _ensure

  def current, do: Config.data|> Map.get(:theme) |> String.split("/") |> _current
  defp _current([local]), do: local
  defp _current([_user, repo]), do: repo
  defp _current(url), do: url |> Enum.reverse |> hd |> String.replace("\.git", "")

  defp _ensure([local]), do: ensure_local(File.dir?("#{Config.content_directory}/themes/#{local}"), local)
  defp _ensure([user, repo]), do: ensure_github(File.dir?("#{Config.content_directory}/themes/#{repo}"), user, repo)
  defp _ensure(url), do: ensure_url(url)

  defp ensure_local(true, _theme), do: true
  defp ensure_local(false, theme), do: raise(Errors.ThemeNotFound, {:local, theme})

  defp ensure_github(true, _user, _repo), do: true
  defp ensure_github(false, user, repo), do: "https://github.com/#{user}/#{repo}.git" |> Git.clone

  defp ensure_url(url) do
    repo = url
    |> Enum.reverse
    |> hd
    |> String.replace("\.git", "")

    _ensure_url(File.dir?("#{Config.content_directory}/themes/#{repo}"), Enum.join(url, "/"))
  end

  def _ensure_url(true, _url), do: true
  def _ensure_url(false, url), do: url |> Git.clone

end

defmodule Dnevnik.Theme do
   alias Dnevnik.{Config, Errors, Utils.Git, Utils.IO}

  def init do
   File.mkdir "#{Config.content_directory}/themes"
   File.cp_r IO.resolve "themes", "#{Config.content_directory}/themes"
  end

  def ensure, do: Config.data |> Map.get(:theme) |> String.split("/") |> _ensure

  def current, do: Config.data|> Map.get(:theme) |> String.split("/") |> _current
  defp _current([local]), do: local
  defp _current([_user, repo]), do: repo
  defp _current(url), do: url |> Enum.reverse |> hd |> String.replace("\.git", "")

  defp _ensure([local]), do: ensure_local(File.dir?("#{Config.content_directory}/themes/#{local}"), local)
  defp _ensure([user, repo]), do: ensure_github(File.dir?("#{Config.content_directory}/themes/#{repo}"), user, repo)
  defp _ensure(url), do: ensure_url(url)

  defp ensure_local(true, _theme), do: true
  defp ensure_local(false, theme), do: raise(Errors.ThemeNotFound, {:local, theme})

  defp ensure_github(true, _user, _repo), do: true
  defp ensure_github(false, user, repo), do: "https://github.com/#{user}/#{repo}.git" |> Git.clone

  defp ensure_url(url) do
    repo = url
    |> Enum.reverse
    |> hd
    |> String.replace("\.git", "")

    _ensure_url(File.dir?("#{Config.content_directory}/themes/#{repo}"), Enum.join(url, "/"))
  end

  def _ensure_url(true, _url), do: true
  def _ensure_url(false, url), do: url |> Git.clone

end


Update Shell.error, Shell.raise to consider --quiet arg

defmodule Doex.Io.Shell do

  def cmd("doex") do
    if has_mix?() do
      "mix doex"
    else
      "doex"
    end
  end
  def cmd(raw_cmd) do
    if has_mix?() do
      String.replace(raw_cmd, "doex ", "mix doex.")
    else
      raw_cmd
    end
  end

  def info(raw_msg, args \\ %{}) do
    case parse(raw_msg, args) do
      {:quiet, msg} -> msg
      {:ok, msg} -> if has_mix?() do
                      Mix.shell.info(msg)
                    else
                      IO.puts(msg)
                    end
    end
  end

  def inspect(data, %{quiet: true_or_false}), do: true_or_false |> _inspect(data)
  def inspect(data, args), do: Enum.member?(args, "--quiet") |> _inspect(data)

  defp _inspect(false, data), do: IO.inspect(data)
  defp _inspect(true, data), do: data

  def error(raw_msg, args \\ %{}) do
    case parse(raw_msg, args) do
      {:quiet, msg} -> msg
      {:ok, msg} -> if has_mix?() do
                      Mix.shell.error(msg)
                    else
                      IO.puts(msg)
                    end
    end
  end

  def raise(raw_msg, args \\ %{}) do
    case parse(raw_msg, args) do
      {:quiet, msg} -> msg
      {:ok, msg} -> if has_mix?() do
                      Mix.raise(msg)
                    else
                      Kernel.raise(msg)
                    end
    end
  end

  def newline, do: info ""

  defp has_mix?, do: function_exported?(Mix, :shell, 1)

  defp parse(msg, args), do: _parse(msg |> clean, args)
  defp _parse(msg, %{quiet: true}), do: {:quiet, msg}
  defp _parse(msg, args) when is_list(args) do
    if Enum.member?(args, "--quiet") do
      {:quiet, msg}
    else
      {:ok, msg}
    end
  end
  defp _parse(msg, _), do: {:ok, msg}
  defp clean(msg) when is_binary(msg), do: msg
  defp clean(msg), do: "#{msg}"

end

defmodule Doex.Io.Shell do

  def cmd("doex") do
    if has_mix?() do
      "mix doex"
    else
      "doex"
    end
  end
  def cmd(raw_cmd) do
    if has_mix?() do
      String.replace(raw_cmd, "doex ", "mix doex.")
    else
      raw_cmd
    end
  end

  def info(_msg, %{quiet: true}), do: nil
  def info(msg, args) when is_list(args) do
    if Enum.member?(args, "--quiet") do
      nil
    else
      info(msg)
    end
  end
  def info(msg, _), do: info(msg)
  def info(msg) when is_binary(msg) do
    if has_mix?() do
      Mix.shell.info(msg)
    else
      IO.puts(msg)
    end
  end
  def info(msg), do: info("#{msg}")

  def inspect(data, %{quiet: true_or_false}), do: true_or_false |> _inspect(data)
  def inspect(data, args), do: Enum.member?(args, "--quiet") |> _inspect(data)

  defp _inspect(false, data), do: IO.inspect(data)
  defp _inspect(true, data), do: data

  def error(msg) do
    if has_mix?() do
      Mix.shell.error(msg)
    else
      IO.puts(msg)
    end
  end

  def raise(msg) do
    if has_mix?() do
      Mix.raise(msg)
    else
      Kernel.raise(msg)
    end
  end

  def newline, do: info ""

  defp has_mix?, do: function_exported?(Mix, :shell, 1)
end


Using Kernel.ParallelRequire.files to load in files for macros now

defmodule ElixirScript do
  alias ESTree.Tools.Builder
  alias ESTree.Tools.Generator
  alias ElixirScript.Translator.Utils
  alias ElixirScript.Translator.ModuleCollector
  alias ElixirScript.Compiler.Cache
  alias ElixirScript.Compiler.Output
  require Logger

  @moduledoc """
  Translates Elixir into JavaScript.

  All compile functions return a list of
  transpiled javascript code or a tuple consisting of
  the file name for the code and the transpiled javascript code.

  All compile functions also take an optional opts parameter
  that controls transpiler output.

  Available options are:
  * `:include_path` - a boolean controlling whether to return just the JavaScript code
  or a tuple of the file name and the JavaScript code
  * `:root` - a binary path prepended to the path of the standard lib imports if needed
  * `:env` - a Macro.env struct to use. This is most useful when using macros. Make sure that the
  env has the macros imported or required.
  * `:core_path` - The es6 import path used to import the elixirscript core.
  When using this option, the Elixir.js file is not exported
  * `:full_build` - For compile_path, tells the compiler to perform a full build instead of incremental one
  * `:output` - option to tell compiler how to output data
      * `nil`: Return as list
      * `:stdout`: Write to standard out
      * `path (string)`: Write to specified path
  """

  defmacro __using__(_) do
    quote do
      import Kernel, only: [&&: 2, use: 2, use: 1]
      import ElixirScript.Kernel
      require ElixirScript.JS, as: JS
      require ElixirScript.Html, as: Html
      require ElixirScript.VDom, as: VDom
    end
  end

  # This is the serialized state of the ElixirScript.State module containing references to the standard library
  @external_resource stdlib_state_path = Path.join([__DIR__, "elixir_script", "translator", "stdlib_state.bin"])
  @stdlib_state File.read(stdlib_state_path)
  @lib_path Application.get_env(:elixir_script, :lib_path)

  @doc """
  Compiles the given Elixir code string
  """
  @spec compile(binary, Map.t) :: [binary | {binary, binary} | :ok]
  def compile(elixir_code, opts \\ %{}) do
    elixir_code
    |> Code.string_to_quoted!
    |> compile_quoted(opts)
  end

  @doc """
  Compiles the given Elixir code in quoted form
  """
  @spec compile_quoted(Macro.t, Map.t) :: [binary | {binary, binary} | :ok]
  def compile_quoted(quoted, opts \\ %{}) do
    { code, _ } = do_compile(opts, [quoted], get_stdlib_state, [])
    Output.out(quoted, code, build_compiler_options(opts))
  end

  @doc """
  Compiles the elixir files found at the given path
  """
  @spec compile_path(binary, Map.t) :: [binary | {binary, binary} | :ok]
  def compile_path(path, opts \\ %{}) do

    {expanded_path, loaded_modules} = case File.dir?(path) do
                                        true ->
                                          process_path(path)
                                        false ->
                                          {[path], []}
                                      end

    opts = build_compiler_options(opts)

    compiler_cache = get_compiler_cache(path, opts)

    new_file_stats = Cache.build_file_stats(expanded_path)

    changed_files = Cache.get_changed_files(compiler_cache.input_files, new_file_stats)
    |> Enum.map(fn {file, state} -> file end)

    code = Enum.map(changed_files, &file_to_quoted/1)

    { code, new_state } = do_compile(opts, code, compiler_cache.state, loaded_modules)
    compiler_cache = %{compiler_cache | input_files: new_file_stats, state: new_state }

    Cache.write(path, compiler_cache)
    Output.out(path, code, opts)
  end

  defp process_path(path) do
    path = Path.join(path, "**/*.{ex,exs,exjs}") |> Path.wildcard
    {exjs, ex} = Enum.partition(path, fn(x) ->
      case Path.extname(x) do
        ext when ext in [".ex", ".exs"] ->
          false
        _ ->
          true
      end
    end)

    ex = Kernel.ParallelRequire.files(ex)
    {exjs, ex}
  end

  defp get_stdlib_state() do
    case @stdlib_state do
      {:ok, data} ->
        data
      {:error, _} ->
        raise RuntimeError, message: "Standard Library state not found. Please run `mix std_lib`"
    end
  end

  defp get_compiler_cache(path, opts) do
    if Map.get(opts, :full_build) or empty?(opts.output) do
      Cache.delete(path)
      Cache.new(get_stdlib_state)
    else
      case Cache.get(path) do
        nil ->
          Cache.new(get_stdlib_state)
        x ->
          %{ x | full_build?: false }
      end
    end
  end

  defp empty?(path) when is_binary(path) do
    case File.ls(path) do
      {:ok, []} ->
        true
      {:error, _} ->
        true
      _ ->
        false
    end
  end

  defp empty?(_) do
    true
  end

  @doc false
  def compile_std_lib() do
    compile_std_lib(Path.join([File.cwd!, "priv"]))
  end

  @doc false
  def compile_std_lib(output_path) do
    compiler_opts = build_compiler_options(%{std_lib: true, include_path: true, output: output_path})
    libs_path = Path.join([__DIR__, "elixir_script", "prelude", "**", "*.ex"])

    code = Path.wildcard(libs_path)
    |> Enum.filter(fn(path) ->
      !String.contains?(path, ["v_dom.ex", "html.ex"])
    end)
    |> Enum.map(&file_to_quoted/1)

    ElixirScript.Translator.State.start_link(compiler_opts, [])

    code
    |> Enum.map(&update_quoted(&1))
    |> ModuleCollector.process_modules

    code = create_code(compiler_opts, ElixirScript.Translator.State.get)
    |> Enum.filter(fn({path, _}) -> !String.contains?(path, "ElixirScript.Temp.js") end)

    new_std_state = ElixirScript.Translator.State.serialize()
    ElixirScript.Translator.State.stop

    File.write!(File.cwd!() <> "/lib/elixir_script/translator/stdlib_state.bin", new_std_state)
    Output.out(libs_path, code, compiler_opts)
  end

  defp do_compile(opts, quoted_code_list, state, loaded_modules) do
    compiler_opts = build_compiler_options(opts)

    ElixirScript.Translator.State.start_link(compiler_opts, loaded_modules)
    ElixirScript.Translator.State.deserialize(state, loaded_modules)

    quoted_code_list
    |> Enum.map(&update_quoted(&1))
    |> ModuleCollector.process_modules

    code = create_code(compiler_opts, ElixirScript.Translator.State.get)
    new_state = ElixirScript.Translator.State.serialize()
    ElixirScript.Translator.State.stop

    { code, new_state }
  end

  defp build_compiler_options(opts) do
    default_options = Map.new
    |> Map.put(:include_path, false)
    |> Map.put(:root, nil)
    |> Map.put(:env, custom_env)
    |> Map.put(:import_standard_libs, true)
    |> Map.put(:core_path, "Elixir")
    |> Map.put(:full_build, false)
    |> Map.put(:output, nil)

    Map.merge(default_options, opts)
  end

  defp file_to_quoted(file) do
    file
    |> File.read!
    |> Code.string_to_quoted!
  end

  defp update_quoted(quoted) do
    Macro.prewalk(quoted, fn
    ({name, context, parms}) ->
      if context[:import] == Kernel do
        context = Keyword.update!(context, :import, fn(_) -> ElixirScript.Kernel end)
      end

      {name, context, parms}
    (x) ->
      x
    end)
  end

  @doc false
  def custom_env() do
    __using__([])
    __ENV__
  end

  defp create_code(compiler_opts, state) do

    parent = self

    Map.values(state.modules)
    |> Enum.reject(fn(ast) ->
        not ast.name in state.added_modules
      end)
      |> Enum.map(fn ast ->
      spawn_link fn ->
          env = ElixirScript.Translator.Env.module_env(ast.name,  Utils.name_to_js_file_name(ast.name) <> ".js", state.compiler_opts.env)

          module = case ast.type do
            :module ->
                       ElixirScript.Translator.Module.make_module(ast.name, ast.body, env)
            :protocol ->
                       ElixirScript.Translator.Protocol.make(ast.name, ast.functions, env)
            :protocol_implementation ->
                       ElixirScript.Translator.Protocol.Implementation.make(ast.name, ast.impl_type, ast.body, env)
                   end

          result = javascript_ast_to_code(module)

          send parent, { self, result }
        end
      end)
      |> Enum.map(fn pid ->
        receive do
          {^pid, result} -> result
        end
    end)
  end

  @doc false
  def update_protocols(compiler_output, compiler_opts) do
    Enum.reduce(compiler_output, %{}, fn
      {file, code}, state ->
        case String.split(file, ".DefImpl.") do
          [protocol, impl] ->
            protocol = String.split(protocol, "/") |> List.last |> String.to_atom
            impl = String.replace(impl, ".js", "") |> String.to_atom
            Map.put(state, protocol, Map.get(state, protocol, []) ++ [impl])
          [_] ->
            state
        end
      _, state ->
        state
    end)
    |> do_make_defimpl(compiler_opts)
  end

  @doc false
  def update_protocols_in_path(compiler_output, compiler_opts, output_path) do
    Enum.reduce(compiler_output, %{}, fn {file, code}, state ->
      case String.split(file, ".DefImpl.") do
        [protocol, _] ->
          protocol = String.split(protocol, "/") |> List.last
          protocol_impls = find_protocols_implementations_in_path(output_path, protocol)
          protocol = String.to_atom(protocol)
          Map.put(state, protocol, Map.get(state, protocol, []) ++ protocol_impls)
        [_] ->
          state
      end
    end)
    |> do_make_defimpl(compiler_opts)
  end

  defp do_make_defimpl(protocols, compiler_opts) do
    Enum.map(protocols, fn {protocol, implementations} ->
      ElixirScript.Translator.Protocol.make_defimpl(protocol, Enum.uniq(implementations), compiler_opts)
    end)
    |> Enum.map(fn(module) ->
      javascript_ast_to_code(module)
    end)
  end

  defp find_protocols_implementations_in_path(path, protocol_prefix) do
    Path.join([path, protocol_prefix <> ".DefImpl*.js"])
    |> Path.wildcard
    |> Enum.filter(fn path -> !String.ends_with?(path, "DefImpl.js") end)
    |> Enum.map(fn impl ->
      Path.basename(impl)
      |> String.split(".DefImpl.")
      |> List.last
      |> String.replace(".js", "")
      |> String.to_atom end)
  end

  @doc """
  Copies the javascript that makes up the ElixirScript stdlib
  to the specified location
  """
  def copy_stdlib_to_destination(destination) do
    Enum.each(Path.wildcard(Path.join([operating_path, "*.js"])), fn(path) ->
      base = Path.basename(path)
      File.cp!(path, Path.join([destination, base]))
    end)
  end

  defp javascript_ast_to_code(module) do
    path = Utils.name_to_js_file_name(module.name) <> ".js"
    js_ast = Builder.program(module.body)

    js_code = js_ast
    |> prepare_js_ast
    |> Generator.generate

    { path, js_code }
  end

  defp prepare_js_ast(js_ast) do
    js_ast = case js_ast do
               modules when is_list(modules) ->
                 modules
                 |> Enum.reduce([], &(&2 ++ &1.body))
                 |> Builder.program
               %ElixirScript.Translator.Group{ body: body } ->
                 Builder.program(body)
               %ElixirScript.Translator.Empty{ } ->
                 Builder.program([])
               _ ->
                 js_ast
             end

    js_ast
  end

  #Gets path to js files whether the mix project is available
  #or when used as an escript
  defp operating_path do
    case @lib_path do
      nil ->
        try do
          Mix.Project.build_path <> "/lib/elixir_script/priv"
        rescue
          UndefinedFunctionError ->
            split_path = Path.split(Application.app_dir(:elixirscript))
          replaced_path = List.delete_at(split_path, length(split_path) - 1)
          replaced_path = List.delete_at(replaced_path, length(replaced_path) - 1)
          Path.join(replaced_path)
        end
      lib_path ->
        lib_path
    end
  end

end

defmodule ElixirScript do
  alias ESTree.Tools.Builder
  alias ESTree.Tools.Generator
  alias ElixirScript.Translator.Utils
  alias ElixirScript.Translator.ModuleCollector
  alias ElixirScript.Compiler.Cache
  alias ElixirScript.Compiler.Output
  require Logger

  @moduledoc """
  Translates Elixir into JavaScript.

  All compile functions return a list of
  transpiled javascript code or a tuple consisting of
  the file name for the code and the transpiled javascript code.

  All compile functions also take an optional opts parameter
  that controls transpiler output.

  Available options are:
  * `:include_path` - a boolean controlling whether to return just the JavaScript code
  or a tuple of the file name and the JavaScript code
  * `:root` - a binary path prepended to the path of the standard lib imports if needed
  * `:env` - a Macro.env struct to use. This is most useful when using macros. Make sure that the
  env has the macros imported or required.
  * `:core_path` - The es6 import path used to import the elixirscript core.
  When using this option, the Elixir.js file is not exported
  * `:full_build` - For compile_path, tells the compiler to perform a full build instead of incremental one
  * `:output` - option to tell compiler how to output data
      * `nil`: Return as list
      * `:stdout`: Write to standard out
      * `path (string)`: Write to specified path
  """

  defmacro __using__(_) do
    quote do
      import Kernel, only: [&&: 2, use: 2, use: 1]
      import ElixirScript.Kernel
      require ElixirScript.JS, as: JS
      require ElixirScript.Html, as: Html
      require ElixirScript.VDom, as: VDom
    end
  end

  # This is the serialized state of the ElixirScript.State module containing references to the standard library
  @external_resource stdlib_state_path = Path.join([__DIR__, "elixir_script", "translator", "stdlib_state.bin"])
  @stdlib_state File.read(stdlib_state_path)
  @lib_path Application.get_env(:elixir_script, :lib_path)

  @doc """
  Compiles the given Elixir code string
  """
  @spec compile(binary, Map.t) :: [binary | {binary, binary} | :ok]
  def compile(elixir_code, opts \\ %{}) do
    elixir_code
    |> Code.string_to_quoted!
    |> compile_quoted(opts)
  end

  @doc """
  Compiles the given Elixir code in quoted form
  """
  @spec compile_quoted(Macro.t, Map.t) :: [binary | {binary, binary} | :ok]
  def compile_quoted(quoted, opts \\ %{}) do
    { code, _ } = do_compile(opts, [quoted], get_stdlib_state, [])
    Output.out(quoted, code, build_compiler_options(opts))
  end

  @doc """
  Compiles the elixir files found at the given path
  """
  @spec compile_path(binary, Map.t) :: [binary | {binary, binary} | :ok]
  def compile_path(path, opts \\ %{}) do

    {expanded_path, loaded_modules} = case File.dir?(path) do
                                        true ->
                                          process_path(path)
                                        false ->
                                          {[path], []}
                                      end

    opts = build_compiler_options(opts)

    compiler_cache = get_compiler_cache(path, opts)

    new_file_stats = Cache.build_file_stats(expanded_path)

    changed_files = Cache.get_changed_files(compiler_cache.input_files, new_file_stats)
    |> Enum.map(fn {file, state} -> file end)

    code = Enum.map(changed_files, &file_to_quoted/1)

    { code, new_state } = do_compile(opts, code, compiler_cache.state, loaded_modules)
    compiler_cache = %{compiler_cache | input_files: new_file_stats, state: new_state }

    Cache.write(path, compiler_cache)
    Output.out(path, code, opts)
  end

  defp process_path(path) do
    path = Path.join(path, "**/*.{ex,exs,exjs}") |> Path.wildcard
    {exjs, ex} = Enum.partition(path, fn(x) ->
      case Path.extname(x) do
        ext when ext in [".ex", ".exs"] ->
          false
        _ ->
          true
      end
    end)

    ex = Enum.flat_map(ex, fn(x) -> Code.load_file(x) end)
    |> Enum.map(fn({module, _}) -> module end)

    {exjs, ex}
  end

  defp get_stdlib_state() do
    case @stdlib_state do
      {:ok, data} ->
        data
      {:error, _} ->
        raise RuntimeError, message: "Standard Library state not found. Please run `mix std_lib`"
    end
  end

  defp get_compiler_cache(path, opts) do
    if Map.get(opts, :full_build) or empty?(opts.output) do
      Cache.delete(path)
      Cache.new(get_stdlib_state)
    else
      case Cache.get(path) do
        nil ->
          Cache.new(get_stdlib_state)
        x ->
          %{ x | full_build?: false }
      end
    end
  end

  defp empty?(path) when is_binary(path) do
    case File.ls(path) do
      {:ok, []} ->
        true
      {:error, _} ->
        true
      _ ->
        false
    end
  end

  defp empty?(_) do
    true
  end

  @doc false
  def compile_std_lib() do
    compile_std_lib(Path.join([File.cwd!, "priv"]))
  end

  @doc false
  def compile_std_lib(output_path) do
    compiler_opts = build_compiler_options(%{std_lib: true, include_path: true, output: output_path})
    libs_path = Path.join([__DIR__, "elixir_script", "prelude", "**", "*.ex"])

    code = Path.wildcard(libs_path)
    |> Enum.filter(fn(path) ->
      !String.contains?(path, ["v_dom.ex", "html.ex"])
    end)
    |> Enum.map(&file_to_quoted/1)

    ElixirScript.Translator.State.start_link(compiler_opts, [])

    code
    |> Enum.map(&update_quoted(&1))
    |> ModuleCollector.process_modules

    code = create_code(compiler_opts, ElixirScript.Translator.State.get)
    |> Enum.filter(fn({path, _}) -> !String.contains?(path, "ElixirScript.Temp.js") end)

    new_std_state = ElixirScript.Translator.State.serialize()
    ElixirScript.Translator.State.stop

    File.write!(File.cwd!() <> "/lib/elixir_script/translator/stdlib_state.bin", new_std_state)
    Output.out(libs_path, code, compiler_opts)
  end

  defp do_compile(opts, quoted_code_list, state, loaded_modules) do
    compiler_opts = build_compiler_options(opts)

    ElixirScript.Translator.State.start_link(compiler_opts, loaded_modules)
    ElixirScript.Translator.State.deserialize(state, loaded_modules)

    quoted_code_list
    |> Enum.map(&update_quoted(&1))
    |> ModuleCollector.process_modules

    code = create_code(compiler_opts, ElixirScript.Translator.State.get)
    new_state = ElixirScript.Translator.State.serialize()
    ElixirScript.Translator.State.stop

    { code, new_state }
  end

  defp build_compiler_options(opts) do
    default_options = Map.new
    |> Map.put(:include_path, false)
    |> Map.put(:root, nil)
    |> Map.put(:env, custom_env)
    |> Map.put(:import_standard_libs, true)
    |> Map.put(:core_path, "Elixir")
    |> Map.put(:full_build, false)
    |> Map.put(:output, nil)

    Map.merge(default_options, opts)
  end

  defp file_to_quoted(file) do
    file
    |> File.read!
    |> Code.string_to_quoted!
  end

  defp update_quoted(quoted) do
    Macro.prewalk(quoted, fn
    ({name, context, parms}) ->
      if context[:import] == Kernel do
        context = Keyword.update!(context, :import, fn(_) -> ElixirScript.Kernel end)
      end

      {name, context, parms}
    (x) ->
      x
    end)
  end

  @doc false
  def custom_env() do
    __using__([])
    __ENV__
  end

  defp create_code(compiler_opts, state) do

    parent = self

    Map.values(state.modules)
    |> Enum.reject(fn(ast) ->
        not ast.name in state.added_modules
      end)
      |> Enum.map(fn ast ->
      spawn_link fn ->
          env = ElixirScript.Translator.Env.module_env(ast.name,  Utils.name_to_js_file_name(ast.name) <> ".js", state.compiler_opts.env)

          module = case ast.type do
            :module ->
                       ElixirScript.Translator.Module.make_module(ast.name, ast.body, env)
            :protocol ->
                       ElixirScript.Translator.Protocol.make(ast.name, ast.functions, env)
            :protocol_implementation ->
                       ElixirScript.Translator.Protocol.Implementation.make(ast.name, ast.impl_type, ast.body, env)
                   end

          result = javascript_ast_to_code(module)

          send parent, { self, result }
        end
      end)
      |> Enum.map(fn pid ->
        receive do
          {^pid, result} -> result
        end
    end)
  end

  @doc false
  def update_protocols(compiler_output, compiler_opts) do
    Enum.reduce(compiler_output, %{}, fn
      {file, code}, state ->
        case String.split(file, ".DefImpl.") do
          [protocol, impl] ->
            protocol = String.split(protocol, "/") |> List.last |> String.to_atom
            impl = String.replace(impl, ".js", "") |> String.to_atom
            Map.put(state, protocol, Map.get(state, protocol, []) ++ [impl])
          [_] ->
            state
        end
      _, state ->
        state
    end)
    |> do_make_defimpl(compiler_opts)
  end

  @doc false
  def update_protocols_in_path(compiler_output, compiler_opts, output_path) do
    Enum.reduce(compiler_output, %{}, fn {file, code}, state ->
      case String.split(file, ".DefImpl.") do
        [protocol, _] ->
          protocol = String.split(protocol, "/") |> List.last
          protocol_impls = find_protocols_implementations_in_path(output_path, protocol)
          protocol = String.to_atom(protocol)
          Map.put(state, protocol, Map.get(state, protocol, []) ++ protocol_impls)
        [_] ->
          state
      end
    end)
    |> do_make_defimpl(compiler_opts)
  end

  defp do_make_defimpl(protocols, compiler_opts) do
    Enum.map(protocols, fn {protocol, implementations} ->
      ElixirScript.Translator.Protocol.make_defimpl(protocol, Enum.uniq(implementations), compiler_opts)
    end)
    |> Enum.map(fn(module) ->
      javascript_ast_to_code(module)
    end)
  end

  defp find_protocols_implementations_in_path(path, protocol_prefix) do
    Path.join([path, protocol_prefix <> ".DefImpl*.js"])
    |> Path.wildcard
    |> Enum.filter(fn path -> !String.ends_with?(path, "DefImpl.js") end)
    |> Enum.map(fn impl ->
      Path.basename(impl)
      |> String.split(".DefImpl.")
      |> List.last
      |> String.replace(".js", "")
      |> String.to_atom end)
  end

  @doc """
  Copies the javascript that makes up the ElixirScript stdlib
  to the specified location
  """
  def copy_stdlib_to_destination(destination) do
    Enum.each(Path.wildcard(Path.join([operating_path, "*.js"])), fn(path) ->
      base = Path.basename(path)
      File.cp!(path, Path.join([destination, base]))
    end)
  end

  defp javascript_ast_to_code(module) do
    path = Utils.name_to_js_file_name(module.name) <> ".js"
    js_ast = Builder.program(module.body)

    js_code = js_ast
    |> prepare_js_ast
    |> Generator.generate

    { path, js_code }
  end

  defp prepare_js_ast(js_ast) do
    js_ast = case js_ast do
               modules when is_list(modules) ->
                 modules
                 |> Enum.reduce([], &(&2 ++ &1.body))
                 |> Builder.program
               %ElixirScript.Translator.Group{ body: body } ->
                 Builder.program(body)
               %ElixirScript.Translator.Empty{ } ->
                 Builder.program([])
               _ ->
                 js_ast
             end

    js_ast
  end

  #Gets path to js files whether the mix project is available
  #or when used as an escript
  defp operating_path do
    case @lib_path do
      nil ->
        try do
          Mix.Project.build_path <> "/lib/elixir_script/priv"
        rescue
          UndefinedFunctionError ->
            split_path = Path.split(Application.app_dir(:elixirscript))
          replaced_path = List.delete_at(split_path, length(split_path) - 1)
          replaced_path = List.delete_at(replaced_path, length(replaced_path) - 1)
          Path.join(replaced_path)
        end
      lib_path ->
        lib_path
    end
  end

end


Expand column responses after coalescing them

defmodule Exsoda.Writer do
  alias Exsoda.Http

  defmodule CreateColumn do
    defstruct name: nil,
    dataTypeName: nil,
    fourfour: nil,
    properties: %{} # description, fieldName
  end

  defmodule CreateColumns do
    defstruct columns: []
  end

  defmodule CreateView do
    defstruct name: nil,
    properties: %{}
  end

  defmodule UpdateView do
    defstruct fourfour: nil,
    properties: %{}
  end

  defmodule Upsert do
    defstruct fourfour: nil,
    mode: nil,
    rows: []
  end

  defmodule Write do
    defstruct opts: %{},
      operations: []
  end

  defmodule Publish do
    defstruct fourfour: nil
  end

  defmodule Permission do
    defstruct fourfour: nil,
      mode: nil
  end

  defmodule PrepareDraftForImport do
    defstruct fourfour: nil
  end

  def write(options \\ []) do
    %Write{
      opts: Http.options(options)
    }
  end

  def create(%Write{} = w, name, properties) do
    operation = %CreateView{name: name, properties: properties}
    %{ w | operations: [operation | w.operations] }
  end

  def update(%Write{} = w, fourfour, properties) do
    operation = %UpdateView{fourfour: fourfour, properties: properties}
    %{ w | operations: [operation | w.operations] }
  end

  def create_column(%Write{} = w, fourfour, name, type, properties) do
    operation = %CreateColumn{name: name, dataTypeName: type, fourfour: fourfour, properties: properties}
    %{ w | operations: [operation | w.operations] }
  end

  # a row looks like: {fieldName: value, fieldName: value}
  def upsert(%Write{} = w, fourfour, rows) do
    operation = %Upsert{fourfour: fourfour, rows: rows, mode: :append}
    %{ w | operations: [operation | w.operations] }
  end

  def replace(%Write{} = w, fourfour, rows) do
    operation = %Upsert{fourfour: fourfour, rows: rows, mode: :replace}
    %{ w | operations: [operation | w.operations] }
  end

  def publish(%Write{} = w, fourfour) do
    operation = %Publish{fourfour: fourfour}
    %{ w | operations: [operation | w.operations]}
  end

  def permission(%Write{} = w, fourfour, :public) do
    operation = %Permission{fourfour: fourfour, mode: "public.read"}
    %{ w | operations: [operation | w.operations]}
  end

  def permission(%Write{} = w, fourfour, :private) do
    operation = %Permission{fourfour: fourfour, mode: "private"}
    %{ w | operations: [operation | w.operations]}
  end

  def prepare_draft_for_import(%Write{} = w, fourfour) do
    operation = %PrepareDraftForImport{fourfour: fourfour}
    %{ w | operations: [operation | w.operations]}
  end

  defp do_run(%CreateView{} = cv, w) do
    data = Map.merge(cv.properties, %{name: cv.name})
    with {:ok, json} <- Poison.encode(data) do
      Http.post("/views.json", w, json)
    end
  end

  defp do_run(%UpdateView{} = uv, w) do
    with {:ok, json} <- Poison.encode(uv.properties) do
      Http.put("/views/#{uv.fourfour}.json", w, json)
    end
  end

  defp do_run(%CreateColumn{} = cc, w) do
    data = merge_column(cc)

    with {:ok, json} <- Poison.encode(data) do
      Http.post("/views/#{cc.fourfour}/columns", w, json)
    end
  end

  defp do_run(%CreateColumns{columns: ccs}, w) do
    data = %{"columns" => Enum.map(ccs, &merge_column/1)}

    with {:ok, json} <- Poison.encode(data) do
      case Http.post("/views/#{hd(ccs).fourfour}/columns?method=multiCreate", w, json) do
        {:ok, list} -> Enum.map(list, fn result -> {:ok, result} end)
        {:error, _} = err -> Enum.map(ccs, fn _ -> err end)
      end
    end
  end

  defp do_run(%Upsert{rows: rows} = u, w) when is_list(rows) do
    with {:ok, json} <- Poison.encode(rows) do
      case u.mode do
        :append -> Http.post("/id/#{u.fourfour}.json", w, json)
        :replace -> Http.put("/id/#{u.fourfour}.json", w, json)
      end
    end
  end
  defp do_run(%Upsert{rows: rows} = u, w) do
    with_commas = Stream.transform(rows, false, fn
      row, false -> {[Poison.encode!(row)], true}
      row, true  -> {[",\n" <> Poison.encode!(row)], true}
    end)

    json_stream = Stream.concat(
      ["["],
      with_commas
    )
    |> Stream.concat(["]"])

    case u.mode do
      :append -> Http.post("/id/#{u.fourfour}.json", w, {:stream, json_stream})
      :replace -> Http.put("/id/#{u.fourfour}.json", w, {:stream, json_stream})
    end
  end

  defp do_run(%Publish{fourfour: fourfour}, w) do
    with {:ok, json} <- Poison.encode(%{}) do
      Http.post("/views/#{fourfour}/publication", w, json)
    end
  end

  defp do_run(%Permission{fourfour: fourfour, mode: mode}, w) do
    with {:ok, json} <- Poison.encode(mode) do
      Http.put("/views/#{fourfour}?method=setPermission", w, json)
    end
  end

  defp do_run(%PrepareDraftForImport{fourfour: fourfour}, w) do
    with {:ok, json} <- Poison.encode(%{}) do
      Http.patch("/views/#{fourfour}?method=prepareDraftForImport", w, json)
    end
  end

  defp merge_column(%CreateColumn{} = cc), do: Map.take(cc, [:dataTypeName, :name]) |> Map.merge(cc.properties)

  defp collapse_column_create([]), do: []
  defp collapse_column_create([%CreateColumn{fourfour: fourfour} = cc | ccs]) do
    {ccs, remainder} = Enum.split_while(ccs, fn
      %CreateColumn{fourfour: ^fourfour} -> true
      _ -> false
    end)
    [%CreateColumns{columns: [cc | ccs]} | collapse_column_create(remainder)]
  end
  defp collapse_column_create([h | t]), do: [h | collapse_column_create(t)]

  def run(%Write{} = w) do
    w.operations
    |> Enum.reverse
    |> collapse_column_create
    |> Enum.reduce_while([], fn op, acc ->
      Enum.reduce_while(List.wrap(do_run(op, w)), {:cont, acc}, fn result, {_, acc} ->
        case result do
          {:error, _} = err -> {:halt, {:halt, [err | acc]}}
          {:ok, _} = ok     -> {:cont, {:cont, [ok  | acc]}}
        end
      end)
    end)
    |> Enum.reverse
  end
end

defmodule Exsoda.Writer do
  alias Exsoda.Http

  defmodule CreateColumn do
    defstruct name: nil,
    dataTypeName: nil,
    fourfour: nil,
    properties: %{} # description, fieldName
  end

  defmodule CreateColumns do
    defstruct columns: []
  end

  defmodule CreateView do
    defstruct name: nil,
    properties: %{}
  end

  defmodule UpdateView do
    defstruct fourfour: nil,
    properties: %{}
  end

  defmodule Upsert do
    defstruct fourfour: nil,
    mode: nil,
    rows: []
  end

  defmodule Write do
    defstruct opts: %{},
      operations: []
  end

  defmodule Publish do
    defstruct fourfour: nil
  end

  defmodule Permission do
    defstruct fourfour: nil,
      mode: nil
  end

  defmodule PrepareDraftForImport do
    defstruct fourfour: nil
  end

  def write(options \\ []) do
    %Write{
      opts: Http.options(options)
    }
  end

  def create(%Write{} = w, name, properties) do
    operation = %CreateView{name: name, properties: properties}
    %{ w | operations: [operation | w.operations] }
  end

  def update(%Write{} = w, fourfour, properties) do
    operation = %UpdateView{fourfour: fourfour, properties: properties}
    %{ w | operations: [operation | w.operations] }
  end

  def create_column(%Write{} = w, fourfour, name, type, properties) do
    operation = %CreateColumn{name: name, dataTypeName: type, fourfour: fourfour, properties: properties}
    %{ w | operations: [operation | w.operations] }
  end

  # a row looks like: {fieldName: value, fieldName: value}
  def upsert(%Write{} = w, fourfour, rows) do
    operation = %Upsert{fourfour: fourfour, rows: rows, mode: :append}
    %{ w | operations: [operation | w.operations] }
  end

  def replace(%Write{} = w, fourfour, rows) do
    operation = %Upsert{fourfour: fourfour, rows: rows, mode: :replace}
    %{ w | operations: [operation | w.operations] }
  end

  def publish(%Write{} = w, fourfour) do
    operation = %Publish{fourfour: fourfour}
    %{ w | operations: [operation | w.operations]}
  end

  def permission(%Write{} = w, fourfour, :public) do
    operation = %Permission{fourfour: fourfour, mode: "public.read"}
    %{ w | operations: [operation | w.operations]}
  end

  def permission(%Write{} = w, fourfour, :private) do
    operation = %Permission{fourfour: fourfour, mode: "private"}
    %{ w | operations: [operation | w.operations]}
  end

  def prepare_draft_for_import(%Write{} = w, fourfour) do
    operation = %PrepareDraftForImport{fourfour: fourfour}
    %{ w | operations: [operation | w.operations]}
  end

  defp do_run(%CreateView{} = cv, w) do
    data = Map.merge(cv.properties, %{name: cv.name})
    with {:ok, json} <- Poison.encode(data) do
      Http.post("/views.json", w, json)
    end
  end

  defp do_run(%UpdateView{} = uv, w) do
    with {:ok, json} <- Poison.encode(uv.properties) do
      Http.put("/views/#{uv.fourfour}.json", w, json)
    end
  end

  defp do_run(%CreateColumn{} = cc, w) do
    data = merge_column(cc)

    with {:ok, json} <- Poison.encode(data) do
      Http.post("/views/#{cc.fourfour}/columns", w, json)
    end
  end

  defp do_run(%CreateColumns{columns: ccs}, w) do
    data = %{"columns" => Enum.map(ccs, &merge_column/1)}

    with {:ok, json} <- Poison.encode(data) do
      Http.post("/views/#{hd(ccs).fourfour}/columns?method=multiCreate", w, json)
    end
  end

  defp do_run(%Upsert{rows: rows} = u, w) when is_list(rows) do
    with {:ok, json} <- Poison.encode(rows) do
      case u.mode do
        :append -> Http.post("/id/#{u.fourfour}.json", w, json)
        :replace -> Http.put("/id/#{u.fourfour}.json", w, json)
      end
    end
  end
  defp do_run(%Upsert{rows: rows} = u, w) do
    with_commas = Stream.transform(rows, false, fn
      row, false -> {[Poison.encode!(row)], true}
      row, true  -> {[",\n" <> Poison.encode!(row)], true}
    end)

    json_stream = Stream.concat(
      ["["],
      with_commas
    )
    |> Stream.concat(["]"])

    case u.mode do
      :append -> Http.post("/id/#{u.fourfour}.json", w, {:stream, json_stream})
      :replace -> Http.put("/id/#{u.fourfour}.json", w, {:stream, json_stream})
    end
  end

  defp do_run(%Publish{fourfour: fourfour}, w) do
    with {:ok, json} <- Poison.encode(%{}) do
      Http.post("/views/#{fourfour}/publication", w, json)
    end
  end

  defp do_run(%Permission{fourfour: fourfour, mode: mode}, w) do
    with {:ok, json} <- Poison.encode(mode) do
      Http.put("/views/#{fourfour}?method=setPermission", w, json)
    end
  end

  defp do_run(%PrepareDraftForImport{fourfour: fourfour}, w) do
    with {:ok, json} <- Poison.encode(%{}) do
      Http.patch("/views/#{fourfour}?method=prepareDraftForImport", w, json)
    end
  end

  defp merge_column(%CreateColumn{} = cc), do: Map.take(cc, [:dataTypeName, :name]) |> Map.merge(cc.properties)

  defp collapse_column_create([]), do: []
  defp collapse_column_create([%CreateColumn{fourfour: fourfour} = cc | ccs]) do
    {ccs, remainder} = Enum.split_while(ccs, fn
      %CreateColumn{fourfour: ^fourfour} -> true
      _ -> false
    end)
    [%CreateColumns{columns: [cc | ccs]} | collapse_column_create(remainder)]
  end
  defp collapse_column_create([h | t]), do: [h | collapse_column_create(t)]

  def run(%Write{} = w) do
    Enum.reduce_while(collapse_column_create(Enum.reverse(w.operations)), [], fn op, acc ->
        case do_run(op, w) do
          {:error, _} = err -> {:halt, [err | acc]}
          {:ok, _} = ok     -> {:cont, [ok  | acc]}
        end
    end)
    |> Enum.reverse
  end
end


Replace metadata with module attributes

Replace metadata with module attributes

defmodule MetaInspector.Meta do
  defstruct og_title: [], og_type: [], og_url: [], og_image: []
  @type t :: %__MODULE__{og_title: list, og_type: list, og_url: list, og_image: list}
end

defmodule MetaInspector do
  defstruct status: nil, title: nil, best_title: nil, best_image: nil, meta: %{}
  @type t :: %__MODULE__{status: integer, title: String.t, best_title: String.t, best_image: String.t, meta: MetaInspector.Meta.t}

  @metadata ["title", "type", "image", "url"]

  @spec new(String.t) :: __MODULE__.t
  def new(url) do
    case HTTPoison.get(url) do
      {:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
        {:ok, fetch(body)}
      {:error, %HTTPoison.Error{reason: reason}} ->
        {:error, reason}
    end
  end

  def new!(url) do
    case new(url) do
      {:ok, response} ->
        response
      {:error, reason} ->
        reason
    end
  end

  def fetch(body) do
    body = body |> to_utf8
    %__MODULE__{status: 200, title: title(body), best_title: best_title(body), best_image: best_image(body), meta: meta(body)}
  end

  def status(response) do
    response.status_code
  end

  @spec title(String.t) :: String.t
  def title(body) do
    body
    |> Floki.find("head title")
    |> Floki.text
  end

  @spec best_title(String.t) :: String.t
  def best_title(body) do
    og_title = og_title(body) |> to_string
    title    = title(body)
    case og_title >= title do
      true  -> og_title
      false -> title
    end
  end

  @spec best_image(String.t) :: String.t
  def best_image(body) do
    og_image(body)
    |> List.first
  end

  for meta <- @metadata do
    def unquote(:"og_#{meta}")(body), do: meta_tag_by(body, unquote(meta))
  end

  @spec meta_tag_by(String.t, String.t) :: [String.t]
  defp meta_tag_by(body, attribute) when attribute in @metadata do
    Floki.find(body, "[property=\"og:#{attribute}\"]")
    |> Floki.attribute("content")
  end

  defp meta(body) do
    %__MODULE__.Meta{og_title: og_title(body), og_type: og_type(body), og_url: og_url(body), og_image: og_image(body)}
  end

  def to_utf8(body) do
    case String.valid?(body) do
      true -> body
      false -> decode_to_utf8(body)
    end
  end

  def decode_to_utf8(body) do
    Mbcs.start
    str = :erlang.binary_to_list(body)
    "#{Mbcs.decode!(str, :cp932, return: :list)}"
  end
end

defmodule MetaInspector.Meta do
  defstruct og_title: [], og_type: [], og_url: [], og_image: []
  @type t :: %__MODULE__{og_title: list, og_type: list, og_url: list, og_image: list}
end

defmodule MetaInspector do
  defstruct status: nil, title: nil, best_title: nil, best_image: nil, meta: %{}
  @type t :: %__MODULE__{status: integer, title: String.t, best_title: String.t, best_image: String.t, meta: MetaInspector.Meta.t}

  @spec new(String.t) :: __MODULE__.t
  def new(url) do
    case HTTPoison.get(url) do
      {:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
        {:ok, fetch(body)}
      {:error, %HTTPoison.Error{reason: reason}} ->
        {:error, reason}
    end
  end

  def new!(url) do
    case new(url) do
      {:ok, response} ->
        response
      {:error, reason} ->
        reason
    end
  end

  def fetch(body) do
    body = body |> to_utf8
    %__MODULE__{status: 200, title: title(body), best_title: best_title(body), best_image: best_image(body), meta: meta(body)}
  end

  def status(response) do
    response.status_code
  end

  @spec title(String.t) :: String.t
  def title(body) do
    body
    |> Floki.find("head title")
    |> Floki.text
  end

  @spec best_title(String.t) :: String.t
  def best_title(body) do
    og_title = og_title(body) |> to_string
    title    = title(body)
    case og_title >= title do
      true  -> og_title
      false -> title
    end
  end

  @spec best_image(String.t) :: String.t
  def best_image(body) do
    og_image(body)
    |> List.first
  end

  metadata = ["title", "type", "image", "url"]
  for meta <- metadata do
    def unquote(:"og_#{meta}")(body), do: meta_tag_by(body, unquote(meta))
  end

  @spec meta_tag_by(String.t, String.t) :: [String.t]
  defp meta_tag_by(body, attribute) when attribute in ["title", "image", "type", "url"] do
    Floki.find(body, "[property=\"og:#{attribute}\"]")
    |> Floki.attribute("content")
  end

  defp meta(body) do
    %__MODULE__.Meta{og_title: og_title(body), og_type: og_type(body), og_url: og_url(body), og_image: og_image(body)}
  end

  def to_utf8(body) do
    case String.valid?(body) do
      true -> body
      false -> decode_to_utf8(body)
    end
  end

  def decode_to_utf8(body) do
    Mbcs.start
    str = :erlang.binary_to_list(body)
    "#{Mbcs.decode!(str, :cp932, return: :list)}"
  end
end