From 497e4795df290345ab706778f9b3a0539485086b Mon Sep 17 00:00:00 2001 From: functora Date: Sun, 28 Jan 2024 14:07:47 -0300 Subject: [PATCH 1/6] mix gleam.toml [--replace] --- lib/mix/tasks/gleam/toml.ex | 72 +++++++++++++++++++++++++++++++++++++ mix.exs | 2 +- 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 lib/mix/tasks/gleam/toml.ex diff --git a/lib/mix/tasks/gleam/toml.ex b/lib/mix/tasks/gleam/toml.ex new file mode 100644 index 0000000..9f8a501 --- /dev/null +++ b/lib/mix/tasks/gleam/toml.ex @@ -0,0 +1,72 @@ +defmodule Mix.Tasks.Gleam.Toml do + use Mix.Task + @impl true + @shell Mix.shell() + def run(argv) do + replace = + argv + |> OptionParser.parse!(switches: [replace: :boolean]) + |> elem(0) + |> Keyword.get(:replace, false) + + Mix.Project.get!() + cfg = Mix.Project.config() + + deps = + Mix.Dep.load_and_cache() + |> Enum.flat_map(fn %Mix.Dep{app: dep, opts: opts} -> + dst = Keyword.fetch!(opts, :dest) + + if dst |> Path.join("gleam.toml") |> File.regular?() do + [{dep, [path: dst]}] + else + [] + end + end) + + toml = + [ + name: Keyword.fetch!(cfg, :app), + version: Keyword.fetch!(cfg, :version), + dependencies: deps + ] + |> mk_toml + + if replace do + Mix.Project.project_file() + |> Path.dirname() + |> Path.join("gleam.toml") + |> File.write!(toml) + else + @shell.info(toml) + end + end + + defp mk_toml(x) do + x + |> Enum.map(&mk_toml_row/1) + |> Enum.join("\n") + end + + defp mk_toml_row({k, v}), do: "#{mk_toml_key(k)} = #{mk_toml_val(v)}" + + defp mk_toml_key(x) when is_atom(x) or is_binary(x) or is_number(x) do + x |> to_string |> inspect + end + + defp mk_toml_val(x) do + cond do + is_map(x) or Keyword.keyword?(x) -> + "{#{x |> Enum.map(&mk_toml_row/1) |> Enum.join(",")}}" + + is_list(x) -> + "[#{x |> Enum.map(&mk_toml_val/1) |> Enum.join(",")}]" + + is_number(x) -> + to_string(x) + + true -> + mk_toml_key(x) + end + end +end diff --git a/mix.exs b/mix.exs index dc37ecf..b008dbd 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule MixGleam.MixProject do def project do [ app: :mix_gleam, - version: "0.6.2", + version: "0.6.3", elixir: "~> 1.9", start_permanent: Mix.env() == :prod, name: "mix_gleam", From 589588d3a6ce747931ccd73295086d1ba9df0a1f Mon Sep 17 00:00:00 2001 From: functora Date: Sun, 28 Jan 2024 14:36:24 -0300 Subject: [PATCH 2/6] mix gleam.toml docs --- CHANGELOG.md | 4 ++++ lib/mix/tasks/gleam/toml.ex | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb0fbf4..0e08bf2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## v0.6.3 - 2024-01-28 + +- Added `mix gleam.toml [--replace]` task. It generates gleam.toml from mix.exs. This might be useful for Gleam tooling support in Mix projects, such as Gleam LSP. + ## v0.6.2 - 2023-11-16 - Updated for Elixir v1.15 and Gleam v0.32 compatibility. Make sure to set diff --git a/lib/mix/tasks/gleam/toml.ex b/lib/mix/tasks/gleam/toml.ex index 9f8a501..83ba165 100644 --- a/lib/mix/tasks/gleam/toml.ex +++ b/lib/mix/tasks/gleam/toml.ex @@ -1,5 +1,25 @@ defmodule Mix.Tasks.Gleam.Toml do use Mix.Task + @shortdoc "Generates gleam.toml from mix.exs." + @moduledoc """ + #{@shortdoc} + + This might be useful for Gleam tooling support in Mix projects, such as Gleam LSP. + + Print gleam.toml into stdout: + + mix gleam.toml + + Replace gleam.toml file: + + mix gleam.toml --replace + + Automate gleam.toml sync using mix.exs project aliases: + + aliases: [ + "deps.get": ["deps.get", "gleam.deps.get", "gleam.toml --replace"] + ] + """ @impl true @shell Mix.shell() def run(argv) do From 58cf6699b31fabac2415620cba7fec6fdc14008b Mon Sep 17 00:00:00 2001 From: functora Date: Sun, 28 Jan 2024 16:10:34 -0300 Subject: [PATCH 3/6] better gleam.toml --- CHANGELOG.md | 2 +- lib/mix/tasks/gleam/toml.ex | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e08bf2..fa9a664 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## v0.6.3 - 2024-01-28 -- Added `mix gleam.toml [--replace]` task. It generates gleam.toml from mix.exs. This might be useful for Gleam tooling support in Mix projects, such as Gleam LSP. +- Added `mix gleam.toml [--replace]` task. It generates gleam.toml in Mix project. This might be useful for Gleam tooling support in Mix projects, such as Gleam LSP. ## v0.6.2 - 2023-11-16 diff --git a/lib/mix/tasks/gleam/toml.ex b/lib/mix/tasks/gleam/toml.ex index 83ba165..15ba40d 100644 --- a/lib/mix/tasks/gleam/toml.ex +++ b/lib/mix/tasks/gleam/toml.ex @@ -1,6 +1,6 @@ defmodule Mix.Tasks.Gleam.Toml do use Mix.Task - @shortdoc "Generates gleam.toml from mix.exs." + @shortdoc "Generates gleam.toml in Mix project." @moduledoc """ #{@shortdoc} @@ -31,6 +31,7 @@ defmodule Mix.Tasks.Gleam.Toml do Mix.Project.get!() cfg = Mix.Project.config() + cwd = Mix.Project.project_file() |> Path.dirname() deps = Mix.Dep.load_and_cache() @@ -38,7 +39,7 @@ defmodule Mix.Tasks.Gleam.Toml do dst = Keyword.fetch!(opts, :dest) if dst |> Path.join("gleam.toml") |> File.regular?() do - [{dep, [path: dst]}] + [{dep, path: Path.relative_to(dst, cwd)}] else [] end @@ -53,8 +54,7 @@ defmodule Mix.Tasks.Gleam.Toml do |> mk_toml if replace do - Mix.Project.project_file() - |> Path.dirname() + cwd |> Path.join("gleam.toml") |> File.write!(toml) else From a905aeca37137e8fdc3eb0ff147f9c1bfbc28090 Mon Sep 17 00:00:00 2001 From: functora Date: Sun, 28 Jan 2024 18:07:39 -0300 Subject: [PATCH 4/6] mix gleam.toml task test --- lib/mix/tasks/gleam/toml.ex | 4 ++-- test_projects/basic_project/mix.exs | 4 ++-- .../basic_project/test/basic_project_test.exs | 13 +++++++++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/lib/mix/tasks/gleam/toml.ex b/lib/mix/tasks/gleam/toml.ex index 15ba40d..f99d07e 100644 --- a/lib/mix/tasks/gleam/toml.ex +++ b/lib/mix/tasks/gleam/toml.ex @@ -77,10 +77,10 @@ defmodule Mix.Tasks.Gleam.Toml do defp mk_toml_val(x) do cond do is_map(x) or Keyword.keyword?(x) -> - "{#{x |> Enum.map(&mk_toml_row/1) |> Enum.join(",")}}" + "{#{x |> Enum.map(&mk_toml_row/1) |> Enum.join(", ")}}" is_list(x) -> - "[#{x |> Enum.map(&mk_toml_val/1) |> Enum.join(",")}]" + "[#{x |> Enum.map(&mk_toml_val/1) |> Enum.join(", ")}]" is_number(x) -> to_string(x) diff --git a/test_projects/basic_project/mix.exs b/test_projects/basic_project/mix.exs index 3da0eea..d1d3453 100644 --- a/test_projects/basic_project/mix.exs +++ b/test_projects/basic_project/mix.exs @@ -35,9 +35,9 @@ defmodule BasicProject.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - # {:mix_gleam, path: "../../"} {:gleam_stdlib, "~> 0.32"}, - {:gleeunit, "~> 1.0", only: [:dev, :test], runtime: false} + {:gleeunit, "~> 1.0", only: [:dev, :test], runtime: false}, + {:mix_gleam, only: [:dev, :test], runtime: false, path: "../../"} ] end end diff --git a/test_projects/basic_project/test/basic_project_test.exs b/test_projects/basic_project/test/basic_project_test.exs index 16881df..f53f60f 100644 --- a/test_projects/basic_project/test/basic_project_test.exs +++ b/test_projects/basic_project/test/basic_project_test.exs @@ -1,5 +1,6 @@ defmodule BasicProjectTest do use ExUnit.Case + import ExUnit.CaptureIO doctest BasicProject test "can call Elixir code" do @@ -13,4 +14,16 @@ defmodule BasicProjectTest do test "can call Gleam library" do assert :gleam@list.reverse([1, 2, 3]) == [3, 2, 1] end + + test "gleam.toml mix task" do + lhs = """ + "name" = "basic_project" + "version" = "0.1.0" + "dependencies" = {"gleam_stdlib" = {"path" = "deps/gleam_stdlib"}, "gleeunit" = {"path" = "deps/gleeunit"}} + """ + + rhs = capture_io(fn -> Mix.Tasks.Gleam.Toml.run([]) end) + + assert lhs == rhs + end end From 5da06e49b48c46b53f78bae9a8e18cf7fd4bfc41 Mon Sep 17 00:00:00 2001 From: functora Date: Sun, 28 Jan 2024 18:36:27 -0300 Subject: [PATCH 5/6] sorting deps just in case mix behaviour will change --- lib/mix/tasks/gleam/toml.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/mix/tasks/gleam/toml.ex b/lib/mix/tasks/gleam/toml.ex index f99d07e..9ca0261 100644 --- a/lib/mix/tasks/gleam/toml.ex +++ b/lib/mix/tasks/gleam/toml.ex @@ -44,6 +44,7 @@ defmodule Mix.Tasks.Gleam.Toml do [] end end) + |> Enum.sort() toml = [ From a73cec9f5a94f169a3ec8b0f8e48f971b2046dfd Mon Sep 17 00:00:00 2001 From: functora Date: Thu, 1 Feb 2024 01:01:10 -0300 Subject: [PATCH 6/6] fix inline toml tables bug, fix maps in exs configs bug --- lib/mix/tasks/gleam/deps/get.ex | 14 +++++++++++--- lib/mix_gleam/config.ex | 30 +++++++++++++++++++++++++----- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/lib/mix/tasks/gleam/deps/get.ex b/lib/mix/tasks/gleam/deps/get.ex index f3a3f27..f00e43a 100644 --- a/lib/mix/tasks/gleam/deps/get.ex +++ b/lib/mix/tasks/gleam/deps/get.ex @@ -13,8 +13,7 @@ defmodule Mix.Tasks.Gleam.Deps.Get do Include this task in your project's `mix.exs` with, e.g.: - def project do - [ + def project do [ aliases: ["deps.get": ["deps.get", "gleam.deps.get"]], ] end @@ -49,7 +48,7 @@ defmodule Mix.Tasks.Gleam.Deps.Get do |> Map.to_list() |> Enum.map(&Tuple.append(&1, only: [:dev, :test], runtime: false)) - deps = deps ++ dev_deps + deps = unmap(deps ++ dev_deps) # TODO use eex template File.write!("mix.exs", MixGleam.Config.render_mix(app, version, deps)) :cont @@ -70,4 +69,13 @@ defmodule Mix.Tasks.Gleam.Deps.Get do MixGleam.IO.debug_info("Deps.Get End") end + + defp unmap(x) do + cond do + is_map(x) -> x |> Map.to_list() |> unmap + is_list(x) -> x |> Enum.map(&unmap/1) + is_tuple(x) -> x |> Tuple.to_list() |> unmap |> List.to_tuple() + true -> x + end + end end diff --git a/lib/mix_gleam/config.ex b/lib/mix_gleam/config.ex index f73970a..6039937 100644 --- a/lib/mix_gleam/config.ex +++ b/lib/mix_gleam/config.ex @@ -70,8 +70,7 @@ end) File.read!(path) |> String.split(definition, trim: true) - |> Stream.map(&tokenize/1) - |> Stream.reject(&({[], nil} == &1)) + |> Stream.flat_map(&tokenize/1) |> Stream.map(&atomize/1) |> Stream.map(&parse/1) |> structure @@ -167,14 +166,35 @@ end) cond do match = Regex.run(table, line) -> [_, key] = match - {[key], nil} + [{[key], nil}] match = Regex.run(assignment, line) -> [_, key, value] = match - {[key], value} + + tokenize_inline_table([key], value) true -> - {[], nil} + [] + end + end + + defp tokenize_inline_table(prev_keys, prev_value) do + re = ~r/\{(.+?)\}/ + + case Regex.run(re, prev_value, capture: :all_but_first) do + [pairs] -> + pairs + |> String.split(",") + |> Stream.map(&String.split(&1, "=")) + |> Enum.flat_map(fn [next_key, next_value] -> + tokenize_inline_table( + prev_keys ++ [String.trim(next_key)], + String.trim(next_value) + ) + end) + + _ -> + [{prev_keys, prev_value}] end end end