ページ

ラベル Elixir/Phoenix の投稿を表示しています。 すべての投稿を表示
ラベル Elixir/Phoenix の投稿を表示しています。 すべての投稿を表示

2015年11月21日土曜日

Elixir/Phoenixで遊ぶ 15 - ElixirでGitHubリポジトリ同期ツールの作成 その4-


今回でリポジトリと同期する処理を実装し、とりあえず完成させる

   モジュールの分離


前回までリポジトリ一覧の処理をSynchubモジュール内に書いていましたが、 一覧表示の所を別モジュールに切り出したいと思います。

新規にlib/synchub/listrepos.exを以下内容で作成
defmodule Synchub.Listrepos do

@moduledoc """
The module of list github repos.
"""

@doc "put repos name."
def put_repo_name([info|tail]) do
  IO.puts info["name"]
  put_repo_name(tail)
end

def put_repo_name(info) do
end

end

lib/mix/tasks/list.github.repos.exを以下に編集
※少し名前を変更してます。(長かったので・・・)
defmodule Mix.Tasks.List.Github do
  use Mix.Task

  @shortdoc "Show github repos list"

  def run(args) do
    url = "users/" <> Application.get_env(:synchub, :userid) <> "/repos"
    Synchub.start
    repos = Synchub.get(url)
    Synchub.Listrepos.put_repo_name(repos)
  end
end
Synchub.get内で一覧表示させていた部分をSynchub.Listrepos.put_repo_name(repos)で実行しています。
$ mix list.githubで一覧表示されればOK


   リポジトリ同期モジュールの作成


上でモジュールを分離させたのは、リポジトリ同期モジュールの為になります。
Synchub.get(url)で取得したリポジトリ一覧に対して処理を行うモジュールを作成します。

lib/synchub/syncrepos.exを以下内容で作成
defmodule Synchub.Syncrepos do

@moduledoc """
The module of sync github repos.
"""

@rootdir "syncrepos"

@doc "check install git client"
def install_git?() do
  ret = System.find_executable("git") # [1]
  ret != nil
end

@doc "sync github repos."
def sync([info|tail]) do
  # create sync repos dir
  unless File.exists?(@rootdir) do
    IO.puts "create dir ==> " <> @rootdir
    File.mkdir(@rootdir)
  end
  File.cd(@rootdir)

  if File.exists?(info["name"]) do # [2]
    # git command
    File.cd(info["name"])
    IO.puts "exists repo dir " <> info["name"]
    case System.cmd("git", Application.get_env(:synchub, :exists_cmd)) do # [3]
      {err, code} -> IO.puts(err)
    end
    File.cd("../")
  else
    # clone repo
    clone_url = info["clone_url"]
    if clone_url do
      IO.puts "clone " <> clone_url
      case System.cmd("git", ["clone", clone_url]) do # [4]
        {err, code} -> IO.puts(err)
      end
    end
  end
  File.cd("../")
  sync(tail)
end

def sync([]), do: nil

end
やってる事のポイントとしては、
  • [1] gitインストールの確認
  • [2] 既にクローン済みか判定
  • [3] config.exsで設定されたgitコマンドを実行
  • [4] gitクローンを実行
になります。

   リポジトリ同期用のタスクを作成


一覧表示の時と同じようにカスタムのタスクを作成したいと思います。

最終的に
$ mix sync.github
としたら同期してくれるようにしたいと思います。

lib/mix/tasks/sync.github.exを以下内容で作成
defmodule Mix.Tasks.Sync.Github do
  use Mix.Task

  @shortdoc "Sync github repos"

  alias Synchub.Syncrepos

  def run(args) do
    IO.puts "start sync github repos..."
    unless Syncrepos.install_git? do # [1]
      IO.puts "not found git. please install"
    else
      url = "users/" <> Application.get_env(:synchub, :userid) <> "/repos"
      Synchub.start
      repos = Synchub.get(url)
      Syncrepos.sync(repos.body)
      IO.puts "success sync github repos"
    end
  end
end
[1] でgitがインストールされているか確認し、インストールされている場合のみ
同期を行うようにしています。

ここまで作成したらmix compileでコンパイルし、mix sync.githubで同期できれば成功です。

   まとめ


荒削りですが、とりあえず作成完了ということにします(笑)

プロジェクト自体はここに上げています。気ままに更新していこうかなと思ってるので良かったら使ってみてください。

2015年11月19日木曜日

Elixir/Phoenixで遊ぶ 14 - ElixirでGitHubリポジトリ同期ツールの作成 その3-

今回はテストの作成 & TravisCIで自動テストを行ってみます

   テストの作成


test/synchub_test.exsを修正していきます
プロジェクト作成したばかりは以下のようになっています。
defmodule SynchubTest do
  use ExUnit.Case

  test "the truth" do
    assert 1 + 1 == 2
  end
end
試しに実行してみます
$ mix test
==> poison
Compiled lib/poison.ex
Compiled lib/poison/decoder.ex
Compiled lib/poison/parser.ex
Compiled lib/poison/encoder.ex
Generated poison app
==> httpotion
Compiled lib/httpotion.ex
Generated httpotion app
==> ibrowse (compile)
==> synchub
Compiled lib/synchub.ex
Generated synchub app
.

Finished in 0.06 seconds (0.06s on load, 0.00s on tests)
1 tests, 0 failures

Randomized with seed 765702
依存関係が解決されテストが実行され成功しているのが分かります。
次は実際にsynchub.exのテストを書きたいと思います。
とりあえず今回はテストとして
  1. リクエストのヘッダのUser-Agentにconfig.exsusernameが設定されていること
  2. レスポンスが正常に帰ってくること
を確認するテストを書いてみます。
defmodule SynchubTest do
  use ExUnit.Case

  # 1. リクエストヘッダの確認
  test "user-agent" do
    headers = Synchub.process_request_headers(%{})
    agent = Map.fetch!(headers, :"User-Agent")
    assert agent == Application.get_env(:synchub, :username)
  end

  # 2. レスポンスの確認
  test "list.github.repos" do
    url = "users/" <> Application.get_env(:synchub, :userid) <> "/repos"
    Synchub.start
    assert_response Synchub.get(url)
  end

  defp assert_response(response) do
    assert HTTPotion.Response.success?(response, :extra)
  end

end
"user-agent"テストではprocess_request_headers内で設定された User-AgentがApplication.get_env(:synchub, :username)と等しいこと
を確認しています。
"list.github.repos"テストでは実際にGithubAPIを使ってリクエストを送り
レスポンスが正常に帰ってくるか確認しています。
defp assert_response(response) do
  assert HTTPotion.Response.success?(response, :extra)
end
ではHTTPotionのレスポンスチェック関数を使ってます
実際にテストを実行してみます。
$ mix test
.https://api.github.com/users/Slowhand0309/repos
.

Finished in 2.4 seconds (0.1s on load, 2.2s on tests)
2 tests, 0 failures

Randomized with seed 978629
0 failuresになればテスト成功です。

   TravisCIで自動テスト


TravisCIでプロジェクトを登録し、プロジェクト直下に
.travis.ymlを作成します。
  • .travis.yml
language: elixir
elixir:
  - 1.0.5
otp_release:
  - 18.0
sudo: false
↑環境に合わせて修正
これでGithubにpushして自動でテストが行われれば成功です

2015年11月14日土曜日

Elixir/Phoenixで遊ぶ 13 - ElixirでGitHubリポジトリ同期ツールの作成 その2-


前回に続いて同期ツールの作成

   config/config.exsの使用


前回GitHubApiを使ってリポジトリの一覧を取得した際にユーザー名や
リクエストURLをソースコード上に直で書いていましたが、今回はconfig/config.exs
を読み込んで使ってみたいと思います。
まずはconfig/config.exsに以下内容を追加
config :synchub,
  apiurl: "https://api.github.com/",
  userid: "{ユーザーID}",
  username: "{ユーザー名}"
※{ユーザーID}/{ユーザー名}は使用するアカウントのユーザー名、ユーザーIDを設定して下さい
lib/synchub.exを修正
defmodule Synchub do
  @moduledoc """
  The module of sync github repos.
  """
  use HTTPotion.Base

  @apiurl Application.get_env(:synchub, :apiurl) # (1)ここ!

  @doc "handle create url."
  def process_url(url) do
    @apiurl <> url
  end

  @doc "handle create request header."
  def process_request_headers(headers) do
    Dict.put headers, :"User-Agent", Application.get_env(:synchub, :username) # (2)ここ!
  end

  @doc "handle response of html body."
  def process_response_body(body) do
    body |> Poison.decode! |> put_repo_name
  end

  @doc "put repos name."
  def put_repo_name([info|tail]) do
    IO.puts info["name"]
    put_repo_name(tail)
  end

  def put_repo_name([]), do: nil
end
config/config.exsで設定した値を読み込む時はApplication.get_envを使う
↑では(1)と(2)でそれぞれapiurlusernameを読み込んでいる

   カスタムタスクの作成


前回は以下のように実行していましたが、
$ iex -S mix
iex(1)> Synchub.get("users/{ユーザーID}/repos")
一回一回iexを起動するのも面倒くさいので、mixのタスクを作成してみたいと思います。 作成したいタスクとしては・・・
$ mix list.github.repos
とすると、config/config.exsで設定されたユーザーのリポジトリ一覧を取得できたら良さげ
  • タスクファイルの作成
lib/mix/tasks/list.github.repos.exを作成し、以下内容に編集する
defmodule Mix.Tasks.List.Github.Repos do
  use Mix.Task

  @shortdoc "Show github repos list" # (1)

  def run(args) do
    url = "users/" <> Application.get_env(:synchub, :userid) <> "/repos" # (2)
    Synchub.start
    Synchub.get(url)
  end
end
モジュール名は作成したファイル名と対応する様な名前で作成
(1) @shortdocを書く事でmix helpの一覧に説明が表示される
(2) ここでconfig/config.exsからuseridを読み込みurlを作成
  • コンパイル
$ mix compile
mix helpで一覧に表示されているか確認してみる
$ mix help
・・・
mix list.github.repos # Show github repos list
・・・
↑の様に表示されればOK
  • 実行
$ mix list.github.repos
前回と同じ様にリポジトリの一覧が表示されればOKです

2015年11月8日日曜日

Elixir/Phoenixで遊ぶ 12 - ElixirでGitHubリポジトリ同期ツールの作成 その1-


GitHubのリポジトリと同期してくれるツールをElixirで作成してみます。
既存のツールがありそうだけど、勉強を兼ねて作成。

やりたい事
  • 自分のリポジトリ一覧を取得
  • 取得したリポジトリ名でカレントディレクトリを検索
  • リポジトリがなければclone、あればgitコマンドを実行
  • gitコマンドは設定で変更可能

リポジトリ一覧の取得


GitHub Apiを使う
httpsでリクエストを投げればJSON形式で情報が取れる
例) ユーザーの情報取得
curl -i https://api.github.com/users/{ユーザーID}
※ -i を付けるとHTTPヘッダ情報も取れる
例) ユーザーのリポジトリ一覧の取得
curl https://api.github.com/users/{ユーザーID}/repos

使用するライブラリ


Github APIを使用するので、HTTPクライアントとJSONパーサーが必要

今回は以下のライブラリを使用します
  • HTTPクライアントライブラリ Httpotion
  • JSONライブラリ posion

プロジェクトの作成


プロジェクト名を「synchub」として作成

$ mix new synchub
mix.exsを修正
defmodule Synchub.Mixfile do
  use Mix.Project

  def project do
    [app: :synchub,
     version: "0.0.1",
     elixir: "~> 1.0",
     build_embedded: Mix.env == :prod,
     start_permanent: Mix.env == :prod,
     deps: deps]
  end

  def application do
    [applications: [:logger, :httpotion]] # httpotionを追加
  end

  defp deps do
    [
      {:ibrowse, github: "cmullaparthi/ibrowse", tag: "v4.1.2"},
      {:httpotion, "~> 2.1.0"}, # HTTPクライアントライブラリ
      {:poison, "~> 1.5"} # JSONを扱うライブラリ
    ]
  end
end
依存ライブラリの取り込み
$ mix deps.get

リポジトリ一覧の表示


HttpotionのREADMEにGitHubApiを使った簡単なサンプルがあるので、

それを少し修正して、APIの結果を表示するmoduelを作成します
  • lib/synchub.ex
defmodule Synchub do
  use HTTPotion.Base

  @apiurl "https://api.github.com/"

  def process_url(url) do
    @apiurl <> url
  end

  def process_request_headers(headers) do
    Dict.put headers, :"User-Agent", "{ユーザー名}"
  end

  def process_response_body(body) do
    body |> Poison.decode! |> put_repo_name
  end

  def put_repo_name([info|tail]) do
    IO.puts info["name"]
    put_repo_name(tail)
  end

  def put_repo_name([]), do: nil
end
GithubApiではUser-Agentが必須、User-Agentがないと以下のようなエラーが出る
Request forbidden by administrative rules.
Please make sure your request has a User-Agent header
(http://developer.github.com/v3/#user-agent-required).
Check https://developer.github.com for other possible causes.
User-Agentにはユーザー名かアプリケーション名を入れとく
処理としては、以下でJSONをパースしてput_repo_name関数へ渡している
def process_response_body(body) do
  body |> Poison.decode! |> put_repo_name
end
put_repo_name関数でリポジトリ一覧のリストを再帰的に呼び出しながらキー"name"の値を表示
def put_repo_name([info|tail]) do
  IO.puts info["name"]
  put_repo_name(tail) # リストの先頭要素(info)以外(tail)を引数に渡す
end

def put_repo_name([]), do: nil # リストが空の時に呼ばれる
実行してみる
$ iex -S mix
iex(1)> Synchub.get("users/{ユーザーID}/repos")
↑でリポジトリ一覧の情報が出力されればOK
続きは次回