-
Notifications
You must be signed in to change notification settings - Fork 561
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(sdk/elixir): rework on Elixir codegen (#6559)
Signed-off-by: Thanabodee Charoenpiriyakij <wingyminus@gmail.com>
- Loading branch information
Showing
149 changed files
with
6,019 additions
and
6,205 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# Used by "mix format" | ||
[ | ||
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
# 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"). | ||
dagger_codegen-*.tar | ||
|
||
# Temporary files, for example, from tests. | ||
/tmp/ | ||
|
||
# Escript | ||
dagger_codegen |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
# Dagger.Codegen | ||
|
||
This packages provides a tool to generate Elixir code Dagger GraphQL. | ||
|
||
## How to build | ||
|
||
Run the following commands below: | ||
|
||
``` | ||
$ mix deps.get | ||
$ mix escript.build | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
defmodule Dagger.Codegen do | ||
@moduledoc """ | ||
Functions for generating code from Dagger GraphQL. | ||
""" | ||
|
||
def generate(generator, introspection_schema) do | ||
generator.generate(introspection_schema) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
defmodule Dagger.Codegen.CLI do | ||
@moduledoc """ | ||
Main entrypoint for Dagger codegen binary. | ||
""" | ||
|
||
def main(args) do | ||
:argparse.run(Enum.map(args, &String.to_charlist/1), cli(), %{progname: :dagger_codegen}) | ||
end | ||
|
||
defp cli() do | ||
%{ | ||
commands: %{ | ||
~c"generate" => %{ | ||
arguments: [ | ||
%{ | ||
name: :outdir, | ||
type: :binary, | ||
long: ~c"-outdir", | ||
required: true | ||
}, | ||
%{ | ||
name: :introspection, | ||
type: :binary, | ||
long: ~c"-introspection", | ||
required: true | ||
} | ||
], | ||
handler: &handle_generate/1 | ||
} | ||
} | ||
} | ||
end | ||
|
||
def handle_generate(%{outdir: outdir, introspection: introspection}) do | ||
%{"__schema" => schema} = introspection |> File.read!() |> Jason.decode!() | ||
|
||
IO.puts("Generate code to #{Path.expand(outdir)}") | ||
|
||
File.mkdir_p!(outdir) | ||
|
||
Dagger.Codegen.generate( | ||
Dagger.Codegen.ElixirGenerator, | ||
Nestru.decode_from_map!(schema, Dagger.Codegen.Introspection.Types.Schema) | ||
) | ||
|> Enum.flat_map(& &1) | ||
|> Enum.each(fn {file, code} -> | ||
Path.join(outdir, file) | ||
|> File.write!(code) | ||
end) | ||
end | ||
end |
49 changes: 49 additions & 0 deletions
49
sdk/elixir/dagger_codegen/lib/dagger/codegen/elixir_generator.ex
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
defmodule Dagger.Codegen.ElixirGenerator do | ||
@moduledoc """ | ||
Dagger Elixir code generator. | ||
""" | ||
|
||
alias Dagger.Codegen.ElixirGenerator.Formatter | ||
alias Dagger.Codegen.Introspection.Types.InputValue | ||
alias Dagger.Codegen.Introspection.Types.TypeRef | ||
alias Dagger.Codegen.Introspection.Visitor | ||
alias Dagger.Codegen.Introspection.VisitorHandlers | ||
|
||
require EEx | ||
|
||
@template_dir Path.join([:code.priv_dir(:dagger_codegen), "templates", "elixir"]) | ||
|
||
@scalar_template Path.join(@template_dir, "scalar.eex") | ||
@object_template Path.join(@template_dir, "object.eex") | ||
@input_template Path.join(@template_dir, "input.eex") | ||
@enum_template Path.join(@template_dir, "enum.eex") | ||
|
||
EEx.function_from_file(:defp, :scalar_template, @scalar_template, [:assigns]) | ||
EEx.function_from_file(:defp, :object_template, @object_template, [:assigns]) | ||
EEx.function_from_file(:defp, :input_template, @input_template, [:assigns]) | ||
EEx.function_from_file(:defp, :enum_template, @enum_template, [:assigns]) | ||
|
||
def generate(schema) do | ||
generate_code(schema) | ||
end | ||
|
||
defp generate_code(schema) do | ||
handlers = %VisitorHandlers{ | ||
scalar: fn type -> | ||
{"#{Formatter.format_var_name(type.name)}.ex", scalar_template(%{type: type})} | ||
end, | ||
object: fn type -> | ||
{"#{Formatter.format_var_name(type.name)}.ex", | ||
object_template(%{type: type, schema: schema})} | ||
end, | ||
input: fn type -> | ||
{"#{Formatter.format_var_name(type.name)}.ex", input_template(%{type: type})} | ||
end, | ||
enum: fn type -> | ||
{"#{Formatter.format_var_name(type.name)}.ex", enum_template(%{type: type})} | ||
end | ||
} | ||
|
||
Visitor.visit(schema, handlers) | ||
end | ||
end |
152 changes: 152 additions & 0 deletions
152
sdk/elixir/dagger_codegen/lib/dagger/codegen/elixir_generator/formatter.ex
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
defmodule Dagger.Codegen.ElixirGenerator.Formatter do | ||
alias Dagger.Codegen.Introspection.Types.TypeRef | ||
|
||
def format_module("Query"), do: format_module("Client") | ||
|
||
def format_module(name) do | ||
Module.concat(Dagger, Macro.camelize(name)) | ||
|> to_string() | ||
|> String.trim_leading("Elixir.") | ||
end | ||
|
||
def format_var_name("Query"), do: format_var_name("Client") | ||
|
||
def format_var_name(name) do | ||
Macro.underscore(name) | ||
end | ||
|
||
# Temporarily fixes for issue https://github.com/dagger/dagger/issues/6310. | ||
@acronym_words %{ | ||
"GPU" => "Gpu", | ||
"VCS" => "Vcs" | ||
} | ||
|
||
def format_function_name(name, acronym_words \\ @acronym_words) do | ||
acronym_words | ||
|> Enum.reduce(name, fn {word, new_word}, name -> | ||
String.replace(name, word, new_word) | ||
end) | ||
|> Macro.underscore() | ||
end | ||
|
||
def format_doc(doc) do | ||
doc = String.replace(doc, "\"", "\\\"") | ||
|
||
for [text, api] <- Regex.scan(~r/`(?<name>[a-zA-Z0-9]+)`/, doc), | ||
reduce: doc do | ||
reason -> String.replace(reason, text, "`#{format_function_name(api)}`") | ||
end | ||
end | ||
|
||
def format_type(%TypeRef{ | ||
kind: "LIST", | ||
of_type: %TypeRef{kind: "NON_NULL", of_type: type} | ||
}) do | ||
"[#{format_type(type, false)}]" | ||
end | ||
|
||
def format_type(%TypeRef{kind: "NON_NULL", of_type: type}) do | ||
if type.kind == "LIST" do | ||
format_type(type) | ||
else | ||
format_type(type, false) | ||
end | ||
end | ||
|
||
def format_type(%TypeRef{} = type) do | ||
format_type(type, true) | ||
end | ||
|
||
defp format_type(%TypeRef{kind: "SCALAR", name: name}, nullable?) do | ||
type = | ||
case name do | ||
"String" -> "String.t()" | ||
"Int" -> "integer()" | ||
"Float" -> "float()" | ||
"Boolean" -> "boolean()" | ||
"DateTime" -> "DateTime.t()" | ||
otherwise -> "#{format_module(otherwise)}.t()" | ||
end | ||
|
||
if nullable? do | ||
"#{type} | nil" | ||
else | ||
type | ||
end | ||
end | ||
|
||
# OBJECT, INPUT_OBJECT, ENUM | ||
defp format_type(%TypeRef{name: name}, nullable?) do | ||
type = "#{format_module(name)}.t()" | ||
|
||
if nullable? do | ||
"#{type} | nil" | ||
else | ||
type | ||
end | ||
end | ||
|
||
def format_typespec_output_type( | ||
%TypeRef{ | ||
kind: "NON_NULL", | ||
of_type: %TypeRef{kind: "SCALAR"} | ||
} = type | ||
) do | ||
"{:ok, #{format_type(type)}} | {:error, term()}" | ||
end | ||
|
||
def format_typespec_output_type( | ||
%TypeRef{ | ||
kind: "SCALAR" | ||
} = type | ||
) do | ||
"{:ok, #{format_type(type)}} | {:error, term()}" | ||
end | ||
|
||
def format_typespec_output_type(%TypeRef{ | ||
kind: "NON_NULL", | ||
of_type: %TypeRef{kind: "LIST"} = type | ||
}) do | ||
"{:ok, #{format_type(type)}} | {:error, term()}" | ||
end | ||
|
||
def format_typespec_output_type( | ||
%TypeRef{ | ||
kind: "LIST" | ||
} = type | ||
) do | ||
"{:ok, #{format_type(type)}} | {:error, term()}" | ||
end | ||
|
||
def format_typespec_output_type(type) do | ||
format_type(type) | ||
end | ||
|
||
# TODO: clarify which pattern match use. | ||
def format_output_type(%TypeRef{ | ||
kind: "NON_NULL", | ||
of_type: %TypeRef{kind: "LIST", of_type: type} | ||
}) do | ||
format_output_type(type) | ||
end | ||
|
||
def format_output_type(%TypeRef{ | ||
kind: "NON_NULL", | ||
of_type: type | ||
}) do | ||
format_output_type(type) | ||
end | ||
|
||
def format_output_type(%TypeRef{ | ||
kind: "LIST", | ||
of_type: type | ||
}) do | ||
format_output_type(type) | ||
end | ||
|
||
def format_output_type(%TypeRef{kind: "OBJECT", name: name}) do | ||
format_module(name) | ||
end | ||
|
||
def format_output_type(_type_ref), do: "DaggerInvalidOutput" | ||
end |
Oops, something went wrong.