mix
mix 是管理 elixir project 的工具
$ mix help
mix # Runs the default task (current: "mix run")
mix app.start # Starts all registered apps
mix app.tree # Prints the application tree
mix archive # Lists installed archives
mix archive.build # Archives this project into a .ez file
mix archive.install # Installs an archive locally
mix archive.uninstall # Uninstalls archives
mix clean # Deletes generated application files
mix cmd # Executes the given command
mix compile # Compiles source files
mix deps # Lists dependencies and their status
mix deps.clean # Deletes the given dependencies' files
mix deps.compile # Compiles dependencies
mix deps.get # Gets all out of date dependencies
mix deps.tree # Prints the dependency tree
mix deps.unlock # Unlocks the given dependencies
mix deps.update # Updates the given dependencies
mix do # Executes the tasks separated by comma
mix escript # Lists installed escripts
mix escript.build # Builds an escript for the project
mix escript.install # Installs an escript locally
mix escript.uninstall # Uninstalls escripts
mix help # Prints help information for tasks
mix loadconfig # Loads and persists the given configuration
mix local # Lists local tasks
mix local.hex # Installs Hex locally
mix local.public_keys # Manages public keys
mix local.rebar # Installs Rebar locally
mix new # Creates a new Elixir project
mix profile.cprof # Profiles the given file or expression with cprof
mix profile.fprof # Profiles the given file or expression with fprof
mix run # Runs the given file or expression
mix test # Runs a project's tests
mix xref # Performs cross reference checks
iex -S mix # Starts IEx and runs the default task
建立新 project
$ mix new test
* creating README.md
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/test.ex
* creating test
* creating test/test_helper.exs
* creating test/test_test.exs
Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:
cd test
mix test
Run "mix help" for more commands.
如果要放到 git
$ git init
$ git add .
$ git commit -m "Initial commit of new project"
project 結構:
config/
內含 config.exs, 設定資料
lib/
內含 test.ex,這是 top-level module
mix.exs
project 設定
README.md
project 說明文件
test/
內含 test_helper.exs, test_test.exs 測試程式
如何處理 command line
lib/issues/cli.ex
defmodule Issues.CLI do
@default_count 4
@moduledoc """
Handle the command line parsing and the dispatch to
the various functions that end up generating a
table of the last _n_ issues in a github project
"""
def run(argv) do
parse_args(argv)
end
@doc """
`argv` can be -h or --help, which returns :help.
Otherwise it is a github user name, project name, and (optionally)
the number of entries to format.
Return a tuple of `{ user, project, count }`, or `:help` if help was given.
"""
def parse_args(argv) do
parse = OptionParser.parse(argv, switches: [ help: :boolean],
aliases: [ h: :help ])
case parse do
{ [ help: true ], _, _ } -> :help
{ _, [ user, project, count ], _ } -> { user, project, String.to_integer(count) }
{ _, [ user, project ], _ } -> { user, project, @default_count }
_ -> :help
end
end
end
簡單的測試 test/issues_test.exs
defmodule IssuesTest do
use ExUnit.Case
test "the truth" do
assert(true)
end
end
test/cli_test.exs
defmodule CliTest do
use ExUnit.Case
test "nil returned by option parsing with -h and --help options" do
assert Issues.CLI.parse_args(["-h", "anything"]) == :help
assert Issues.CLI.parse_args(["--help", "anything"]) == :help
end
test "three values returned if three given" do
assert Issues.CLI.parse_args(["user", "project", "99"]) == { "user", "project", 99 }
end
test "count is defaulted if two values given" do
assert Issues.CLI.parse_args(["user", "project"]) == { "user", "project", 4 }
end
end
mix.exs
defmodule Issues.Mixfile do
use Mix.Project
def project do
[ app: :issues,
version: "0.0.1",
deps: deps
]
end
# Configuration for the OTP application
def application do
[]
end
# Returns the list of dependencies in the format:
# { :foobar, "0.1", git: "https://github.com/elixir-lang/foobar.git" }
defp deps do
[]
end
end
列印 project deps 相關 libs
$ mix deps
取得 deps
$ mix deps.get
執行測試
$ mix test
warning: variable "deps" does not exist and is being expanded to "deps()", please use parentheses to remove the ambiguity or change the variable name
mix.exs:7
Compiling 2 files (.ex)
Generated issues app
warning: this check/guard will always yield the same result
test/issues_test.exs:5
....
Finished in 0.05 seconds
4 tests, 0 failures
Randomized with seed 80914
在 mix.exs 裡面定義使用 library
mix.exs 增加 defp deps 的部分
defmodule Issues.Mixfile do
use Mix.Project
def project do
[ app: :issues,
version: "0.0.1",
deps: deps
]
end
# Configuration for the OTP application
def application do
[ applications: [:httpotion ] ]
end
# Returns the list of dependencies in the format:
# { :foobar, "0.1", git: "https://github.com/elixir-lang/foobar.git" }
defp deps do
[
{ :httpotion, github: "myfreeweb/httpotion" }
]
end
end
修改 cli.exs
defmodule Issues.CLI do
@default_count 4
@moduledoc """
Handle the command line parsing and the dispatch to
the various functions that end up generating a
table of the last _n_ issues in a github project
"""
def run(argv) do
argv
|> parse_args
|> process
end
@doc """
`argv` can be -h or --help, which returns `:help`.
Otherwise it is a github user name, project name, and (optionally)
the number of entries to format
Return a tuple of `{ user, project, count }`, or `nil` if help was given.
"""
def parse_args(argv) do
parse = OptionParser.parse(argv, switches: [ help: :boolean],
aliases: [ h: :help ])
case parse do
{ [ help: true ], _, _ } -> :help
{ _, [ user, project, count ], _ } -> { user, project, String.to_integer(count) }
{ _, [ user, project ], _ } -> { user, project, @default_count }
_ -> :help
end
end
def process(:help) do
IO.puts """
usage: issues <user> <project> [ count | #{@default_count} ]
"""
System.halt(0)
end
def process({user, project, count}) do
Issues.GithubIssues.fetch(user, project)
end
end
增加 lib/issues/github_issues.ex
defmodule Issues.GithubIssues do
@user_agent [ {"User-agent", "Elixir dave@pragprog.com"} ]
def fetch(user, project) do
issues_url(user, project)
|> HTTPoison.get(@user_agent)
|> handle_response
end
def issues_url(user, project) do
"https://api.github.com/repos/#{user}/#{project}/issues"
end
def handle_response({ :ok, %{status_code: 200, body: body}}) do
{ :ok, body }
end
def handle_response({ _, %{status_code: _, body: body}}) do
{ :error, body }
end
end
增加 jsonex library
defp deps do
[
{:httpotion, github: "myfreeweb/httpotion" },
{:jsonex, "2.0", github: "marcelog/jsonex", tag: "2.0" }
]
end
修改 github_issues.ex
defmodule Issues.GithubIssues do
@user_agent [ {"User-agent", "Elixir dave@pragprog.com"} ]
def fetch(user, project) do
issues_url(user, project)
|> HTTPoison.get(@user_agent)
|> handle_response
end
def handle_response({:ok, %{status_code: 200, body: body}}) do
{ :ok, Poison.Parser.parse!(body) }
end
def handle_response({_, %{status_code: _, body: body}}) do
{ :error, Poison.Parser.parse!(body) }
end
# use a module attribute to fetch the value at compile time
@github_url Application.get_env(:issues, :github_url)
def issues_url(user, project) do
"#{@github_url}/repos/#{user}/#{project}/issues"
end
end
cli_test.exs
defmodule CliTest do
use ExUnit.Case
import Issues.CLI, only: [ parse_args: 1,
sort_into_ascending_order: 1 ]
test ":help returned by option parsing with -h and --help options" do
assert parse_args(["-h", "anything"]) == :help
assert parse_args(["--help", "anything"]) == :help
end
test "three values returned if three given" do
assert parse_args(["user", "project", "99"]) == { "user", "project", 99 }
end
test "count is defaulted if two values given" do
assert parse_args(["user", "project"]) == { "user", "project", 4 }
end
test "sort ascending orders the correct way" do
result = sort_into_ascending_order(fake_created_at_list(["c", "a", "b"]))
issues = for issue <- result, do: Map.get(issue, "created_at")
assert issues == ~w{a b c}
end
defp fake_created_at_list(values) do
for value <- values,
do: %{"created_at" => value, "other_data" => "xxx"}
end
end
cli.ex
defmodule Issues.CLI do
@default_count 4
@moduledoc """
Handle the command line parsing and the dispatch to
the various functions that end up generating a
table of the last _n_ issues in a github project
"""
def run(argv) do
argv
|> parse_args
|> process
end
@doc """
`argv` can be -h or --help, which returns `:help`.
Otherwise it is a github user name, project name, and (optionally)
the number of entries to format
Return a tuple of `{ user, project, count }`, or `nil` if help was given.
"""
def parse_args(argv) do
parse = OptionParser.parse(argv, switches: [ help: :boolean],
aliases: [ h: :help ])
case parse do
{ [ help: true ], _, _ } -> :help
{ _, [ user, project, count ], _ } -> { user, project, String.to_integer(count) }
{ _, [ user, project ], _ } -> { user, project, @default_count }
_ -> :help
end
end
def process(:help) do
IO.puts """
usage: issues <user> <project> [ count | #{@default_count} ]
"""
System.halt(0)
end
def process({user, project, count}) do
Issues.GithubIssues.fetch(user, project)
|> decode_response
|> convert_to_list_of_hashdicts
|> sort_into_ascending_order
end
def decode_response({:ok, body}), do: Jsonex.decode(body)
def decode_response({:error, msg}) do
error = Jsonex.decode(msg)["message"]
IO.puts "Error fetching from Github: #{error}"
System.halt(2)
end
def convert_to_list_of_hashdicts(list) do
list |> Enum.map(&HashDict.new/1)
end
def sort_into_ascending_order(list_of_issues) do
Enum.sort list_of_issues,
fn i1, i2 -> i1["created_at"] <= i2["created_at"] end
end
end
編譯時會出現 jsonex 錯誤
could not compile dependency :jsonex, "mix compile" failed. You can recompile this dependency with "mix deps.compile jsonex", update it with "mix deps.update jsonex" or clean it with "mix deps.clean jsonex"
==> issues
** (Mix) Expected :version to be a SemVer version, got: "2.0"
ref: Heroku compile issue (Elixir Buildpaack) ** (Mix) Expected :version to be a SemVer version
必須修改 Jsonex 的 mix.exs
defmodule Jsonex.Mixfile do
use Mix.Project
def project do
[ app: :jsonex,
version: "2.0.0",
deps: deps ]
end
同時要修改 issues 的 mix.exs
defp deps do
[
{:httpotion, github: "myfreeweb/httpotion" },
{:jsonex, "2.0.0", github: "marcelog/jsonex", tag: "2.0" }
]
end
ref: Building an Elixir CLI application
產生新的 project
$ mix new elixir_calc
* creating README.md
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/elixir_calc.ex
* creating test
* creating test/test_helper.exs
* creating test/elixir_calc_test.exs
Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:
cd elixir_calc
mix test
Run "mix help" for more commands.
修改 mix.exs
:ex_doc 及 :earmark 是用來產生 docs 的 library
defmodule ElixirCalc.Mixfile do
use Mix.Project
def project do
[
app: :elixir_calc,
version: "0.1.0",
elixir: "~> 1.5",
start_permanent: Mix.env == :prod,
build_embedded: Mix.env == :prod,
escript: [main_module: ElixirCalc],
deps: deps()
]
end
# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger]
]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[
# {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"},
{:ex_doc, "~> 0.12"},
{:earmark, "~> 1.0", override: true}
]
end
end
elixir_cals.ex: 處理 command line args
加上了 Logger.info,前面必須要先 require Logger @moduledoc 是 docs
defmodule ElixirCalc do
require Logger
defmodule Parser do
def parse(args) do
{options, args, _} = OptionParser.parse(args)
{options, args}
end
def parse_args({[fib: x], _}) do
Logger.info "arg fib= #{x} "
IO.puts x |> String.to_integer |> ElixirCalc.Calculator.fib
end
end
@moduledoc """
main function USAGE: ./elixir_calc --fib num
"""
def main(args) do
args
|> Parser.parse
|> Parser.parse_args
end
end
config/config.exs: 要加上 :logger 的設定值
use Mix.Config
config :logger, compile_time_purge_level: :info
lib/calculator/calculator.ex: fib 的主程式
defmodule ElixirCalc.Calculator do
def fib(0) do 0 end
def fib(1) do 1 end
def fib(n) do
fib(n - 1) + fib(n - 2)
end
end
test/elixircalctest.exs
defmodule ElixirCalcTest do
use ExUnit.Case
doctest ElixirCalc
test "fibonacci of 1 is 1" do
assert ElixirCalc.Calculator.fib(1) == 1
end
test "fibonacci of 2 is 1" do
assert ElixirCalc.Calculator.fib(2) == 1
end
test "fibonacci of 10 is 55" do
assert ElixirCalc.Calculator.fib(10) == 55
end
end
## 取得 deps libraries
$ mix deps.get
Running dependency resolution...
Dependency resolution completed:
earmark 1.2.3
ex_doc 0.16.3
* Getting ex_doc (Hex package)
Checking package (https://repo.hex.pm/tarballs/ex_doc-0.16.3.tar)
Using locally cached package
* Getting earmark (Hex package)
Checking package (https://repo.hex.pm/tarballs/earmark-1.2.3.tar)
Using locally cached package
## 編譯 deps
$ mix deps.compile
==> earmark
Compiling 3 files (.erl)
Compiling 24 files (.ex)
Generated earmark app
==> ex_doc
Compiling 15 files (.ex)
Generated ex_doc app
## 單元測試
$ mix test
==> earmark
Compiling 3 files (.erl)
Compiling 24 files (.ex)
Generated earmark app
==> ex_doc
Compiling 15 files (.ex)
Generated ex_doc app
==> elixir_calc
Compiling 2 files (.ex)
Generated elixir_calc app
...
Finished in 0.04 seconds
3 tests, 0 failures
Randomized with seed 704861
直接在 shell 測試
$ iex -S mix
iex(1)> ElixirCalc.main(["--fib", "10"])
22:51:00.013 [info] arg fib= 10
55
:ok
產生獨立的執行檔及文件
## 產生獨立執行的 binary 執行檔
$ mix escript.build
Compiling 2 files (.ex)
Generated elixir_calc app
Generated escript elixir_calc with MIX_ENV=dev
## 執行 elixir_calc
$ ./elixir_calc --fib 10
55
## 產生文件
$ mix docs
Docs successfully generated.
View them at "doc/index.html".
mix 的一些指令
mix xref unreachable
列出沒有被呼叫的 functions
mix xref warnings
列出跟 dependencies 有關的 warnings(ex: 呼叫unknown functions)
mix xref callers Mod | Mod.func | Mod.func/arity
列出呼叫 module/function 的 callers
$ mix xref callers Logger lib/elixir_calc.ex:11: Logger.bare_log/3 lib/elixir_calc.ex:11: Logger.info/1
mix xref graph
列印 application dependency tree
$ mix xref graph lib/calculator/calulator.ex lib/elixir_calc.ex └── lib/calculator/calulator.ex
mix xref graph --format dot dot -Grankdir=LR -Epenwidth=2 -Ecolor=#a0a0a0 -Tpng xref_graph.dot -o xref_graph.png
server monitor
iex> :observer.start()
沒有留言:
張貼留言