From 89f36f15bb9b69088d22639ba9000e701f0a4117 Mon Sep 17 00:00:00 2001 From: AlexV Date: Thu, 29 Apr 2021 15:47:19 +0200 Subject: [PATCH 1/4] add ratatouille for TUI attempts with example remove pre_commit config --- apps/xest/mix.exs | 2 ++ apps/xest/tui.exs | 23 +++++++++++++++++++++++ config/dev.exs | 4 ---- 3 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 apps/xest/tui.exs diff --git a/apps/xest/mix.exs b/apps/xest/mix.exs index 4e43685f..c030cb71 100644 --- a/apps/xest/mix.exs +++ b/apps/xest/mix.exs @@ -54,6 +54,8 @@ defmodule Xest.MixProject do {:excoveralls, "~> 0.10", only: :test}, {:doctor, "~> 0.17.0", only: :dev}, + # TUI tooling + {:ratatouille, "~> 0.5.0"}, # phoenix communication {:phoenix_pubsub, "~> 2.0"}, diff --git a/apps/xest/tui.exs b/apps/xest/tui.exs new file mode 100644 index 00000000..64aeba25 --- /dev/null +++ b/apps/xest/tui.exs @@ -0,0 +1,23 @@ +defmodule Counter do + @behaviour Ratatouille.App + + import Ratatouille.View + + def init(_context), do: 0 + + def update(model, msg) do + case msg do + {:event, %{ch: ?+}} -> model + 1 + {:event, %{ch: ?-}} -> model - 1 + _ -> model + end + end + + def render(model) do + view do + label(content: "Counter is #{model} (+/-)") + end + end +end + +Ratatouille.run(Counter) diff --git a/config/dev.exs b/config/dev.exs index f60b23dd..5a7719ea 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -68,7 +68,3 @@ config :phoenix, :stacktrace_depth, 20 # clear screen when test_watch running again config :mix_test_watch, clear: true - -# to make sure we format before committing -# TODO : add credo and coveralls -config :pre_commit, commands: ["format"] From 4c247892e3b49b203ec18a93b9dec6f6e8408ce4 Mon Sep 17 00:00:00 2001 From: AlexV Date: Fri, 30 Apr 2021 15:23:00 +0200 Subject: [PATCH 2/4] add a simple TUI module for tests --- apps/xest/lib/xest/application.ex | 5 ++- apps/xest/lib/xest/tui.ex | 53 +++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 apps/xest/lib/xest/tui.ex diff --git a/apps/xest/lib/xest/application.ex b/apps/xest/lib/xest/application.ex index 7a5964d0..b1606f5d 100644 --- a/apps/xest/lib/xest/application.ex +++ b/apps/xest/lib/xest/application.ex @@ -14,7 +14,10 @@ defmodule Xest.Application do # Starting Binance Client GenServer {Xest.BinanceClient, name: Xest.BinanceClient}, # Starting Agents managing retrieved state - {Xest.BinanceExchange, name: Xest.BinanceExchange} + {Xest.BinanceExchange, name: Xest.BinanceExchange}, + + # TUI process + {Ratatouille.Runtime.Supervisor, runtime: [app: Xest.TUI]} ] Supervisor.start_link(children, strategy: :one_for_one, name: Xest.Supervisor) diff --git a/apps/xest/lib/xest/tui.ex b/apps/xest/lib/xest/tui.ex new file mode 100644 index 00000000..6be6df6f --- /dev/null +++ b/apps/xest/lib/xest/tui.ex @@ -0,0 +1,53 @@ +defmodule Xest.TUI do + @behaviour Ratatouille.App + + alias Ratatouille.{EventManager, Window} + alias Ratatouille.Runtime.{Subscription, State} + + require DateTime + import Ratatouille.View + + @impl true + def init(_context) do + {DateTime.utc_now(), 0} + end + + @impl true + def subscribe(_model) do + Subscription.interval(1_000, :tick) + end + + @impl true + def update({now, counter}, msg) do + case msg do + {:event, %{ch: ?q}} -> + :ok = Window.close() + + {:event, %{ch: ?+}} -> + {now, counter + 1} + + {:event, %{ch: ?-}} -> + {now, counter - 1} + + :tick -> + {DateTime.utc_now(), counter} + + _ -> + IO.inspect(msg) + {now, counter} + end + end + + @impl true + def render({now, counter}) do + view do + panel title: "Clock Example ('q' to quit)" do + label(content: "The time is: #{DateTime.to_string(now)}") + end + + panel title: "Counter" do + label(content: "Counter is #{counter} (+/-)") + end + end + end +end From 4f2d7a199f018cbbb3f4cd6d3d0861e7bf28e480 Mon Sep 17 00:00:00 2001 From: AlexV Date: Sat, 1 May 2021 17:27:55 +0200 Subject: [PATCH 3/4] add generated TUI app --- apps/xest_tui/.formatter.exs | 4 ++++ apps/xest_tui/.gitignore | 27 +++++++++++++++++++++ apps/xest_tui/README.md | 21 ++++++++++++++++ apps/xest_tui/lib/xest/tui.ex | 18 ++++++++++++++ apps/xest_tui/lib/xest/tui/application.ex | 20 ++++++++++++++++ apps/xest_tui/mix.exs | 29 +++++++++++++++++++++++ apps/xest_tui/test/test_helper.exs | 1 + apps/xest_tui/test/xest/tui_test.exs | 8 +++++++ 8 files changed, 128 insertions(+) create mode 100644 apps/xest_tui/.formatter.exs create mode 100644 apps/xest_tui/.gitignore create mode 100644 apps/xest_tui/README.md create mode 100644 apps/xest_tui/lib/xest/tui.ex create mode 100644 apps/xest_tui/lib/xest/tui/application.ex create mode 100644 apps/xest_tui/mix.exs create mode 100644 apps/xest_tui/test/test_helper.exs create mode 100644 apps/xest_tui/test/xest/tui_test.exs diff --git a/apps/xest_tui/.formatter.exs b/apps/xest_tui/.formatter.exs new file mode 100644 index 00000000..d2cda26e --- /dev/null +++ b/apps/xest_tui/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/apps/xest_tui/.gitignore b/apps/xest_tui/.gitignore new file mode 100644 index 00000000..5216c8e5 --- /dev/null +++ b/apps/xest_tui/.gitignore @@ -0,0 +1,27 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +xest_tui-*.tar + + +# Temporary files for e.g. tests +/tmp diff --git a/apps/xest_tui/README.md b/apps/xest_tui/README.md new file mode 100644 index 00000000..364fe6e3 --- /dev/null +++ b/apps/xest_tui/README.md @@ -0,0 +1,21 @@ +# Xest.TUI + +**TODO: Add description** + +## Installation + +If [available in Hex](https://hex.pm/docs/publish), the package can be installed +by adding `xest_tui` to your list of dependencies in `mix.exs`: + +```elixir +def deps do + [ + {:xest_tui, "~> 0.1.0"} + ] +end +``` + +Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) +and published on [HexDocs](https://hexdocs.pm). Once published, the docs can +be found at [https://hexdocs.pm/xest_tui](https://hexdocs.pm/xest_tui). + diff --git a/apps/xest_tui/lib/xest/tui.ex b/apps/xest_tui/lib/xest/tui.ex new file mode 100644 index 00000000..b7b3c211 --- /dev/null +++ b/apps/xest_tui/lib/xest/tui.ex @@ -0,0 +1,18 @@ +defmodule Xest.TUI do + @moduledoc """ + Documentation for `Xest.TUI`. + """ + + @doc """ + Hello world. + + ## Examples + + iex> Xest.TUI.hello() + :world + + """ + def hello do + :world + end +end diff --git a/apps/xest_tui/lib/xest/tui/application.ex b/apps/xest_tui/lib/xest/tui/application.ex new file mode 100644 index 00000000..4abbaacf --- /dev/null +++ b/apps/xest_tui/lib/xest/tui/application.ex @@ -0,0 +1,20 @@ +defmodule Xest.TUI.Application do + # See https://hexdocs.pm/elixir/Application.html + # for more information on OTP Applications + @moduledoc false + + use Application + + @impl true + def start(_type, _args) do + children = [ + # Starts a worker by calling: Xest.TUI.Worker.start_link(arg) + # {Xest.TUI.Worker, arg} + ] + + # See https://hexdocs.pm/elixir/Supervisor.html + # for other strategies and supported options + opts = [strategy: :one_for_one, name: Xest.TUI.Supervisor] + Supervisor.start_link(children, opts) + end +end diff --git a/apps/xest_tui/mix.exs b/apps/xest_tui/mix.exs new file mode 100644 index 00000000..2858f145 --- /dev/null +++ b/apps/xest_tui/mix.exs @@ -0,0 +1,29 @@ +defmodule Xest.TUI.MixProject do + use Mix.Project + + def project do + [ + app: :xest_tui, + version: "0.1.0", + elixir: "~> 1.11", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger], + mod: {Xest.TUI.Application, []} + ] + 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"} + ] + end +end diff --git a/apps/xest_tui/test/test_helper.exs b/apps/xest_tui/test/test_helper.exs new file mode 100644 index 00000000..869559e7 --- /dev/null +++ b/apps/xest_tui/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start() diff --git a/apps/xest_tui/test/xest/tui_test.exs b/apps/xest_tui/test/xest/tui_test.exs new file mode 100644 index 00000000..6e6d4d77 --- /dev/null +++ b/apps/xest_tui/test/xest/tui_test.exs @@ -0,0 +1,8 @@ +defmodule Xest.TUITest do + use ExUnit.Case + doctest Xest.TUI + + test "greets the world" do + assert Xest.TUI.hello() == :world + end +end From 7c1fedaa56e66fae4ed6895e2e1424409b7c6bcc Mon Sep 17 00:00:00 2001 From: AlexV Date: Sat, 1 May 2021 17:41:35 +0200 Subject: [PATCH 4/4] setup xest_tui application --- .committee.exs | 6 +- apps/xest/lib/xest/application.ex | 5 +- apps/xest/lib/xest/tui.ex | 53 --------------- apps/xest/mix.exs | 2 - apps/xest_tui/lib/xest/tui/application.ex | 4 +- apps/xest_tui/lib/xest/tui/tui.ex | 79 +++++++++++++++++++++++ apps/xest_tui/mix.exs | 7 ++ config/config.exs | 3 + 8 files changed, 96 insertions(+), 63 deletions(-) delete mode 100644 apps/xest/lib/xest/tui.ex create mode 100644 apps/xest_tui/lib/xest/tui/tui.ex diff --git a/.committee.exs b/.committee.exs index 51f72ef1..91e94e2f 100644 --- a/.committee.exs +++ b/.committee.exs @@ -13,8 +13,10 @@ defmodule YourApp.Commit do To test: `mix committee.runner [pre_commit | post_commit]` """ def pre_commit do - {_format_output, 0} = System.cmd("mix", ["format"] ++ staged_files([".ex", ".exs"])) - {_add_output, 0} = System.cmd("git", ["add"] ++ staged_files()) + existing_staged_files = staged_files([".ex", ".exs"]) |> Enum.filter(&File.exists?(&1)) + + {_format_output, 0} = System.cmd("mix", ["format"] ++ existing_staged_files) + {_add_output, 0} = System.cmd("git", ["add"] ++ existing_staged_files) {:ok, "SUCCESS!"} end end diff --git a/apps/xest/lib/xest/application.ex b/apps/xest/lib/xest/application.ex index b1606f5d..7a5964d0 100644 --- a/apps/xest/lib/xest/application.ex +++ b/apps/xest/lib/xest/application.ex @@ -14,10 +14,7 @@ defmodule Xest.Application do # Starting Binance Client GenServer {Xest.BinanceClient, name: Xest.BinanceClient}, # Starting Agents managing retrieved state - {Xest.BinanceExchange, name: Xest.BinanceExchange}, - - # TUI process - {Ratatouille.Runtime.Supervisor, runtime: [app: Xest.TUI]} + {Xest.BinanceExchange, name: Xest.BinanceExchange} ] Supervisor.start_link(children, strategy: :one_for_one, name: Xest.Supervisor) diff --git a/apps/xest/lib/xest/tui.ex b/apps/xest/lib/xest/tui.ex deleted file mode 100644 index 6be6df6f..00000000 --- a/apps/xest/lib/xest/tui.ex +++ /dev/null @@ -1,53 +0,0 @@ -defmodule Xest.TUI do - @behaviour Ratatouille.App - - alias Ratatouille.{EventManager, Window} - alias Ratatouille.Runtime.{Subscription, State} - - require DateTime - import Ratatouille.View - - @impl true - def init(_context) do - {DateTime.utc_now(), 0} - end - - @impl true - def subscribe(_model) do - Subscription.interval(1_000, :tick) - end - - @impl true - def update({now, counter}, msg) do - case msg do - {:event, %{ch: ?q}} -> - :ok = Window.close() - - {:event, %{ch: ?+}} -> - {now, counter + 1} - - {:event, %{ch: ?-}} -> - {now, counter - 1} - - :tick -> - {DateTime.utc_now(), counter} - - _ -> - IO.inspect(msg) - {now, counter} - end - end - - @impl true - def render({now, counter}) do - view do - panel title: "Clock Example ('q' to quit)" do - label(content: "The time is: #{DateTime.to_string(now)}") - end - - panel title: "Counter" do - label(content: "Counter is #{counter} (+/-)") - end - end - end -end diff --git a/apps/xest/mix.exs b/apps/xest/mix.exs index c030cb71..4e43685f 100644 --- a/apps/xest/mix.exs +++ b/apps/xest/mix.exs @@ -54,8 +54,6 @@ defmodule Xest.MixProject do {:excoveralls, "~> 0.10", only: :test}, {:doctor, "~> 0.17.0", only: :dev}, - # TUI tooling - {:ratatouille, "~> 0.5.0"}, # phoenix communication {:phoenix_pubsub, "~> 2.0"}, diff --git a/apps/xest_tui/lib/xest/tui/application.ex b/apps/xest_tui/lib/xest/tui/application.ex index 4abbaacf..e88fd233 100644 --- a/apps/xest_tui/lib/xest/tui/application.ex +++ b/apps/xest_tui/lib/xest/tui/application.ex @@ -8,8 +8,8 @@ defmodule Xest.TUI.Application do @impl true def start(_type, _args) do children = [ - # Starts a worker by calling: Xest.TUI.Worker.start_link(arg) - # {Xest.TUI.Worker, arg} + # TUI process + {Ratatouille.Runtime.Supervisor, runtime: [app: Xest.TUI.TUI]} ] # See https://hexdocs.pm/elixir/Supervisor.html diff --git a/apps/xest_tui/lib/xest/tui/tui.ex b/apps/xest_tui/lib/xest/tui/tui.ex new file mode 100644 index 00000000..d929aad7 --- /dev/null +++ b/apps/xest_tui/lib/xest/tui/tui.ex @@ -0,0 +1,79 @@ +defmodule Xest.TUI.TUI do + @behaviour Ratatouille.App + + alias Ratatouille.{EventManager, Window} + alias Ratatouille.Runtime.{Subscription, State} + + require DateTime + import Ratatouille.View + + alias Xest.BinanceExchange + alias Xest.Models + + @impl true + def init(_context) do + {DateTime.utc_now(), 0, Xest.BinanceExchange.model(Xest.BinanceExchange)} + end + + @impl true + def subscribe(_model) do + Subscription.interval(1_000, :tick) + end + + @impl true + def update({now, counter, exchange}, msg) do + case msg do + {:event, %{ch: ?q}} -> + :ok = Window.close() + + {:event, %{ch: ?+}} -> + {now, counter + 1, exchange} + + {:event, %{ch: ?-}} -> + {now, counter - 1, exchange} + + :tick -> + {DateTime.utc_now(), counter, exchange} + + _ -> + IO.inspect(msg) + {now, counter, exchange} + end + end + + # TODO : TUI design... + + # Various windows: + # - HTTP requests (a nice log view) + # - websockets (a nice log view) + # - Events (from both rest polling and websockets, a debugging interface for commanded) + # - Exchange (model) State (from Events, but other untracked sources) - default view + + @impl true + def render({now, counter, exchange}) do + view do + panel title: "Clock Example ('q' to quit)" do + label(content: "The time is: #{DateTime.to_string(now)}") + end + + panel title: "Counter" do + label(content: "Counter is #{counter} (+/-)") + end + + render_exchange(exchange) + end + end + + defp render_exchange(%Models.Exchange{} = exchange) do + panel title: "Exchange" do + label(content: "code: #{exchange.status.code} ") + label(content: "status: #{exchange.status.message} ") + end + end + + defp render_events() do + end + + defp render_adapters() do + end +end diff --git a/apps/xest_tui/mix.exs b/apps/xest_tui/mix.exs index 2858f145..fc4f88fc 100644 --- a/apps/xest_tui/mix.exs +++ b/apps/xest_tui/mix.exs @@ -5,6 +5,10 @@ defmodule Xest.TUI.MixProject do [ app: :xest_tui, version: "0.1.0", + build_path: "../../_build", + config_path: "../../config/config.exs", + deps_path: "../../deps", + lockfile: "../../mix.lock", elixir: "~> 1.11", start_permanent: Mix.env() == :prod, deps: deps() @@ -22,6 +26,9 @@ defmodule Xest.TUI.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ + # TUI tooling + {:ratatouille, "~> 0.5.0"}, + {:xest, in_umbrella: true} # {:dep_from_hexpm, "~> 0.3.0"}, # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} ] diff --git a/config/config.exs b/config/config.exs index a7b8d6e6..c831a86c 100644 --- a/config/config.exs +++ b/config/config.exs @@ -12,6 +12,9 @@ use Mix.Config config :xest_web, generators: [context_app: :xest] +config :xest_tui, + generators: [context_app: :xest] + # Configures the endpoint config :xest_web, XestWeb.Endpoint, url: [host: "localhost"],