This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

initial commit

Finnian Brewer (Jun 2, 2026, 3:06 PM -0700) dd20991d

+494
+23
.github/workflows/test.yml
··· 1 + name: test 2 + 3 + on: 4 + push: 5 + branches: 6 + - master 7 + - main 8 + pull_request: 9 + 10 + jobs: 11 + test: 12 + runs-on: ubuntu-latest 13 + steps: 14 + - uses: actions/checkout@v4 15 + - uses: erlef/setup-beam@v1 16 + with: 17 + otp-version: "28" 18 + gleam-version: "1.13.0" 19 + rebar3-version: "3" 20 + # elixir-version: "1" 21 + - run: gleam deps download 22 + - run: gleam test 23 + - run: gleam format --check src test
+4
.gitignore
··· 1 + *.beam 2 + *.ez 3 + /build 4 + erl_crash.dump
+24
README.md
··· 1 + # deriving 2 + 3 + [![Package Version](https://img.shields.io/hexpm/v/deriving)](https://hex.pm/packages/deriving) 4 + [![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/deriving/) 5 + 6 + ```sh 7 + gleam add deriving@1 8 + ``` 9 + ```gleam 10 + import deriving 11 + 12 + pub fn main() -> Nil { 13 + // TODO: An example of the project in use 14 + } 15 + ``` 16 + 17 + Further documentation can be found at <https://hexdocs.pm/deriving>. 18 + 19 + ## Development 20 + 21 + ```sh 22 + gleam run # Run the project 23 + gleam test # Run the tests 24 + ```
+24
gleam.toml
··· 1 + name = "deriving" 2 + version = "1.0.0" 3 + 4 + # Fill out these fields if you intend to generate HTML documentation or publish 5 + # your project to the Hex package manager. 6 + # 7 + # description = "" 8 + # licences = ["Apache-2.0"] 9 + # repository = { type = "github", user = "", repo = "" } 10 + # links = [{ title = "Website", href = "" }] 11 + # 12 + # For a full reference of all the available options, you can have a look at 13 + # https://gleam.run/writing-gleam/gleam-toml/. 14 + 15 + [dependencies] 16 + gleam_stdlib = ">= 0.44.0 and < 2.0.0" 17 + glance = ">= 6.0.0 and < 7.0.0" 18 + simplifile = ">= 2.3.1 and < 3.0.0" 19 + gleam_json = ">= 3.1.0 and < 4.0.0" 20 + gleamgen = { path = "../gleamgen" } 21 + justin = ">= 1.0.1 and < 2.0.0" 22 + 23 + [dev-dependencies] 24 + gleeunit = ">= 1.0.0 and < 2.0.0"
+25
manifest.toml
··· 1 + # This file was generated by Gleam 2 + # You typically do not need to edit this file 3 + 4 + packages = [ 5 + { name = "filepath", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "B06A9AF0BF10E51401D64B98E4B627F1D2E48C154967DA7AF4D0914780A6D40A" }, 6 + { name = "glam", version = "2.0.3", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glam", source = "hex", outer_checksum = "237C2CE218A2A0A5D46D625F8EF5B78F964BC91018B78D692B17E1AB84295229" }, 7 + { name = "glance", version = "6.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "glexer"], otp_app = "glance", source = "hex", outer_checksum = "49E0ED4793BB3F56C3E5ED00528D70CAE21D263F70A735604124B95C5F62E2DB" }, 8 + { name = "gleam_json", version = "3.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "44FDAA8847BE8FC48CA7A1C089706BD54BADCC4C45B237A992EDDF9F2CDB2836" }, 9 + { name = "gleam_stdlib", version = "0.67.1", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "6CE3E4189A8B8EC2F73AB61A2FBDE49F159D6C9C61C49E3B3082E439F260D3D0" }, 10 + { name = "gleamgen", version = "0.3.6", build_tools = ["gleam"], requirements = ["glam", "glance", "gleam_stdlib"], source = "local", path = "../gleamgen" }, 11 + { name = "gleeunit", version = "1.9.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "DA9553CE58B67924B3C631F96FE3370C49EB6D6DC6B384EC4862CC4AAA718F3C" }, 12 + { name = "glexer", version = "2.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "splitter"], otp_app = "glexer", source = "hex", outer_checksum = "40A1FB0919FA080AD6C5809B4C7DBA545841CAAC8168FACDFA0B0667C22475CC" }, 13 + { name = "justin", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "justin", source = "hex", outer_checksum = "7FA0C6DB78640C6DC5FBFD59BF3456009F3F8B485BF6825E97E1EB44E9A1E2CD" }, 14 + { name = "simplifile", version = "2.3.1", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "957E0E5B75927659F1D2A1B7B75D7B9BA96FAA8D0C53EA71C4AD9CD0C6B848F6" }, 15 + { name = "splitter", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "splitter", source = "hex", outer_checksum = "3DFD6B6C49E61EDAF6F7B27A42054A17CFF6CA2135FF553D0CB61C234D281DD0" }, 16 + ] 17 + 18 + [requirements] 19 + glance = { version = ">= 6.0.0 and < 7.0.0" } 20 + gleam_json = { version = ">= 3.1.0 and < 4.0.0" } 21 + gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } 22 + gleamgen = { path = "../gleamgen" } 23 + gleeunit = { version = ">= 1.0.0 and < 2.0.0" } 24 + justin = { version = ">= 1.0.1 and < 2.0.0" } 25 + simplifile = { version = ">= 2.3.1 and < 3.0.0" }
+279
src/deriving.gleam
··· 1 + import deriving/location 2 + import glance 3 + import gleam/io 4 + import gleam/json 5 + import gleam/list 6 + import gleam/option 7 + import gleam/result 8 + import gleamgen/expression 9 + import gleamgen/expression/block 10 + import gleamgen/expression/statement 11 + import gleamgen/function 12 + import gleamgen/import_ 13 + import gleamgen/module 14 + import gleamgen/module/definition 15 + import gleamgen/parameter 16 + import gleamgen/render 17 + import gleamgen/source 18 + import gleamgen/type_.{type Dynamic} 19 + import justin 20 + import simplifile 21 + 22 + const location = "deriving/location:Other" 23 + 24 + pub fn main() -> Nil { 25 + let assert Ok(context) = location.new_context() 26 + let assert Ok(location) = location.parse_location(location) 27 + 28 + let assert Ok(#(definition, _context)) = 29 + location.get_type_at_location(context, location) 30 + let new_contents = update_module(definition, location) 31 + io.println(new_contents) 32 + } 33 + 34 + fn update_module(definition, location) { 35 + let assert Ok(contents) = simplifile.read("src/deriving/location.gleam") 36 + let assert Ok(source_map) = source.module_from_string(contents) 37 + let module = { 38 + use json_module <- module.with_import(import_.new(["gleam", "json"])) 39 + use decode_module <- module.with_import( 40 + import_.new(["gleam", "dynamic", "decode"]), 41 + ) 42 + let assert Ok(to_json_function) = 43 + create_to_json_function(definition, json_module) 44 + 45 + let type_name = definition.definition.name 46 + let to_json_definition = 47 + definition.new(justin.snake_case(type_name) <> "_to_json") 48 + |> definition.with_position(definition.AfterDefinition(type_name)) 49 + 50 + let assert Ok(decoder_function) = 51 + create_decoder_function(definition, decode_module) 52 + 53 + let decoder_definition = 54 + definition.new(justin.snake_case(type_name) <> "_decoder") 55 + |> definition.with_position(definition.AfterDefinition(type_name)) 56 + 57 + use _ <- module.with_function(to_json_definition, to_json_function) 58 + use _ <- module.with_function(decoder_definition, decoder_function) 59 + module.from_source_map(source_map) 60 + } 61 + module |> module.render(render.default_context()) |> render.to_string 62 + } 63 + 64 + fn create_to_json_function( 65 + definition: glance.Definition(glance.CustomType), 66 + json_module: import_.ImportReference, 67 + ) { 68 + let json_type: type_.GeneratedType(json.Json) = type_.raw("Json") 69 + // custom.to_type(import_.raw_type(json_module, "Json")) 70 + 71 + let handler_result = case definition.definition.variants { 72 + [] -> Error(Nil) 73 + [variant] -> { 74 + use object_encoders <- result.try( 75 + list.try_map(variant.fields, fn(field) { 76 + generate_json_encoder_for_field(field, json_module) 77 + }), 78 + ) 79 + 80 + let create_call = 81 + expression.call1( 82 + import_.value_of_type( 83 + json_module, 84 + "object", 85 + type_.reference(json.object), 86 + ), 87 + expression.list(object_encoders), 88 + ) 89 + Ok(fn(_data) { create_call }) 90 + } 91 + _variants -> Error(Nil) 92 + } 93 + 94 + use handler <- result.try(handler_result) 95 + 96 + Ok(function.new1( 97 + param1: parameter.new("data", type_.raw(definition.definition.name)), 98 + returns: json_type, 99 + handler: handler, 100 + )) 101 + } 102 + 103 + fn generate_json_encoder_for_field( 104 + field: glance.VariantField, 105 + json_module: import_.ImportReference, 106 + ) -> Result(expression.Expression(#(String, json.Json)), Nil) { 107 + use encoder <- result.try(type_to_encoder(field.item, json_module)) 108 + case field { 109 + glance.LabelledVariantField(label:, ..) -> 110 + Ok(expression.tuple2( 111 + expression.string(label), 112 + expression.call1(encoder, expression.raw("data." <> label)), 113 + )) 114 + _ -> Error(Nil) 115 + } 116 + } 117 + 118 + fn type_to_encoder( 119 + item: glance.Type, 120 + json_module: import_.ImportReference, 121 + ) -> Result(expression.Expression(fn(Dynamic) -> json.Json), Nil) { 122 + let wrap_in_function = fn(handler) { 123 + Ok( 124 + function.anonymous(function.new1( 125 + parameter.new("object", type_.dynamic()), 126 + type_.reference(json.bool(False)), 127 + handler, 128 + )), 129 + ) 130 + } 131 + case item { 132 + glance.NamedType(name: "String", module: option.None, ..) -> 133 + Ok(import_.raw_ident(json_module, "string")) 134 + glance.NamedType(name: "Int", module: option.None, ..) -> 135 + Ok(import_.raw_ident(json_module, "int")) 136 + glance.NamedType(name: "Bool", module: option.None, ..) -> 137 + Ok(import_.raw_ident(json_module, "bool")) 138 + glance.NamedType(name: "Float", module: option.None, ..) -> 139 + Ok(import_.raw_ident(json_module, "float")) 140 + glance.NamedType( 141 + name: "List", 142 + parameters: [list_type], 143 + module: option.None, 144 + .., 145 + ) -> { 146 + use encoder <- result.try(type_to_encoder(list_type, json_module)) 147 + let handler = fn(object) { 148 + expression.call2( 149 + import_.value_of_type( 150 + json_module, 151 + "array", 152 + type_.reference(json.array), 153 + ), 154 + expression.coerce_dynamic_unsafe(object), 155 + encoder, 156 + ) 157 + } 158 + wrap_in_function(handler) 159 + } 160 + glance.TupleType(elements:, ..) -> { 161 + let possible_encoders = 162 + list.try_map(elements, fn(element) { 163 + type_to_encoder(element, json_module) 164 + }) 165 + use encoders <- result.try(possible_encoders) 166 + let handler = fn(object) { 167 + let elements = 168 + encoders 169 + |> list.map(fn(encoder) { 170 + expression.call1(encoder, expression.coerce_dynamic_unsafe(object)) 171 + }) 172 + |> expression.list 173 + expression.call1( 174 + import_.value_of_type( 175 + json_module, 176 + "preprocessed_array", 177 + type_.reference(json.preprocessed_array), 178 + ), 179 + elements, 180 + ) 181 + } 182 + wrap_in_function(handler) 183 + } 184 + _ -> Error(Nil) 185 + } 186 + } 187 + 188 + fn create_decoder_function( 189 + definition: glance.Definition(glance.CustomType), 190 + decode_module: import_.ImportReference, 191 + ) { 192 + let handler_result = case definition.definition.variants { 193 + [variant] -> { 194 + let information_from_fields = 195 + list.try_map(variant.fields, fn(field) { 196 + use_decoder_for_field(field, decode_module) 197 + }) 198 + |> result.map(list.unzip) 199 + use #(handlers, fields) <- result.try(information_from_fields) 200 + Ok(fn() { 201 + use <- block.with_statements(handlers) 202 + expression.call_dynamic(expression.raw(variant.name), fields) 203 + }) 204 + } 205 + _ -> Error(Nil) 206 + } 207 + use handler <- result.try(handler_result) 208 + 209 + Ok(function.new0(returns: type_.dynamic(), handler:)) 210 + } 211 + 212 + fn use_decoder_for_field( 213 + field: glance.VariantField, 214 + decode_module: import_.ImportReference, 215 + ) -> Result(#(statement.Statement, expression.Expression(Dynamic)), Nil) { 216 + case field { 217 + glance.LabelledVariantField(label:, item:) -> { 218 + use decoder <- result.try(decoder_from_type(item, decode_module)) 219 + let use_statement = 220 + statement.dynamic_use( 221 + import_.raw_ident(decode_module, "field"), 222 + [expression.to_dynamic(expression.string(label)), decoder], 223 + [ 224 + label, 225 + ], 226 + ) 227 + Ok(#(use_statement, expression.raw(label))) 228 + } 229 + _ -> Error(Nil) 230 + } 231 + } 232 + 233 + fn decoder_from_type( 234 + item: glance.Type, 235 + decode_module: import_.ImportReference, 236 + ) -> Result(expression.Expression(Dynamic), Nil) { 237 + case item { 238 + glance.NamedType(name: "String", module: option.None, ..) -> 239 + Ok(import_.raw_ident(decode_module, "string")) 240 + glance.NamedType(name: "Int", module: option.None, ..) -> 241 + Ok(import_.raw_ident(decode_module, "int")) 242 + glance.NamedType(name: "Bool", module: option.None, ..) -> 243 + Ok(import_.raw_ident(decode_module, "bool")) 244 + glance.NamedType(name: "Float", module: option.None, ..) -> 245 + Ok(import_.raw_ident(decode_module, "float")) 246 + // glance.NamedType( 247 + // name: "List", 248 + // parameters: [list_type], 249 + // module: option.None, 250 + // .., 251 + // ) -> { 252 + // use decoder <- result.try(decoder_from_type(list_type, decode_module)) 253 + // Ok(expression.call1( 254 + // import_.value_of_type( 255 + // decode_module, 256 + // "list", 257 + // types.reference(decode_module), 258 + // ), 259 + // decoder, 260 + // )) 261 + // } 262 + // glance.TupleType(elements:, ..) -> { 263 + // let possible_decoders = 264 + // list.try_map(elements, fn(element) { 265 + // decoder_from_type(element, decode_module) 266 + // }) 267 + // use decoders <- result.try(possible_decoders) 268 + // Ok(expression.call1( 269 + // import_.value_of_type( 270 + // decode_module, 271 + // "tuple", 272 + // types.reference(decode_module), 273 + // ), 274 + // expression.list(decoders), 275 + // )) 276 + // } 277 + _ -> Error(Nil) 278 + } 279 + }
+3
src/deriving/error.gleam
··· 1 + pub type Error { 2 + ParsingError 3 + }
+99
src/deriving/location.gleam
··· 1 + import glance 2 + import gleam/dict 3 + import gleam/list 4 + import gleam/result 5 + import gleam/string 6 + import simplifile 7 + 8 + pub type Location { 9 + Location(module: List(String), definition: String) 10 + } 11 + 12 + pub type Other { 13 + Other(module: Int, definition: String) 14 + } 15 + 16 + pub type Context { 17 + Context(root_dir: String, read_files: dict.Dict(List(String), String)) 18 + } 19 + 20 + pub type Error { 21 + FileError(simplifile.FileError) 22 + ParseError(glance.Error) 23 + NotFound 24 + } 25 + 26 + fn get_root_dir() -> Result(String, simplifile.FileError) { 27 + use files <- result.try(simplifile.read_directory(".")) 28 + use current_directory <- result.try(simplifile.current_directory()) 29 + case list.contains(files, "gleam.toml") { 30 + True -> Ok(current_directory) 31 + False -> current_directory |> string.split("/") |> get_root_from_src_dir 32 + } 33 + } 34 + 35 + fn get_root_from_src_dir( 36 + strings: List(String), 37 + ) -> Result(String, simplifile.FileError) { 38 + case strings { 39 + ["src", root, ..rest] -> Ok(string.join([root, ..rest], "/")) 40 + [_, ..rest] -> get_root_from_src_dir(rest) 41 + [] -> Error(simplifile.Enolink) 42 + } 43 + } 44 + 45 + pub fn new_context() -> Result(Context, simplifile.FileError) { 46 + case get_root_dir() { 47 + Ok(root_dir) -> Ok(Context(root_dir, dict.new())) 48 + Error(e) -> Error(e) 49 + } 50 + } 51 + 52 + pub fn parse_location(input: String) -> Result(Location, Nil) { 53 + case string.split_once(input, on: ":") { 54 + Ok(#(module_str, definition)) -> { 55 + let module_parts = string.split(module_str, on: "/") 56 + Ok(Location(module_parts, definition)) 57 + } 58 + Error(Nil) -> Error(Nil) 59 + } 60 + } 61 + 62 + pub fn get_type_at_location( 63 + context: Context, 64 + location: Location, 65 + ) -> Result(#(glance.Definition(glance.CustomType), Context), Error) { 66 + use #(contents, context) <- result.try(read_file(context, location.module)) 67 + use module <- result.try(result.map_error(glance.module(contents), ParseError)) 68 + let found_type = 69 + module.custom_types 70 + |> list.find(fn(t) { t.definition.name == location.definition }) 71 + |> result.map_error(fn(_) { NotFound }) 72 + 73 + use custom_type <- result.try(found_type) 74 + Ok(#(custom_type, context)) 75 + } 76 + 77 + pub fn read_file( 78 + context: Context, 79 + module: List(String), 80 + ) -> Result(#(String, Context), Error) { 81 + case dict.get(context.read_files, module) { 82 + Ok(contents) -> Ok(#(contents, context)) 83 + Error(Nil) -> { 84 + let path = 85 + context.root_dir <> "/src/" <> string.join(module, "/") <> ".gleam" 86 + case simplifile.read(path) { 87 + Ok(contents) -> 88 + Ok(#( 89 + contents, 90 + Context( 91 + ..context, 92 + read_files: dict.insert(context.read_files, module, contents), 93 + ), 94 + )) 95 + Error(e) -> Error(FileError(e)) 96 + } 97 + } 98 + } 99 + }
+13
test/deriving_test.gleam
··· 1 + import gleeunit 2 + 3 + pub fn main() -> Nil { 4 + gleeunit.main() 5 + } 6 + 7 + // gleeunit test functions end in `_test` 8 + pub fn hello_world_test() { 9 + let name = "Joe" 10 + let greeting = "Hello, " <> name <> "!" 11 + 12 + assert greeting == "Hello, Joe!" 13 + }