init
This commit is contained in:
commit
4632519b99
4
.formatter.exs
Normal file
4
.formatter.exs
Normal file
@ -0,0 +1,4 @@
|
||||
# Used by "mix format"
|
||||
[
|
||||
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||
]
|
26
.gitignore
vendored
Normal file
26
.gitignore
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
# 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").
|
||||
makeup_prisma-*.tar
|
||||
|
||||
# Temporary files, for example, from tests.
|
||||
/tmp/
|
22
README.md
Normal file
22
README.md
Normal file
@ -0,0 +1,22 @@
|
||||
# MakeupPrisma
|
||||
|
||||
A [Makeup](https://github.com/elixir-makeup/makeup/) lexer for Prisma.
|
||||
|
||||
## Installation
|
||||
|
||||
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
|
||||
by adding `makeup_prisma` to your list of dependencies in `mix.exs`:
|
||||
|
||||
```elixir
|
||||
def deps do
|
||||
[
|
||||
{:makeup_prisma, "~> 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/makeup_prisma>.
|
||||
|
||||
MakeupPrisma is derived from [MakeupGraphql](https://github.com/Billzabob/makeup_graphql).
|
226
lib/makeup_prisma.ex
Normal file
226
lib/makeup_prisma.ex
Normal file
@ -0,0 +1,226 @@
|
||||
defmodule Makeup.Lexers.MakeupPrisma do
|
||||
@moduledoc """
|
||||
A `makeup` lexer for Prisma.
|
||||
"""
|
||||
|
||||
import Makeup.Lexer.Combinators
|
||||
import Makeup.Lexer.Groups
|
||||
import NimbleParsec
|
||||
|
||||
@behaviour Makeup.Lexer
|
||||
|
||||
@impl Makeup.Lexer
|
||||
def lex(text, opts \\ []) do
|
||||
group_prefix = Keyword.get(opts, :group_prefix, random_prefix(10))
|
||||
{:ok, tokens, "", _, _, _} = root(text)
|
||||
|
||||
tokens
|
||||
|> postprocess([])
|
||||
|> match_groups(group_prefix)
|
||||
end
|
||||
|
||||
@impl Makeup.Lexer
|
||||
def postprocess(tokens, _opts \\ []), do: tokens
|
||||
|
||||
@impl Makeup.Lexer
|
||||
defgroupmatcher(:match_groups,
|
||||
parentheses: [
|
||||
open: [[{:punctuation, %{language: :prisma}, "("}]],
|
||||
close: [[{:punctuation, %{language: :prisma}, ")"}]]
|
||||
],
|
||||
list: [
|
||||
open: [
|
||||
[{:punctuation, %{language: :prisma}, "["}]
|
||||
],
|
||||
close: [
|
||||
[{:punctuation, %{language: :prisma}, "]"}]
|
||||
]
|
||||
],
|
||||
curly: [
|
||||
open: [
|
||||
[{:punctuation, %{language: :prisma}, "{"}]
|
||||
],
|
||||
close: [
|
||||
[{:punctuation, %{language: :prisma}, "}"}]
|
||||
]
|
||||
]
|
||||
)
|
||||
|
||||
# Codepoints
|
||||
@horizontal_tab 0x0009
|
||||
@newline 0x000A
|
||||
@carriage_return 0x000D
|
||||
@space 0x0020
|
||||
@unicode_bom 0xFEFF
|
||||
|
||||
any_unicode = utf8_char([])
|
||||
|
||||
unicode_bom = ignore(utf8_char([@unicode_bom]))
|
||||
|
||||
whitespace =
|
||||
ascii_string(
|
||||
[
|
||||
@horizontal_tab,
|
||||
@space
|
||||
],
|
||||
min: 1
|
||||
)
|
||||
|> token(:whitespace)
|
||||
|
||||
line_terminator =
|
||||
choice([
|
||||
ascii_char([@newline]),
|
||||
ascii_char([@carriage_return])
|
||||
|> optional(ascii_char([@newline]))
|
||||
])
|
||||
|> token(:whitespace)
|
||||
|
||||
comment =
|
||||
string("//")
|
||||
|> repeat_while(any_unicode, {:not_line_terminator, []})
|
||||
|> token(:comment_single)
|
||||
|
||||
comma = ascii_char([?,]) |> token(:punctuation)
|
||||
|
||||
punctuator =
|
||||
ascii_char([
|
||||
?(,
|
||||
?),
|
||||
?:,
|
||||
?=,
|
||||
?@,
|
||||
?[,
|
||||
?],
|
||||
?{,
|
||||
?|,
|
||||
?}
|
||||
])
|
||||
|> token(:punctuation)
|
||||
|
||||
boolean_value_or_name_or_reserved_word =
|
||||
ascii_char([?_, ?A..?Z, ?a..?z])
|
||||
|> repeat(ascii_char([?_, ?0..?9, ?A..?Z, ?a..?z]))
|
||||
|> optional(ascii_char([??]))
|
||||
|> post_traverse({:boolean_value_or_name_or_reserved_word, []})
|
||||
|
||||
negative_sign = ascii_char([?-])
|
||||
|
||||
digit = ascii_char([?0..?9])
|
||||
|
||||
non_zero_digit = ascii_char([?1..?9])
|
||||
|
||||
integer_part =
|
||||
optional(negative_sign)
|
||||
|> choice([
|
||||
ascii_char([?0]),
|
||||
non_zero_digit |> repeat(digit)
|
||||
])
|
||||
|
||||
int_value =
|
||||
empty()
|
||||
|> concat(integer_part)
|
||||
|> token(:number_integer)
|
||||
|
||||
fractional_part =
|
||||
ascii_char([?.])
|
||||
|> times(digit, min: 1)
|
||||
|
||||
float_value =
|
||||
choice([
|
||||
integer_part |> concat(fractional_part),
|
||||
integer_part |> post_traverse({:fill_mantissa, []}),
|
||||
integer_part |> concat(fractional_part)
|
||||
])
|
||||
|> token(:number_float)
|
||||
|
||||
unicode_char_in_string =
|
||||
string("\\u")
|
||||
|> ascii_string([?0..?9, ?a..?f, ?A..?F], 4)
|
||||
|> token(:string_escape)
|
||||
|
||||
escaped_char =
|
||||
string("\\")
|
||||
|> utf8_string([], 1)
|
||||
|> token(:string_escape)
|
||||
|
||||
combinators_inside_string = [
|
||||
unicode_char_in_string,
|
||||
escaped_char
|
||||
]
|
||||
|
||||
string_value = string_like("\"", "\"", combinators_inside_string, :string)
|
||||
|
||||
block_string_value = string_like(~S["""], ~S["""], combinators_inside_string, :string)
|
||||
|
||||
root_element_combinator =
|
||||
choice([
|
||||
unicode_bom,
|
||||
whitespace,
|
||||
line_terminator,
|
||||
comment,
|
||||
comma,
|
||||
punctuator,
|
||||
block_string_value,
|
||||
string_value,
|
||||
float_value,
|
||||
int_value,
|
||||
boolean_value_or_name_or_reserved_word
|
||||
])
|
||||
|
||||
@doc false
|
||||
def __as_prisma_language__({ttype, meta, value}) do
|
||||
{ttype, Map.put(meta, :language, :prisma), value}
|
||||
end
|
||||
|
||||
defparsec(
|
||||
:root_element,
|
||||
root_element_combinator |> map({__MODULE__, :__as_prisma_language__, []})
|
||||
)
|
||||
|
||||
defparsec(
|
||||
:root,
|
||||
repeat(parsec(:root_element))
|
||||
)
|
||||
|
||||
defp fill_mantissa(_rest, raw, context, _, _), do: {~c"0." ++ raw, context}
|
||||
|
||||
@boolean_words ~w(
|
||||
true
|
||||
false
|
||||
) |> Enum.map(&String.to_charlist/1)
|
||||
|
||||
@reserved_words ~w(
|
||||
enum
|
||||
model
|
||||
datasource
|
||||
generator
|
||||
) |> Enum.map(&String.to_charlist/1)
|
||||
|
||||
defp boolean_value_or_name_or_reserved_word(rest, chars, context, loc, byte_offset) do
|
||||
value = chars |> Enum.reverse()
|
||||
do_boolean_value_or_name_or_reserved_word(rest, value, context, loc, byte_offset)
|
||||
end
|
||||
|
||||
defp do_boolean_value_or_name_or_reserved_word(_rest, value, context, _loc, _byte_offset)
|
||||
when value in @boolean_words do
|
||||
{[{:name_constant, %{}, value}], context}
|
||||
end
|
||||
|
||||
defp do_boolean_value_or_name_or_reserved_word(_rest, value, context, _loc, _byte_offset)
|
||||
when value in @reserved_words do
|
||||
{[{:keyword_reserved, %{}, value}], context}
|
||||
end
|
||||
|
||||
defp do_boolean_value_or_name_or_reserved_word(_rest, value, context, _loc, _byte_offset) do
|
||||
{[{:name, %{}, value}], context}
|
||||
end
|
||||
|
||||
def line_and_column({line, line_offset}, byte_offset, column_correction) do
|
||||
column = byte_offset - line_offset - column_correction + 1
|
||||
{line, column}
|
||||
end
|
||||
|
||||
defp not_line_terminator(<<?\n, _::binary>>, context, _, _), do: {:halt, context}
|
||||
defp not_line_terminator(<<?\r, _::binary>>, context, _, _), do: {:halt, context}
|
||||
defp not_line_terminator(_, context, _, _), do: {:cont, context}
|
||||
end
|
17
lib/makeup_prisma/application.ex
Normal file
17
lib/makeup_prisma/application.ex
Normal file
@ -0,0 +1,17 @@
|
||||
defmodule MakeupPrisma.Application do
|
||||
@moduledoc false
|
||||
use Application
|
||||
|
||||
alias Makeup.Registry
|
||||
alias Makeup.Lexers.MakeupPrisma
|
||||
|
||||
def start(_type, _args) do
|
||||
Registry.register_lexer(MakeupPrisma,
|
||||
options: [],
|
||||
names: ["prisma"],
|
||||
extensions: ["prisma"]
|
||||
)
|
||||
|
||||
Supervisor.start_link([], strategy: :one_for_one)
|
||||
end
|
||||
end
|
38
mix.exs
Normal file
38
mix.exs
Normal file
@ -0,0 +1,38 @@
|
||||
defmodule MakeupPrisma.MixProject do
|
||||
use Mix.Project
|
||||
|
||||
@url "https://github.com/nullndr/makeup_prisma"
|
||||
|
||||
def project do
|
||||
[
|
||||
app: :makeup_prisma,
|
||||
version: "0.1.0",
|
||||
elixir: "~> 1.17",
|
||||
start_permanent: Mix.env() == :prod,
|
||||
deps: deps(),
|
||||
package: package()
|
||||
]
|
||||
end
|
||||
|
||||
defp package do
|
||||
[
|
||||
name: "makeup_prisma",
|
||||
links: %{"GitHub" => @url}
|
||||
]
|
||||
end
|
||||
|
||||
# Run "mix help compile.app" to learn about applications.
|
||||
def application do
|
||||
[
|
||||
extra_applications: [:logger]
|
||||
]
|
||||
end
|
||||
|
||||
# Run "mix help deps" to learn about dependencies.
|
||||
defp deps do
|
||||
[
|
||||
{:makeup, "~> 1.0"},
|
||||
{:nimble_parsec, "~> 1.1"}
|
||||
]
|
||||
end
|
||||
end
|
4
mix.lock
Normal file
4
mix.lock
Normal file
@ -0,0 +1,4 @@
|
||||
%{
|
||||
"makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"},
|
||||
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
|
||||
}
|
237
test/makeup_prisma_test.exs
Normal file
237
test/makeup_prisma_test.exs
Normal file
@ -0,0 +1,237 @@
|
||||
defmodule MakeupPrismaTest do
|
||||
use ExUnit.Case
|
||||
doctest MakeupPrisma
|
||||
alias Makeup.Lexer.Postprocess
|
||||
|
||||
defp lex(text) do
|
||||
text
|
||||
|> MakeupPrisma.lex(group_prefix: "group")
|
||||
|> Postprocess.token_values_to_binaries()
|
||||
|> Enum.map(fn {ttype, meta, value} -> {ttype, Map.delete(meta, :language), value} end)
|
||||
end
|
||||
|
||||
test "session from Shopify" do
|
||||
schema = """
|
||||
model Session {
|
||||
id String @id
|
||||
shop String
|
||||
state String
|
||||
isOnline Boolean @default(false)
|
||||
scope String?
|
||||
expires DateTime?
|
||||
accessToken String
|
||||
userId BigInt?
|
||||
firstName String?
|
||||
lastName String?
|
||||
email String?
|
||||
accountOwner Boolean @default(false)
|
||||
locale String?
|
||||
collaborator Boolean? @default(false)
|
||||
emailVerified Boolean? @default(false)
|
||||
}
|
||||
"""
|
||||
|
||||
assert lex(schema) == [
|
||||
{:keyword_reserved, %{}, "model"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "Session"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:punctuation, %{}, "{"},
|
||||
{:whitespace, %{}, "\n"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "id"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "String"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:punctuation, %{}, "@"},
|
||||
{:name, %{}, "id"},
|
||||
{:whitespace, %{}, "\n"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "shop"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "String"},
|
||||
{:whitespace, %{}, "\n"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "state"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "String"},
|
||||
{:whitespace, %{}, "\n"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "isOnline"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "Boolean"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:punctuation, %{}, "@"},
|
||||
{:name, %{}, "default"},
|
||||
{:punctuation, %{}, "("},
|
||||
{:name_constant, %{}, "false"},
|
||||
{:punctuation, %{}, ")"},
|
||||
{:whitespace, %{}, "\n"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "scope"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "String?"},
|
||||
{:whitespace, %{}, "\n"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "expires"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "DateTime?"},
|
||||
{:whitespace, %{}, "\n"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "accessToken"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "String"},
|
||||
{:whitespace, %{}, "\n"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "userId"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "BigInt?"},
|
||||
{:whitespace, %{}, "\n"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "firstName"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "String?"},
|
||||
{:whitespace, %{}, "\n"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "lastName"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "String?"},
|
||||
{:whitespace, %{}, "\n"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "email"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "String?"},
|
||||
{:whitespace, %{}, "\n"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "accountOwner"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "Boolean"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:punctuation, %{}, "@"},
|
||||
{:name, %{}, "default"},
|
||||
{:punctuation, %{}, "("},
|
||||
{:name_constant, %{}, "false"},
|
||||
{:punctuation, %{}, ")"},
|
||||
{:whitespace, %{}, "\n"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "locale"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "String?"},
|
||||
{:whitespace, %{}, "\n"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "collaborator"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "Boolean?"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:punctuation, %{}, "@"},
|
||||
{:name, %{}, "default"},
|
||||
{:punctuation, %{}, "("},
|
||||
{:name_constant, %{}, "false"},
|
||||
{:punctuation, %{}, ")"},
|
||||
{:whitespace, %{}, "\n"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "emailVerified"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "Boolean?"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:punctuation, %{}, "@"},
|
||||
{:name, %{}, "default"},
|
||||
{:punctuation, %{}, "("},
|
||||
{:name_constant, %{}, "false"},
|
||||
{:punctuation, %{}, ")"},
|
||||
{:whitespace, %{}, "\n"},
|
||||
{:punctuation, %{}, "}"},
|
||||
{:whitespace, %{}, "\n"}
|
||||
]
|
||||
end
|
||||
|
||||
test "generators and datasource" do
|
||||
schema = """
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
previewFeatures = ["fullTextSearch", "omitApi"]
|
||||
}
|
||||
|
||||
generator json {
|
||||
provider = "prisma-json-types-generator"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
"""
|
||||
|
||||
assert lex(schema) == [
|
||||
{:keyword_reserved, %{}, "generator"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "client"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:punctuation, %{}, "{"},
|
||||
{:whitespace, %{}, "\n"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "provider"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:punctuation, %{}, "="},
|
||||
{:whitespace, %{}, " "},
|
||||
{:string, %{}, "\"prisma-client-js\""},
|
||||
{:whitespace, %{}, "\n"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "previewFeatures"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:punctuation, %{}, "="},
|
||||
{:whitespace, %{}, " "},
|
||||
{:punctuation, %{}, "["},
|
||||
{:string, %{}, "\"fullTextSearch\""},
|
||||
{:punctuation, %{}, ","},
|
||||
{:whitespace, %{}, " "},
|
||||
{:string, %{}, "\"omitApi\""},
|
||||
{:punctuation, %{}, "]"},
|
||||
{:whitespace, %{}, "\n"},
|
||||
{:punctuation, %{}, "}"},
|
||||
{:whitespace, %{}, "\n"},
|
||||
{:whitespace, %{}, "\n"},
|
||||
{:keyword_reserved, %{}, "generator"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "json"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:punctuation, %{}, "{"},
|
||||
{:whitespace, %{}, "\n"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "provider"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:punctuation, %{}, "="},
|
||||
{:whitespace, %{}, " "},
|
||||
{:string, %{}, "\"prisma-json-types-generator\""},
|
||||
{:whitespace, %{}, "\n"},
|
||||
{:punctuation, %{}, "}"},
|
||||
{:whitespace, %{}, "\n"},
|
||||
{:whitespace, %{}, "\n"},
|
||||
{:keyword_reserved, %{}, "datasource"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "db"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:punctuation, %{}, "{"},
|
||||
{:whitespace, %{}, "\n"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "provider"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:punctuation, %{}, "="},
|
||||
{:whitespace, %{}, " "},
|
||||
{:string, %{}, "\"postgresql\""},
|
||||
{:whitespace, %{}, "\n"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "url"},
|
||||
{:whitespace, %{}, " "},
|
||||
{:punctuation, %{}, "="},
|
||||
{:whitespace, %{}, " "},
|
||||
{:name, %{}, "env"},
|
||||
{:punctuation, %{}, "("},
|
||||
{:string, %{}, "\"DATABASE_URL\""},
|
||||
{:punctuation, %{}, ")"},
|
||||
{:whitespace, %{}, "\n"},
|
||||
{:punctuation, %{}, "}"},
|
||||
{:whitespace, %{}, "\n"}
|
||||
]
|
||||
end
|
||||
end
|
1
test/test_helper.exs
Normal file
1
test/test_helper.exs
Normal file
@ -0,0 +1 @@
|
||||
ExUnit.start()
|
Loading…
x
Reference in New Issue
Block a user