Skip to content

Commit

Permalink
feat: init
Browse files Browse the repository at this point in the history
  • Loading branch information
cdaringe committed Jan 21, 2024
1 parent 0cb555d commit c5e7c3a
Show file tree
Hide file tree
Showing 9 changed files with 256 additions and 46 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
*.ez
/build
erl_crash.dump
src/foo**
src/internal/foo**
46 changes: 41 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,61 @@
# gserde

**warning**: alpha package with poor code hygiene, including assert/panic/todo
statements. Use understanding that this package is not ready for primetime.

[![Package Version](https://img.shields.io/hexpm/v/gserde)](https://hex.pm/packages/gserde)
[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/gserde/)

```sh
gleam add gserde
```

## usage

1. Create custom type with a singular variant constructor:
2. Run `gleam run -m gserde`
3. Observe `src/foo_json.gleam`, which has the goodies you need for json (de)serialization.

```gleam
import gserde
// src/foo.gleam
import gleam/option.{type Option}
pub type FooJSON {
Foo(
a_bool: Bool,
b_int: Int,
c_float: Float,
d_two_tuple: #(Int, String),
e_option_int: Option(Int),
f_string_list: List(String),
)
}
pub fn main() {
// TODO: An example of the project in use
// src/my_module.gleam
import foo
import foo_json
pub fn serialization_identity_test() {
let foo_1 = foo.Foo(..)
let foo_2 = foo_1
|> foo_json.to_string // 👀
|> foo_json.from_string // 👀
foo_1 == foo_2
}
```

## todo

- [ ] complete all cases
- [ ] remove all invocations of assert/panic/todo
- [ ] support non-gleam primitive types
- [ ] handle all module references properly

Further documentation can be found at <https://hexdocs.pm/gserde>.

## Development

```sh
gleam run # Run the project
gleam test # Run the tests
gleam shell # Run an Erlang shell
```
3 changes: 2 additions & 1 deletion gleam.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ gleam_json = "~> 1.0"
glance = "~> 0.8"
simplifile = "~> 1.2"
tom = "~> 0.3"
fswalk = "~> 1.0"
fswalk = "~> 2.0"
shellout = "~> 1.5"

[dev-dependencies]
gleeunit = "~> 1.0"
Expand Down
6 changes: 4 additions & 2 deletions manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,25 @@
# You typically do not need to edit this file

packages = [
{ name = "fswalk", version = "1.0.4", build_tools = ["gleam"], requirements = ["gleam_community_path", "simplifile", "gleam_stdlib"], otp_app = "fswalk", source = "hex", outer_checksum = "0FF9D3D5AFDB90A87BEB638F720CEBFEA4673E7C7481199745FD7750B1EB1C6E" },
{ name = "fswalk", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_community_path", "gleam_stdlib", "simplifile"], otp_app = "fswalk", source = "hex", outer_checksum = "50C250FA4CF97D6C383ECF69D7446EFB9D4F8261328A13099478AAAD0CEDC081" },
{ name = "glance", version = "0.8.2", build_tools = ["gleam"], requirements = ["gleam_stdlib", "glexer"], otp_app = "glance", source = "hex", outer_checksum = "ACF09457E8B564AD7A0D823DAFDD326F58263C01ACB0D432A9BEFDEDD1DA8E73" },
{ name = "gleam_community_path", version = "0.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_community_path", source = "hex", outer_checksum = "916C2829E2ED81036BBA180CFD5E8633D05E25C304FDF6E3BC8A048459B89725" },
{ name = "gleam_json", version = "1.0.0", build_tools = ["gleam"], requirements = ["thoas", "gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "8B197DD5D578EA6AC2C0D4BDC634C71A5BCA8E7DB5F47091C263ECB411A60DF3" },
{ name = "gleam_stdlib", version = "0.34.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "1FB8454D2991E9B4C0C804544D8A9AD0F6184725E20D63C3155F0AEB4230B016" },
{ name = "gleeunit", version = "1.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "D364C87AFEB26BDB4FB8A5ABDE67D635DC9FA52D6AB68416044C35B096C6882D" },
{ name = "glexer", version = "0.7.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glexer", source = "hex", outer_checksum = "4484942A465482A0A100936E1E5F12314DB4B5AC0D87575A7B9E9062090B96BE" },
{ name = "shellout", version = "1.5.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "shellout", source = "hex", outer_checksum = "7B5DE499DBB3DDC25051FC1BB3770DD5466938B6A2AFA91A6FB4A4D49F4CB0D4" },
{ name = "simplifile", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "359CD7006E2F69255025C858CCC6407C11A876EC179E6ED1E46809E8DC6B1AAD" },
{ name = "thoas", version = "0.4.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "4918D50026C073C4AB1388437132C77A6F6F7C8AC43C60C13758CC0ADCE2134E" },
{ name = "tom", version = "0.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "tom", source = "hex", outer_checksum = "0831C73E45405A2153091226BF98FB485ED16376988602CC01A5FD086B82D577" },
]

[requirements]
fswalk = { version = "~> 1.0" }
fswalk = { version = "~> 2.0"}
glance = { version = "~> 0.8" }
gleam_json = { version = "~> 1.0" }
gleam_stdlib = { version = "~> 0.34 or ~> 1.0" }
gleeunit = { version = "~> 1.0" }
shellout = { version = "~> 1.5" }
simplifile = { version = "~> 1.2" }
tom = { version = "~> 0.3" }
87 changes: 87 additions & 0 deletions src/deserializer.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import glance
import internal/codegen/statements as gens
import internal/codegen/types as t
import gleam/string
import gleam/list
import gleam/option
import gleam/io
import gleam/int
import request.{type Request, Request}
import internal/path.{basename}

fn quote(str) {
"\"" <> str <> "\""
}

fn gen_decoder(typ) {
case typ {
glance.NamedType(name, _module_todo, parameters) -> {
case name {
"List" -> {
let assert Ok(t0) = list.at(parameters, 0)
gens.call("dynamic.list", [gen_decoder(t0)])
}
"Option" -> {
let assert Ok(t0) = list.at(parameters, 0)
gens.call("dynamic.optional", [gen_decoder(t0)])
}
_ -> gens.VarPrimitive("dynamic." <> string.lowercase(name))
}
}
glance.TupleType(parts) -> {
let m_tuple =
list.length(of: parts)
|> int.to_string
gens.call(
"dynamic.tuple" <> string.lowercase(m_tuple),
parts
|> list.map(gen_decoder),
)
}
x -> {
io.debug(#("warning: unsupported decoding", x))
gens.VarPrimitive("dynamic.toodoo")
}
}
}

fn gen_root_decoder(req) {
let Request(
src_module_name: src_module_name,
type_name: _type_name,
variant: variant,
..,
) = req
let n_str =
list.length(of: variant.fields)
|> int.to_string
gens.Function(
"from_json",
[gens.arg_typed("json_str", t.AnonymousType("String"))],
[
gens.call("json.decode", [
gens.VarPrimitive("json_str"),
gens.call("dynamic.decode" <> n_str, [
gens.VarPrimitive( basename(src_module_name) <> "." <> variant.name),
..list.map(req.variant.fields, fn(field) {
gens.call("dynamic.field", [
gens.VarPrimitive(
option.lazy_unwrap(field.label, fn() {
panic as "@todo/panic variants must be labeled"
})
|> quote,
),
gen_decoder(field.item),
])
})
]),
]),
],
)
}

pub fn to(req: Request) {
[gen_root_decoder(req)]
|> list.map(gens.generate)
|> string.join(with: "\n")
}
34 changes: 21 additions & 13 deletions src/gserde.gleam
Original file line number Diff line number Diff line change
@@ -1,23 +1,30 @@
import gleam/io
import gleam/list
import gleam/bool
import gleam/string
import gleam/result
import glance
import serializer
import deserializer
import simplifile
import request.{type Request, Request}
// import foo
// import foo_json
import fswalk

pub fn gen(req: Request) {
let ser = case req.ser {
True -> Ok(serializer.from(req))
_ -> Error(Nil)
}
let ser =
bool.guard(when: req.ser, return: serializer.from(req), otherwise: fn() {
""
})
let de =
bool.guard(when: req.de, return: deserializer.to(req), otherwise: fn() {
""
})

let assert Ok(ser) = ser
#(req, ser)
#(
req,
[ser, de]
|> string.join("\n\n"),
)
}

fn to_output_filename(src_filename) {
Expand All @@ -37,13 +44,14 @@ fn expect(x, msg) {
pub fn main() {
fswalk.builder()
|> fswalk.with_path("src")
|> fswalk.with_filter(fswalk.only_files)
|> fswalk.with_entry_filter(fswalk.only_files)
|> fswalk.walk
|> fswalk.map(fn(v) { expect(v, "failed to walk").filename })
|> fswalk.each(process_single)
}

pub fn process_single(src_filename: String) {
io.debug(#(src_filename))
// let assert Ok(gleam_toml_str) = simplifile.read(from: "gleam.toml")
// let assert Ok(gleam_toml) = tom.parse(gleam_toml_str)
// let assert Ok(pkg_name) = tom.get_string(gleam_toml, ["name"])
Expand All @@ -52,16 +60,15 @@ pub fn process_single(src_filename: String) {
|> string.replace("src/", "")
|> string.replace(".gleam", "")
let dest_filename = to_output_filename(src_filename)
io.debug(#("reading", src_filename))

let assert Ok(code) = simplifile.read(from: src_filename)
io.debug(#("parsing", src_filename))

let assert Ok(parsed) =
glance.module(code)
|> result.map_error(fn(err) {
io.debug(err)
panic
})
io.debug(#("next", src_filename))

let custom_types =
list.map(parsed.custom_types, fn(def) { def.definition })
Expand All @@ -75,7 +82,7 @@ pub fn process_single(src_filename: String) {
type_name: custom_type.name,
variant: variant,
ser: True,
de: False,
de: True,
)
})
})
Expand All @@ -92,6 +99,7 @@ pub fn process_single(src_filename: String) {
to: dest_filename,
contents: [
"import gleam/json",
"import gleam/dynamic",
"import " <> src_module_name,
filecontent,
]
Expand Down
9 changes: 9 additions & 0 deletions src/internal/path.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import gleam/string
import gleam/list
import gleam/result

pub fn basename(path: String) {
string.split(path, on: "/")
|> list.last
|> result.unwrap(or: "")
}
24 changes: 12 additions & 12 deletions src/serializer.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ import gleam/option
import gleam/io
import gleam/int
import request.{Request}
import internal/path.{basename}

fn glance_t_to_codegen_t(x: glance.Type) -> t.GleamType {
case x {
glance.NamedType(name, _module, parameters) -> {
// uhhhh how will we resolve types from modules
io.debug(#("glance_t_to_codegen_t", name))
// @todo resolve types from modules
// io.debug(#("glance_t_to_codegen_t", name))
case name {
"List" -> {
let assert Ok(t0) = list.at(parameters, 0)
Expand Down Expand Up @@ -40,8 +41,8 @@ fn glance_t_to_codegen_t(x: glance.Type) -> t.GleamType {
panic as "cannot serialize entities with functions"
}

glance.VariableType(name) -> {
io.debug(name)
glance.VariableType(_name) -> {
// io.debug(name)
panic as "unimplemented! VariableType"
}
}
Expand All @@ -65,14 +66,14 @@ pub fn get_json_serializer_str(ct: t.GleamType) {
}
}
_ -> {
io.debug(#("codegen type failed: ", ct))
// io.debug(#("codegen type failed: ", ct))
todo
}
}
}

fn codegen_t_to_codegen_json_t(ct, field_name) {
io.debug(#("codegen type: ", ct))
// io.debug(#("codegen type: ", ct))
let json_call_fn_str = get_json_serializer_str(ct)
let field_name_var = gens.VarPrimitive("t." <> field_name)
case ct {
Expand Down Expand Up @@ -104,7 +105,7 @@ fn codegen_t_to_codegen_json_t(ct, field_name) {
])
}
_ -> {
io.debug(#("codegen_t_to_codegen_json_t failed: ", ct))
// io.debug(#("codegen_t_to_codegen_json_t failed: ", ct))
todo
}
}
Expand All @@ -126,22 +127,21 @@ fn gen_to_json(req) {
gens.Function(
// string.lowercase(variant.name) <> "_to_json",
"to_json",
[gens.arg_typed("t", t.AnonymousType(src_module_name <> "." <> type_name))],
[gens.arg_typed("t", t.AnonymousType(basename(src_module_name) <> "." <> type_name))],
[
gens.call("json.object", [
gens.list(
list.map(variant.fields, fn(field) {
case option.to_result(field.label, Nil) {
Ok(label) -> {
// @todo produce a tuple here not just the value
io.debug(#(field))
// io.debug(#(field))
gens.TupleVal([
gens.StringVal(label),
serializer_of_t(field.item, label),
])
}
_ -> {
io.println(
io.println_error(
"Variant "
<> variant.name
<> " must have labels for all fields",
Expand All @@ -160,7 +160,7 @@ fn gen_to_string(req) {
let Request(src_module_name: src_module_name, type_name: type_name, ..) = req
gens.Function(
"to_string",
[gens.arg_typed("t", t.AnonymousType(src_module_name <> "." <> type_name))],
[gens.arg_typed("t", t.AnonymousType(basename(src_module_name) <> "." <> type_name))],
[gens.call("json.to_string", [gens.call("to_json", [gens.variable("t")])])],
)
}
Expand Down
Loading

0 comments on commit c5e7c3a

Please sign in to comment.