diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d0de3f6..7efbe07 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,7 +9,7 @@ jobs: uses: TanklesXL/gleam_actions/.github/workflows/release.yaml@main secrets: inherit with: - gleam_version: 1.6.1 + gleam_version: 1.11.1 erlang_version: 26 test_erlang: true test_node: false diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 87f9966..39660ac 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,6 +11,6 @@ jobs: test: uses: TanklesXL/gleam_actions/.github/workflows/test.yaml@main with: - gleam_version: 1.6.1 + gleam_version: 1.11.1 test_node: false test_erlang: true diff --git a/README.md b/README.md index 413f77f..38dca81 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,13 @@ _Note:_ Part 1: error: todo - unimplemented in module aoc_2024/day_1 in function pt_1 at line 2 ``` +## Fetching problem inputs + +When stubbing out a new day's solution with the `new` command, you can use the `--fetch` flag to tell gladvent to fetch your problem input from the advent of code website. + +Some things to note: +- The `AOC_COOKIE` environment variable must be set with your advent of code session cookie. +- Gladvent will only attempt to fetch your input if the input file for the day being requested does not exist. This is to prevent accidental and redundant calls to the advent of code website, there should be no reason to fetch input data for the same day more than once. ## Reusable parse funtions @@ -110,17 +117,18 @@ Gladvent makes it easy for you to define expected outputs in your `gleam.toml` f Defining expectations is as simple as adding sections to your `gleam.toml` in the following format: ```toml -[gladvent..] -pt_1 = -pt_2 = +[gladvent.] +1 = { pt_1 = , pt_2 = } +2 = { pt_1 = , pt_2 = } +3 = { pt_1 = , pt_2 = } +... ``` For example, to set the expectations for Dec 1st 2024 (2024 day 1) you would add something like: ```toml -[gladvent.2024.1] -pt_1 = 1 -pt_2 = 2 +[gladvent.2024] +1 = { pt_1 = 1, pt_2 = 2 } ``` When running, gladvent will detect whether a specific day has it's expectations set and if so will print out the result for you. @@ -141,20 +149,40 @@ Example input files will be generated at and run from `input//.exampl _Note_: gladvent will not compare your solution output against the expectations defined in `gleam.toml` when running in example mode. -## FAQ -### Why did you make this? +## Display execution time + +Use the `--timed` flag when running your solutions to display how long each part took to solve. + +For example: + +``` +Ran 2024 day 1: + Part 1: ✅ met expected value: 1579939 (in 885 µs) + Part 2: ✅ met expected value: 20351745 (in 605 µs) +``` -It seemed fun, I like small command line utilities and I wanted a way to get advent of code done in gleam without having the additional overhead of lots of copy-pasting and connecting things to get it to run +Note: as the output of the `parse` function is reused for both parts, its execution time is not included in the displayed time. -### Why does this not download the input from the advent of code website? +## FAQ -A few reasons: +### Why did you make this? -1. I wanted to keep this utility as simple as possible to start with -2. I like the advent of code website and I felt like it was a shame to circumvent visiting it, especially since you should access it to read the daily challenge. On top of that, I would like to avoid spamming the `advent of code` api if possible. +It seemed fun, I like small command line utilities and I wanted a way to get advent of code done in gleam without having the overhead of lots of copy-pasting and connecting things to get it to run. ### Why run as a command line utility and not just use unit tests? I thought a lot about that and I just prefer the overall interactivity of a CLI better, as well as allowing for endless runs or runs with configurable timeouts. Having it run as part of `eunit` doesnt provide as much flexibility as I would like. Other testing frameworks have been popping up but I leave the decision to use them up to you! + +### Why did you change your mind on fetching inputs? + +I started to reflect a bit after gladvent's users kept asking for the feature... + +While my initial rationale was twofold: +1. To encourage people to use the advent of code website, and I felt like fetching inputs somehow took away from that. +2. To minimise the risk that people would use a tool I made to spam the advent of code website with requests. + +Fetching inputs in a smart way (only ever if your input file does not already exist, so you should only need to do it once per day) still requires users to visit the advent of code website for the following (things gladvent will never do): +- fetching the description of the daily problems +- submitting solutions diff --git a/gleam.toml b/gleam.toml index 076047d..40d6be7 100644 --- a/gleam.toml +++ b/gleam.toml @@ -1,27 +1,31 @@ name = "gladvent" -version = "2.0.2" +version = "2.2.0" repository = { type = "github", user = "TanklesXL", repo = "gladvent" } description = "An Advent Of Code runner for gleam" licences = ["Apache-2.0"] internal_modules = ["gladvent/internal/*"] -gleam = ">=1.4.0 and < 2.0.0" +gleam = ">=1.11.0 and < 2.0.0" [dependencies] -glint = ">=1.0.0 and < 2.0.0" -simplifile = "~> 2.0" -argv = "~> 1.0" -shellout = "~> 1.6" -gleam_package_interface = "~> 1.0" -spinner = "~> 1.1" -gleam_stdlib = "~> 0.39 or ~> 1.0" -gleam_otp = "~> 0.4 or ~> 1.0" -gleam_erlang = "~> 0.7 or ~> 1.0" -snag = "~> 1.0" -parallel_map = ">= 2.0.0 and < 3.0.0" -filepath = ">= 1.0.0 and < 2.0.0" -tom = ">= 1.0.0 and < 2.0.0" -decode = ">= 0.2.0 and < 1.0.0" -gleam_json = ">= 1.0.0 and < 3.0.0" +glint = { git = "https://github.com/tanklesxl/glint.git", ref = "next" } +envoy = ">= 1.0.2 and < 2.0.0" +simplifile = ">= 2.2.1 and < 3.0.0" +gleam_json = ">= 3.0.1 and < 4.0.0" +parallel_map = ">= 3.0.1 and < 4.0.0" +gleam_stdlib = ">= 0.60.0 and < 1.0.0" +gleam_otp = ">= 0.16.1 and < 1.0.0" +gleam_erlang = ">= 0.34.0 and < 1.0.0" +gleam_http = ">= 4.0.0 and < 5.0.0" +gleam_httpc = ">= 4.1.1 and < 5.0.0" +filepath = ">= 1.1.2 and < 2.0.0" +gleam_package_interface = ">= 3.0.1 and < 4.0.0" +shellout = ">= 1.7.0 and < 2.0.0" +spinner = ">= 1.3.1 and < 2.0.0" +tom = ">= 2.0.0 and < 3.0.0" +argv = ">= 1.0.2 and < 2.0.0" +snag = ">= 1.1.0 and < 2.0.0" +asset = ">= 1.2.0 and < 2.0.0" + [dev-dependencies] gleeunit = ">= 1.2.0 and < 2.0.0" diff --git a/manifest.toml b/manifest.toml index d124eed..2f4e15b 100644 --- a/manifest.toml +++ b/manifest.toml @@ -3,41 +3,55 @@ packages = [ { name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" }, - { name = "decode", version = "0.5.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "decode", source = "hex", outer_checksum = "05E14DC95A550BA51B8774485B04894B87A898C588B9B1C920104B110AED218B" }, - { name = "filepath", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "67A6D15FB39EEB69DD31F8C145BB5A421790581BD6AA14B33D64D5A55DBD6587" }, - { name = "gleam_community_ansi", version = "1.4.1", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "4CD513FC62523053E62ED7BAC2F36136EC17D6A8942728250A9A00A15E340E4B" }, - { name = "gleam_community_colour", version = "1.4.1", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "386CB9B01B33371538672EEA8A6375A0A0ADEF41F17C86DDCB81C92AD00DA610" }, - { name = "gleam_erlang", version = "0.30.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "760618870AE4A497B10C73548E6E44F43B76292A54F0207B3771CBB599C675B4" }, - { name = "gleam_json", version = "2.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "0A57FB5666E695FD2BEE74C0428A98B0FC11A395D2C7B4CDF5E22C5DD32C74C6" }, - { name = "gleam_otp", version = "0.14.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "5A8CE8DBD01C29403390A7BD5C0A63D26F865C83173CF9708E6E827E53159C65" }, - { name = "gleam_package_interface", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_package_interface", source = "hex", outer_checksum = "CF3BFC5D0997750D9550D8D73A90F4B8D71C6C081B20ED4E70FFBE1E99AFC3C2" }, - { name = "gleam_stdlib", version = "0.43.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "69EF22E78FDCA9097CBE7DF91C05B2A8B5436826D9F66680D879182C0860A747" }, - { name = "glearray", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glearray", source = "hex", outer_checksum = "B99767A9BC63EF9CC8809F66C7276042E5EFEACAA5B25188B552D3691B91AC6D" }, - { name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" }, - { name = "glint", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_community_colour", "gleam_stdlib", "snag"], otp_app = "glint", source = "hex", outer_checksum = "5F6720081150AED8023131B0F3A35F9B0D6426A96CE02BEC52AD7018DF70566A" }, - { name = "parallel_map", version = "2.1.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], otp_app = "parallel_map", source = "hex", outer_checksum = "DE2BA9878728EF9EE34BE83FEDC7A18A1ABE4B2AC1E79C710E3E5D95F5E73404" }, + { name = "asset", version = "1.2.0", build_tools = ["gleam"], requirements = ["argv", "filepath", "glance", "gleam_stdlib", "glexer", "simplifile"], otp_app = "asset", source = "hex", outer_checksum = "71B572581CE75CEA23F444EB56CD845DB64766FB5D52573DAE5F465D752526A5" }, + { name = "bigi", version = "3.2.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "bigi", source = "hex", outer_checksum = "5BE723D3F5C9BCD7C20C432FEDD9EF55AC8B357BD0592433E275483F3F14CB98" }, + { name = "envoy", version = "1.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "envoy", source = "hex", outer_checksum = "95FD059345AA982E89A0B6E2A3BF1CF43E17A7048DCD85B5B65D3B9E4E39D359" }, + { name = "filepath", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "B06A9AF0BF10E51401D64B98E4B627F1D2E48C154967DA7AF4D0914780A6D40A" }, + { name = "glance", version = "5.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "glexer"], otp_app = "glance", source = "hex", outer_checksum = "FAA3DAC74AF71D47C67D88EB32CE629075169F878D148BB1FF225439BE30070A" }, + { name = "gleam_community_ansi", version = "1.4.3", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_regexp", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "8A62AE9CC6EA65BEA630D95016D6C07E4F9973565FA3D0DE68DC4200D8E0DD27" }, + { name = "gleam_community_colour", version = "2.0.1", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "F0ACE69E3A47E913B03D3D0BB23A5563A91A4A7D20956916286068F4A9F817FE" }, + { name = "gleam_deque", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_deque", source = "hex", outer_checksum = "64D77068931338CF0D0CB5D37522C3E3CCA7CB7D6C5BACB41648B519CC0133C7" }, + { name = "gleam_erlang", version = "0.34.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "0C38F2A128BAA0CEF17C3000BD2097EB80634E239CE31A86400C4416A5D0FDCC" }, + { name = "gleam_http", version = "4.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "0A62451FC85B98062E0907659D92E6A89F5F3C0FBE4AB8046C99936BF6F91DBC" }, + { name = "gleam_httpc", version = "4.1.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gleam_httpc", source = "hex", outer_checksum = "C670EBD46FC1472AD5F1F74F1D3938D1D0AC1C7531895ED1D4DDCB6F07279F43" }, + { name = "gleam_json", version = "3.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "5BA154440B22D9800955B1AB854282FA37B97F30F409D76B0824D0A60C934188" }, + { name = "gleam_otp", version = "0.16.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "50DA1539FC8E8FA09924EB36A67A2BBB0AD6B27BCDED5A7EF627057CF69D035E" }, + { name = "gleam_package_interface", version = "3.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_package_interface", source = "hex", outer_checksum = "8F2D19DE9876D9401BB0626260958A6B1580BB233489C32831FE74CE0ACAE8B4" }, + { name = "gleam_regexp", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "9C215C6CA84A5B35BB934A9B61A9A306EC743153BE2B0425A0D032E477B062A9" }, + { name = "gleam_stdlib", version = "0.60.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "621D600BB134BC239CB2537630899817B1A42E60A1D46C5E9F3FAE39F88C800B" }, + { name = "gleam_time", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_time", source = "hex", outer_checksum = "D71F1AFF7FEB534FF55E5DC58E534E9201BA75A444619788A2E4DEA4EBD87D16" }, + { name = "gleam_yielder", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_yielder", source = "hex", outer_checksum = "8E4E4ECFA7982859F430C57F549200C7749823C106759F4A19A78AEA6687717A" }, + { name = "glearray", version = "2.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glearray", source = "hex", outer_checksum = "5E272F7CB278CC05A929C58DEB58F5D5AC6DB5B879A681E71138658D0061C38A" }, + { name = "gleeunit", version = "1.5.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "D33B7736CF0766ED3065F64A1EBB351E72B2E8DE39BAFC8ADA0E35E92A6A934F" }, + { name = "glexer", version = "2.2.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glexer", source = "hex", outer_checksum = "5C235CBDF4DA5203AD5EAB1D6D8B456ED8162C5424FE2309CFFB7EF438B7C269" }, + { name = "glint", version = "1.2.1", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_community_colour", "gleam_deque", "gleam_stdlib", "lenient_parse", "snag"], source = "git", repo = "https://github.com/tanklesxl/glint.git", commit = "33829b9aaf2db6a9f53b16c4d108f81af7adb32f" }, + { name = "lenient_parse", version = "3.0.0", build_tools = ["gleam"], requirements = ["bigi", "gleam_deque", "gleam_stdlib"], otp_app = "lenient_parse", source = "hex", outer_checksum = "E6C654A75E6EB17B559418C5216583D0B9E7D82018E0608A7FF1AFF328D9F474" }, + { name = "parallel_map", version = "3.0.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib", "gleam_yielder"], otp_app = "parallel_map", source = "hex", outer_checksum = "E5A38C9294831F67DF8EAC17870FEA97FF0D8F891049E0CA634B30A1AFE37A8F" }, { name = "repeatedly", version = "2.1.2", build_tools = ["gleam"], requirements = [], otp_app = "repeatedly", source = "hex", outer_checksum = "93AE1938DDE0DC0F7034F32C1BF0D4E89ACEBA82198A1FE21F604E849DA5F589" }, - { name = "shellout", version = "1.6.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "shellout", source = "hex", outer_checksum = "E2FCD18957F0E9F67E1F497FC9FF57393392F8A9BAEAEA4779541DE7A68DD7E0" }, - { name = "simplifile", version = "2.2.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "0DFABEF7DC7A9E2FF4BB27B108034E60C81BEBFCB7AB816B9E7E18ED4503ACD8" }, - { name = "snag", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "snag", source = "hex", outer_checksum = "08E9EB87C413457DB1DD66CD704C6878DACC9C93B418600F63873D0CD224E756" }, - { name = "spinner", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_stdlib", "glearray", "repeatedly"], otp_app = "spinner", source = "hex", outer_checksum = "9EE43AA33BE2DA5731B8F3F170AAB59AF1A815AFA5BF615F12C1B91F3B03F157" }, - { name = "tom", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "tom", source = "hex", outer_checksum = "228E667239504B57AD05EC3C332C930391592F6C974D0EFECF32FFD0F3629A27" }, + { name = "shellout", version = "1.7.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "shellout", source = "hex", outer_checksum = "1BDC03438FEB97A6AF3E396F4ABEB32BECF20DF2452EC9A8C0ACEB7BDDF70B14" }, + { name = "simplifile", version = "2.2.1", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "C88E0EE2D509F6D86EB55161D631657675AA7684DAB83822F7E59EB93D9A60E3" }, + { name = "snag", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "snag", source = "hex", outer_checksum = "7E9F06390040EB5FAB392CE642771484136F2EC103A92AE11BA898C8167E6E17" }, + { name = "spinner", version = "1.3.1", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_stdlib", "glearray", "repeatedly"], otp_app = "spinner", source = "hex", outer_checksum = "21BDE7FF9D7D9ACBB4086C0D5C86F0A90CE6B0F3CB593B41D03384AE7724B5B4" }, + { name = "tom", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "gleam_time"], otp_app = "tom", source = "hex", outer_checksum = "74D0C5A3761F7A7D06994755D4D5AD854122EF8E9F9F76A3E7547606D8C77091" }, ] [requirements] -argv = { version = "~> 1.0" } -decode = { version = ">= 0.2.0 and < 1.0.0" } -filepath = { version = ">= 1.0.0 and < 2.0.0" } -gleam_erlang = { version = "~> 0.7 or ~> 1.0" } -gleam_json = { version = ">= 1.0.0 and < 3.0.0" } -gleam_otp = { version = "~> 0.4 or ~> 1.0" } -gleam_package_interface = { version = "~> 1.0" } -gleam_stdlib = { version = "~> 0.39 or ~> 1.0" } +argv = { version = ">= 1.0.2 and < 2.0.0" } +asset = { version = ">= 1.2.0 and < 2.0.0" } +envoy = { version = ">= 1.0.2 and < 2.0.0" } +filepath = { version = ">= 1.1.2 and < 2.0.0" } +gleam_erlang = { version = ">= 0.34.0 and < 1.0.0" } +gleam_http = { version = ">= 4.0.0 and < 5.0.0" } +gleam_httpc = { version = ">= 4.1.1 and < 5.0.0" } +gleam_json = { version = ">= 3.0.1 and < 4.0.0" } +gleam_otp = { version = ">= 0.16.1 and < 1.0.0" } +gleam_package_interface = { version = ">= 3.0.1 and < 4.0.0" } +gleam_stdlib = { version = ">= 0.60.0 and < 1.0.0" } gleeunit = { version = ">= 1.2.0 and < 2.0.0" } -glint = { version = ">=1.0.0 and < 2.0.0" } -parallel_map = { version = ">= 2.0.0 and < 3.0.0" } -shellout = { version = "~> 1.6" } -simplifile = { version = "~> 2.0" } -snag = { version = "~> 1.0" } -spinner = { version = "~> 1.1" } -tom = { version = ">= 1.0.0 and < 2.0.0" } +glint = { git = "https://github.com/tanklesxl/glint.git", ref = "next" } +parallel_map = { version = ">= 3.0.1 and < 4.0.0" } +shellout = { version = ">= 1.7.0 and < 2.0.0" } +simplifile = { version = ">= 2.2.1 and < 3.0.0" } +snag = { version = ">= 1.1.0 and < 2.0.0" } +spinner = { version = ">= 1.3.1 and < 2.0.0" } +tom = { version = ">= 2.0.0 and < 3.0.0" } diff --git a/.mise.toml b/mise.toml similarity index 100% rename from .mise.toml rename to mise.toml diff --git a/src/gladvent.gleam b/src/gladvent.gleam index e3101da..799fe11 100644 --- a/src/gladvent.gleam +++ b/src/gladvent.gleam @@ -2,50 +2,35 @@ import argv import gladvent/internal/cmd import gladvent/internal/cmd/new import gladvent/internal/cmd/run -import gleam/io import gleam/string import glint -import snag +import glintio /// Add this function to your project's `main` function in order to run the gladvent CLI. /// /// This function gets its input from the command line arguments by using the `argv` library. /// pub fn run() { - let commands = - glint.new() - |> glint.path_help( - [], - "gladvent is an advent of code runner and generator for gleam. + glint.new() + |> glint.with_name("gladvent") + |> glint.with_version("2.3.0") + |> glint.path_help( + [], + "gladvent is an advent of code runner and generator for gleam. Please use either the 'run' or 'new' commands. ", - ) - |> glint.pretty_help(glint.default_pretty_help()) - |> glint.group_flag(at: [], of: cmd.year_flag()) - |> glint.add(at: ["new"], do: new.new_command()) - |> glint.group_flag(at: ["run"], of: run.timeout_flag()) - |> glint.group_flag(at: ["run"], of: run.allow_crash_flag()) - |> glint.add(at: ["run"], do: run.run_command()) - |> glint.add(at: ["run", "all"], do: run.run_all_command()) - - use out <- glint.run_and_handle(commands, argv.load().arguments) - case out { - Ok(out) -> - out - |> string.join("\n\n") - |> io.println - Error(err) -> print_snag_and_halt(err) - } -} - -@external(erlang, "erlang", "halt") -fn exit(a: Int) -> Nil - -fn print_snag_and_halt(err: snag.Snag) -> Nil { - err - |> snag.pretty_print() - |> io.println() - exit(1) + ) + |> glint.with_default_header_style + |> glint.group_flag(at: [], of: cmd.year_flag()) + |> glint.add(at: ["new"], do: new.new_command()) + |> glint.group_flag(at: ["run"], of: run.timeout_flag()) + |> glint.group_flag(at: ["run"], of: run.allow_crash_flag()) + |> glint.group_flag(at: ["run"], of: run.timed_flag()) + |> glint.add(at: ["run"], do: run.run_command()) + |> glint.add(at: ["run", "all"], do: run.run_all_command()) + |> glint.run(argv.load().arguments) + |> glintio.print(string.join(_, "\n\n")) + |> glintio.exit } diff --git a/src/gladvent/internal/cmd.gleam b/src/gladvent/internal/cmd.gleam index f985e9a..3ef38ed 100644 --- a/src/gladvent/internal/cmd.gleam +++ b/src/gladvent/internal/cmd.gleam @@ -82,9 +82,9 @@ fn current_year() -> Int { } pub fn year_flag() { - use year <- glint.flag_constraint( - glint.int_flag("year") - |> glint.flag_default(current_year()), + use year <- glint.constraint( + glint.int("year") + |> glint.default(current_year()), ) case year < 2015 { True -> diff --git a/src/gladvent/internal/cmd/new.gleam b/src/gladvent/internal/cmd/new.gleam index 6ccc362..f415aed 100644 --- a/src/gladvent/internal/cmd/new.gleam +++ b/src/gladvent/internal/cmd/new.gleam @@ -1,8 +1,14 @@ +import envoy import filepath import gladvent/internal/cmd import gladvent/internal/input import gladvent/internal/parse.{type Day, pad} import gladvent/internal/util +import gleam/bool +import gleam/http +import gleam/http/request +import gleam/http/response +import gleam/httpc import gleam/int import gleam/list import gleam/pair @@ -11,17 +17,25 @@ import gleam/string import glint import simplifile +const aoc_cookie_name = "AOC_COOKIE" + type Context { - Context(year: Int, day: Day, add_parse: Bool, create_example_file: Bool) + Context( + year: Int, + day: Day, + add_parse: Bool, + create_example_file: Bool, + fetch_input: Bool, + ) } -fn create_src_file(ctx: Context) -> fn() -> Result(String, Err) { +fn create_src_file(ctx: Context) -> fn() -> Result(Success, Err) { fn() { let gleam_src_path = gleam_src_path(ctx.year, ctx.day) use _ <- result.try( simplifile.create_file(gleam_src_path) - |> result.map_error(handle_file_open_failure(_, gleam_src_path)), + |> result.map_error(FailedToCreateFile(gleam_src_path, _)), ) let file_data = case ctx.add_parse { @@ -30,34 +44,63 @@ fn create_src_file(ctx: Context) -> fn() -> Result(String, Err) { } simplifile.write(gleam_src_path, file_data) - |> result.map_error(handle_file_open_failure(_, gleam_src_path)) - |> result.replace(gleam_src_path) + |> result.map_error(FailedToWriteToFile(gleam_src_path, _)) + |> result.replace(File(gleam_src_path)) } } fn create_input_file( ctx: Context, kind: input.Kind, -) -> fn() -> Result(String, Err) { +) -> fn() -> Result(Success, Err) { fn() { let input_path = input.get_file_path(ctx.year, ctx.day, kind) - simplifile.create_file(input_path) - |> result.map_error(handle_file_open_failure(_, input_path)) - |> result.replace(input_path) + use Nil <- result.try( + simplifile.create_file(input_path) + |> result.map_error(FailedToCreateFile(input_path, _)), + ) + use <- bool.guard( + when: kind == input.Example || !ctx.fetch_input, + return: Ok(File(input_path)), + ) + use content <- result.try(download_input(ctx)) + simplifile.write(input_path, content) + |> result.map_error(FailedToWriteToFile(input_path, _)) + |> result.replace(File(input_path)) } } +type Success { + Dir(String) + File(String) +} + type Err { - FailedToCreateDir(String) - FailedToCreateFile(String) - FileAlreadyExists(String) + CookieNotDefined + FailedToCreateDir(String, simplifile.FileError) + FailedToCreateFile(String, simplifile.FileError) + FailedToWriteToFile(String, simplifile.FileError) + HttpError(httpc.HttpError) + UnexpectedHttpResponse(response.Response(String)) } fn err_to_string(e: Err) -> String { case e { - FailedToCreateDir(d) -> "failed to create dir: " <> d - FailedToCreateFile(f) -> "failed to create file: " <> f - FileAlreadyExists(f) -> "file already exists: " <> f + CookieNotDefined -> + "'" <> aoc_cookie_name <> "' environment variable not defined" + FailedToCreateDir(d, e) -> + "failed to create dir '" <> d <> "': " <> simplifile.describe_error(e) + FailedToCreateFile(f, e) -> + "failed to create file '" <> f <> "': " <> simplifile.describe_error(e) + FailedToWriteToFile(f, e) -> + "failed to write to file '" <> f <> "': " <> simplifile.describe_error(e) + HttpError(e) -> + "HTTP error while fetching input file: " <> string.inspect(e) + UnexpectedHttpResponse(r) -> + "unexpected HTTP response (" + <> int.to_string(r.status) + <> ") while fetching input file: " + <> r.body } } @@ -65,10 +108,11 @@ fn gleam_src_path(year: Int, day: Day) -> String { filepath.join(cmd.src_dir(year), "day_" <> pad(day) <> ".gleam") } -fn create_dir(dir: String) -> fn() -> Result(String, Err) { +fn create_dir(dir: String) -> fn() -> Result(Success, Err) { fn() { simplifile.create_directory_all(dir) |> handle_dir_open_res(dir) + |> result.map(Dir) } } @@ -79,20 +123,37 @@ fn handle_dir_open_res( case res { Ok(_) -> Ok(filename) Error(simplifile.Eexist) -> Ok("") - _ -> - filename - |> FailedToCreateDir - |> Error + Error(e) -> Error(FailedToCreateDir(filename, e)) } } -fn handle_file_open_failure( - reason: simplifile.FileError, - filename: String, -) -> Err { - case reason { - simplifile.Eexist -> FileAlreadyExists(filename) - _ -> FailedToCreateFile(filename) +fn get_cookie_value() -> Result(String, Err) { + aoc_cookie_name + |> envoy.get() + |> result.replace_error(CookieNotDefined) +} + +fn download_input(ctx: Context) -> Result(String, Err) { + use cookie <- result.try(get_cookie_value()) + use resp <- result.try( + request.new() + |> request.set_host("adventofcode.com") + |> request.set_path( + "/" + <> int.to_string(ctx.year) + <> "/day/" + <> int.to_string(ctx.day) + <> "/input", + ) + |> request.set_scheme(http.Https) + |> request.set_cookie("session", cookie) + |> request.set_header("user-agent", "github.com/TanklesXL/gladvent") + |> httpc.send() + |> result.map_error(HttpError), + ) + case resp.status { + 200 -> Ok(resp.body) + _ -> Error(UnexpectedHttpResponse(resp)) } } @@ -128,8 +189,8 @@ fn do(ctx: Context) -> String { { use acc, f <- list.fold(seq, #("", "")) case f() { - Ok("") -> acc - Ok(o) -> pair.map_first(acc, newline_tab(_, o)) + Ok(Dir(_)) -> acc + Ok(File(o)) -> pair.map_first(acc, newline_tab(_, o)) Error(err) -> pair.map_second(acc, newline_tab(_, err_to_string(err))) } } @@ -173,32 +234,44 @@ fn collect(year: Int, x: #(Day, String)) -> String { "initialized " <> year <> " day " <> day <> "\n" <> x.1 } -pub fn new_command() { +pub fn new_command() -> glint.Command(List(String), glint.ArgsSet) { use <- glint.command_help("Create .gleam and input files") - use <- glint.unnamed_args(glint.MinArgs(1)) + use <- glint.min_args("days", 1, "The days to create files for.") use parse_flag <- glint.flag( - glint.bool_flag("parse") - |> glint.flag_default(False) - |> glint.flag_help("Generate day runners with a parse function"), + glint.bool("parse") + |> glint.default(False) + |> glint.param_help("Generate day runners with a parse function"), ) use example_flag <- glint.flag( - glint.bool_flag("example") - |> glint.flag_default(False) - |> glint.flag_help( + glint.bool("example") + |> glint.default(False) + |> glint.param_help( "Generate example input files to run your solution against", ), ) + use fetch_flag <- glint.flag( + glint.bool("fetch") + |> glint.default(False) + |> glint.param_help("Fetch your own input from the AoC website. + + Needs to have your AoC cookie stored in the '" <> aoc_cookie_name <> "' environment variable"), + ) use _, args, flags <- glint.command() - use days <- result.map(parse.days(args)) + use days <- glint.try(parse.days(args)) let days = util.deduplicate_sort(days) - let assert Ok(year) = glint.get_flag(flags, cmd.year_flag()) - let assert Ok(add_parse) = parse_flag(flags) - let assert Ok(create_example_file) = example_flag(flags) - - cmd.exec( - days, - cmd.Endless, - fn(day) { do(Context(year:, day:, add_parse:, create_example_file:)) }, - collect_async(year, _), + use year <- glint.with_flag(flags, cmd.year_flag()) + use add_parse <- parse_flag(flags) + use create_example_file <- example_flag(flags) + use fetch_input <- fetch_flag(flags) + + glint.Success( + cmd.exec( + days, + cmd.Endless, + fn(day) { + do(Context(year:, day:, add_parse:, create_example_file:, fetch_input:)) + }, + collect_async(year, _), + ), ) } diff --git a/src/gladvent/internal/cmd/run.gleam b/src/gladvent/internal/cmd/run.gleam index 8a5d806..3d8784a 100644 --- a/src/gladvent/internal/cmd/run.gleam +++ b/src/gladvent/internal/cmd/run.gleam @@ -1,4 +1,3 @@ -import decode import filepath import gladvent/internal/cmd.{Ending, Endless} import gladvent/internal/input @@ -8,8 +7,8 @@ import gladvent/internal/util import gleam import gleam/dict import gleam/dynamic.{type Dynamic} +import gleam/dynamic/decode import gleam/erlang -import gleam/erlang/atom import gleam/erlang/charlist.{type Charlist} import gleam/int import gleam/list @@ -19,7 +18,7 @@ import gleam/result import gleam/string import glint import simplifile -import snag.{type Result, type Snag} +import snag.{type Snag} import spinner import tom @@ -43,7 +42,11 @@ type SolveErr { } type SolveResult = - gleam.Result(Dynamic, SolveErr) + gleam.Result(Solution, SolveErr) + +type Solution { + Solution(value: Dynamic, execution_time: Int) +} fn run_err_to_snag(err: RunErr) -> Snag { case err { @@ -99,12 +102,12 @@ fn do( |> result.replace_error(FailedToReadInput(input_path)), ) - let parse = option.unwrap(parse, dynamic.from) + let parse = option.unwrap(parse, dynamic.string) case allow_crash { True -> { let input = parse(input) - Ok(#(Ok(pt_1(input)), Ok(pt_2(input)))) + Ok(#(Ok(solve(pt_1, input)), Ok(solve(pt_2, input)))) } False -> { use input <- result.try( @@ -114,11 +117,11 @@ fn do( |> result.map_error(FailedToParseInput), ) let pt_1 = - fn() { pt_1(input) } + fn() { solve(pt_1, input) } |> erlang.rescue |> result.map_error(crash_to_solve_err) let pt_2 = - fn() { pt_2(input) } + fn() { solve(pt_2, input) } |> erlang.rescue |> result.map_error(crash_to_solve_err) Ok(#(pt_1, pt_2)) @@ -126,57 +129,28 @@ fn do( } } +fn solve(solver: fn(a) -> Dynamic, input: a) -> Solution { + let #(execution_time, value) = util.timed(fn() { solver(input) }) + Solution(value, execution_time) +} + fn crash_to_dyn(err: erlang.Crash) -> dynamic.Dynamic { case err { erlang.Errored(dyn) | erlang.Exited(dyn) | erlang.Thrown(dyn) -> dyn } } -type GleamErr { - GleamErr( - gleam_error: atom.Atom, - module: String, - function: String, - line: Int, - message: String, - value: Option(Dynamic), - ) -} +import gleeunit/internal/gleam_panic fn decode_gleam_err(dyn: dynamic.Dynamic) { - decode.into({ - use gleam_error <- decode.parameter - use module <- decode.parameter - use function <- decode.parameter - use line <- decode.parameter - use message <- decode.parameter - use value <- decode.parameter - GleamErr(gleam_error, module, function, line, message, value) - }) - |> decode.field(atom.create_from_string("gleam_error"), { - use dyn <- decode.then(decode.dynamic) - case atom.from_dynamic(dyn) { - Ok(a) -> decode.into(a) - Error(e) -> - decode.fail("failed to decode gleam error: " <> string.inspect(e)) - } - }) - |> decode.field(atom.create_from_string("module"), decode.string) - |> decode.field(atom.create_from_string("function"), decode.string) - |> decode.field(atom.create_from_string("line"), decode.int) - |> decode.field(atom.create_from_string("message"), decode.string) - |> decode.field( - atom.create_from_string("value"), - decode.optional(decode.dynamic), - ) - |> decode.from(dyn) + gleam_panic.from_dynamic(dyn) } -fn gleam_err_to_string(g: GleamErr) -> String { +fn gleam_err_to_string(g: gleam_panic.GleamPanic) -> String { string.join( [ "error:", - atom.to_string(g.gleam_error), + string.inspect(g.kind), "-", g.message, "in module", @@ -185,9 +159,6 @@ fn gleam_err_to_string(g: GleamErr) -> String { g.function, "at line", int.to_string(g.line), - g.value - |> option.map(fn(val) { "with value " <> string.inspect(val) }) - |> option.unwrap(""), ], " ", ) @@ -218,17 +189,20 @@ fn solve_err_to_string(solve_err: SolveErr) -> String { fn solve_res_to_string( res: SolveResult, expectation: option.Option(Expectation), + timed: Bool, ) -> String { result.unwrap_both({ - use res <- result.map(res |> result.map_error(solve_err_to_string)) + use solution <- result.map(res |> result.map_error(solve_err_to_string)) option.lazy_unwrap( { use expect <- option.map(expectation) case expect { ExpectInt(expect) -> { - case dynamic.int(res) { + case decode.run(solution.value, decode.int) { Ok(i) if expect == i -> - "✅ met expected value: " <> int.to_string(i) + "✅ met expected value: " + <> int.to_string(i) + <> exec_time_suffix(solution, timed) Ok(i) -> "❌ unmet expectation: expected " <> int.to_string(expect) @@ -242,8 +216,11 @@ fn solve_res_to_string( } } ExpectString(expect) -> { - case dynamic.string(res) { - Ok(s) if expect == s -> "✅ met expected value: " <> s + case decode.run(solution.value, decode.string) { + Ok(s) if expect == s -> + "✅ met expected value: " + <> s + <> exec_time_suffix(solution, timed) Ok(s) -> "❌ unmet expectation: expected " <> expect <> ", got " <> s Error(_) -> @@ -255,17 +232,36 @@ fn solve_res_to_string( } } }, - or: fn() { string.inspect(res) }, + or: fn() { + string.inspect(solution.value) <> exec_time_suffix(solution, timed) + }, ) }) } +fn exec_time_suffix(solution: Solution, timed: Bool) -> String { + case timed { + True -> " (in " <> format_execution_time(solution.execution_time) <> ")" + False -> "" + } +} + +fn format_execution_time(execution_time: Int) -> String { + let #(divisor, precision, unit) = case execution_time { + t if t > 1_000_000 -> #(1_000_000.0, 3, " s") + t if t > 1000 -> #(1000.0, 3, " ms") + _ -> #(1.0, 0, " µs") + } + util.format_float(int.to_float(execution_time) /. divisor, precision) <> unit +} + import gleam/pair fn collect_async( year: Int, x: #(Day, AsyncResult), expectations: Option(dict.Dict(String, tom.Toml)), + timed: Bool, ) -> String { let expect = fn(key) { use ex <- option.then(expectations) @@ -276,7 +272,7 @@ fn collect_async( x |> pair.map_second(result.map_error(_, Other)) |> pair.map_second(result.flatten) - |> collect(year, _, expect("pt_1"), expect("pt_2")) + |> collect(year, _, expect("pt_1"), expect("pt_2"), timed) } fn collect( @@ -284,6 +280,7 @@ fn collect( x: #(Day, RunResult), expect_pt_1: Option(Expectation), expect_pt_2: Option(Expectation), + timed: Bool, ) -> String { let day = int.to_string(x.0) case x.1 { @@ -294,10 +291,10 @@ fn collect( <> day <> ":\n" <> " Part 1: " - <> solve_res_to_string(res_1, expect_pt_1) + <> solve_res_to_string(res_1, expect_pt_1, timed) <> "\n" <> " Part 2: " - <> solve_res_to_string(res_2, expect_pt_2) + <> solve_res_to_string(res_2, expect_pt_2, timed) Error(err) -> err @@ -310,9 +307,9 @@ fn collect( // ----- CLI ----- pub fn timeout_flag() { - use i <- glint.flag_constraint( - glint.int_flag("timeout") - |> glint.flag_help("Run with specified timeout"), + use i <- glint.constraint( + glint.int("timeout") + |> glint.param_help("Run with specified timeout"), ) case i > 0 { @@ -322,30 +319,41 @@ pub fn timeout_flag() { } pub fn allow_crash_flag() { - glint.bool_flag("allow-crash") - |> glint.flag_default(False) - |> glint.flag_help("Don't catch exceptions thrown by runners") + glint.bool("allow-crash") + |> glint.default(False) + |> glint.param_help("Don't catch exceptions thrown by runners.") +} + +pub fn timed_flag() { + glint.bool("timed") + |> glint.default(False) + |> glint.param_help( + "Display solution execution time. + +The time displayed does not include any work done in a 'parse' function.", + ) } -pub fn run_command() -> glint.Command(Result(List(String))) { +pub fn run_command() -> glint.Command(List(String), glint.ArgsSet) { use <- glint.command_help("Run the specified days") - use <- glint.unnamed_args(glint.MinArgs(1)) + use <- glint.min_args("days", 1, "The days to run solutions for.") use example_flag <- glint.flag( - glint.bool_flag("example") - |> glint.flag_default(False) - |> glint.flag_help( + glint.bool("example") + |> glint.default(False) + |> glint.param_help( "Run solutions against example inputs (found at input//.example.txt)", ), ) use _, args, flags <- glint.command() - use days <- result.then(parse.days(args)) + use days <- glint.try(parse.days(args)) let days = util.deduplicate_sort(days) - let assert Ok(year) = glint.get_flag(flags, cmd.year_flag()) - let assert Ok(allow_crash) = glint.get_flag(flags, allow_crash_flag()) - let assert Ok(input_kind) = case example_flag(flags) { - Error(a) -> Error(a) - Ok(True) -> Ok(input.Example) - _ -> Ok(input.Puzzle) + use year <- glint.with_flag(flags, cmd.year_flag()) + use allow_crash <- glint.with_flag(flags, allow_crash_flag()) + use timed <- glint.with_flag(flags, timed_flag()) + use input_kind <- example_flag(flags) + let input_kind = case input_kind { + True -> input.Example + False -> input.Puzzle } let spinner = @@ -359,12 +367,12 @@ pub fn run_command() -> glint.Command(Result(List(String))) { use <- util.defer(do: fn() { spinner.stop(spinner) }) - let timing = - glint.get_flag(flags, timeout_flag()) - |> result.map(Ending) - |> result.unwrap(Endless) + let timing = case glint.get_flag(flags, timeout_flag()) { + Ok(timeout) -> Ending(timeout) + Error(_) -> Endless + } - use gleam_toml <- result.try(read_gleam_toml()) + use gleam_toml <- glint.try(read_gleam_toml()) let expectations = case input_kind { input.Puzzle -> @@ -374,26 +382,29 @@ pub fn run_command() -> glint.Command(Result(List(String))) { input.Example -> option.None } - use package <- result.map( + use package <- glint.try( runners.pkg_interface() |> snag.context("failed to generate package interface"), ) - days - |> cmd.exec( - timing, - do(year, _, package, allow_crash, input_kind), - collect_async(year, _, expectations), + glint.Success( + days + |> cmd.exec( + timing, + do(year, _, package, allow_crash, input_kind), + collect_async(year, _, expectations, timed), + ), ) } -pub fn run_all_command() -> glint.Command(Result(List(String))) { +pub fn run_all_command() -> glint.Command(List(String), glint.ArgsSet) { use <- glint.command_help("Run all registered days") - use <- glint.unnamed_args(glint.EqArgs(0)) + use <- glint.no_args() use _, _, flags <- glint.command() - let assert Ok(year) = glint.get_flag(flags, cmd.year_flag()) - let assert Ok(allow_crash) = glint.get_flag(flags, allow_crash_flag()) + use year <- glint.with_flag(flags, cmd.year_flag()) + use allow_crash <- glint.with_flag(flags, allow_crash_flag()) + use timed <- glint.with_flag(flags, timed_flag()) let spinner = spinner.new("running all days in " <> int.to_string(year)) @@ -401,39 +412,41 @@ pub fn run_all_command() -> glint.Command(Result(List(String))) { use <- util.defer(do: fn() { spinner.stop(spinner) }) - let timing = - glint.get_flag(flags, timeout_flag()) - |> result.map(Ending) - |> result.unwrap(Endless) + let timing = case glint.get_flag(flags, timeout_flag()) { + Ok(timeout) -> Ending(timeout) + Error(_) -> Endless + } - use gleam_toml <- result.try(read_gleam_toml()) + use gleam_toml <- glint.try(read_gleam_toml()) let expectations = option.from_result( tom.get_table(gleam_toml, ["gladvent", int.to_string(year)]), ) - use package <- result.map( + use package <- glint.try( runners.pkg_interface() |> snag.context("failed to generate package interface"), ) - package.modules - |> dict.keys - |> list.filter_map(fn(k) { - use day <- result.try(string.split_once( - k, - "aoc_" <> int.to_string(year) <> "/day_", - )) - day.1 - |> parse.day - |> result.replace_error(Nil) - }) - |> list.sort(int.compare) - |> cmd.exec( - timing, - do(year, _, package, allow_crash, input.Puzzle), - collect_async(year, _, expectations), + glint.Success( + package.modules + |> dict.keys + |> list.filter_map(fn(k) { + use day <- result.try(string.split_once( + k, + "aoc_" <> int.to_string(year) <> "/day_", + )) + day.1 + |> parse.day + |> result.replace_error(Nil) + }) + |> list.sort(int.compare) + |> cmd.exec( + timing, + do(year, _, package, allow_crash, input.Puzzle), + collect_async(year, _, expectations, timed), + ), ) } diff --git a/src/gladvent/internal/runners.gleam b/src/gladvent/internal/runners.gleam index 8354a81..c0a8886 100644 --- a/src/gladvent/internal/runners.gleam +++ b/src/gladvent/internal/runners.gleam @@ -18,7 +18,6 @@ import shellout import simplifile import snag.{type Result} import spinner -import tom pub type PartRunner = fn(Dynamic) -> Dynamic @@ -30,12 +29,8 @@ const package_interface_path = "build/.gladvent/pkg.json" type PkgInterfaceErr { FailedToGleamBuild(String) - FailedToReadGleamToml(simplifile.FileError) - FailedToDecodeGleamToml(tom.ParseError) - FailedToGetPackageName(tom.GetError) FailedToGeneratePackageInterface(String) FailedToReadPackageInterface(simplifile.FileError) - FailedToClearBuildCache(simplifile.FileError) FailedToDecodePackageInterface(json.DecodeError) } @@ -44,18 +39,6 @@ fn package_interface_error_to_snag(e: PkgInterfaceErr) -> snag.Snag { FailedToGleamBuild(s) -> snag.new(s) |> snag.layer("failed to build gleam project") - FailedToDecodeGleamToml(e) -> - snag.new(string.inspect(e)) - |> snag.layer("failed to decode gleam.toml") - FailedToGetPackageName(e) -> - snag.new(string.inspect(e)) - |> snag.layer("failed to get package name") - FailedToReadGleamToml(e) -> - snag.new(string.inspect(e)) - |> snag.layer("failed to read gleam.toml") - FailedToClearBuildCache(e) -> - snag.new(string.inspect(e)) - |> snag.layer("failed to clear build cache") FailedToGeneratePackageInterface(s) -> snag.new(s) |> snag.layer("failed to generate " <> package_interface_path) @@ -78,40 +61,6 @@ pub fn pkg_interface() -> Result(package_interface.Package) { let root = cmd.root() - spinner.set_text(spinner, "reading gleam.toml") - use gleam_toml <- result.try( - simplifile.read(filepath.join(root, "gleam.toml")) - |> result.map_error(FailedToReadGleamToml), - ) - - spinner.set_text(spinner, "decoding gleam.toml") - use toml <- result.try( - tom.parse(gleam_toml) - |> result.map_error(FailedToDecodeGleamToml), - ) - - spinner.set_text(spinner, "fetching project name") - use name <- result.try( - tom.get_string(toml, ["name"]) - |> result.map_error(FailedToGetPackageName), - ) - - spinner.set_text(spinner, "clearing build cache") - use _ <- result.try( - ["build/prod/erlang", "build/dev/erlang"] - |> list.try_map(fn(cache) { - filepath.join(root, cache) - |> filepath.join(name) - |> simplifile.delete - }) - |> result.try_recover(fn(e) { - case e { - simplifile.Enoent -> Ok([]) - _ -> Error(FailedToClearBuildCache(e)) - } - }), - ) - spinner.set_text(spinner, "rebuilding project") use _ <- result.try( shellout.command("gleam", ["build"], root, []) @@ -137,7 +86,7 @@ pub fn pkg_interface() -> Result(package_interface.Package) { spinner.set_text(spinner, "decoding package interface JSON") use pkg_interface_details <- result.try( - json.decode(from: pkg_interface_contents, using: package_interface.decoder) + json.parse(from: pkg_interface_contents, using: package_interface.decoder()) |> result.map_error(FailedToDecodePackageInterface), ) @@ -274,7 +223,7 @@ fn to_erlang_module_name(name) { string.replace(name, "/", "@") } -@external(erlang, "gladvent_ffi", "function_arity_one") +@external(erlang, "runners_ffi", "function_arity_one") fn function_arity_one( module: atom.Atom, function: atom.Atom, @@ -284,7 +233,7 @@ fn parse_function(module: String) -> fn(String) -> Dynamic { do_parse_function(atom.create_from_string(to_erlang_module_name(module))) } -@external(erlang, "gladvent_ffi", "parse_function") +@external(erlang, "runners_ffi", "parse_function") fn do_parse_function(module: atom.Atom) -> fn(String) -> Dynamic const string = package_interface.Named( diff --git a/src/gladvent_ffi.erl b/src/gladvent/internal/runners_ffi.erl similarity index 89% rename from src/gladvent_ffi.erl rename to src/gladvent/internal/runners_ffi.erl index 90053f9..09d2fed 100644 --- a/src/gladvent_ffi.erl +++ b/src/gladvent/internal/runners_ffi.erl @@ -1,4 +1,4 @@ --module(gladvent_ffi). +-module(runners_ffi). -export([ function_arity_one/2, diff --git a/src/gladvent/internal/util.gleam b/src/gladvent/internal/util.gleam index 463c5b2..5a6c9b1 100644 --- a/src/gladvent/internal/util.gleam +++ b/src/gladvent/internal/util.gleam @@ -1,3 +1,5 @@ +import gleam/erlang/charlist.{type Charlist} +import gleam/float import gleam/int import gleam/list import gleam/set @@ -11,3 +13,18 @@ pub fn defer(do later: fn() -> _, after now: fn() -> a) -> a { pub fn deduplicate_sort(l: List(Int)) -> List(Int) { l |> set.from_list |> set.to_list |> list.sort(int.compare) } + +@external(erlang, "timer", "tc") +pub fn timed(fun: fn() -> a) -> #(Int, a) + +pub fn format_float(input: Float, precision: Int) -> String { + case precision { + p if p >= 1 -> + do_format("~." <> int.to_string(precision) <> "f", [input]) + |> charlist.to_string + _ -> float.truncate(input) |> int.to_string + } +} + +@external(erlang, "io_lib", "format") +fn do_format(format: String, data: List(a)) -> Charlist diff --git a/test/parse_test.gleam b/test/parse_test.gleam index 528213f..1cb9b2e 100644 --- a/test/parse_test.gleam +++ b/test/parse_test.gleam @@ -1,29 +1,26 @@ import gladvent/internal/parse.{day, pad} import gleam/int import gleam/list -import gleeunit/should pub fn day_success_test() { use x <- list.each(list.range(1, 25)) - x - |> int.to_string() - |> day - |> should.equal(Ok(x)) + assert x + |> int.to_string() + |> day + == Ok(x) } pub fn day_error_test() { list.each(["", "0", "-1", "26"], fn(x) { - x - |> day - |> should.be_error + let assert Error(_) = day(x) }) } pub fn pad_test() { - pad(1) |> should.equal("01") + assert pad(1) == "01" } pub fn padded_day_to_int_test() { - "01" |> int.parse() |> should.be_ok() |> should.equal(1) + assert "01" |> int.parse() == Ok(1) } diff --git a/test/util_test.gleam b/test/util_test.gleam new file mode 100644 index 0000000..8cf80e4 --- /dev/null +++ b/test/util_test.gleam @@ -0,0 +1,15 @@ +import gladvent/internal/util +import gleam/list + +pub fn format_float_test() { + use #(float, precision, expected) <- list.each([ + #(123.123, 4, "123.1230"), + #(123.123, 2, "123.12"), + #(123.123, 0, "123"), + #(123.123, -3, "123"), + #(-123.123, 2, "-123.12"), + #(0.0, 0, "0"), + ]) + + assert util.format_float(float, precision) == expected +}