diff --git a/README.md b/README.md index ca8ff43d..c7517242 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ Features: * Syntax highlighting for Elixir and EEx files * Filetype detection for `.ex`, `.exs` and `.eex` files * Automatic indentation +* Integration between Ecto projects and [vim-dadbod][] for running SQL queries + on defined Ecto repositories ## Installation @@ -54,3 +56,5 @@ We've decided not to include `mix format` integration into `vim-elixir`. If you' Run the tests: `bundle exec parallel_rspec spec` Spawn a vim instance with dev configs: `bin/spawn_vim` + +[vim-dadbod]: https://github.com/tpope/vim-dadbod diff --git a/autoload/db/adapter/ecto.vim b/autoload/db/adapter/ecto.vim new file mode 100644 index 00000000..9eb36239 --- /dev/null +++ b/autoload/db/adapter/ecto.vim @@ -0,0 +1,20 @@ +let s:path = expand(':h') +let s:cmd = join(['mix', 'run', '--no-start', '--no-compile', shellescape(s:path.'/get_repos.exs')]) + +function! s:repo_list() abort + return map(systemlist(s:cmd), 'split(v:val)') +endfunction + +function! db#adapter#ecto#canonicalize(url) abort + for l:item in s:repo_list() + let l:name = get(l:item, 0) + let l:url = get(l:item, 1) + if !empty(l:name) && 'ecto:'.l:name ==# a:url + return l:url + endif + endfor +endfunction + +function! db#adapter#ecto#complete_opaque(url) abort + return map(s:repo_list(), 'v:val[0]') +endfunction diff --git a/autoload/db/adapter/get_repos.exs b/autoload/db/adapter/get_repos.exs new file mode 100644 index 00000000..fd98c324 --- /dev/null +++ b/autoload/db/adapter/get_repos.exs @@ -0,0 +1,54 @@ +defmodule LoadRepos do + defp load_apps do + :code.get_path() + |> Enum.flat_map(fn app_dir -> + Path.join(app_dir, "*.app") |> Path.wildcard() + end) + |> Enum.map(fn app_file -> + app_file |> Path.basename() |> Path.rootname(".app") |> String.to_atom() + end) + |> Enum.map(&Application.load/1) + end + + defp configs do + for {app, _, _} <- Application.loaded_applications(), + repos = Application.get_env(app, :ecto_repos), + is_list(repos) and repos != [], + repo <- repos, + do: {repo, Map.new(Application.get_env(app, repo))} + end + + defp config_to_url(_, %{url: url}), do: url + + defp config_to_url(repo, %{ + hostname: hostname, + username: username, + password: password, + database: database + }) do + %URI{ + scheme: adapter_to_string(repo.__adapter__), + userinfo: "#{username}:#{password}", + host: hostname, + path: Path.join("/", database) + } + |> URI.to_string() + end + + defp adapter_to_string(Ecto.Adapters.Postgres), do: "postgres" + defp adapter_to_string(Ecto.Adapters.MySQL), do: "mysql" + defp adapter_to_string(mod), do: raise("Unknown adapter #{inspect(mod)}") + + def main do + load_apps() + + configs() + |> Enum.map(fn {repo, config} -> + [inspect(repo), ?\s, config_to_url(repo, config)] + end) + |> Enum.intersperse(?\n) + |> IO.puts() + end +end + +LoadRepos.main()