Skip to content

Commit

Permalink
feat: support non primitives
Browse files Browse the repository at this point in the history
  • Loading branch information
cdaringe committed Mar 2, 2024
1 parent 6cf2785 commit 9a9addc
Show file tree
Hide file tree
Showing 14 changed files with 393 additions and 156 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
/build
erl_crash.dump
src/internal/foo**
src/internal/bar**
40 changes: 21 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ gleam add gserde

## usage

1. Create custom type with a singular variant constructor. See the example `src/foo.gleam` below.
1. Create custom type with a singular variant constructor. See the example
`src/foo.gleam` below.
2. Run `gleam run -m gserde`.
3. Observe the generated file `src/foo_json.gleam`.
4. Use it!
4. Use the new `foo_json` module!

```gleam
// src/foo.gleam
Expand Down Expand Up @@ -58,33 +59,34 @@ pub fn to_string(t: foo.FooJson) {
json.to_string(to_json(t))
}
pub fn from_json(json_str: String) {
json.decode(
json_str,
dynamic.decode6(
foo.Foo,
dynamic.field("a_bool", dynamic.bool),
dynamic.field("b_int", dynamic.int),
dynamic.field("c_float", dynamic.float),
dynamic.field("d_two_tuple", dynamic.tuple2(dynamic.int, dynamic.string)),
dynamic.field("e_option_int", dynamic.optional(dynamic.int)),
dynamic.field("f_string_list", dynamic.list(dynamic.string)),
),
pub fn get_decoder_foo() {
dynamic.decode6(
foo.Foo,
dynamic.field("a_bool", dynamic.bool),
dynamic.field("b_int", dynamic.int),
dynamic.field("c_float", dynamic.float),
dynamic.field("d_two_tuple", dynamic.tuple2(dynamic.int, dynamic.string)),
dynamic.field("e_option_int", dynamic.optional(dynamic.int)),
dynamic.field("f_string_list", dynamic.list(dynamic.string)),
)
}
pub fn from_string(json_str: String) {
json.decode(json_str, get_decoder_foo())
}
// src/my_module.gleam
import foo
import foo_json
pub fn serialization_identity_test() {
let foo_1 = foo.Foo(..)
let foo_1 = foo.Foo(..) // make a Foo
let foo_2 = foo_1
|> foo_json.to_string // 👀
|> foo_json.from_string // 👀
|> foo_json.to_string // 👀, stringify the Foo to JSON!
|> foo_json.from_string // 👀, parse the Foo from JSON!
foo_1 == foo_2
foo_1 == foo_2 // pass the identity test
}
```

Expand All @@ -94,7 +96,7 @@ You can set `DEBUG=1` to get verbose output during codegen.

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

Further documentation can be found at <https://hexdocs.pm/gserde>.
Expand Down
3 changes: 2 additions & 1 deletion gleam.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name = "gserde"
version = "1.1.0"
version = "1.2.0"

# Fill out these fields if you intend to generate HTML documentation or publish
# your project to the Hex package manager.
Expand All @@ -23,6 +23,7 @@ simplifile = "~> 1.2"
fswalk = "~> 2.0"
shellout = "~> 1.5"
dot_env = "~> 0.2"
justin = "~> 1.0"

[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 @@ -3,13 +3,14 @@

packages = [
{ name = "dot_env", version = "0.2.4", build_tools = ["gleam"], requirements = ["gleam_stdlib", "simplifile"], otp_app = "dot_env", source = "hex", outer_checksum = "FFAC6F89A2BB6896A10128E5850496C372821BFDB807C837A1404BEBDD1AB2B9" },
{ name = "fswalk", version = "2.0.2", build_tools = ["gleam"], requirements = ["dot_env", "gleam_stdlib", "gleam_community_path", "simplifile"], otp_app = "fswalk", source = "hex", outer_checksum = "5D8E9C34C4C1BF3E65A79A292FE98B4AD35E525D18BB068518359687FA7BD1EB" },
{ name = "fswalk", version = "2.0.2", build_tools = ["gleam"], requirements = ["simplifile", "dot_env", "gleam_community_path", "gleam_stdlib"], otp_app = "fswalk", source = "hex", outer_checksum = "5D8E9C34C4C1BF3E65A79A292FE98B4AD35E525D18BB068518359687FA7BD1EB" },
{ 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_json", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "thoas"], otp_app = "gleam_json", source = "hex", outer_checksum = "8B197DD5D578EA6AC2C0D4BDC634C71A5BCA8E7DB5F47091C263ECB411A60DF3" },
{ name = "gleam_stdlib", version = "0.36.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "C0D14D807FEC6F8A08A7C9EF8DFDE6AE5C10E40E21325B2B29365965D82EB3D4" },
{ 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 = "justin", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "justin", source = "hex", outer_checksum = "7FA0C6DB78640C6DC5FBFD59BF3456009F3F8B485BF6825E97E1EB44E9A1E2CD" },
{ name = "shellout", version = "1.6.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "shellout", source = "hex", outer_checksum = "E2FCD18957F0E9F67E1F497FC9FF57393392F8A9BAEAEA4779541DE7A68DD7E0" },
{ name = "simplifile", version = "1.5.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "EB9AA8E65E5C1E3E0FDCFC81BC363FD433CB122D7D062750FFDF24DE4AC40116" },
{ name = "thoas", version = "0.4.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "4918D50026C073C4AB1388437132C77A6F6F7C8AC43C60C13758CC0ADCE2134E" },
Expand All @@ -22,5 +23,6 @@ glance = { version = "~> 0.8" }
gleam_json = { version = ">= 0.0.0 and < 2.0.0" }
gleam_stdlib = { version = "~> 0.34 or ~> 1.0" }
gleeunit = { version = "~> 1.0" }
justin = { version = "~> 1.0"}
shellout = { version = "~> 1.5" }
simplifile = { version = "~> 1.2" }
33 changes: 33 additions & 0 deletions rad.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { Task, Tasks } from "https://deno.land/x/rad/src/mod.ts";

const build: Task = `gleam build`;
const clean: Task = `rm -rf src/foo* src/internal/foo* src/bar* src/internal/bar*`;
const format: Task = `gleam format`;

const gleamTest: Task = {
dependsOn: [clean],
fn: async (toolkit) => {
const { fs, path, logger, Deno, sh, task } = toolkit;
await sh(`gleam test`);
},
};

const test: Task = {
dependsOn: [gleamTest, clean],
// fn: async (toolkit) => {
// const { fs, path, logger, Deno, sh, task } = toolkit;
// await sh(`gleam test`);
// },
};

export const tasks: Tasks = {
clean,
build,
b: build,
format,
f: format,
gleamTest,
gt: gleamTest,
test,
t: test,
};
18 changes: 18 additions & 0 deletions src/ast.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import gleam/list
import gleam/string
import request.{type Request}
import evil.{expect}

pub fn get_import_path_from_mod_name(module_str: String, req: Request) {
list.find_map(in: req.module.imports, with: fn(imp) {
let full_module_str = imp.definition.module
case
full_module_str == module_str
|| string.ends_with(full_module_str, "/" <> module_str)
{
True -> Ok(full_module_str)
_ -> Error(Nil)
}
})
|> expect(module_str <> ": module not found in import list")
}
11 changes: 11 additions & 0 deletions src/common.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import justin
import gleam/string

pub fn decoder_name_of_t(raw_name: String) -> String {
let snake_name = justin.snake_case(raw_name)
let name = case string.ends_with(snake_name, "_json") {
True -> string.drop_right(snake_name, 5)
False -> raw_name
}
"get_decoder_" <> name
}
11 changes: 11 additions & 0 deletions src/evil.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import gleam/io

pub fn expect(x, msg) {
case x {
Ok(v) -> v
Error(_) -> {
io.print_error(msg)
panic
}
}
}
34 changes: 14 additions & 20 deletions src/gserde.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import internal/deserializer
import simplifile
import request.{type Request, Request}
import fswalk
import evil.{expect}

pub fn gen(req: Request) {
let ser =
Expand All @@ -32,16 +33,6 @@ fn to_output_filename(src_filename) {
string.replace(in: src_filename, each: ".gleam", with: "_json.gleam")
}

fn expect(x, msg) {
case x {
Ok(v) -> v
Error(_) -> {
io.print_error(msg)
panic
}
}
}

pub fn main() {
let is_debug = case env.get_bool("DEBUG") {
Ok(_) -> True
Expand All @@ -56,18 +47,16 @@ pub fn main() {
}

pub fn process_single(src_filename: String, is_debug) {
case is_debug {
True -> {
io.debug(#("Processing", src_filename))
Nil
}
_ -> Nil
}
bool.guard(!is_debug, Nil, fn() {
io.debug(#("Processing", src_filename))
Nil
})

let src_module_name =
src_filename
|> string.replace("src/", "")
|> string.replace(".gleam", "")

let dest_filename = to_output_filename(src_filename)

let assert Ok(code) = simplifile.read(from: src_filename)
Expand All @@ -82,13 +71,21 @@ pub fn process_single(src_filename: String, is_debug) {
let custom_types =
list.map(parsed.custom_types, fn(def) { def.definition })
|> list.filter(fn(x) { string.ends_with(x.name, "Json") })

bool.guard(
when: list.length(of: custom_types) <= 1,
return: Nil,
otherwise: fn() { panic as "Only one json type is allowed per file" },
)

let requests =
custom_types
|> list.flat_map(fn(custom_type) {
list.map(custom_type.variants, fn(variant) {
Request(
src_module_name: src_module_name,
type_name: custom_type.name,
module: parsed,
variant: variant,
ser: True,
de: True,
Expand Down Expand Up @@ -116,7 +113,4 @@ pub fn process_single(src_filename: String, is_debug) {
)
|> result.unwrap(Nil)
}
// foo.Foo(a: True, b: #(123))
// |> foo_json.to_string
// |> io.println
}
47 changes: 47 additions & 0 deletions src/internal/codegen/modules.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import gleam/option
import gleam/list
import gleam/string
import gleam/set
import internal/codegen/types.{type GleamType} as t
import internal/codegen/statements.{type GleamStatement} as gens

pub type Mod {
Mod(
name: String,
functions: List(GleamStatement),
types: List(GleamType),
imports: List(String),
)
}

pub fn empty() -> Mod {
Mod(name: "", functions: [], types: [], imports: [])
}

pub fn add_functions(mod: Mod, functions: List(GleamStatement)) -> Mod {
Mod(..mod, functions: list.concat([mod.functions, functions]))
}

pub fn add_imports(mod: Mod, imports: List(String)) -> Mod {
Mod(..mod, imports: list.concat([mod.imports, imports]))
}

pub fn merge(m1: Mod, m2: Mod) {
Mod(
name: m1.name,
functions: list.concat([m1.functions, m2.functions]),
types: list.concat([m1.types, m2.types]),
imports: list.concat([m1.imports, m2.imports])
|> set.from_list
|> set.to_list,
)
}

pub fn to_string(m: Mod) {
list.concat([
list.map(m.imports, fn(i) { "import " <> i }),
list.map(m.types, t.generate_type_def),
list.map(m.functions, gens.generate),
])
|> string.join("\n")
}

0 comments on commit 9a9addc

Please sign in to comment.