Compare commits

..

25 Commits

Author SHA1 Message Date
11e33eca2f Set up a shared types library 2025-02-20 09:45:53 -05:00
fd3ca9f561 Set up the most basic of authentication clients 2025-02-20 07:39:35 -05:00
e8a8a12de3 Start capturing input 2025-02-19 22:39:42 -05:00
7f0b7982ec Switch from println to log from gloo-console 2025-02-18 23:23:46 -05:00
5e4fd97aca Set up some callbacks to handle the login page state 2025-02-18 23:18:23 -05:00
1c4894df9a Start on the client module 2025-02-18 21:36:01 -05:00
20b214df10 Start adding some concepts around UI state 2025-02-18 08:25:40 -05:00
ca89455d4d Set up a Yew login page 2025-02-17 23:03:12 -05:00
2ff981e28a Nuke another speculative UI 2025-02-17 21:51:14 -05:00
672578b9a9 Add a micro-prototype Yew application 2025-02-17 18:28:18 -05:00
fb2fcf4d36 Abortive attempt to set up a trivial web application 2025-02-17 16:19:33 -05:00
a1dc573fc5 Adjust all build processes 2025-02-17 15:44:01 -05:00
0d39690560 Start rebuilding the typescript config, this time for web components 2025-02-17 15:17:30 -05:00
9439cfea34 Purge the Vite/React application 2025-02-17 09:34:26 -05:00
1d050f014a Set up rudimentary state, App, and a test for the App 2025-02-17 08:48:46 -05:00
df1dfeaae3 Set up dependencies 2025-02-16 20:50:50 -05:00
8ab8cd0684 Set up a package for just the types 2025-02-16 20:22:20 -05:00
aa7229eae4 Rename VResponse to AuthResponse and use it only in Authentication 2025-02-16 19:41:05 -05:00
0663a70c97 Force the password-reset state to Unauthorized on most auth-required routes 2025-02-16 15:54:59 -05:00
41bb21c254 Set up client tests
These are definitely temporary tests, as testing the lcient API will become more difficult and require more infrastructure as real data starts entering the system.
2025-02-16 14:12:17 -05:00
182020e136 Create a typescript client library for the server 2025-02-14 09:53:08 -05:00
79af050f53 Make a sample auth endpoint 2025-02-14 09:26:04 -05:00
dca9c3c39e Set up automated testing 2025-02-13 22:58:04 -05:00
e9f89e1bdb Create a tiny server for testing the Fetch API 2025-02-13 22:41:17 -05:00
f6534d5d05 Switch to vite instead of typescript 2025-02-13 19:01:21 -05:00
51 changed files with 3137 additions and 4074 deletions

1717
Cargo.lock generated

File diff suppressed because it is too large Load Diff

2044
Cargo.nix

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,6 @@
resolver = "2" resolver = "2"
members = [ members = [
"authdb", "authdb",
# "bike-lights/bike",
"bike-lights/core", "bike-lights/core",
"bike-lights/simulator", "bike-lights/simulator",
"changeset", "changeset",
@ -23,16 +22,18 @@ members = [
"gm-dash/server", "gm-dash/server",
"hex-grid", "hex-grid",
"icon-test", "icon-test",
"l10n-db",
"memorycache", "memorycache",
"nom-training", "nom-training",
"otg/core", "otg/core",
"otg/gtk", "otg/gtk",
"pico-st7789",
"result-extended", "result-extended",
"screenplay", "screenplay",
"sgf", "sgf",
"timezone-testing", "timezone-testing",
"tree", "tree",
"visions/server", "visions/server",
"visions/types",
"visions/ui",
"visions/yew-app",
# "bike-lights/bike",
] ]

View File

@ -3,6 +3,7 @@
"registry+https://github.com/rust-lang/crates.io-index#adler2@2.0.0": "09r6drylvgy8vv8k20lnbvwq8gp09h7smfn6h1rxsy15pgh629si", "registry+https://github.com/rust-lang/crates.io-index#adler2@2.0.0": "09r6drylvgy8vv8k20lnbvwq8gp09h7smfn6h1rxsy15pgh629si",
"registry+https://github.com/rust-lang/crates.io-index#adler32@1.2.0": "0d7jq7jsjyhsgbhnfq5fvrlh9j0i9g1fqrl2735ibv5f75yjgqda", "registry+https://github.com/rust-lang/crates.io-index#adler32@1.2.0": "0d7jq7jsjyhsgbhnfq5fvrlh9j0i9g1fqrl2735ibv5f75yjgqda",
"registry+https://github.com/rust-lang/crates.io-index#adler@1.0.2": "1zim79cvzd5yrkzl3nyfx0avijwgk9fqv3yrscdy1cc79ih02qpj", "registry+https://github.com/rust-lang/crates.io-index#adler@1.0.2": "1zim79cvzd5yrkzl3nyfx0avijwgk9fqv3yrscdy1cc79ih02qpj",
"registry+https://github.com/rust-lang/crates.io-index#ahash@0.8.11": "04chdfkls5xmhp1d48gnjsmglbqibizs3bpbj6rsj604m10si7g8",
"registry+https://github.com/rust-lang/crates.io-index#aho-corasick@1.1.3": "05mrpkvdgp5d20y2p989f187ry9diliijgwrs254fs9s1m1x6q4f", "registry+https://github.com/rust-lang/crates.io-index#aho-corasick@1.1.3": "05mrpkvdgp5d20y2p989f187ry9diliijgwrs254fs9s1m1x6q4f",
"registry+https://github.com/rust-lang/crates.io-index#allocator-api2@0.2.21": "08zrzs022xwndihvzdn78yqarv2b9696y67i6h78nla3ww87jgb8", "registry+https://github.com/rust-lang/crates.io-index#allocator-api2@0.2.21": "08zrzs022xwndihvzdn78yqarv2b9696y67i6h78nla3ww87jgb8",
"registry+https://github.com/rust-lang/crates.io-index#android-tzdata@0.1.1": "1w7ynjxrfs97xg3qlcdns4kgfpwcdv824g611fq32cag4cdr96g9", "registry+https://github.com/rust-lang/crates.io-index#android-tzdata@0.1.1": "1w7ynjxrfs97xg3qlcdns4kgfpwcdv824g611fq32cag4cdr96g9",
@ -14,6 +15,7 @@
"registry+https://github.com/rust-lang/crates.io-index#anstyle-wincon@3.0.7": "0kmf0fq4c8yribdpdpylzz1zccpy84hizmcsac3wrac1f7kk8dfa", "registry+https://github.com/rust-lang/crates.io-index#anstyle-wincon@3.0.7": "0kmf0fq4c8yribdpdpylzz1zccpy84hizmcsac3wrac1f7kk8dfa",
"registry+https://github.com/rust-lang/crates.io-index#anstyle@1.0.10": "1yai2vppmd7zlvlrp9grwll60knrmscalf8l2qpfz8b7y5lkpk2m", "registry+https://github.com/rust-lang/crates.io-index#anstyle@1.0.10": "1yai2vppmd7zlvlrp9grwll60knrmscalf8l2qpfz8b7y5lkpk2m",
"registry+https://github.com/rust-lang/crates.io-index#anyhow@1.0.95": "010vd1ki8w84dzgx6c81sc8qm9n02fxic1gkpv52zp4nwrn0kb1l", "registry+https://github.com/rust-lang/crates.io-index#anyhow@1.0.95": "010vd1ki8w84dzgx6c81sc8qm9n02fxic1gkpv52zp4nwrn0kb1l",
"registry+https://github.com/rust-lang/crates.io-index#assert-json-diff@2.0.2": "04mg3w0rh3schpla51l18362hsirl23q93aisws2irrj32wg5r27",
"registry+https://github.com/rust-lang/crates.io-index#async-channel@1.9.0": "0dbdlkzlncbibd3ij6y6jmvjd0cmdn48ydcfdpfhw09njd93r5c1", "registry+https://github.com/rust-lang/crates.io-index#async-channel@1.9.0": "0dbdlkzlncbibd3ij6y6jmvjd0cmdn48ydcfdpfhw09njd93r5c1",
"registry+https://github.com/rust-lang/crates.io-index#async-channel@2.3.1": "0skvwxj6ysfc6d7bhczz9a2550260g62bm5gl0nmjxxyn007id49", "registry+https://github.com/rust-lang/crates.io-index#async-channel@2.3.1": "0skvwxj6ysfc6d7bhczz9a2550260g62bm5gl0nmjxxyn007id49",
"registry+https://github.com/rust-lang/crates.io-index#async-executor@1.13.1": "1v6w1dbvsmw6cs4dk4lxj5dvrikc6xi479wikwaab2qy3h09mjih", "registry+https://github.com/rust-lang/crates.io-index#async-executor@1.13.1": "1v6w1dbvsmw6cs4dk4lxj5dvrikc6xi479wikwaab2qy3h09mjih",
@ -25,8 +27,13 @@
"registry+https://github.com/rust-lang/crates.io-index#async-trait@0.1.85": "0mm0gwad44zs7mna4a0m1z4dhzpmydfj73w4wm23c8xpnhrli4rz", "registry+https://github.com/rust-lang/crates.io-index#async-trait@0.1.85": "0mm0gwad44zs7mna4a0m1z4dhzpmydfj73w4wm23c8xpnhrli4rz",
"registry+https://github.com/rust-lang/crates.io-index#atoi@2.0.0": "0a05h42fggmy7h0ajjv6m7z72l924i7igbx13hk9d8pyign9k3gj", "registry+https://github.com/rust-lang/crates.io-index#atoi@2.0.0": "0a05h42fggmy7h0ajjv6m7z72l924i7igbx13hk9d8pyign9k3gj",
"registry+https://github.com/rust-lang/crates.io-index#atomic-waker@1.1.2": "1h5av1lw56m0jf0fd3bchxq8a30xv0b4wv8s4zkp4s0i7mfvs18m", "registry+https://github.com/rust-lang/crates.io-index#atomic-waker@1.1.2": "1h5av1lw56m0jf0fd3bchxq8a30xv0b4wv8s4zkp4s0i7mfvs18m",
"registry+https://github.com/rust-lang/crates.io-index#auto-future@1.0.0": "0wykbakzh227vz6frx9p48zsq0wpswgmb7v3917m53m7gr2pw7iw",
"registry+https://github.com/rust-lang/crates.io-index#autocfg@0.1.8": "0y4vw4l4izdxq1v0rrhvmlbqvalrqrmk60v1z0dqlgnlbzkl7phd", "registry+https://github.com/rust-lang/crates.io-index#autocfg@0.1.8": "0y4vw4l4izdxq1v0rrhvmlbqvalrqrmk60v1z0dqlgnlbzkl7phd",
"registry+https://github.com/rust-lang/crates.io-index#autocfg@1.4.0": "09lz3by90d2hphbq56znag9v87gfpd9gb8nr82hll8z6x2nhprdc", "registry+https://github.com/rust-lang/crates.io-index#autocfg@1.4.0": "09lz3by90d2hphbq56znag9v87gfpd9gb8nr82hll8z6x2nhprdc",
"registry+https://github.com/rust-lang/crates.io-index#axum-core@0.4.5": "16b1496c4gm387q20hkv5ic3k5bd6xmnvk50kwsy6ymr8rhvvwh9",
"registry+https://github.com/rust-lang/crates.io-index#axum-macros@0.4.2": "1klv77c889jm05bzayaaiinalarhvh2crc2w4nvp3l581xaj7lap",
"registry+https://github.com/rust-lang/crates.io-index#axum-test@16.4.1": "1p5qxacvxsagnqq30nr2wznjyhgb8svsfb925ah3d2b0s91s9qv3",
"registry+https://github.com/rust-lang/crates.io-index#axum@0.7.9": "07z7wqczi9i8xb4460rvn39p4wjqwr32hx907crd1vwb2fy8ijpd",
"registry+https://github.com/rust-lang/crates.io-index#az@1.2.1": "0ww9k1w3al7x5qmb7f13v3s9c2pg1pdxbs8xshqy6zyrchj4qzkv", "registry+https://github.com/rust-lang/crates.io-index#az@1.2.1": "0ww9k1w3al7x5qmb7f13v3s9c2pg1pdxbs8xshqy6zyrchj4qzkv",
"registry+https://github.com/rust-lang/crates.io-index#backtrace@0.3.74": "06pfif7nwx66qf2zaanc2fcq7m64i91ki9imw9xd3bnz5hrwp0ld", "registry+https://github.com/rust-lang/crates.io-index#backtrace@0.3.74": "06pfif7nwx66qf2zaanc2fcq7m64i91ki9imw9xd3bnz5hrwp0ld",
"registry+https://github.com/rust-lang/crates.io-index#base64@0.21.7": "0rw52yvsk75kar9wgqfwgb414kvil1gn7mqkrhn9zf1537mpsacx", "registry+https://github.com/rust-lang/crates.io-index#base64@0.21.7": "0rw52yvsk75kar9wgqfwgb414kvil1gn7mqkrhn9zf1537mpsacx",
@ -46,6 +53,7 @@
"registry+https://github.com/rust-lang/crates.io-index#bytemuck@1.21.0": "18wj81x9xhqcd6985r8qxmbik6szjfjfj62q3xklw8h2p3x7srgg", "registry+https://github.com/rust-lang/crates.io-index#bytemuck@1.21.0": "18wj81x9xhqcd6985r8qxmbik6szjfjfj62q3xklw8h2p3x7srgg",
"registry+https://github.com/rust-lang/crates.io-index#byteorder@1.5.0": "0jzncxyf404mwqdbspihyzpkndfgda450l0893pz5xj685cg5l0z", "registry+https://github.com/rust-lang/crates.io-index#byteorder@1.5.0": "0jzncxyf404mwqdbspihyzpkndfgda450l0893pz5xj685cg5l0z",
"registry+https://github.com/rust-lang/crates.io-index#bytes@1.9.0": "16ykzx24v1x4f42v2lxyvlczqhdfji3v7r4ghwckpwijzvb1hn9j", "registry+https://github.com/rust-lang/crates.io-index#bytes@1.9.0": "16ykzx24v1x4f42v2lxyvlczqhdfji3v7r4ghwckpwijzvb1hn9j",
"registry+https://github.com/rust-lang/crates.io-index#bytesize@1.3.0": "1k3aak70iwz4s2gsjbxf0ws4xnixqbdz6p2ha96s06748fpniqx3",
"registry+https://github.com/rust-lang/crates.io-index#cairo-rs@0.18.5": "1qjfkcq3mrh3p01nnn71dy3kn99g21xx3j8xcdvzn8ll2pq6x8lc", "registry+https://github.com/rust-lang/crates.io-index#cairo-rs@0.18.5": "1qjfkcq3mrh3p01nnn71dy3kn99g21xx3j8xcdvzn8ll2pq6x8lc",
"registry+https://github.com/rust-lang/crates.io-index#cairo-sys-rs@0.18.2": "0lfsxl7ylw3phbnwmz3k58j1gnqi6kc2hdc7g3bb7f4hwnl9yp38", "registry+https://github.com/rust-lang/crates.io-index#cairo-sys-rs@0.18.2": "0lfsxl7ylw3phbnwmz3k58j1gnqi6kc2hdc7g3bb7f4hwnl9yp38",
"registry+https://github.com/rust-lang/crates.io-index#cc@1.2.10": "0aaj2ivamhfzhgb9maasnfkh03s2mzhzpzwrkghgzbkfnv5qy80k", "registry+https://github.com/rust-lang/crates.io-index#cc@1.2.10": "0aaj2ivamhfzhgb9maasnfkh03s2mzhzpzwrkghgzbkfnv5qy80k",
@ -56,9 +64,9 @@
"registry+https://github.com/rust-lang/crates.io-index#chrono-tz@0.8.6": "0vlksnmpb6rd4h55245agnfhphnpslwnq9al3aw3is43dd3f16nm", "registry+https://github.com/rust-lang/crates.io-index#chrono-tz@0.8.6": "0vlksnmpb6rd4h55245agnfhphnpslwnq9al3aw3is43dd3f16nm",
"registry+https://github.com/rust-lang/crates.io-index#chrono@0.4.39": "09g8nf409lb184kl9j4s85k0kn8wzgjkp5ls9zid50b886fwqdky", "registry+https://github.com/rust-lang/crates.io-index#chrono@0.4.39": "09g8nf409lb184kl9j4s85k0kn8wzgjkp5ls9zid50b886fwqdky",
"registry+https://github.com/rust-lang/crates.io-index#clang-sys@1.8.1": "1x1r9yqss76z8xwpdanw313ss6fniwc1r7dzb5ycjn0ph53kj0hb", "registry+https://github.com/rust-lang/crates.io-index#clang-sys@1.8.1": "1x1r9yqss76z8xwpdanw313ss6fniwc1r7dzb5ycjn0ph53kj0hb",
"registry+https://github.com/rust-lang/crates.io-index#clap@4.5.30": "0vcyrn4ymq2gd56sl3xnfki8q8llg64sj3rj3qx33mgsf66v3dwj", "registry+https://github.com/rust-lang/crates.io-index#clap@4.5.26": "10v7qvn90calfbhap1c4r249i5c7fbxj09fn3szfz9pkis85xsx8",
"registry+https://github.com/rust-lang/crates.io-index#clap_builder@4.5.30": "0369xis2ar46icsaxqyy37976mlb62alzyx4j53k99vq2w3v4pd3", "registry+https://github.com/rust-lang/crates.io-index#clap_builder@4.5.26": "08f1mzcvi7zjhm7hvz6al4jnv70ccqhwiaq74hihlspwnl0iic4n",
"registry+https://github.com/rust-lang/crates.io-index#clap_derive@4.5.28": "1vgigkhljp3r8r5lwdrn1ij93nafmjwh8cx77nppb9plqsaysk5z", "registry+https://github.com/rust-lang/crates.io-index#clap_derive@4.5.24": "131ih3dm76srkbpfx7zfspp9b556zgzj31wqhl0ji2b39lcmbdsl",
"registry+https://github.com/rust-lang/crates.io-index#clap_lex@0.7.4": "19nwfls5db269js5n822vkc8dw0wjq2h1wf0hgr06ld2g52d2spl", "registry+https://github.com/rust-lang/crates.io-index#clap_lex@0.7.4": "19nwfls5db269js5n822vkc8dw0wjq2h1wf0hgr06ld2g52d2spl",
"registry+https://github.com/rust-lang/crates.io-index#cloudabi@0.0.3": "0kxcg83jlihy0phnd2g8c2c303px3l2p3pkjz357ll6llnd5pz6x", "registry+https://github.com/rust-lang/crates.io-index#cloudabi@0.0.3": "0kxcg83jlihy0phnd2g8c2c303px3l2p3pkjz357ll6llnd5pz6x",
"registry+https://github.com/rust-lang/crates.io-index#color_quant@1.1.0": "12q1n427h2bbmmm1mnglr57jaz2dj9apk0plcxw7nwqiai7qjyrx", "registry+https://github.com/rust-lang/crates.io-index#color_quant@1.1.0": "12q1n427h2bbmmm1mnglr57jaz2dj9apk0plcxw7nwqiai7qjyrx",
@ -68,6 +76,7 @@
"registry+https://github.com/rust-lang/crates.io-index#convert_case@0.6.0": "1jn1pq6fp3rri88zyw6jlhwwgf6qiyc08d6gjv0qypgkl862n67c", "registry+https://github.com/rust-lang/crates.io-index#convert_case@0.6.0": "1jn1pq6fp3rri88zyw6jlhwwgf6qiyc08d6gjv0qypgkl862n67c",
"registry+https://github.com/rust-lang/crates.io-index#cookie-factory@0.3.3": "18mka6fk3843qq3jw1fdfvzyv05kx7kcmirfbs2vg2kbw9qzm1cq", "registry+https://github.com/rust-lang/crates.io-index#cookie-factory@0.3.3": "18mka6fk3843qq3jw1fdfvzyv05kx7kcmirfbs2vg2kbw9qzm1cq",
"registry+https://github.com/rust-lang/crates.io-index#cookie@0.17.0": "096c52jg9iq4lfcps2psncswv33fc30mmnaa2sbzzcfcw71kgyvy", "registry+https://github.com/rust-lang/crates.io-index#cookie@0.17.0": "096c52jg9iq4lfcps2psncswv33fc30mmnaa2sbzzcfcw71kgyvy",
"registry+https://github.com/rust-lang/crates.io-index#cookie@0.18.1": "0iy749flficrlvgr3hjmf3igr738lk81n5akzf4ym4cs6cxg7pjd",
"registry+https://github.com/rust-lang/crates.io-index#cool_asserts@2.0.3": "1v18dg7ifx41k2f82j3gsnpm1fg9wk5s4zv7sf42c7pnad72b7zf", "registry+https://github.com/rust-lang/crates.io-index#cool_asserts@2.0.3": "1v18dg7ifx41k2f82j3gsnpm1fg9wk5s4zv7sf42c7pnad72b7zf",
"registry+https://github.com/rust-lang/crates.io-index#core-foundation-sys@0.8.7": "12w8j73lazxmr1z0h98hf3z623kl8ms7g07jch7n4p8f9nwlhdkp", "registry+https://github.com/rust-lang/crates.io-index#core-foundation-sys@0.8.7": "12w8j73lazxmr1z0h98hf3z623kl8ms7g07jch7n4p8f9nwlhdkp",
"registry+https://github.com/rust-lang/crates.io-index#core-foundation@0.9.4": "13zvbbj07yk3b61b8fhwfzhy35535a583irf23vlcg59j7h9bqci", "registry+https://github.com/rust-lang/crates.io-index#core-foundation@0.9.4": "13zvbbj07yk3b61b8fhwfzhy35535a583irf23vlcg59j7h9bqci",
@ -85,6 +94,7 @@
"registry+https://github.com/rust-lang/crates.io-index#deflate@0.8.6": "0x6iqlayg129w63999kz97m279m0jj4x4sm6gkqlvmp73y70yxvk", "registry+https://github.com/rust-lang/crates.io-index#deflate@0.8.6": "0x6iqlayg129w63999kz97m279m0jj4x4sm6gkqlvmp73y70yxvk",
"registry+https://github.com/rust-lang/crates.io-index#der@0.7.9": "1h4vzjfa1lczxdf8avfj9qlwh1qianqlxdy1g5rn762qnvkzhnzm", "registry+https://github.com/rust-lang/crates.io-index#der@0.7.9": "1h4vzjfa1lczxdf8avfj9qlwh1qianqlxdy1g5rn762qnvkzhnzm",
"registry+https://github.com/rust-lang/crates.io-index#deranged@0.3.11": "1d1ibqqnr5qdrpw8rclwrf1myn3wf0dygl04idf4j2s49ah6yaxl", "registry+https://github.com/rust-lang/crates.io-index#deranged@0.3.11": "1d1ibqqnr5qdrpw8rclwrf1myn3wf0dygl04idf4j2s49ah6yaxl",
"registry+https://github.com/rust-lang/crates.io-index#diff@0.1.13": "1j0nzjxci2zqx63hdcihkp0a4dkdmzxd7my4m7zk6cjyfy34j9an",
"registry+https://github.com/rust-lang/crates.io-index#digest@0.10.7": "14p2n6ih29x81akj097lvz7wi9b6b9hvls0lwrv7b6xwyy0s5ncy", "registry+https://github.com/rust-lang/crates.io-index#digest@0.10.7": "14p2n6ih29x81akj097lvz7wi9b6b9hvls0lwrv7b6xwyy0s5ncy",
"registry+https://github.com/rust-lang/crates.io-index#dimensioned@0.7.0": "09ky8s3higkf677lmyqg30hmj66gpg7hx907s6hfvbk2a9av05r5", "registry+https://github.com/rust-lang/crates.io-index#dimensioned@0.7.0": "09ky8s3higkf677lmyqg30hmj66gpg7hx907s6hfvbk2a9av05r5",
"registry+https://github.com/rust-lang/crates.io-index#dimensioned@0.8.0": "15s3j4ry943xqlac63bp81sgdk9s3yilysabzww35j9ibmnaic50", "registry+https://github.com/rust-lang/crates.io-index#dimensioned@0.8.0": "15s3j4ry943xqlac63bp81sgdk9s3yilysabzww35j9ibmnaic50",
@ -100,6 +110,8 @@
"registry+https://github.com/rust-lang/crates.io-index#event-listener@2.5.3": "1q4w3pndc518crld6zsqvvpy9lkzwahp2zgza9kbzmmqh9gif1h2", "registry+https://github.com/rust-lang/crates.io-index#event-listener@2.5.3": "1q4w3pndc518crld6zsqvvpy9lkzwahp2zgza9kbzmmqh9gif1h2",
"registry+https://github.com/rust-lang/crates.io-index#event-listener@5.4.0": "1bii2gn3vaa33s0gr2zph7cagiq0ppcfxcxabs24ri9z9kgar4il", "registry+https://github.com/rust-lang/crates.io-index#event-listener@5.4.0": "1bii2gn3vaa33s0gr2zph7cagiq0ppcfxcxabs24ri9z9kgar4il",
"registry+https://github.com/rust-lang/crates.io-index#exr@1.73.0": "1q47yq78q9k210r6jy1wwrilxwwxqavik9l3l426rd17k7srfcgq", "registry+https://github.com/rust-lang/crates.io-index#exr@1.73.0": "1q47yq78q9k210r6jy1wwrilxwwxqavik9l3l426rd17k7srfcgq",
"registry+https://github.com/rust-lang/crates.io-index#fallible-iterator@0.3.0": "0ja6l56yka5vn4y4pk6hn88z0bpny7a8k1919aqjzp0j1yhy9k1a",
"registry+https://github.com/rust-lang/crates.io-index#fallible-streaming-iterator@0.1.9": "0nj6j26p71bjy8h42x6jahx1hn0ng6mc2miwpgwnp8vnwqf4jq3k",
"registry+https://github.com/rust-lang/crates.io-index#fastrand@2.3.0": "1ghiahsw1jd68df895cy5h3gzwk30hndidn3b682zmshpgmrx41p", "registry+https://github.com/rust-lang/crates.io-index#fastrand@2.3.0": "1ghiahsw1jd68df895cy5h3gzwk30hndidn3b682zmshpgmrx41p",
"registry+https://github.com/rust-lang/crates.io-index#fdeflate@0.3.7": "130ga18vyxbb5idbgi07njymdaavvk6j08yh1dfarm294ssm6s0y", "registry+https://github.com/rust-lang/crates.io-index#fdeflate@0.3.7": "130ga18vyxbb5idbgi07njymdaavvk6j08yh1dfarm294ssm6s0y",
"registry+https://github.com/rust-lang/crates.io-index#field-offset@0.3.6": "0zq5sssaa2ckmcmxxbly8qgz3sxpb8g1lwv90sdh1z74qif2gqiq", "registry+https://github.com/rust-lang/crates.io-index#field-offset@0.3.6": "0zq5sssaa2ckmcmxxbly8qgz3sxpb8g1lwv90sdh1z74qif2gqiq",
@ -134,7 +146,6 @@
"registry+https://github.com/rust-lang/crates.io-index#generic-array@0.11.2": "0a7w8w0rg47nmcinnfzv443lcyb8mplwc251p1jyr5xj2yh6wzv6", "registry+https://github.com/rust-lang/crates.io-index#generic-array@0.11.2": "0a7w8w0rg47nmcinnfzv443lcyb8mplwc251p1jyr5xj2yh6wzv6",
"registry+https://github.com/rust-lang/crates.io-index#generic-array@0.14.7": "16lyyrzrljfq424c3n8kfwkqihlimmsg5nhshbbp48np3yjrqr45", "registry+https://github.com/rust-lang/crates.io-index#generic-array@0.14.7": "16lyyrzrljfq424c3n8kfwkqihlimmsg5nhshbbp48np3yjrqr45",
"registry+https://github.com/rust-lang/crates.io-index#getrandom@0.2.15": "1mzlnrb3dgyd1fb84gvw10pyr8wdqdl4ry4sr64i1s8an66pqmn4", "registry+https://github.com/rust-lang/crates.io-index#getrandom@0.2.15": "1mzlnrb3dgyd1fb84gvw10pyr8wdqdl4ry4sr64i1s8an66pqmn4",
"registry+https://github.com/rust-lang/crates.io-index#getrandom@0.3.1": "1y154yzby383p63ndw6zpfm0fz3vf6c0zdwc7df6vkl150wrr923",
"registry+https://github.com/rust-lang/crates.io-index#gif@0.11.4": "01hbw3isapzpzff8l6aw55jnaqx2bcscrbwyf3rglkbbfp397p9y", "registry+https://github.com/rust-lang/crates.io-index#gif@0.11.4": "01hbw3isapzpzff8l6aw55jnaqx2bcscrbwyf3rglkbbfp397p9y",
"registry+https://github.com/rust-lang/crates.io-index#gif@0.13.1": "1whrkvdg26gp1r7f95c6800y6ijqw5y0z8rgj6xihpi136dxdciz", "registry+https://github.com/rust-lang/crates.io-index#gif@0.13.1": "1whrkvdg26gp1r7f95c6800y6ijqw5y0z8rgj6xihpi136dxdciz",
"registry+https://github.com/rust-lang/crates.io-index#gimli@0.31.1": "0gvqc0ramx8szv76jhfd4dms0zyamvlg4whhiz11j34hh3dqxqh7", "registry+https://github.com/rust-lang/crates.io-index#gimli@0.31.1": "0gvqc0ramx8szv76jhfd4dms0zyamvlg4whhiz11j34hh3dqxqh7",
@ -159,8 +170,10 @@
"registry+https://github.com/rust-lang/crates.io-index#gtk4@0.7.3": "0hh8nzglmz94v1m1h6vy8z12m6fr7ia467ry0md5fa4p7sm53sss", "registry+https://github.com/rust-lang/crates.io-index#gtk4@0.7.3": "0hh8nzglmz94v1m1h6vy8z12m6fr7ia467ry0md5fa4p7sm53sss",
"registry+https://github.com/rust-lang/crates.io-index#h2@0.3.26": "1s7msnfv7xprzs6xzfj5sg6p8bjcdpcqcmjjbkd345cyi1x55zl1", "registry+https://github.com/rust-lang/crates.io-index#h2@0.3.26": "1s7msnfv7xprzs6xzfj5sg6p8bjcdpcqcmjjbkd345cyi1x55zl1",
"registry+https://github.com/rust-lang/crates.io-index#half@2.4.1": "123q4zzw1x4309961i69igzd1wb7pj04aaii3kwasrz3599qrl3d", "registry+https://github.com/rust-lang/crates.io-index#half@2.4.1": "123q4zzw1x4309961i69igzd1wb7pj04aaii3kwasrz3599qrl3d",
"registry+https://github.com/rust-lang/crates.io-index#hashbrown@0.14.5": "1wa1vy1xs3mp11bn3z9dv0jricgr6a2j0zkf1g19yz3vw4il89z5",
"registry+https://github.com/rust-lang/crates.io-index#hashbrown@0.15.2": "12dj0yfn59p3kh3679ac0w1fagvzf4z2zp87a13gbbqbzw0185dz", "registry+https://github.com/rust-lang/crates.io-index#hashbrown@0.15.2": "12dj0yfn59p3kh3679ac0w1fagvzf4z2zp87a13gbbqbzw0185dz",
"registry+https://github.com/rust-lang/crates.io-index#hashlink@0.10.0": "1h8lzvnl9qxi3zyagivzz2p1hp6shgddfmccyf6jv7s1cdicz0kk", "registry+https://github.com/rust-lang/crates.io-index#hashlink@0.10.0": "1h8lzvnl9qxi3zyagivzz2p1hp6shgddfmccyf6jv7s1cdicz0kk",
"registry+https://github.com/rust-lang/crates.io-index#hashlink@0.9.1": "1byq4nyrflm5s6wdx5qwp96l1qbp2d0nljvrr5yqrsfy51qzz93b",
"registry+https://github.com/rust-lang/crates.io-index#headers-core@0.2.0": "0ab469xfpd411mc3dhmjhmzrhqikzyj8a17jn5bkj9zfpy0n9xp7", "registry+https://github.com/rust-lang/crates.io-index#headers-core@0.2.0": "0ab469xfpd411mc3dhmjhmzrhqikzyj8a17jn5bkj9zfpy0n9xp7",
"registry+https://github.com/rust-lang/crates.io-index#headers@0.3.9": "0w62gnwh2p1lml0zqdkrx9dp438881nhz32zrzdy61qa0a9kns06", "registry+https://github.com/rust-lang/crates.io-index#headers@0.3.9": "0w62gnwh2p1lml0zqdkrx9dp438881nhz32zrzdy61qa0a9kns06",
"registry+https://github.com/rust-lang/crates.io-index#heck@0.4.1": "1a7mqsnycv5z4z5vnv1k34548jzmc0ajic7c1j8jsaspnhw5ql4m", "registry+https://github.com/rust-lang/crates.io-index#heck@0.4.1": "1a7mqsnycv5z4z5vnv1k34548jzmc0ajic7c1j8jsaspnhw5ql4m",
@ -172,15 +185,19 @@
"registry+https://github.com/rust-lang/crates.io-index#hkdf@0.12.4": "1xxxzcarz151p1b858yn5skmhyrvn8fs4ivx5km3i1kjmnr8wpvv", "registry+https://github.com/rust-lang/crates.io-index#hkdf@0.12.4": "1xxxzcarz151p1b858yn5skmhyrvn8fs4ivx5km3i1kjmnr8wpvv",
"registry+https://github.com/rust-lang/crates.io-index#hmac@0.12.1": "0pmbr069sfg76z7wsssfk5ddcqd9ncp79fyz6zcm6yn115yc6jbc", "registry+https://github.com/rust-lang/crates.io-index#hmac@0.12.1": "0pmbr069sfg76z7wsssfk5ddcqd9ncp79fyz6zcm6yn115yc6jbc",
"registry+https://github.com/rust-lang/crates.io-index#home@0.5.11": "1kxb4k87a9sayr8jipr7nq9wpgmjk4hk4047hmf9kc24692k75aq", "registry+https://github.com/rust-lang/crates.io-index#home@0.5.11": "1kxb4k87a9sayr8jipr7nq9wpgmjk4hk4047hmf9kc24692k75aq",
"registry+https://github.com/rust-lang/crates.io-index#http-body-util@0.1.2": "0kslwazg4400qnc2azkrgqqci0fppv12waicnsy5d8hncvbjjd3r",
"registry+https://github.com/rust-lang/crates.io-index#http-body@0.4.6": "1lmyjfk6bqk6k9gkn1dxq770sb78pqbqshga241hr5p995bb5skw", "registry+https://github.com/rust-lang/crates.io-index#http-body@0.4.6": "1lmyjfk6bqk6k9gkn1dxq770sb78pqbqshga241hr5p995bb5skw",
"registry+https://github.com/rust-lang/crates.io-index#http-body@1.0.1": "111ir5k2b9ihz5nr9cz7cwm7fnydca7dx4hc7vr16scfzghxrzhy",
"registry+https://github.com/rust-lang/crates.io-index#http@0.2.12": "1w81s4bcbmcj9bjp7mllm8jlz6b31wzvirz8bgpzbqkpwmbvn730", "registry+https://github.com/rust-lang/crates.io-index#http@0.2.12": "1w81s4bcbmcj9bjp7mllm8jlz6b31wzvirz8bgpzbqkpwmbvn730",
"registry+https://github.com/rust-lang/crates.io-index#http@1.2.0": "1skglzdf98j5nzxlii540n11is0w4l80mi5sm3xrj716asps4v7i", "registry+https://github.com/rust-lang/crates.io-index#http@1.2.0": "1skglzdf98j5nzxlii540n11is0w4l80mi5sm3xrj716asps4v7i",
"registry+https://github.com/rust-lang/crates.io-index#httparse@1.9.5": "0ip9v8m9lvgvq1lznl31wvn0ch1v254na7lhid9p29yx9rbx6wbx", "registry+https://github.com/rust-lang/crates.io-index#httparse@1.9.5": "0ip9v8m9lvgvq1lznl31wvn0ch1v254na7lhid9p29yx9rbx6wbx",
"registry+https://github.com/rust-lang/crates.io-index#httpdate@1.0.3": "1aa9rd2sac0zhjqh24c9xvir96g188zldkx0hr6dnnlx5904cfyz", "registry+https://github.com/rust-lang/crates.io-index#httpdate@1.0.3": "1aa9rd2sac0zhjqh24c9xvir96g188zldkx0hr6dnnlx5904cfyz",
"registry+https://github.com/rust-lang/crates.io-index#humantime@2.1.0": "1r55pfkkf5v0ji1x6izrjwdq9v6sc7bv99xj6srywcar37xmnfls", "registry+https://github.com/rust-lang/crates.io-index#humantime@2.1.0": "1r55pfkkf5v0ji1x6izrjwdq9v6sc7bv99xj6srywcar37xmnfls",
"registry+https://github.com/rust-lang/crates.io-index#hyper-tls@0.5.0": "01crgy13102iagakf6q4mb75dprzr7ps1gj0l5hxm1cvm7gks66n", "registry+https://github.com/rust-lang/crates.io-index#hyper-tls@0.5.0": "01crgy13102iagakf6q4mb75dprzr7ps1gj0l5hxm1cvm7gks66n",
"registry+https://github.com/rust-lang/crates.io-index#hyper-util@0.1.10": "1d1iwrkysjhq63pg54zk3vfby1j7zmxzm9zzyfr4lwvp0szcybfz",
"registry+https://github.com/rust-lang/crates.io-index#hyper@0.10.16": "0wwjh9p3mzvg3fss2lqz5r7ddcgl1fh9w6my2j69d6k0lbcm41ha", "registry+https://github.com/rust-lang/crates.io-index#hyper@0.10.16": "0wwjh9p3mzvg3fss2lqz5r7ddcgl1fh9w6my2j69d6k0lbcm41ha",
"registry+https://github.com/rust-lang/crates.io-index#hyper@0.14.32": "1rvcb0smz8q1i0y6p7rwxr02x5sclfg2hhxf3g0774zczn0cgps1", "registry+https://github.com/rust-lang/crates.io-index#hyper@0.14.32": "1rvcb0smz8q1i0y6p7rwxr02x5sclfg2hhxf3g0774zczn0cgps1",
"registry+https://github.com/rust-lang/crates.io-index#hyper@1.5.2": "1q7akfb443yrjzkmnnbp2vs8zi15hgbk466rr4y144v4ppabhvr5",
"registry+https://github.com/rust-lang/crates.io-index#iana-time-zone-haiku@0.1.2": "17r6jmj31chn7xs9698r122mapq85mfnv98bb4pg6spm0si2f67k", "registry+https://github.com/rust-lang/crates.io-index#iana-time-zone-haiku@0.1.2": "17r6jmj31chn7xs9698r122mapq85mfnv98bb4pg6spm0si2f67k",
"registry+https://github.com/rust-lang/crates.io-index#iana-time-zone@0.1.61": "085jjsls330yj1fnwykfzmb2f10zp6l7w4fhq81ng81574ghhpi3", "registry+https://github.com/rust-lang/crates.io-index#iana-time-zone@0.1.61": "085jjsls330yj1fnwykfzmb2f10zp6l7w4fhq81ng81574ghhpi3",
"registry+https://github.com/rust-lang/crates.io-index#icu_collections@1.5.0": "09j5kskirl59mvqc8kabhy7005yyy7dp88jw9f6f3gkf419a8byv", "registry+https://github.com/rust-lang/crates.io-index#icu_collections@1.5.0": "09j5kskirl59mvqc8kabhy7005yyy7dp88jw9f6f3gkf419a8byv",
@ -198,6 +215,8 @@
"registry+https://github.com/rust-lang/crates.io-index#idna_adapter@1.2.0": "0wggnkiivaj5lw0g0384ql2d7zk4ppkn3b1ry4n0ncjpr7qivjns", "registry+https://github.com/rust-lang/crates.io-index#idna_adapter@1.2.0": "0wggnkiivaj5lw0g0384ql2d7zk4ppkn3b1ry4n0ncjpr7qivjns",
"registry+https://github.com/rust-lang/crates.io-index#image@0.23.14": "18gn2f7xp30pf9aqka877knlq308khxqiwjvsccvzaa4f9zcpzr4", "registry+https://github.com/rust-lang/crates.io-index#image@0.23.14": "18gn2f7xp30pf9aqka877knlq308khxqiwjvsccvzaa4f9zcpzr4",
"registry+https://github.com/rust-lang/crates.io-index#image@0.24.9": "17gnr6ifnpzvhjf6dwbl9hki8x6bji5mwcqp0048x1jm5yfi742n", "registry+https://github.com/rust-lang/crates.io-index#image@0.24.9": "17gnr6ifnpzvhjf6dwbl9hki8x6bji5mwcqp0048x1jm5yfi742n",
"registry+https://github.com/rust-lang/crates.io-index#include_dir@0.7.4": "1pfh3g45z88kwq93skng0n6g3r7zkhq9ldqs9y8rvr7i11s12gcj",
"registry+https://github.com/rust-lang/crates.io-index#include_dir_macros@0.7.4": "0x8smnf6knd86g69p19z5lpfsaqp8w0nx14kdpkz1m8bxnkqbavw",
"registry+https://github.com/rust-lang/crates.io-index#indent_write@2.2.0": "1hqjp80argdskrhd66g9sh542yxy8qi77j6rc69qd0l7l52rdzhc", "registry+https://github.com/rust-lang/crates.io-index#indent_write@2.2.0": "1hqjp80argdskrhd66g9sh542yxy8qi77j6rc69qd0l7l52rdzhc",
"registry+https://github.com/rust-lang/crates.io-index#indexmap@2.7.0": "07s7jmdymvd0rm4yswp0j3napx57hkjm9gs9n55lvs2g78vj5y32", "registry+https://github.com/rust-lang/crates.io-index#indexmap@2.7.0": "07s7jmdymvd0rm4yswp0j3napx57hkjm9gs9n55lvs2g78vj5y32",
"registry+https://github.com/rust-lang/crates.io-index#intl-memoizer@0.5.2": "1nkvql7c7b76axv4g68di1p2m9bnxq1cbn6mlqcawf72zhhf08py", "registry+https://github.com/rust-lang/crates.io-index#intl-memoizer@0.5.2": "1nkvql7c7b76axv4g68di1p2m9bnxq1cbn6mlqcawf72zhhf08py",
@ -232,6 +251,7 @@
"registry+https://github.com/rust-lang/crates.io-index#log@0.4.25": "17ydv5zhfv1zzygy458bmg3f3jx1vfziv9d74817w76yhfqgbjq4", "registry+https://github.com/rust-lang/crates.io-index#log@0.4.25": "17ydv5zhfv1zzygy458bmg3f3jx1vfziv9d74817w76yhfqgbjq4",
"registry+https://github.com/rust-lang/crates.io-index#logger@0.4.0": "14xlxvkspcfnspjil0xi63qj5cybxn1hjmr5gq8m4v1g9k5p54bc", "registry+https://github.com/rust-lang/crates.io-index#logger@0.4.0": "14xlxvkspcfnspjil0xi63qj5cybxn1hjmr5gq8m4v1g9k5p54bc",
"registry+https://github.com/rust-lang/crates.io-index#matches@0.1.10": "1994402fq4viys7pjhzisj4wcw894l53g798kkm2y74laxk0jci5", "registry+https://github.com/rust-lang/crates.io-index#matches@0.1.10": "1994402fq4viys7pjhzisj4wcw894l53g798kkm2y74laxk0jci5",
"registry+https://github.com/rust-lang/crates.io-index#matchit@0.7.3": "156bgdmmlv4crib31qhgg49nsjk88dxkdqp80ha2pk2rk6n6ax0f",
"registry+https://github.com/rust-lang/crates.io-index#md-5@0.10.6": "1kvq5rnpm4fzwmyv5nmnxygdhhb2369888a06gdc9pxyrzh7x7nq", "registry+https://github.com/rust-lang/crates.io-index#md-5@0.10.6": "1kvq5rnpm4fzwmyv5nmnxygdhhb2369888a06gdc9pxyrzh7x7nq",
"registry+https://github.com/rust-lang/crates.io-index#memchr@2.7.4": "18z32bhxrax0fnjikv475z7ii718hq457qwmaryixfxsl2qrmjkq", "registry+https://github.com/rust-lang/crates.io-index#memchr@2.7.4": "18z32bhxrax0fnjikv475z7ii718hq457qwmaryixfxsl2qrmjkq",
"registry+https://github.com/rust-lang/crates.io-index#memoffset@0.9.1": "12i17wh9a9plx869g7j4whf62xw68k5zd4k0k5nh6ys5mszid028", "registry+https://github.com/rust-lang/crates.io-index#memoffset@0.9.1": "12i17wh9a9plx869g7j4whf62xw68k5zd4k0k5nh6ys5mszid028",
@ -297,9 +317,10 @@
"registry+https://github.com/rust-lang/crates.io-index#polling@3.7.4": "0bs4nhwfwsvlzlhah2gbhj3aa9ynvchv2g350wapswh26a65c156", "registry+https://github.com/rust-lang/crates.io-index#polling@3.7.4": "0bs4nhwfwsvlzlhah2gbhj3aa9ynvchv2g350wapswh26a65c156",
"registry+https://github.com/rust-lang/crates.io-index#powerfmt@0.2.0": "14ckj2xdpkhv3h6l5sdmb9f1d57z8hbfpdldjc2vl5givq2y77j3", "registry+https://github.com/rust-lang/crates.io-index#powerfmt@0.2.0": "14ckj2xdpkhv3h6l5sdmb9f1d57z8hbfpdldjc2vl5givq2y77j3",
"registry+https://github.com/rust-lang/crates.io-index#ppv-lite86@0.2.20": "017ax9ssdnpww7nrl1hvqh2lzncpv04nnsibmnw9nxjnaqlpp5bp", "registry+https://github.com/rust-lang/crates.io-index#ppv-lite86@0.2.20": "017ax9ssdnpww7nrl1hvqh2lzncpv04nnsibmnw9nxjnaqlpp5bp",
"registry+https://github.com/rust-lang/crates.io-index#pretty_assertions@1.4.1": "0v8iq35ca4rw3rza5is3wjxwsf88303ivys07anc5yviybi31q9s",
"registry+https://github.com/rust-lang/crates.io-index#pretty_env_logger@0.5.0": "076w9dnvcpx6d3mdbkqad8nwnsynb7c8haxmscyrz7g3vga28mw6", "registry+https://github.com/rust-lang/crates.io-index#pretty_env_logger@0.5.0": "076w9dnvcpx6d3mdbkqad8nwnsynb7c8haxmscyrz7g3vga28mw6",
"registry+https://github.com/rust-lang/crates.io-index#proc-macro-crate@1.3.1": "069r1k56bvgk0f58dm5swlssfcp79im230affwk6d9ck20g04k3z", "registry+https://github.com/rust-lang/crates.io-index#proc-macro-crate@1.3.1": "069r1k56bvgk0f58dm5swlssfcp79im230affwk6d9ck20g04k3z",
"registry+https://github.com/rust-lang/crates.io-index#proc-macro-crate@2.0.0": "1s23imns07vmacn2xjd5hv2h6rr94iqq3fd2frwa6i4h2nk6d0vy", "registry+https://github.com/rust-lang/crates.io-index#proc-macro-crate@2.0.2": "092x5acqnic14cw6vacqap5kgknq3jn4c6jij9zi6j85839jc3xh",
"registry+https://github.com/rust-lang/crates.io-index#proc-macro-error-attr@1.0.4": "0sgq6m5jfmasmwwy8x4mjygx5l7kp8s4j60bv25ckv2j1qc41gm1", "registry+https://github.com/rust-lang/crates.io-index#proc-macro-error-attr@1.0.4": "0sgq6m5jfmasmwwy8x4mjygx5l7kp8s4j60bv25ckv2j1qc41gm1",
"registry+https://github.com/rust-lang/crates.io-index#proc-macro-error@1.0.4": "1373bhxaf0pagd8zkyd03kkx6bchzf6g0dkwrwzsnal9z47lj9fs", "registry+https://github.com/rust-lang/crates.io-index#proc-macro-error@1.0.4": "1373bhxaf0pagd8zkyd03kkx6bchzf6g0dkwrwzsnal9z47lj9fs",
"registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.93": "169dw9wch753if1mgyi2nfl1il77gslvh6y2q46qplprwml6m530", "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.93": "169dw9wch753if1mgyi2nfl1il77gslvh6y2q46qplprwml6m530",
@ -332,7 +353,11 @@
"registry+https://github.com/rust-lang/crates.io-index#regex@1.11.1": "148i41mzbx8bmq32hsj1q4karkzzx5m60qza6gdw4pdc9qdyyi5m", "registry+https://github.com/rust-lang/crates.io-index#regex@1.11.1": "148i41mzbx8bmq32hsj1q4karkzzx5m60qza6gdw4pdc9qdyyi5m",
"registry+https://github.com/rust-lang/crates.io-index#remove_dir_all@0.5.3": "1rzqbsgkmr053bxxl04vmvsd1njyz0nxvly97aip6aa2cmb15k9s", "registry+https://github.com/rust-lang/crates.io-index#remove_dir_all@0.5.3": "1rzqbsgkmr053bxxl04vmvsd1njyz0nxvly97aip6aa2cmb15k9s",
"registry+https://github.com/rust-lang/crates.io-index#reqwest@0.11.27": "0qjary4hpplpgdi62d2m0xvbn6lnzckwffm0rgkm2x51023m6ryx", "registry+https://github.com/rust-lang/crates.io-index#reqwest@0.11.27": "0qjary4hpplpgdi62d2m0xvbn6lnzckwffm0rgkm2x51023m6ryx",
"registry+https://github.com/rust-lang/crates.io-index#reserve-port@2.0.1": "10x21rdb1hjzp6n5flbbw3hfd7brmirckz1q0zsf3a7s5d516f4q",
"registry+https://github.com/rust-lang/crates.io-index#rsa@0.9.7": "06amqm85raq26v6zg00fbf93lbj3kx559n2lpxc3wrvbbiy5vis7", "registry+https://github.com/rust-lang/crates.io-index#rsa@0.9.7": "06amqm85raq26v6zg00fbf93lbj3kx559n2lpxc3wrvbbiy5vis7",
"registry+https://github.com/rust-lang/crates.io-index#rusqlite@0.32.1": "0vlx040bppl414pbjgbp7qr4jdxwszi9krx0m63zzf2f2whvflvp",
"registry+https://github.com/rust-lang/crates.io-index#rusqlite_migration@1.3.1": "076dm65g0sngzrb93r07va4l5zl3gjx9gq5mlsh21p7p0bl44fwj",
"registry+https://github.com/rust-lang/crates.io-index#rust-multipart-rfc7578_2@0.6.1": "0mwd3i2mk91n6diaxnkw28vyjbifhrm5ls73pcpfzz8a1i0lidq3",
"registry+https://github.com/rust-lang/crates.io-index#rustc-demangle@0.1.24": "07zysaafgrkzy2rjgwqdj2a8qdpsm6zv6f5pgpk9x0lm40z9b6vi", "registry+https://github.com/rust-lang/crates.io-index#rustc-demangle@0.1.24": "07zysaafgrkzy2rjgwqdj2a8qdpsm6zv6f5pgpk9x0lm40z9b6vi",
"registry+https://github.com/rust-lang/crates.io-index#rustc-hash@1.1.0": "1qkc5khrmv5pqi5l5ca9p5nl5hs742cagrndhbrlk3dhlrx3zm08", "registry+https://github.com/rust-lang/crates.io-index#rustc-hash@1.1.0": "1qkc5khrmv5pqi5l5ca9p5nl5hs742cagrndhbrlk3dhlrx3zm08",
"registry+https://github.com/rust-lang/crates.io-index#rustc_version@0.4.1": "14lvdsmr5si5qbqzrajgb6vfn69k0sfygrvfvr2mps26xwi3mjyg", "registry+https://github.com/rust-lang/crates.io-index#rustc_version@0.4.1": "14lvdsmr5si5qbqzrajgb6vfn69k0sfygrvfvr2mps26xwi3mjyg",
@ -352,9 +377,10 @@
"registry+https://github.com/rust-lang/crates.io-index#self_cell@1.1.0": "1gmxk5bvnnimcif7v1jk8ai2azfvh9djki545nd86vsnphjgrzf2", "registry+https://github.com/rust-lang/crates.io-index#self_cell@1.1.0": "1gmxk5bvnnimcif7v1jk8ai2azfvh9djki545nd86vsnphjgrzf2",
"registry+https://github.com/rust-lang/crates.io-index#semver@1.0.24": "1fmvjjkd3f64y5fqr1nakkq371mnwzv09fbz5mbmdxril63ypdiw", "registry+https://github.com/rust-lang/crates.io-index#semver@1.0.24": "1fmvjjkd3f64y5fqr1nakkq371mnwzv09fbz5mbmdxril63ypdiw",
"registry+https://github.com/rust-lang/crates.io-index#serde@0.9.15": "1bsla8l5xr9pp5sirkal6mngxcq6q961km88jvf339j5ff8j7dil", "registry+https://github.com/rust-lang/crates.io-index#serde@0.9.15": "1bsla8l5xr9pp5sirkal6mngxcq6q961km88jvf339j5ff8j7dil",
"registry+https://github.com/rust-lang/crates.io-index#serde@1.0.218": "0q6z4bnrwagnms0bds4886711l6mc68s979i49zd3xnvkg8wkpz8", "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.217": "0w2ck1p1ajmrv1cf51qf7igjn2nc51r0izzc00fzmmhkvxjl5z02",
"registry+https://github.com/rust-lang/crates.io-index#serde_derive@1.0.218": "0azqd74xbpb1v5vf6w1fdbgmwp39ljjfj25cib5rgrzlj7hh75gh", "registry+https://github.com/rust-lang/crates.io-index#serde_derive@1.0.217": "180r3rj5gi5s1m23q66cr5wlfgc5jrs6n1mdmql2njnhk37zg6ss",
"registry+https://github.com/rust-lang/crates.io-index#serde_json@1.0.139": "19kj3irpa22a7djz1jaf4wambzh7psiqa6zyafqnb76crhx6ry24", "registry+https://github.com/rust-lang/crates.io-index#serde_json@1.0.136": "1lipcjhh1zazh283i4wsl4l14knh81q2rlkwmag8v8s2rwihqsik",
"registry+https://github.com/rust-lang/crates.io-index#serde_path_to_error@0.1.16": "19hlz2359l37ifirskpcds7sxg0gzpqvfilibs7whdys0128i6dg",
"registry+https://github.com/rust-lang/crates.io-index#serde_spanned@0.6.8": "1q89g70azwi4ybilz5jb8prfpa575165lmrffd49vmcf76qpqq47", "registry+https://github.com/rust-lang/crates.io-index#serde_spanned@0.6.8": "1q89g70azwi4ybilz5jb8prfpa575165lmrffd49vmcf76qpqq47",
"registry+https://github.com/rust-lang/crates.io-index#serde_urlencoded@0.7.1": "1zgklbdaysj3230xivihs30qi5vkhigg323a9m62k8jwf4a1qjfk", "registry+https://github.com/rust-lang/crates.io-index#serde_urlencoded@0.7.1": "1zgklbdaysj3230xivihs30qi5vkhigg323a9m62k8jwf4a1qjfk",
"registry+https://github.com/rust-lang/crates.io-index#serde_yml@0.0.12": "1p8xwz4znd6fj962y22fdvvv16gb8c0hx4iv5hjplngiidcdvqjr", "registry+https://github.com/rust-lang/crates.io-index#serde_yml@0.0.12": "1p8xwz4znd6fj962y22fdvvv16gb8c0hx4iv5hjplngiidcdvqjr",
@ -386,13 +412,14 @@
"registry+https://github.com/rust-lang/crates.io-index#syn@1.0.109": "0ds2if4600bd59wsv7jjgfkayfzy3hnazs394kz6zdkmna8l3dkj", "registry+https://github.com/rust-lang/crates.io-index#syn@1.0.109": "0ds2if4600bd59wsv7jjgfkayfzy3hnazs394kz6zdkmna8l3dkj",
"registry+https://github.com/rust-lang/crates.io-index#syn@2.0.96": "102wk3cgawimi3i0q3r3xw3i858zkyingg6y7gsxfy733amsvl6m", "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.96": "102wk3cgawimi3i0q3r3xw3i858zkyingg6y7gsxfy733amsvl6m",
"registry+https://github.com/rust-lang/crates.io-index#sync_wrapper@0.1.2": "0q01lyj0gr9a93n10nxsn8lwbzq97jqd6b768x17c8f7v7gccir0", "registry+https://github.com/rust-lang/crates.io-index#sync_wrapper@0.1.2": "0q01lyj0gr9a93n10nxsn8lwbzq97jqd6b768x17c8f7v7gccir0",
"registry+https://github.com/rust-lang/crates.io-index#sync_wrapper@1.0.2": "0qvjyasd6w18mjg5xlaq5jgy84jsjfsvmnn12c13gypxbv75dwhb",
"registry+https://github.com/rust-lang/crates.io-index#synstructure@0.13.1": "0wc9f002ia2zqcbj0q2id5x6n7g1zjqba7qkg2mr0qvvmdk7dby8", "registry+https://github.com/rust-lang/crates.io-index#synstructure@0.13.1": "0wc9f002ia2zqcbj0q2id5x6n7g1zjqba7qkg2mr0qvvmdk7dby8",
"registry+https://github.com/rust-lang/crates.io-index#system-configuration-sys@0.5.0": "1jckxvdr37bay3i9v52izgy52dg690x5xfg3hd394sv2xf4b2px7", "registry+https://github.com/rust-lang/crates.io-index#system-configuration-sys@0.5.0": "1jckxvdr37bay3i9v52izgy52dg690x5xfg3hd394sv2xf4b2px7",
"registry+https://github.com/rust-lang/crates.io-index#system-configuration@0.5.1": "1rz0r30xn7fiyqay2dvzfy56cvaa3km74hnbz2d72p97bkf3lfms", "registry+https://github.com/rust-lang/crates.io-index#system-configuration@0.5.1": "1rz0r30xn7fiyqay2dvzfy56cvaa3km74hnbz2d72p97bkf3lfms",
"registry+https://github.com/rust-lang/crates.io-index#system-deps@6.2.2": "0j93ryw031n3h8b0nfpj5xwh3ify636xmv8kxianvlyyipmkbrd3", "registry+https://github.com/rust-lang/crates.io-index#system-deps@6.2.2": "0j93ryw031n3h8b0nfpj5xwh3ify636xmv8kxianvlyyipmkbrd3",
"registry+https://github.com/rust-lang/crates.io-index#target-lexicon@0.12.16": "1cg3bnx1gdkdr5hac1hzxy64fhw4g7dqkd0n3dxy5lfngpr1mi31", "registry+https://github.com/rust-lang/crates.io-index#target-lexicon@0.12.16": "1cg3bnx1gdkdr5hac1hzxy64fhw4g7dqkd0n3dxy5lfngpr1mi31",
"registry+https://github.com/rust-lang/crates.io-index#tempdir@0.3.7": "1n5n86zxpgd85y0mswrp5cfdisizq2rv3la906g6ipyc03xvbwhm", "registry+https://github.com/rust-lang/crates.io-index#tempdir@0.3.7": "1n5n86zxpgd85y0mswrp5cfdisizq2rv3la906g6ipyc03xvbwhm",
"registry+https://github.com/rust-lang/crates.io-index#tempfile@3.17.1": "0c52ggq5vy5mzgk5ly36cgzs1cig3cv6r1jarijmzxgkn6na1r92", "registry+https://github.com/rust-lang/crates.io-index#tempfile@3.15.0": "016pmkbwn3shas44gcwq1kc9lajalb90qafhiip5fvv8h6f5b2ls",
"registry+https://github.com/rust-lang/crates.io-index#termcolor@1.4.1": "0mappjh3fj3p2nmrg4y7qv94rchwi9mzmgmfflr8p2awdj7lyy86", "registry+https://github.com/rust-lang/crates.io-index#termcolor@1.4.1": "0mappjh3fj3p2nmrg4y7qv94rchwi9mzmgmfflr8p2awdj7lyy86",
"registry+https://github.com/rust-lang/crates.io-index#thiserror-impl@1.0.69": "1h84fmn2nai41cxbhk6pqf46bxqq1b344v8yz089w1chzi76rvjg", "registry+https://github.com/rust-lang/crates.io-index#thiserror-impl@1.0.69": "1h84fmn2nai41cxbhk6pqf46bxqq1b344v8yz089w1chzi76rvjg",
"registry+https://github.com/rust-lang/crates.io-index#thiserror-impl@2.0.11": "1hkkn7p2y4cxbffcrprybkj0qy1rl1r6waxmxqvr764axaxc3br6", "registry+https://github.com/rust-lang/crates.io-index#thiserror-impl@2.0.11": "1hkkn7p2y4cxbffcrprybkj0qy1rl1r6waxmxqvr764axaxc3br6",
@ -413,12 +440,14 @@
"registry+https://github.com/rust-lang/crates.io-index#tokio-tungstenite@0.21.0": "0f5wj0crsx74rlll97lhw0wk6y12nhdnqvmnjx002hjn08fmcfy8", "registry+https://github.com/rust-lang/crates.io-index#tokio-tungstenite@0.21.0": "0f5wj0crsx74rlll97lhw0wk6y12nhdnqvmnjx002hjn08fmcfy8",
"registry+https://github.com/rust-lang/crates.io-index#tokio-util@0.7.13": "0y0h10a52c7hrldmr3410bp7j3fadq0jn9nf7awddgd2an6smz6p", "registry+https://github.com/rust-lang/crates.io-index#tokio-util@0.7.13": "0y0h10a52c7hrldmr3410bp7j3fadq0jn9nf7awddgd2an6smz6p",
"registry+https://github.com/rust-lang/crates.io-index#tokio@1.43.0": "17pdm49ihlhfw3rpxix3kdh2ppl1yv7nwp1kxazi5r1xz97zlq9x", "registry+https://github.com/rust-lang/crates.io-index#tokio@1.43.0": "17pdm49ihlhfw3rpxix3kdh2ppl1yv7nwp1kxazi5r1xz97zlq9x",
"registry+https://github.com/rust-lang/crates.io-index#toml@0.8.20": "0j012b37iz1mihksr6a928s6dzszxvblzg3l5wxp7azzsv6sb1yd", "registry+https://github.com/rust-lang/crates.io-index#toml@0.8.2": "0g9ysjaqvm2mv8q85xpqfn7hi710hj24sd56k49wyddvvyq8lp8q",
"registry+https://github.com/rust-lang/crates.io-index#toml_datetime@0.6.8": "0hgv7v9g35d7y9r2afic58jvlwnf73vgd1mz2k8gihlgrf73bmqd", "registry+https://github.com/rust-lang/crates.io-index#toml_datetime@0.6.3": "0jsy7v8bdvmzsci6imj8fzgd255fmy5fzp6zsri14yrry7i77nkw",
"registry+https://github.com/rust-lang/crates.io-index#toml_edit@0.19.15": "08bl7rp5g6jwmfpad9s8jpw8wjrciadpnbaswgywpr9hv9qbfnqv", "registry+https://github.com/rust-lang/crates.io-index#toml_edit@0.19.15": "08bl7rp5g6jwmfpad9s8jpw8wjrciadpnbaswgywpr9hv9qbfnqv",
"registry+https://github.com/rust-lang/crates.io-index#toml_edit@0.20.2": "0f7k5svmxw98fhi28jpcyv7ldr2s3c867pjbji65bdxjpd44svir", "registry+https://github.com/rust-lang/crates.io-index#toml_edit@0.20.2": "0f7k5svmxw98fhi28jpcyv7ldr2s3c867pjbji65bdxjpd44svir",
"registry+https://github.com/rust-lang/crates.io-index#toml_edit@0.22.24": "0x0lgp70x5cl9nla03xqs5vwwwlrwmd0djkdrp3h3lpdymgpkd0p", "registry+https://github.com/rust-lang/crates.io-index#tower-http@0.6.2": "15wnvhl6cpir9125s73bqjzjsvfb0fmndmsimnl2ddnlhfvs6gs0",
"registry+https://github.com/rust-lang/crates.io-index#tower-layer@0.3.3": "03kq92fdzxin51w8iqix06dcfgydyvx7yr6izjq0p626v9n2l70j",
"registry+https://github.com/rust-lang/crates.io-index#tower-service@0.3.3": "1hzfkvkci33ra94xjx64vv3pp0sq346w06fpkcdwjcid7zhvdycd", "registry+https://github.com/rust-lang/crates.io-index#tower-service@0.3.3": "1hzfkvkci33ra94xjx64vv3pp0sq346w06fpkcdwjcid7zhvdycd",
"registry+https://github.com/rust-lang/crates.io-index#tower@0.5.2": "1ybmd59nm4abl9bsvy6rx31m4zvzp5rja2slzpn712y9b68ssffh",
"registry+https://github.com/rust-lang/crates.io-index#tracing-attributes@0.1.28": "0v92l9cxs42rdm4m5hsa8z7ln1xsiw1zc2iil8c6k7lzq0jf2nir", "registry+https://github.com/rust-lang/crates.io-index#tracing-attributes@0.1.28": "0v92l9cxs42rdm4m5hsa8z7ln1xsiw1zc2iil8c6k7lzq0jf2nir",
"registry+https://github.com/rust-lang/crates.io-index#tracing-core@0.1.33": "170gc7cxyjx824r9kr17zc9gvzx89ypqfdzq259pr56gg5bwjwp6", "registry+https://github.com/rust-lang/crates.io-index#tracing-core@0.1.33": "170gc7cxyjx824r9kr17zc9gvzx89ypqfdzq259pr56gg5bwjwp6",
"registry+https://github.com/rust-lang/crates.io-index#tracing@0.1.41": "1l5xrzyjfyayrwhvhldfnwdyligi1mpqm8mzbi2m1d6y6p2hlkkq", "registry+https://github.com/rust-lang/crates.io-index#tracing@0.1.41": "1l5xrzyjfyayrwhvhldfnwdyligi1mpqm8mzbi2m1d6y6p2hlkkq",
@ -445,6 +474,7 @@
"registry+https://github.com/rust-lang/crates.io-index#unsafe-any@0.4.2": "0zwwphsqkw5qaiqmjwngnfpv9ym85qcsyj7adip9qplzjzbn00zk", "registry+https://github.com/rust-lang/crates.io-index#unsafe-any@0.4.2": "0zwwphsqkw5qaiqmjwngnfpv9ym85qcsyj7adip9qplzjzbn00zk",
"registry+https://github.com/rust-lang/crates.io-index#url@1.7.2": "0nim1c90mxpi9wgdw2xh8dqd72vlklwlzam436akcrhjac6pqknx", "registry+https://github.com/rust-lang/crates.io-index#url@1.7.2": "0nim1c90mxpi9wgdw2xh8dqd72vlklwlzam436akcrhjac6pqknx",
"registry+https://github.com/rust-lang/crates.io-index#url@2.5.4": "0q6sgznyy2n4l5lm16zahkisvc9nip9aa5q1pps7656xra3bdy1j", "registry+https://github.com/rust-lang/crates.io-index#url@2.5.4": "0q6sgznyy2n4l5lm16zahkisvc9nip9aa5q1pps7656xra3bdy1j",
"registry+https://github.com/rust-lang/crates.io-index#urlencoding@2.1.3": "1nj99jp37k47n0hvaz5fvz7z6jd0sb4ppvfy3nphr1zbnyixpy6s",
"registry+https://github.com/rust-lang/crates.io-index#utf-8@0.7.6": "1a9ns3fvgird0snjkd3wbdhwd3zdpc2h5gpyybrfr6ra5pkqxk09", "registry+https://github.com/rust-lang/crates.io-index#utf-8@0.7.6": "1a9ns3fvgird0snjkd3wbdhwd3zdpc2h5gpyybrfr6ra5pkqxk09",
"registry+https://github.com/rust-lang/crates.io-index#utf16_iter@1.0.5": "0ik2krdr73hfgsdzw0218fn35fa09dg2hvbi1xp3bmdfrp9js8y8", "registry+https://github.com/rust-lang/crates.io-index#utf16_iter@1.0.5": "0ik2krdr73hfgsdzw0218fn35fa09dg2hvbi1xp3bmdfrp9js8y8",
"registry+https://github.com/rust-lang/crates.io-index#utf8_iter@1.0.4": "1gmna9flnj8dbyd8ba17zigrp9c4c3zclngf5lnb5yvz1ri41hdn", "registry+https://github.com/rust-lang/crates.io-index#utf8_iter@1.0.4": "1gmna9flnj8dbyd8ba17zigrp9c4c3zclngf5lnb5yvz1ri41hdn",
@ -462,7 +492,6 @@
"registry+https://github.com/rust-lang/crates.io-index#warp@0.3.7": "07137zd13lchy5hxpspd0hs6sl19b0fv2zc1chf02nwnzw1d4y23", "registry+https://github.com/rust-lang/crates.io-index#warp@0.3.7": "07137zd13lchy5hxpspd0hs6sl19b0fv2zc1chf02nwnzw1d4y23",
"registry+https://github.com/rust-lang/crates.io-index#wasi@0.10.0+wasi-snapshot-preview1": "07y3l8mzfzzz4cj09c8y90yak4hpsi9g7pllyzpr6xvwrabka50s", "registry+https://github.com/rust-lang/crates.io-index#wasi@0.10.0+wasi-snapshot-preview1": "07y3l8mzfzzz4cj09c8y90yak4hpsi9g7pllyzpr6xvwrabka50s",
"registry+https://github.com/rust-lang/crates.io-index#wasi@0.11.0+wasi-snapshot-preview1": "08z4hxwkpdpalxjps1ai9y7ihin26y9f476i53dv98v45gkqg3cw", "registry+https://github.com/rust-lang/crates.io-index#wasi@0.11.0+wasi-snapshot-preview1": "08z4hxwkpdpalxjps1ai9y7ihin26y9f476i53dv98v45gkqg3cw",
"registry+https://github.com/rust-lang/crates.io-index#wasi@0.13.3+wasi-0.2.2": "1lnapbvdcvi3kc749wzqvwrpd483win2kicn1faa4dja38p6v096",
"registry+https://github.com/rust-lang/crates.io-index#wasite@0.1.0": "0nw5h9nmcl4fyf4j5d4mfdjfgvwi1cakpi349wc4zrr59wxxinmq", "registry+https://github.com/rust-lang/crates.io-index#wasite@0.1.0": "0nw5h9nmcl4fyf4j5d4mfdjfgvwi1cakpi349wc4zrr59wxxinmq",
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-backend@0.2.100": "1ihbf1hq3y81c4md9lyh6lcwbx6a5j0fw4fygd423g62lm8hc2ig", "registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-backend@0.2.100": "1ihbf1hq3y81c4md9lyh6lcwbx6a5j0fw4fygd423g62lm8hc2ig",
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-futures@0.4.50": "0q8ymi6i9r3vxly551dhxcyai7nc491mspj0j1wbafxwq074fpam", "registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-futures@0.4.50": "0q8ymi6i9r3vxly551dhxcyai7nc491mspj0j1wbafxwq074fpam",
@ -499,13 +528,11 @@
"registry+https://github.com/rust-lang/crates.io-index#windows_x86_64_msvc@0.48.5": "0f4mdp895kkjh9zv8dxvn4pc10xr7839lf5pa9l0193i2pkgr57d", "registry+https://github.com/rust-lang/crates.io-index#windows_x86_64_msvc@0.48.5": "0f4mdp895kkjh9zv8dxvn4pc10xr7839lf5pa9l0193i2pkgr57d",
"registry+https://github.com/rust-lang/crates.io-index#windows_x86_64_msvc@0.52.6": "1v7rb5cibyzx8vak29pdrk8nx9hycsjs4w0jgms08qk49jl6v7sq", "registry+https://github.com/rust-lang/crates.io-index#windows_x86_64_msvc@0.52.6": "1v7rb5cibyzx8vak29pdrk8nx9hycsjs4w0jgms08qk49jl6v7sq",
"registry+https://github.com/rust-lang/crates.io-index#winnow@0.5.40": "0xk8maai7gyxda673mmw3pj1hdizy5fpi7287vaywykkk19sk4zm", "registry+https://github.com/rust-lang/crates.io-index#winnow@0.5.40": "0xk8maai7gyxda673mmw3pj1hdizy5fpi7287vaywykkk19sk4zm",
"registry+https://github.com/rust-lang/crates.io-index#winnow@0.7.3": "1c9bmhpdwbdmll6b4l6skabz0296dchnmnxw84hh2y3ggyllwzqf",
"registry+https://github.com/rust-lang/crates.io-index#winreg@0.50.0": "1cddmp929k882mdh6i9f2as848f13qqna6czwsqzkh1pqnr5fkjj", "registry+https://github.com/rust-lang/crates.io-index#winreg@0.50.0": "1cddmp929k882mdh6i9f2as848f13qqna6czwsqzkh1pqnr5fkjj",
"registry+https://github.com/rust-lang/crates.io-index#wit-bindgen-rt@0.33.0": "0g4lwfp9x6a2i1hgjn8k14nr4fsnpd5izxhc75zpi2s5cvcg6s1j",
"registry+https://github.com/rust-lang/crates.io-index#write16@1.0.0": "0dnryvrrbrnl7vvf5vb1zkmwldhjkf2n5znliviam7bm4900z2fi", "registry+https://github.com/rust-lang/crates.io-index#write16@1.0.0": "0dnryvrrbrnl7vvf5vb1zkmwldhjkf2n5znliviam7bm4900z2fi",
"registry+https://github.com/rust-lang/crates.io-index#writeable@0.5.5": "0lawr6y0bwqfyayf3z8zmqlhpnzhdx0ahs54isacbhyjwa7g778y", "registry+https://github.com/rust-lang/crates.io-index#writeable@0.5.5": "0lawr6y0bwqfyayf3z8zmqlhpnzhdx0ahs54isacbhyjwa7g778y",
"registry+https://github.com/rust-lang/crates.io-index#xml-rs@0.8.25": "1i73ajf6scni5bi1a51r19xykgrambdx5fkks0fyg5jqqbml1ff5",
"registry+https://github.com/rust-lang/crates.io-index#yansi-term@0.1.2": "1w8vjlvxba6yvidqdvxddx3crl6z66h39qxj8xi6aqayw2nk0p7y", "registry+https://github.com/rust-lang/crates.io-index#yansi-term@0.1.2": "1w8vjlvxba6yvidqdvxddx3crl6z66h39qxj8xi6aqayw2nk0p7y",
"registry+https://github.com/rust-lang/crates.io-index#yansi@1.0.1": "0jdh55jyv0dpd38ij4qh60zglbw9aa8wafqai6m0wa7xaxk3mrfg",
"registry+https://github.com/rust-lang/crates.io-index#yoke-derive@0.7.5": "0m4i4a7gy826bfvnqa9wy6sp90qf0as3wps3wb0smjaamn68g013", "registry+https://github.com/rust-lang/crates.io-index#yoke-derive@0.7.5": "0m4i4a7gy826bfvnqa9wy6sp90qf0as3wps3wb0smjaamn68g013",
"registry+https://github.com/rust-lang/crates.io-index#yoke@0.7.5": "0h3znzrdmll0a7sglzf9ji0p5iqml11wrj1dypaf6ad6kbpnl3hj", "registry+https://github.com/rust-lang/crates.io-index#yoke@0.7.5": "0h3znzrdmll0a7sglzf9ji0p5iqml11wrj1dypaf6ad6kbpnl3hj",
"registry+https://github.com/rust-lang/crates.io-index#zerocopy-derive@0.7.35": "0gnf2ap2y92nwdalzz3x7142f2b83sni66l39vxp2ijd6j080kzs", "registry+https://github.com/rust-lang/crates.io-index#zerocopy-derive@0.7.35": "0gnf2ap2y92nwdalzz3x7142f2b83sni66l39vxp2ijd6j080kzs",

24
flake.lock generated
View File

@ -1,20 +1,5 @@
{ {
"nodes": { "nodes": {
"crane": {
"locked": {
"lastModified": 1739936662,
"narHash": "sha256-x4syUjNUuRblR07nDPeLDP7DpphaBVbUaSoeZkFbGSk=",
"owner": "ipetkov",
"repo": "crane",
"rev": "19de14aaeb869287647d9461cbd389187d8ecdb7",
"type": "github"
},
"original": {
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
},
"flake-utils": { "flake-utils": {
"inputs": { "inputs": {
"systems": "systems" "systems": "systems"
@ -35,16 +20,16 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1740339700, "lastModified": 1704732714,
"narHash": "sha256-cbrw7EgQhcdFnu6iS3vane53bEagZQy/xyIkDWpCgVE=", "narHash": "sha256-ABqK/HggMYA/jMUXgYyqVAcQ8QjeMyr1jcXfTpSHmps=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "04ef94c4c1582fd485bbfdb8c4a8ba250e359195", "rev": "6723fa4e4f1a30d42a633bef5eb01caeb281adc3",
"type": "github" "type": "github"
}, },
"original": { "original": {
"id": "nixpkgs", "id": "nixpkgs",
"ref": "nixos-24.11", "ref": "nixos-23.11",
"type": "indirect" "type": "indirect"
} }
}, },
@ -65,7 +50,6 @@
}, },
"root": { "root": {
"inputs": { "inputs": {
"crane": "crane",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"typeshare": "typeshare", "typeshare": "typeshare",
"unstable": "unstable" "unstable": "unstable"

View File

@ -2,13 +2,12 @@
description = "Lumenescent Dreams Tools"; description = "Lumenescent Dreams Tools";
inputs = { inputs = {
nixpkgs.url = "nixpkgs/nixos-24.11"; nixpkgs.url = "nixpkgs/nixos-23.11";
unstable.url = "nixpkgs/nixos-unstable"; unstable.url = "nixpkgs/nixos-unstable";
typeshare.url = "github:1Password/typeshare"; typeshare.url = "github:1Password/typeshare";
crane.url = "github:ipetkov/crane";
}; };
outputs = { self, nixpkgs, unstable, typeshare, crane, ... }: outputs = { self, nixpkgs, unstable, typeshare, ... }:
let let
version = builtins.string 0 8 self.lastModifiedDate; version = builtins.string 0 8 self.lastModifiedDate;
supportedSystems = [ "x86_64-linux" ]; supportedSystems = [ "x86_64-linux" ];
@ -26,6 +25,7 @@
pkgs.cargo-watch pkgs.cargo-watch
pkgs.clang pkgs.clang
pkgs.crate2nix pkgs.crate2nix
pkgs.trunk
pkgs.glib pkgs.glib
pkgs.gst_all_1.gst-plugins-bad pkgs.gst_all_1.gst-plugins-bad
pkgs.gst_all_1.gst-plugins-base pkgs.gst_all_1.gst-plugins-base
@ -57,8 +57,6 @@
packages."x86_64-linux" = packages."x86_64-linux" =
let let
pkgs = import nixpkgs { system = "x86_64-linux"; }; pkgs = import nixpkgs { system = "x86_64-linux"; };
craneLib = crane.mkLib pkgs;
src = craneLib.cleanCargoSource ./.;
gtkNativeInputs = [ gtkNativeInputs = [
pkgs.pkg-config pkgs.pkg-config
@ -91,11 +89,6 @@
dashboard = cargo_nix.workspaceMembers.dashboard.build; dashboard = cargo_nix.workspaceMembers.dashboard.build;
# file-service = cargo_nix.workspaceMembers.file-service.build; # file-service = cargo_nix.workspaceMembers.file-service.build;
fitnesstrax = cargo_nix.workspaceMembers.fitnesstrax.build; fitnesstrax = cargo_nix.workspaceMembers.fitnesstrax.build;
l10n-db = craneLib.buildPackage {
pname = "l10n-db";
cargoExtraArgs = "-p l10n-db";
src = ./.;
};
otg-gtk = cargo_nix.workspaceMembers.otg-gtk.build; otg-gtk = cargo_nix.workspaceMembers.otg-gtk.build;
all = pkgs.symlinkJoin { all = pkgs.symlinkJoin {
@ -106,7 +99,6 @@
dashboard dashboard
# file-service # file-service
fitnesstrax fitnesstrax
l10n-db
otg-gtk otg-gtk
]; ];
}; };

View File

@ -1,24 +0,0 @@
[package]
name = "l10n-db"
version = "0.1.0"
edition = "2021"
[dependencies]
chrono = { version = "0.4.39", features = ["serde"] }
clap = { version = "4.5.30", features = ["derive"] }
icu_locid = { version = "1.5.0", features = ["serde"] }
serde = { version = "1.0.218", features = ["derive"] }
serde_json = "1.0.139"
tempfile = "3.17.1"
thiserror = "2.0.11"
toml = "0.8.20"
xml-rs = "0.8.25"
# [lib]
# name = "l10n_db"
# path = "src/lib.rs"
#
# [[bin]]
# name = "l10n-db"
# path = "src/main.rs"

View File

@ -1,8 +0,0 @@
db_path = "./i18n"
base_locale = "en"
locales = [
"en",
"eo",
"de",
"es",
]

View File

@ -1,22 +0,0 @@
key = "SaveSettings"
description = "This is a label on a button which will save the settings when clicked"
[variants.eo]
locale = "eo"
content = "Konservi Agordojn"
modified = "2025-02-24T19:32:11.246639077Z"
[variants.es]
locale = "es"
content = "Guardar Configuraciones"
modified = "2025-02-24T19:33:23.861329923Z"
[variants.en]
locale = "en"
content = "Save Settings"
modified = "2025-02-22T23:44:18.874218939Z"
[variants.de]
locale = "de"
content = "Einstellungen Speichern"
modified = "2025-02-24T19:33:19.516005843Z"

View File

@ -1,22 +0,0 @@
key = "TimeDistance"
description = "A summary of a workout or many workouts that involve a time and a distance"
[variants.es]
locale = "es"
content = "{distance} de {activity} en {hours, plural, one {}=1 {{hours} hora} other {{hours} horas}} y {minutes, plural, one {}=1 {{minutes} minuto} other {{minutes} minutos}}"
modified = "2025-02-24T19:33:23.861604738Z"
[variants.eo]
locale = "eo"
content = "{distance} de {activity} en {hours, plural, =1 {{hours} horo} other {{hours} horoj}} {minutes, plural, =1 {{minutes} minuto} other {{minutes} minutoj}}"
modified = "2025-02-24T19:32:11.246943602Z"
[variants.en]
locale = "en"
content = "{distance} of {activity} in {hours, plural, =1 {{hours} hour} other {{hours} hours}} and {minutes, plural, =1 {{minutes} minute} other {{minutes} minutes}}"
modified = "2025-02-24T14:09:17.361641899Z"
[variants.de]
locale = "de"
content = "{distance} von {activity} in {hours, plural, one {}=1 {{hours} Stunde} other {{hours} Stunden}} und {minutes, plural, one {}=1 {{minutes} Minute} other {{minutes} Minuten}}"
modified = "2025-02-24T19:33:19.516210807Z"

View File

@ -1,22 +0,0 @@
key = "Welcome"
description = "This is a welcome content that will be shown on first app opening, before configuration."
[variants.en]
locale = "en"
content = "Welcome to FitnessTrax, the privacy-centered fitness tracker"
modified = "2025-02-25T02:12:25.757240004Z"
[variants.eo]
locale = "eo"
content = "Bonvenon al FitnessTrax"
modified = "2025-02-24T19:32:11.246407627Z"
[variants.es]
locale = "es"
content = "Bienvenido a FitnessTrax"
modified = "2025-02-24T19:33:23.861143003Z"
[variants.de]
locale = "de"
content = "Willkommen bei FitnessTrax"
modified = "2025-02-24T19:33:19.515861453Z"

View File

@ -1,143 +0,0 @@
use std::{fmt, path::PathBuf};
use clap::{Parser, Subcommand};
use icu_locid::{langid, LanguageIdentifier};
use l10n_db::{
self, js, read_file,
xliff::{self, import_file},
Bundle, Editor, ReadError,
};
use serde::Deserialize;
#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Cli {
#[command(subcommand)]
command: Option<Commands>,
}
#[derive(Subcommand)]
enum Commands {
/// Edit, potentially creating, a key
EditKey {
#[arg(short, long)]
key: String,
#[arg(short, long)]
locale: Option<String>,
},
/// List al keys in the database
ListKeys,
// Search the database
// Search { },
Import {
#[arg(short, long)]
file: String,
},
/// Export the database
Export {
#[arg(short, long)]
format: String,
#[arg(short, long)]
file: String,
#[arg(short, long)]
locale: Option<String>,
},
Report,
}
#[derive(Debug, Deserialize)]
struct Config {
db_path: PathBuf,
base_locale: LanguageIdentifier,
}
fn edit_key(bundle: &mut Bundle, key: String, locale: LanguageIdentifier, editor: &str) {
let message = bundle.message(key);
Editor::edit(message, locale, editor);
bundle.save();
}
#[derive(Clone, Debug, Default)]
struct Report {
keys: Vec<String>,
// source_deleted: Vec<String>,
out_of_date: Vec<String>,
}
impl fmt::Display for Report {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "{} messages in bundle", self.keys.len())?;
writeln!(f, "Out of date messages")?;
for key in self.out_of_date.iter() {
writeln!(f, "\t{}", key)?;
}
Ok(())
}
}
fn generate_report(bundle: &Bundle, base_locale: &LanguageIdentifier) -> Report {
let mut report: Report = Default::default();
for (key, message) in bundle.message_iter() {
report.keys.push(key.to_owned());
if !message.variants_out_of_date(base_locale).is_empty() {
report.out_of_date.push(key.to_owned())
}
}
report
}
fn main() {
let editor = std::env::var("EDITOR").expect("Set EDITOR to the path to your favorite editor");
let config: Config = read_file(&PathBuf::from("./config.toml"))
.and_then(|bytes| String::from_utf8(bytes).map_err(|_| ReadError::InvalidFormat))
.and_then(|content| toml::from_str(&content).map_err(|_| ReadError::InvalidFormat))
.unwrap();
let cli = Cli::parse();
let mut bundle = Bundle::load_from_disk(PathBuf::from(&config.db_path));
match &cli.command {
Some(Commands::EditKey { key, locale }) => {
let identifier = locale.as_ref()
.map(|l| l.parse::<LanguageIdentifier>().unwrap())
.unwrap_or(config.base_locale);
edit_key(&mut bundle, key.to_owned(), identifier, &editor)
}
Some(Commands::ListKeys) => {
for (key, _) in bundle.message_iter() {
println!("{}", key);
}
}
Some(Commands::Import { file }) => {
import_file(&mut bundle, &PathBuf::from(file)).unwrap();
bundle.save();
}
Some(Commands::Export {
format,
file,
locale,
}) => {
let locale = locale
.as_ref()
.map(|l| l.clone().parse::<LanguageIdentifier>().unwrap())
.unwrap_or(langid!("en"));
match format.as_ref() {
"js" => js::export_file(&bundle, locale, &PathBuf::from(file)).unwrap(),
"xliff" => {
xliff::export_file(&bundle, locale, &PathBuf::from(file)).unwrap()
}
_ => todo!(),
}
}
Some(Commands::Report) => {
let report = generate_report(&bundle, &config.base_locale);
println!("{}", report);
}
None => {}
}
}

View File

@ -1,143 +0,0 @@
use std::{collections::HashMap, fs::File, io::{BufReader, Read, Write}, path::{Path, PathBuf}};
use chrono::{DateTime, Utc};
use icu_locid::LanguageIdentifier;
use serde::{Deserialize, Serialize};
pub struct Bundle {
path: PathBuf,
messages: HashMap<String, Message>,
}
impl Bundle {
pub fn load_from_disk(path: PathBuf) -> Self {
let mut messages = HashMap::new();
if path.is_dir() {
for entry in std::fs::read_dir(&path).unwrap() {
let entry = entry.unwrap();
let path = entry.path().clone();
let key = path.file_stem().unwrap();
let message = Message::from_file(&entry.path());
messages.insert(key.to_str().unwrap().to_owned(), message);
}
}
Self { path, messages }
}
pub fn message_iter(&self) -> impl Iterator<Item = (&String, &Message)> {
self.messages.iter()
}
pub fn message(&mut self, name: String) -> &mut Message {
self.messages
.entry(name.to_owned())
.or_insert(Message::new(name.to_owned()))
}
pub fn save(&self) {
self.messages.iter().for_each(|(key, value)| {
let mut path = self.path.clone();
path.push(key);
path.set_extension("toml");
save_file(&path, toml::to_string(value).unwrap().as_bytes());
});
}
}
fn save_file(path: &PathBuf, s: &[u8]) {
let mut f = File::create(path).unwrap();
let _ = f.write(s).unwrap();
}
#[derive(Deserialize, Serialize, Debug)]
pub struct Message {
key: String,
description: String,
variants: HashMap<LanguageIdentifier, Variant>,
}
impl Message {
pub fn new(key: String) -> Self {
Self {
key,
description: "".to_owned(),
variants: HashMap::new(),
}
}
pub fn from_file(path: &Path) -> Message {
let file = std::fs::File::open(path).unwrap();
let mut content = Vec::new();
let mut reader = BufReader::new(file);
let _ = reader.read_to_end(&mut content);
toml::from_str(&String::from_utf8(content).unwrap()).unwrap()
}
pub fn set_description(&mut self, desc: String) {
self.description = desc;
}
pub fn description(&self) -> &str {
&self.description
}
pub fn variant(&self, locale: &LanguageIdentifier) -> Option<&Variant> {
self.variants.get(locale)
}
pub fn variant_mut(&mut self, locale: LanguageIdentifier) -> &mut Variant {
self.variants.entry(locale.clone()).or_insert(Variant {
locale,
content: "".to_owned(),
modified: Utc::now(),
})
}
pub fn variants_out_of_date(
&self,
base_locale: &LanguageIdentifier,
) -> Vec<LanguageIdentifier> {
match self
.variants
.get(base_locale)
.map(|variant| variant.modified())
{
Some(base_date) => self
.variants
.iter()
.filter(|(_, value)| base_date > value.modified())
.map(|(locale, _)| locale.clone())
.collect(),
None => vec![],
}
}
// pub fn missing_variants(&self, locals: Vec<LanguageIdentifier>) -> Vec<LanguageIdentifier> {}
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Variant {
locale: LanguageIdentifier,
content: String,
modified: DateTime<Utc>,
}
impl Variant {
pub fn content(&self) -> &str {
&self.content
}
pub fn set_content(&mut self, content: String) {
self.content = content;
self.modified = Utc::now();
}
pub fn modified(&self) -> DateTime<Utc> {
self.modified
}
}
#[cfg(test)]
mod test {}

View File

@ -1,43 +0,0 @@
use std::{io::Write, process::Command};
use icu_locid::{langid, LanguageIdentifier};
use serde::{Deserialize, Serialize};
use crate::{read_fh, Message};
#[derive(Serialize, Deserialize, Debug, Clone)]
struct EditorMessage {
description: String,
source: String,
content: String,
}
pub struct Editor {}
impl Editor {
pub fn edit(msg: &mut Message, locale: LanguageIdentifier, editor: &str) {
let description = msg.description().to_owned();
let source_string = msg.variant_mut(langid!("en")).content().to_owned();
let variant = msg.variant_mut(locale);
// let editable_content = EditorMessage::from_variant(description, &variant);
let editable_content = EditorMessage {
description,
source: source_string,
content: variant.content().to_owned(),
};
let mut file = tempfile::NamedTempFile::new().unwrap();
let _ = file.write(toml::to_string(&editable_content).unwrap().as_bytes());
let _ = file.flush();
let mut cmd = Command::new(editor).args([file.path()]).spawn().unwrap();
cmd.wait().unwrap();
let file = file.reopen().unwrap();
let content = read_fh(&file).unwrap();
let new_variant: EditorMessage =
toml::from_str(&String::from_utf8(content).unwrap()).unwrap();
variant.set_content(new_variant.content);
msg.set_description(new_variant.description);
}
}

View File

@ -1,22 +0,0 @@
use std::{collections::BTreeMap, fs::File, io::Write, path::Path};
use icu_locid::LanguageIdentifier;
use crate::{Bundle, WriteError};
pub fn export_file(bundle: &Bundle, locale: LanguageIdentifier, path: &Path) -> Result<(), WriteError> {
let mut file = File::create(path).unwrap();
export_fh(bundle, locale, &mut file)
}
pub fn export_fh(bundle: &Bundle, locale: LanguageIdentifier, fh: &mut File) -> Result<(), WriteError> {
let messages = bundle.message_iter().map(|(key, message)| {
let content = message.variant(&locale).unwrap().content().to_owned();
(key.to_owned(), content)
}).collect::<Vec<(String, String)>>();
let messages: BTreeMap<String, String> = messages.into_iter().collect();
let _ = fh.write(serde_json::to_string_pretty(&messages).unwrap().as_bytes()).unwrap();
Ok(())
}

View File

@ -1,4 +0,0 @@
pub mod js;
pub mod xliff;

View File

@ -1,146 +0,0 @@
use std::{
fs::File,
io::{BufReader, Read, Write},
path::Path,
};
use icu_locid::{langid, LanguageIdentifier};
use xml::{attribute::OwnedAttribute, reader, writer, EmitterConfig, EventReader, EventWriter};
use crate::{Bundle, Message, ReadError, WriteError};
pub fn export_file(
bundle: &Bundle,
locale: LanguageIdentifier,
path: &Path,
) -> Result<(), WriteError> {
let mut file = File::create(path).unwrap();
export_fh(bundle, locale, &mut file)
}
pub fn export_fh<W>(bundle: &Bundle, locale: LanguageIdentifier, fh: W) -> Result<(), WriteError>
where
W: Write,
{
let mut writer = EmitterConfig::new().perform_indent(true).create_writer(fh);
writer
.write(
writer::XmlEvent::start_element("xliff")
.attr("xmlns", "urn:oasis:names:tc:xliff:document:2.0")
.attr("version", "2.0")
.attr("srcLang", &format!("{}", locale)),
)
.unwrap();
writer
.write(writer::XmlEvent::start_element("file").attr("id", "main"))
.unwrap();
for (key, message) in bundle.message_iter() {
write_message(&mut writer, key, message, &locale);
}
writer.write(writer::XmlEvent::end_element()).unwrap();
writer.write(writer::XmlEvent::end_element()).unwrap();
Ok(())
}
pub fn import_file(bundle: &mut Bundle, path: &Path) -> Result<(), ReadError> {
let file = File::open(path).unwrap();
let file = BufReader::new(file);
import_reader(bundle, file)
}
pub fn import_reader<R>(bundle: &mut Bundle, fh: R) -> Result<(), ReadError>
where
R: Read,
{
let parser = EventReader::new(fh);
let mut locale: LanguageIdentifier = langid!("en");
let mut current_key = None;
let mut current_text: Option<String> = None;
let mut in_target = false;
for event in parser {
match event {
Ok(reader::XmlEvent::StartElement {
name, attributes, ..
}) => match name.local_name.as_ref() {
"xliff" => {
locale = find_attribute(&attributes, "trgLang")
.unwrap()
.parse::<LanguageIdentifier>()
.unwrap();
}
"unit" => current_key = find_attribute(&attributes, "id"),
"target" => in_target = true,
_ => println!("name: {}", name),
},
Ok(reader::XmlEvent::EndElement { name }) => match name.local_name.as_ref() {
"unit" => {
if let Some(key) = current_key {
let message = bundle.message(key);
let variant = message.variant_mut(locale.clone());
if let Some(ref text) = current_text {
variant.set_content(text.clone());
}
}
current_key = None;
}
"target" => in_target = false,
_ => {}
},
Ok(reader::XmlEvent::Characters(data)) => {
if in_target {
current_text = Some(data)
}
}
Err(e) => {
eprintln!("error: {e}");
break;
}
_ => {}
}
}
Ok(())
}
fn write_message<T>(
writer: &mut EventWriter<T>,
key: &str,
message: &Message,
locale: &LanguageIdentifier,
) where
T: std::io::Write,
{
[
writer::XmlEvent::start_element("unit")
.attr("id", key)
.into(),
writer::XmlEvent::start_element("notes").into(),
writer::XmlEvent::start_element("note").into(),
writer::XmlEvent::characters(message.description()),
writer::XmlEvent::end_element().into(),
writer::XmlEvent::end_element().into(),
writer::XmlEvent::start_element("segment").into(),
writer::XmlEvent::start_element("source").into(),
writer::XmlEvent::characters(message.variant(locale).unwrap().content()),
writer::XmlEvent::end_element().into(),
writer::XmlEvent::end_element().into(),
writer::XmlEvent::end_element().into(),
]
.into_iter()
.for_each(|elem: writer::XmlEvent| writer.write(elem).unwrap());
}
fn find_attribute(attrs: &Vec<OwnedAttribute>, name: &str) -> Option<String> {
for f in attrs {
if name == f.name.local_name {
return Some(f.value.clone());
}
}
None
}

View File

@ -1,11 +0,0 @@
mod bundle;
pub use bundle::{Bundle, Message, Variant};
mod editor;
pub use editor::Editor;
mod formats;
pub use formats::{js, xliff};
mod utils;
pub use utils::*;

View File

@ -1,37 +0,0 @@
use std::{fs::File, io::{BufReader, ErrorKind, Read}, path::Path};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ReadError {
#[error("file not found")]
FileNotFound,
#[error("invalid file format")]
InvalidFormat,
#[error("unhandled read error")]
Unhandled(ErrorKind),
}
pub fn read_file(path: &Path) -> Result<Vec<u8>, ReadError> {
let file = File::open(path).map_err(|err| {
match err.kind() {
ErrorKind::NotFound => ReadError::FileNotFound,
_ => ReadError::Unhandled( err.kind()),
}
})?;
read_fh(&file)
}
pub fn read_fh(file: &File) -> Result<Vec<u8>, ReadError> {
let mut content = Vec::new();
let mut reader = BufReader::new(file);
reader.read_to_end(&mut content).map_err(|err| ReadError::Unhandled(err.kind()))?;
Ok(content)
}
#[derive(Debug, Error)]
pub enum WriteError {
#[error("unhandled write error")]
Unhandled(ErrorKind),
}

View File

@ -1,11 +0,0 @@
[build]
target = "thumbv6m-none-eabi"
[target.thumbv6m-none-eabi]
rustflags = [
"-C", "link-arg=--nmagic",
"-C", "link-arg=-Tlink.x",
"-C", "no-vectorize-loops",
]
runner = "elf2uf2-rs -d"

View File

@ -1,13 +0,0 @@
[package]
name = "pico-st7789"
version = "0.1.0"
edition = "2021"
[dependencies]
bitflags = "2.9.0"
cortex-m-rt = "0.7.3"
embedded-alloc = "0.6.0"
embedded-hal = "1.0.0"
fugit = "0.3.7"
panic-halt = "1.0.0"
rp-pico = "0.9.0"

View File

@ -1,62 +0,0 @@
// a a a
// f b
// f b
// f b
// g g g
// e c
// e c
// e c
// d d d
use crate::font::{Font, Glyph};
pub struct RGB {
pub r: u8,
pub g: u8,
pub b: u8,
}
pub trait Canvas {
fn set_pixel(&mut self, x: usize, y: usize, color: &RGB);
fn fill(&mut self, x1: usize, y1: usize, x2: usize, y2: usize, color: &RGB) {
for x in x1..x2 + 1 {
for y in y1..y2 + 1 {
self.set_pixel(x, y, color);
}
}
}
fn square(&mut self, x1: usize, y1: usize, x2: usize, y2: usize, color: &RGB) {
for x in x1..x2 + 1 {
self.set_pixel(x, y1, color);
}
for x in x1..x2 + 1 {
self.set_pixel(x, y2, color);
}
for y in y1..y2 + 1 {
self.set_pixel(x1, y, color);
}
for y in y1..y2 + 1 {
self.set_pixel(x2, y, color);
}
}
}
pub fn print<A>(
canvas: &mut impl Canvas,
font: &impl Font<A>,
sx: usize,
sy: usize,
text: &str,
color: &RGB,
) where
A: Glyph,
{
let mut x = sx;
for c in text.chars().map(|c| font.glyph(c)) {
c.draw(canvas, x, sy, color);
let (dx, _) = c.extents();
x = x + dx + 1;
}
}

View File

@ -1,513 +0,0 @@
use alloc::collections::btree_map::BTreeMap;
use crate::canvas::{Canvas, RGB};
use super::{Font, Glyph};
pub struct BitmapGlyph([u8; 7]);
pub struct BitmapFont(BTreeMap<char, BitmapGlyph>);
impl BitmapFont {
pub fn new() -> Self {
let mut font = BTreeMap::new();
font.insert(' ', BitmapGlyph([
0b00000,
0b00000,
0b00000,
0b00000,
0b00000,
0b00000,
0b00000,
]));
font.insert(
':',
BitmapGlyph([
0b00000,
0b00100,
0b00100,
0b00000,
0b00100,
0b00100,
0b00000,
]),
);
font.insert(
'/',
BitmapGlyph([
0b00001,
0b00010,
0b00010,
0b00100,
0b01000,
0b01000,
0b10000,
]),
);
font.insert(
'0',
BitmapGlyph([
0b01110,
0b10001,
0b10001,
0b10101,
0b10001,
0b10001,
0b01110,
]),
);
font.insert(
'1',
BitmapGlyph([
0b00001,
0b00011,
0b00101,
0b00001,
0b00001,
0b00001,
0b00001,
]),
);
font.insert(
'2',
BitmapGlyph([
0b01110,
0b10001,
0b00001,
0b00010,
0b00100,
0b01000,
0b11111,
]),
);
font.insert(
'3',
BitmapGlyph([
0b01110,
0b10001,
0b00001,
0b01110,
0b00001,
0b10001,
0b01110,
]),
);
font.insert(
'4',
BitmapGlyph([
0b10001,
0b10001,
0b10001,
0b11111,
0b00001,
0b00001,
0b00001,
]),
);
font.insert(
'5',
BitmapGlyph([
0b11111,
0b10000,
0b10000,
0b11111,
0b00001,
0b10001,
0b01110,
]),
);
font.insert(
'6',
BitmapGlyph([
0b01110,
0b10001,
0b10000,
0b11110,
0b10001,
0b10001,
0b01110,
]),
);
font.insert(
'7',
BitmapGlyph([
0b11111,
0b00001,
0b00010,
0b00110,
0b00100,
0b01000,
0b01000,
]),
);
font.insert(
'8',
BitmapGlyph([
0b01110,
0b10001,
0b10001,
0b01110,
0b10001,
0b10001,
0b01110,
]),
);
font.insert(
'9',
BitmapGlyph([
0b01110,
0b10001,
0b10001,
0b01111,
0b00001,
0b00001,
0b01110,
]),
);
font.insert(
'A',
BitmapGlyph([
0b00100,
0b01010,
0b01010,
0b10001,
0b11111,
0b10001,
0b10001,
]),
);
font.insert(
'B',
BitmapGlyph([
0b11110,
0b10001,
0b10001,
0b1111 ,
0b10001,
0b10001,
0b11110,
]),
);
font.insert(
'C',
BitmapGlyph([
0b01110,
0b10001,
0b10000,
0b10000,
0b10000,
0b10001,
0b01110,
]),
);
font.insert(
'D',
BitmapGlyph([
0b11110,
0b10001,
0b10001,
0b10001,
0b10001,
0b10001,
0b11110,
]),
);
font.insert(
'E',
BitmapGlyph([
0b11111,
0b10000,
0b10000,
0b11111,
0b10000,
0b10000,
0b11111,
]),
);
font.insert(
'F',
BitmapGlyph([
0b11111,
0b10000,
0b10000,
0b11110,
0b10000,
0b10000,
0b10000,
]),
);
font.insert(
'G',
BitmapGlyph([
0b01110,
0b10001,
0b10000,
0b10011,
0b10001,
0b10001,
0b01110,
]),
);
font.insert(
'H',
BitmapGlyph([
0b10001,
0b10001,
0b10001,
0b11111,
0b10001,
0b10001,
0b10001,
]),
);
font.insert(
'I',
BitmapGlyph([
0b11111,
0b00100,
0b00100,
0b00100,
0b00100,
0b00100,
0b11111,
]),
);
font.insert(
'J',
BitmapGlyph([
0b00111,
0b00001,
0b00001,
0b00001,
0b00001,
0b10001,
0b01110,
]),
);
font.insert(
'K',
BitmapGlyph([
0b10001,
0b10010,
0b10100,
0b11000,
0b10100,
0b10010,
0b10001,
]),
);
font.insert(
'L',
BitmapGlyph([
0b10000,
0b10000,
0b10000,
0b10000,
0b10000,
0b10000,
0b11111,
]),
);
font.insert(
'M',
BitmapGlyph([
0b10001,
0b11011,
0b10101,
0b10001,
0b10001,
0b10001,
0b10001,
]),
);
font.insert(
'N',
BitmapGlyph([
0b10001,
0b11001,
0b11001,
0b10101,
0b10011,
0b10011,
0b10001,
]),
);
font.insert(
'O',
BitmapGlyph([
0b01110,
0b10001,
0b10001,
0b10001,
0b10001,
0b10001,
0b01110,
]),
);
font.insert(
'P',
BitmapGlyph([
0b11110,
0b10001,
0b10001,
0b11110,
0b10000,
0b10000,
0b10000,
]),
);
font.insert(
'Q',
BitmapGlyph([
0b01110,
0b10001,
0b10001,
0b10001,
0b10101,
0b10011,
0b01110,
]),
);
font.insert(
'R',
BitmapGlyph([
0b11110,
0b10001,
0b10001,
0b11110,
0b10100,
0b10010,
0b10001,
]),
);
font.insert(
'S',
BitmapGlyph([
0b01110,
0b10001,
0b10000,
0b01110,
0b00001,
0b10001,
0b01110,
]),
);
font.insert(
'T',
BitmapGlyph([
0b11111,
0b00100,
0b00100,
0b00100,
0b00100,
0b00100,
0b00100,
]),
);
font.insert(
'U',
BitmapGlyph([
0b10001,
0b10001,
0b10001,
0b10001,
0b10001,
0b10001,
0b01110,
]),
);
font.insert(
'V',
BitmapGlyph([
0b10001,
0b10001,
0b10001,
0b10001,
0b01010,
0b01010,
0b00100,
]),
);
font.insert(
'W',
BitmapGlyph([
0b10001,
0b10001,
0b10001,
0b10001,
0b10101,
0b10101,
0b01010,
]),
);
font.insert(
'X',
BitmapGlyph([
0b10001,
0b10001,
0b01010,
0b00100,
0b01010,
0b10001,
0b10001,
]),
);
font.insert(
'Y',
BitmapGlyph([
0b10001,
0b10001,
0b01010,
0b00100,
0b00100,
0b00100,
0b00100,
]),
);
font.insert(
'Z',
BitmapGlyph([
0b11111,
0b00001,
0b00010,
0b00100,
0b01000,
0b10000,
0b11111,
]),
);
Self(font)
}
}
impl Font<BitmapGlyph> for BitmapFont {
fn glyph(&self, c: char) -> &BitmapGlyph {
self.0.get(&c).unwrap_or(self.0.get(&' ').unwrap())
}
}
impl Glyph for BitmapGlyph {
fn draw(&self, canvas: &mut impl Canvas, x: usize, y: usize, color: &RGB) {
for row in 0..7 {
if self.0[row] & (1 << 4) > 0 {
canvas.set_pixel(x, y + row, color);
}
if self.0[row] & (1 << 3) > 0 {
canvas.set_pixel(x + 1, y + row, color);
}
if self.0[row] & (1 << 2) > 0 {
canvas.set_pixel(x + 2, y + row, color);
}
if self.0[row] & (1 << 1) > 0 {
canvas.set_pixel(x + 3, y + row, color);
}
if self.0[row] & 1 > 0 {
canvas.set_pixel(x + 4, y + row, color);
}
}
}
fn extents(&self) -> (usize, usize) {
(5, 7)
}
}

View File

@ -1,20 +0,0 @@
use crate::canvas::{Canvas, RGB};
mod bits_5_8;
pub use bits_5_8::BitmapFont;
mod seven_segment;
pub use seven_segment::SevenSegmentFont;
mod sixteen_segment;
pub use sixteen_segment::SixteenSegmentFont;
pub trait Font<A> {
fn glyph(&self, c: char) -> &A;
}
pub trait Glyph {
fn draw(&self, canvas: &mut impl Canvas, x: usize, y: usize, color: &RGB);
fn extents(&self) -> (usize, usize);
}

View File

@ -1,211 +0,0 @@
use alloc::collections::btree_map::BTreeMap;
use bitflags::bitflags;
use crate::canvas::{Canvas, RGB};
use super::{Font, Glyph};
// Seven Segments
//
// a a a
// f b
// f b
// f b
// g g g
// e c
// e c
// e c
// d d d
bitflags! {
pub struct SevenSegmentGlyph: u8 {
const NONE = 0;
const A = 0x01;
const B = 0x01 << 1;
const C = 0x01 << 2;
const D = 0x01 << 3;
const E = 0x01 << 4;
const F = 0x01 << 5;
const G = 0x01 << 6;
const DOT = 0x01 << 7;
}
}
pub struct SevenSegmentFont(BTreeMap<char, SevenSegmentGlyph>);
impl SevenSegmentFont {
pub fn new() -> Self {
let mut font = BTreeMap::new();
font.insert(' ', SevenSegmentGlyph::NONE);
font.insert(
'0',
SevenSegmentGlyph::A
| SevenSegmentGlyph::B
| SevenSegmentGlyph::C
| SevenSegmentGlyph::D
| SevenSegmentGlyph::E
| SevenSegmentGlyph::F,
);
font.insert('1', SevenSegmentGlyph::B | SevenSegmentGlyph::C);
font.insert(
'2',
SevenSegmentGlyph::A
| SevenSegmentGlyph::B
| SevenSegmentGlyph::D
| SevenSegmentGlyph::E
| SevenSegmentGlyph::G,
);
font.insert(
'3',
SevenSegmentGlyph::A
| SevenSegmentGlyph::B
| SevenSegmentGlyph::C
| SevenSegmentGlyph::G
| SevenSegmentGlyph::D,
);
font.insert(
'4',
SevenSegmentGlyph::B
| SevenSegmentGlyph::C
| SevenSegmentGlyph::F
| SevenSegmentGlyph::G,
);
font.insert(
'5',
SevenSegmentGlyph::A
| SevenSegmentGlyph::C
| SevenSegmentGlyph::D
| SevenSegmentGlyph::F
| SevenSegmentGlyph::G,
);
font.insert(
'6',
SevenSegmentGlyph::A
| SevenSegmentGlyph::E
| SevenSegmentGlyph::F
| SevenSegmentGlyph::G
| SevenSegmentGlyph::C
| SevenSegmentGlyph::D,
);
font.insert(
'7',
SevenSegmentGlyph::A | SevenSegmentGlyph::B | SevenSegmentGlyph::C,
);
font.insert(
'8',
SevenSegmentGlyph::A
| SevenSegmentGlyph::B
| SevenSegmentGlyph::C
| SevenSegmentGlyph::D
| SevenSegmentGlyph::E
| SevenSegmentGlyph::F
| SevenSegmentGlyph::G,
);
font.insert(
'9',
SevenSegmentGlyph::A
| SevenSegmentGlyph::B
| SevenSegmentGlyph::C
| SevenSegmentGlyph::F
| SevenSegmentGlyph::G,
);
font.insert(
'A',
SevenSegmentGlyph::A
| SevenSegmentGlyph::B
| SevenSegmentGlyph::C
| SevenSegmentGlyph::E
| SevenSegmentGlyph::F
| SevenSegmentGlyph::G,
);
font.insert(
'B',
SevenSegmentGlyph::C
| SevenSegmentGlyph::D
| SevenSegmentGlyph::E
| SevenSegmentGlyph::F
| SevenSegmentGlyph::G,
);
font.insert(
'C',
SevenSegmentGlyph::A
| SevenSegmentGlyph::D
| SevenSegmentGlyph::E
| SevenSegmentGlyph::F,
);
font.insert(
'D',
SevenSegmentGlyph::B
| SevenSegmentGlyph::C
| SevenSegmentGlyph::D
| SevenSegmentGlyph::E
| SevenSegmentGlyph::G,
);
font.insert(
'E',
SevenSegmentGlyph::A
| SevenSegmentGlyph::D
| SevenSegmentGlyph::E
| SevenSegmentGlyph::F
| SevenSegmentGlyph::G,
);
font.insert(
'F',
SevenSegmentGlyph::A
| SevenSegmentGlyph::E
| SevenSegmentGlyph::F
| SevenSegmentGlyph::G,
);
Self(font)
}
}
impl Font<SevenSegmentGlyph> for SevenSegmentFont {
fn glyph(&self, c: char) -> &SevenSegmentGlyph {
self.0.get(&c).unwrap()
}
}
impl Glyph for SevenSegmentGlyph {
fn draw(&self, canvas: &mut impl Canvas, x: usize, y: usize, color: &RGB) {
if self.contains(SevenSegmentGlyph::A) {
canvas.set_pixel(x + 1, y, color);
canvas.set_pixel(x + 2, y, color);
canvas.set_pixel(x + 3, y, color);
}
if self.contains(SevenSegmentGlyph::B) {
canvas.set_pixel(x + 4, y + 1, color);
canvas.set_pixel(x + 4, y + 2, color);
canvas.set_pixel(x + 4, y + 3, color);
}
if self.contains(SevenSegmentGlyph::C) {
canvas.set_pixel(x + 4, y + 5, color);
canvas.set_pixel(x + 4, y + 6, color);
canvas.set_pixel(x + 4, y + 7, color);
}
if self.contains(SevenSegmentGlyph::D) {
canvas.set_pixel(x + 1, y + 8, color);
canvas.set_pixel(x + 2, y + 8, color);
canvas.set_pixel(x + 3, y + 8, color);
}
if self.contains(SevenSegmentGlyph::E) {
canvas.set_pixel(x, y + 5, color);
canvas.set_pixel(x, y + 6, color);
canvas.set_pixel(x, y + 7, color);
}
if self.contains(SevenSegmentGlyph::F) {
canvas.set_pixel(x, y + 1, color);
canvas.set_pixel(x, y + 2, color);
canvas.set_pixel(x, y + 3, color);
}
if self.contains(SevenSegmentGlyph::G) {
canvas.set_pixel(x + 1, y + 4, color);
canvas.set_pixel(x + 2, y + 4, color);
canvas.set_pixel(x + 3, y + 4, color);
}
}
fn extents(&self) -> (usize, usize) {
(5, 8)
}
}

View File

@ -1,689 +0,0 @@
use alloc::collections::btree_map::BTreeMap;
use bitflags::bitflags;
use crate::canvas::{Canvas, RGB};
use super::{Font, Glyph};
// Sixteen Segments
// https://www.partsnotincluded.com/segmented-led-display-ascii-library/
//
// a1 a2
// f h i jb
// f i j b
// f hij b
// g1 g2
// e klm c
// e k l m c
// ek l mc
// d1 d2
bitflags! {
pub struct SixteenSegmentGlyph: u32 {
const NONE = 0;
const A1 = 0x0001;
const A2 = 0x0001 << 1;
const B = 0x0001 << 2;
const C = 0x0001 << 3;
const D1 = 0x0001 << 4;
const D2 = 0x0001 << 5;
const E = 0x0001 << 6;
const F = 0x0001 << 7;
const G1 = 0x0001 << 8;
const G2 = 0x0001 << 9;
const H = 0x0001 << 10;
const I = 0x0001 << 11;
const J = 0x0001 << 12;
const K = 0x0001 << 13;
const L = 0x0001 << 14;
const M = 0x0001 << 15;
const DOT = 0x0001 << 16;
}
}
pub struct SixteenSegmentFont(BTreeMap<char, SixteenSegmentGlyph>);
impl SixteenSegmentFont {
pub fn new() -> Self {
let mut font = BTreeMap::new();
font.insert(' ', SixteenSegmentGlyph::NONE);
font.insert(
'!',
SixteenSegmentGlyph::B | SixteenSegmentGlyph::C | SixteenSegmentGlyph::DOT,
);
font.insert('"', SixteenSegmentGlyph::B | SixteenSegmentGlyph::I);
font.insert(
'0',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::J
| SixteenSegmentGlyph::K,
);
font.insert(
'1',
SixteenSegmentGlyph::B | SixteenSegmentGlyph::C | SixteenSegmentGlyph::J,
);
font.insert(
'2',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2,
);
font.insert(
'3',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::G2,
);
font.insert(
'4',
SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2,
);
font.insert(
'5',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::M,
);
font.insert(
'6',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2,
);
font.insert(
'7',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C,
);
font.insert(
'8',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2,
);
font.insert(
'9',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2,
);
font.insert(
'A',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2,
);
font.insert(
'B',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::G2
| SixteenSegmentGlyph::I
| SixteenSegmentGlyph::L,
);
font.insert(
'C',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F,
);
font.insert(
'D',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::I
| SixteenSegmentGlyph::L,
);
font.insert(
'E',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1,
);
font.insert(
'F',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1,
);
font.insert(
'G',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G2,
);
font.insert(
'H',
SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2,
);
font.insert(
'I',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::I
| SixteenSegmentGlyph::L,
);
font.insert(
'J',
SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E,
);
font.insert(
'K',
SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::J
| SixteenSegmentGlyph::M,
);
font.insert(
'L',
SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F,
);
font.insert(
'M',
SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::H
| SixteenSegmentGlyph::J,
);
font.insert(
'N',
SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::H
| SixteenSegmentGlyph::M,
);
font.insert(
'O',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F,
);
font.insert(
'P',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2,
);
font.insert(
'Q',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::M,
);
font.insert(
'R',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2
| SixteenSegmentGlyph::M,
);
font.insert(
'S',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2,
);
font.insert(
'T',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::I
| SixteenSegmentGlyph::L,
);
font.insert(
'U',
SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F,
);
font.insert(
'V',
SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::J
| SixteenSegmentGlyph::K,
);
font.insert(
'W',
SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::K
| SixteenSegmentGlyph::M,
);
font.insert(
'X',
SixteenSegmentGlyph::H
| SixteenSegmentGlyph::J
| SixteenSegmentGlyph::K
| SixteenSegmentGlyph::M,
);
font.insert(
'Y',
SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2,
);
font.insert(
'Z',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::J
| SixteenSegmentGlyph::K,
);
font.insert(
'a',
SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::L,
);
font.insert(
'b',
SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::L,
);
font.insert(
'c',
SixteenSegmentGlyph::D1 | SixteenSegmentGlyph::E | SixteenSegmentGlyph::G1,
);
font.insert(
'd',
SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::G2
| SixteenSegmentGlyph::L,
);
font.insert(
'e',
SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::K,
);
font.insert(
'f',
SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2
| SixteenSegmentGlyph::I
| SixteenSegmentGlyph::L,
);
font.insert(
'g',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::I
| SixteenSegmentGlyph::L,
);
font.insert(
'h',
SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::L,
);
font.insert('i', SixteenSegmentGlyph::L);
font.insert(
'j',
SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::I
| SixteenSegmentGlyph::L,
);
font.insert(
'k',
SixteenSegmentGlyph::I
| SixteenSegmentGlyph::J
| SixteenSegmentGlyph::L
| SixteenSegmentGlyph::M,
);
font.insert('l', SixteenSegmentGlyph::E | SixteenSegmentGlyph::F);
font.insert(
'm',
SixteenSegmentGlyph::C
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::L
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2,
);
font.insert(
'n',
SixteenSegmentGlyph::E | SixteenSegmentGlyph::L | SixteenSegmentGlyph::G1,
);
font.insert(
'o',
SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::L,
);
font.insert(
'p',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::I,
);
font.insert(
'q',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::I
| SixteenSegmentGlyph::L,
);
font.insert('r', SixteenSegmentGlyph::E | SixteenSegmentGlyph::G1);
font.insert(
's',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::L,
);
font.insert(
't',
SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1,
);
font.insert(
'u',
SixteenSegmentGlyph::D1 | SixteenSegmentGlyph::E | SixteenSegmentGlyph::L,
);
font.insert('v', SixteenSegmentGlyph::E | SixteenSegmentGlyph::K);
font.insert(
'w',
SixteenSegmentGlyph::C
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::K
| SixteenSegmentGlyph::M,
);
font.insert(
'x',
SixteenSegmentGlyph::H
| SixteenSegmentGlyph::J
| SixteenSegmentGlyph::K
| SixteenSegmentGlyph::M,
);
font.insert(
'y',
SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::I
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::G2,
);
font.insert(
'z',
SixteenSegmentGlyph::D1 | SixteenSegmentGlyph::G1 | SixteenSegmentGlyph::K,
);
font.insert(
'@',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G2,
);
Self(font)
}
}
impl Font<SixteenSegmentGlyph> for SixteenSegmentFont {
fn glyph(&self, c: char) -> &SixteenSegmentGlyph {
self.0.get(&c).unwrap_or(&SixteenSegmentGlyph::NONE)
}
}
impl Glyph for SixteenSegmentGlyph {
fn draw(&self, canvas: &mut impl Canvas, x: usize, y: usize, color: &RGB) {
if self.contains(SixteenSegmentGlyph::A1) {
canvas.set_pixel(x + 1, y, color);
canvas.set_pixel(x + 2, y, color);
canvas.set_pixel(x + 3, y, color);
}
if self.contains(SixteenSegmentGlyph::A2) {
canvas.set_pixel(x + 5, y, color);
canvas.set_pixel(x + 6, y, color);
canvas.set_pixel(x + 7, y, color);
}
if self.contains(SixteenSegmentGlyph::B) {
canvas.set_pixel(x + 8, y + 1, color);
canvas.set_pixel(x + 8, y + 2, color);
canvas.set_pixel(x + 8, y + 3, color);
canvas.set_pixel(x + 8, y + 4, color);
canvas.set_pixel(x + 8, y + 5, color);
}
if self.contains(SixteenSegmentGlyph::C) {
canvas.set_pixel(x + 8, y + 7, color);
canvas.set_pixel(x + 8, y + 8, color);
canvas.set_pixel(x + 8, y + 9, color);
canvas.set_pixel(x + 8, y + 10, color);
canvas.set_pixel(x + 8, y + 11, color);
}
if self.contains(SixteenSegmentGlyph::D1) {
canvas.set_pixel(x + 1, y + 12, color);
canvas.set_pixel(x + 2, y + 12, color);
canvas.set_pixel(x + 3, y + 12, color);
}
if self.contains(SixteenSegmentGlyph::D2) {
canvas.set_pixel(x + 5, y + 12, color);
canvas.set_pixel(x + 6, y + 12, color);
canvas.set_pixel(x + 7, y + 12, color);
}
if self.contains(SixteenSegmentGlyph::E) {
canvas.set_pixel(x, y + 7, color);
canvas.set_pixel(x, y + 8, color);
canvas.set_pixel(x, y + 9, color);
canvas.set_pixel(x, y + 10, color);
canvas.set_pixel(x, y + 11, color);
}
if self.contains(SixteenSegmentGlyph::F) {
canvas.set_pixel(x, y + 1, color);
canvas.set_pixel(x, y + 2, color);
canvas.set_pixel(x, y + 3, color);
canvas.set_pixel(x, y + 4, color);
canvas.set_pixel(x, y + 5, color);
}
if self.contains(SixteenSegmentGlyph::G1) {
canvas.set_pixel(x + 1, y + 6, color);
canvas.set_pixel(x + 2, y + 6, color);
canvas.set_pixel(x + 3, y + 6, color);
}
if self.contains(SixteenSegmentGlyph::G2) {
canvas.set_pixel(x + 5, y + 6, color);
canvas.set_pixel(x + 6, y + 6, color);
canvas.set_pixel(x + 7, y + 6, color);
}
if self.contains(SixteenSegmentGlyph::H) {
canvas.set_pixel(x + 1, y + 1, color);
canvas.set_pixel(x + 1, y + 2, color);
canvas.set_pixel(x + 2, y + 3, color);
canvas.set_pixel(x + 3, y + 4, color);
canvas.set_pixel(x + 3, y + 5, color);
}
if self.contains(SixteenSegmentGlyph::I) {
canvas.set_pixel(x + 4, y + 1, color);
canvas.set_pixel(x + 4, y + 2, color);
canvas.set_pixel(x + 4, y + 3, color);
canvas.set_pixel(x + 4, y + 4, color);
canvas.set_pixel(x + 4, y + 5, color);
}
if self.contains(SixteenSegmentGlyph::J) {
canvas.set_pixel(x + 7, y + 1, color);
canvas.set_pixel(x + 7, y + 2, color);
canvas.set_pixel(x + 6, y + 3, color);
canvas.set_pixel(x + 5, y + 4, color);
canvas.set_pixel(x + 5, y + 5, color);
}
if self.contains(SixteenSegmentGlyph::K) {
canvas.set_pixel(x + 3, y + 7, color);
canvas.set_pixel(x + 3, y + 8, color);
canvas.set_pixel(x + 2, y + 9, color);
canvas.set_pixel(x + 1, y + 10, color);
canvas.set_pixel(x + 1, y + 11, color);
}
if self.contains(SixteenSegmentGlyph::L) {
canvas.set_pixel(x + 4, y + 7, color);
canvas.set_pixel(x + 4, y + 8, color);
canvas.set_pixel(x + 4, y + 9, color);
canvas.set_pixel(x + 4, y + 10, color);
canvas.set_pixel(x + 4, y + 11, color);
}
if self.contains(SixteenSegmentGlyph::M) {
canvas.set_pixel(x + 5, y + 7, color);
canvas.set_pixel(x + 5, y + 8, color);
canvas.set_pixel(x + 6, y + 9, color);
canvas.set_pixel(x + 7, y + 10, color);
canvas.set_pixel(x + 7, y + 11, color);
}
}
fn extents(&self) -> (usize, usize) {
(9, 13)
}
}

View File

@ -1,184 +0,0 @@
#![no_main]
#![no_std]
extern crate alloc;
use alloc::fmt::format;
use embedded_alloc::LlffHeap as Heap;
use embedded_hal::{delay::DelayNs, digital::OutputPin};
use fugit::RateExtU32;
use panic_halt as _;
use rp_pico::{
entry,
hal::{clocks::init_clocks_and_plls, spi::Spi, Clock, Sio, Timer, Watchdog},
pac, Pins,
};
mod canvas;
use canvas::{Canvas, RGB, print};
mod font;
use font::{BitmapFont, Font, Glyph, SevenSegmentFont, SixteenSegmentFont};
mod st7789;
use st7789::{ST7789Display, SETUP_PROGRAM};
const XOSC_CRYSTAL_FREQ: u32 = 12_000_000; // MHz, https://forums.raspberrypi.com/viewtopic.php?t=356764
const ROWS: usize = 320;
const COLUMNS: usize = 170;
const FRAMEBUF: usize = ROWS * COLUMNS * 3;
#[global_allocator]
static HEAP: Heap = Heap::empty();
static mut BUF: [u8; 163200] = [0; 163200];
pub struct FrameBuf {
pub buf: &'static mut [u8; 163200],
pub width: usize,
}
impl FrameBuf {
pub fn new() -> Self {
Self {
buf: unsafe { &mut BUF },
width: 170,
}
}
}
impl Canvas for FrameBuf {
fn set_pixel(&mut self, x: usize, y: usize, color: &RGB) {
self.buf[(y * self.width + x) * 3 + 0] = color.r << 2;
self.buf[(y * self.width + x) * 3 + 1] = color.g << 2;
self.buf[(y * self.width + x) * 3 + 2] = color.b << 2;
}
}
#[entry]
unsafe fn main() -> ! {
{
use core::mem::MaybeUninit;
const HEAP_SIZE: usize = 64 * 1024;
static mut HEAP_MEM: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE];
unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) }
}
let font_bitmap = BitmapFont::new();
// rp_pico::pac::Peripherals is a reference to physical hardware defined on the Pico.
let mut peripherals = pac::Peripherals::take().unwrap();
// SIO inidcates "Single Cycle IO". I don't know what this means, but it could mean that this
// is a class of IO operations that can be run in a single clock cycle, such as switching a
// GPIO pin on or off.
let sio = Sio::new(peripherals.SIO);
// Many of the following systems require a watchdog. I do not know what this does, either, but
// it may be some failsafe software that will reset operations if the watchdog detects a lack
// of activity.
let mut watchdog = Watchdog::new(peripherals.WATCHDOG);
// Here we grab the GPIO pins in bank 0.
let pins = Pins::new(
peripherals.IO_BANK0,
peripherals.PADS_BANK0,
sio.gpio_bank0,
&mut peripherals.RESETS,
);
// Initialize an abstraction of the clock system with a batch of standard hardware clocks.
let clocks = init_clocks_and_plls(
XOSC_CRYSTAL_FREQ,
peripherals.XOSC,
peripherals.CLOCKS,
peripherals.PLL_SYS,
peripherals.PLL_USB,
&mut peripherals.RESETS,
&mut watchdog,
)
.ok()
.unwrap();
// An abstraction for a timer which we can use to delay the code.
let mut timer = Timer::new(peripherals.TIMER, &mut peripherals.RESETS, &clocks);
let mut led = pins.led.into_function();
// Grab the clock and data pins for SPI1. For Clock pins and for Data pins, there are only two
// pins each on the Pico which can function for SPI1.
let spi_clk = pins.gpio2.into_function();
let spi_sdo = pins.gpio3.into_function();
// let spi_sdi = pins.gpio4.into_function();
// Chip select 1 means the chip is not enabled
let mut board_select = pins.gpio13.into_function();
let mut data_command = pins.gpio15.into_function();
let mut reset = pins.gpio14.into_function();
let _ = reset.set_low();
let _ = board_select.set_high();
let _ = data_command.set_high();
// Now, create the SPI function abstraction for SPI1 with spi_clk and spi_sdo.
let mut spi = Spi::<_, _, _, 8>::new(peripherals.SPI0, (spi_sdo, spi_clk)).init(
&mut peripherals.RESETS,
// The SPI system uses the peripheral clock
clocks.peripheral_clock.freq(),
// Transmit data at a rate of 32Mbit.
32_u32.MHz(),
// Run with SPI Mode 1. This means that the clock line should start high and that data will
// be sampled starting at the first falling edge.
embedded_hal::spi::MODE_3,
);
let mut display = ST7789Display::new(board_select, data_command, spi);
let _ = reset.set_high();
timer.delay_ms(10);
for step in SETUP_PROGRAM {
let mut display = display.acquire();
display.send_command(&step, &mut timer);
}
timer.delay_ms(1000);
/*
let mut framebuf = [0; FRAMEBUF];
let mut canvas = Canvas::new(&mut framebuf, COLUMNS);
*/
let mut canvas = FrameBuf::new();
let white = RGB { r: 63, g: 63, b: 63 };
let mut count = 0;
loop {
canvas.fill(0, 10, 170, 20, &RGB{r: 0, g: 0, b: 0 });
print(&mut canvas, &font_bitmap, 1, 10, &format(format_args!("COUNT: {:03}", count)), &RGB{ r: 32, g: 32, b: 63 });
print(&mut canvas, &font_bitmap, 1, 200, " !\"#$%&'<>*+,-./", &RGB{ r: 63, g: 63, b: 63 });
print(&mut canvas, &font_bitmap, 1, 220, "0123456789|: = ?", &RGB{ r: 63, g: 63, b: 63 });
print(&mut canvas, &font_bitmap, 1, 240, "@ABCDEFGHIJKLMNO", &RGB{ r: 63, g: 63, b: 63 });
print(&mut canvas, &font_bitmap, 1, 260, "PQRSTUVWXYZ[\\]^_", &RGB{ r: 63, g: 63, b: 63 });
print(&mut canvas, &font_bitmap, 1, 280, "`abcdefghijklmno", &RGB{ r: 63, g: 63, b: 63 });
print(&mut canvas, &font_bitmap, 1, 300, "pqrstuvwxyz{|}", &RGB{ r: 63, g: 63, b: 63 });
// canvas.square(10, 70, 160, 310, &white);
{
let display = display.acquire();
let _ = led.set_high();
timer.delay_ms(100);
display.send_buf(canvas.buf);
let _ = led.set_low();
}
count = count + 1;
/*
for x in 80..90 {
for y in 155..165 {
draw_pixel(&mut frame, x, y, (0, 0, 63));
}
}
*/
timer.delay_ms(1000);
}
}

View File

@ -1,204 +0,0 @@
use embedded_hal::{delay::DelayNs, digital::OutputPin, spi::SpiBus};
use rp_pico::hal::{
gpio::{FunctionSio, Pin, PinId, PullDown, SioOutput},
spi::{Enabled, SpiDevice, ValidSpiPinout},
Spi, Timer,
};
pub struct Step {
param_cnt: usize,
command: u8,
params: [u8; 4],
delay: Option<u32>,
}
impl Step {
pub fn send_command<D, Pinout, P>(
&self,
spi: &mut Spi<Enabled, D, Pinout, 8>,
data_command: &mut Pin<P, FunctionSio<SioOutput>, PullDown>,
) where
D: SpiDevice,
Pinout: ValidSpiPinout<D>,
P: PinId,
{
let _ = data_command.set_low();
let _ = spi.write(&[self.command]);
if self.param_cnt > 0 {
let _ = data_command.set_high();
let _ = spi.write(&self.params[0..self.param_cnt]);
}
}
}
const NOP: u8 = 0x00;
const SWRESET: Step = Step {
param_cnt: 0,
command: 0x01,
params: [0, 0, 0, 0],
delay: Some(150),
};
const SLPOUT: Step = Step {
param_cnt: 0,
command: 0x11,
params: [0, 0, 0, 0],
delay: Some(10),
};
const COLMOD: u8 = 0x3a;
const MADCTL: Step = Step {
param_cnt: 1,
command: 0x36,
params: [0x00, 0, 0, 0],
delay: None,
};
const CASET: u8 = 0x2a;
const RASET: u8 = 0x2b;
const INVON: Step = Step {
param_cnt: 0,
command: 0x21,
params: [0, 0, 0, 0],
delay: Some(10),
};
const NORON: Step = Step {
param_cnt: 0,
command: 0x13,
params: [0, 0, 0, 0],
delay: Some(10),
};
const DISPOFF: Step = Step {
param_cnt: 0,
command: 0x28,
params: [0, 0, 0, 0],
delay: Some(10),
};
const DISPON: Step = Step {
param_cnt: 0,
command: 0x29,
params: [0, 0, 0, 0],
delay: Some(10),
};
const RAMWR: u8 = 0x2c;
// Adafruit setup instructions
// SWRESET (0x01), 150ms delay
// SLPOUT (0x11), 10ms delay
// COLMOD (0x3a) 0x55 (65K RGB, 16bit/pixel), 10ms delay
// MADCTL (0x36) 0x00,
// memory data access control, RGB
// CASET 0x00, 0, 0, 170,
// column address set, 4 parameters
// 0x00, 0x00 indicates xstart is 0
// 0x00, 170 indicates xend is 170
// RASET 0x00, 0, 320 >> 8, 320 & 0xFF,
// row address set, 4 parameters
// 0x00, 0x00 indicates ystart is 0
// 3230 >> 8, 320 & 0xff indicates that 320 is the last y address
// INVON, 10ms delay
// invert the display
// NORON, 10ms delay
// normal display mode
// DISPON, 10ms delay
// turn the display on
pub const SETUP_PROGRAM: [Step; 8] = [
SWRESET,
SLPOUT,
Step {
param_cnt: 1,
command: COLMOD,
params: [0x66, 0, 0, 0],
delay: Some(10),
},
MADCTL,
Step {
param_cnt: 4,
command: CASET,
params: [0, 35, 0, 204],
delay: None,
},
/*
Step {
param_cnt: 4,
command: RASET,
params: [0, 0, (320 >> 8) as u8, (320 & 0xff) as u8],
delay: None,
},
*/
INVON,
NORON,
DISPON,
];
pub struct ST7789Display<
BoardSelectId: PinId,
DataCommandId: PinId,
D: SpiDevice,
Pinout: ValidSpiPinout<D>,
> {
inner: ST7789DisplayEnabled<BoardSelectId, DataCommandId, D, Pinout>,
}
impl<BoardSelectId: PinId, DataCommandId: PinId, D: SpiDevice, Pinout: ValidSpiPinout<D>>
ST7789Display<BoardSelectId, DataCommandId, D, Pinout>
{
pub fn new(
board_select: Pin<BoardSelectId, FunctionSio<SioOutput>, PullDown>,
data_command: Pin<DataCommandId, FunctionSio<SioOutput>, PullDown>,
spi: Spi<Enabled, D, Pinout, 8>,
) -> Self {
Self {
inner: ST7789DisplayEnabled {
board_select,
data_command,
spi,
},
}
}
pub fn acquire(
&mut self,
) -> &mut ST7789DisplayEnabled<BoardSelectId, DataCommandId, D, Pinout> {
self.inner.board_select.set_low();
&mut self.inner
}
}
pub struct ST7789DisplayEnabled<
BoardSelectId: PinId,
DataCommandId: PinId,
D: SpiDevice,
Pinout: ValidSpiPinout<D>,
> {
board_select: Pin<BoardSelectId, FunctionSio<SioOutput>, PullDown>,
data_command: Pin<DataCommandId, FunctionSio<SioOutput>, PullDown>,
spi: Spi<Enabled, D, Pinout, 8>,
}
impl<BoardSelectId: PinId, DataCommandId: PinId, D: SpiDevice, Pinout: ValidSpiPinout<D>>
ST7789DisplayEnabled<BoardSelectId, DataCommandId, D, Pinout>
{
pub fn send_command(&mut self, step: &Step, timer: &mut Timer) {
step.send_command(&mut self.spi, &mut self.data_command);
if let Some(delay) = step.delay {
timer.delay_ms(delay);
}
}
pub fn send_buf(&mut self, frame: &[u8]) {
// let _ = DISPOFF.send_command(&mut self.spi, &mut self.data_command);
let _ = self.data_command.set_low();
let _ = self.spi.write(&[RAMWR]);
let _ = self.data_command.set_high();
let _ = self.spi.write(&frame);
// let _ = DISPON.send_command(&mut self.spi, &mut self.data_command);
}
}
impl<BoardSelectId: PinId, DataCommandId: PinId, D: SpiDevice, Pinout: ValidSpiPinout<D>> Drop
for ST7789DisplayEnabled<BoardSelectId, DataCommandId, D, Pinout>
{
fn drop(&mut self) {
self.board_select.set_high();
}
}

View File

@ -1,4 +1,4 @@
[toolchain] [toolchain]
channel = "1.85.0" channel = "1.81.0"
targets = [ "wasm32-unknown-unknown", "thumbv6m-none-eabi" ] targets = [ "wasm32-unknown-unknown", "thumbv6m-none-eabi" ]
components = [ "rustfmt", "rust-analyzer", "clippy" ] components = [ "rustfmt", "rust-analyzer", "clippy" ]

View File

@ -4,3 +4,12 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
axum = { version = "0.8.1", features = ["macros"] }
visions-types = { path = "../types" }
serde = { version = "1.0.217", features = ["derive", "serde_derive"] }
tokio = { version = "1.43.0", features = ["full", "rt"] }
tower-http = { version = "0.6.2", features = ["cors"] }
typeshare = "1.0.4"
uuid = { version = "1.13.1", features = ["v4"] }
result-extended = { path = "../../result-extended" }
thiserror = "2.0.11"

View File

@ -1,3 +1,155 @@
fn main() { use std::future::Future;
println!("Hello, world!");
use axum::{
http::{
header::{AUTHORIZATION, CONTENT_TYPE},
HeaderMap, Method, StatusCode,
},
routing::{get, post},
Json, Router,
};
use visions_types::{AccountStatus, AuthRequest, AuthResponse, SessionId, UserOverview};
use result_extended::{error, ok, ResultExt};
use thiserror::Error;
use tower_http::cors::{Any, CorsLayer};
#[axum::debug_handler]
async fn check_password(
request: Json<AuthRequest>,
) -> (StatusCode, Json<Option<AuthResponse>>) {
let Json(request) = request;
if request.username == "vakarian" && request.password == "aoeu" {
(
StatusCode::OK,
Json(Some(AuthResponse::Success("vakarian-session-id".into()))),
)
} else if request.username == "shephard" && request.password == "aoeu" {
(
StatusCode::OK,
Json(Some(AuthResponse::PasswordReset(
"shephard-session-id".into(),
))),
)
} else {
(StatusCode::UNAUTHORIZED, Json(None))
}
}
#[derive(Debug, Error)]
enum AppError {
#[error("no user authorized")]
Unauthorized,
#[error("bad request")]
BadRequest,
}
#[derive(Debug, Error)]
enum FatalError {
#[error("on unknown fatal error occurred")]
Unknown,
}
impl result_extended::FatalError for FatalError {}
fn parse_session_header(headers: HeaderMap) -> ResultExt<Option<SessionId>, AppError, FatalError> {
match headers.get("Authorization") {
Some(token) => {
match token
.to_str()
.unwrap()
.split(" ")
.collect::<Vec<&str>>()
.as_slice()
{
[_schema, token] => ok(Some(SessionId::from(*token))),
_ => error(AppError::BadRequest),
}
}
None => ok(None),
}
}
async fn auth_required<B, F, Fut>(headers: HeaderMap, f: F) -> (StatusCode, Json<Option<B>>)
where
F: Fn() -> Fut,
Fut: Future<Output = (StatusCode, B)>,
{
match parse_session_header(headers) {
ResultExt::Ok(Some(session_id)) => {
if session_id == "vakarian-session-id".into() {
let (code, result) = f().await;
(code, Json(Some(result)))
} else {
(StatusCode::UNAUTHORIZED, Json(None))
}
}
ResultExt::Ok(None) => (StatusCode::UNAUTHORIZED, Json(None)),
ResultExt::Err(AppError::Unauthorized) => (StatusCode::UNAUTHORIZED, Json(None)),
ResultExt::Err(AppError::BadRequest) => (StatusCode::BAD_REQUEST, Json(None)),
ResultExt::Fatal(err) => {
panic!("{}", err);
}
}
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route(
"/api/test/health",
get(|| async { (StatusCode::OK, Json(None::<String>)) }).layer(
CorsLayer::new()
.allow_methods([Method::GET])
.allow_origin(Any),
),
)
.route(
"/api/test/auth",
post(|req: Json<AuthRequest>| check_password(req)).layer(
CorsLayer::new()
.allow_methods([Method::POST])
.allow_headers([CONTENT_TYPE])
.allow_origin(Any),
),
)
.route(
"/api/test/list-users",
get(|headers: HeaderMap| {
auth_required(headers, || async {
(
StatusCode::OK,
Some(vec![
UserOverview {
id: "vakarian-id".into(),
name: "vakarian".to_owned(),
status: AccountStatus::Ok,
},
UserOverview {
id: "shephard-id".into(),
name: "shephard".to_owned(),
status: AccountStatus::PasswordReset(
"2050-01-01 00:00:00".to_owned(),
),
},
UserOverview {
id: "tali-id".into(),
name: "tali".to_owned(),
status: AccountStatus::Locked,
},
]),
)
})
})
.layer(
CorsLayer::new()
.allow_headers([AUTHORIZATION])
.allow_methods([Method::GET])
.allow_origin(Any),
),
);
let listener = tokio::net::TcpListener::bind("127.0.0.1:8001")
.await
.unwrap();
axum::serve(listener, app).await.unwrap();
} }

2
visions/types/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
gen/
dist/

8
visions/types/Cargo.toml Normal file
View File

@ -0,0 +1,8 @@
[package]
name = "visions-types"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1.0.218", features = ["derive"] }
uuid = { version = "1.13.2", features = ["v4", "js"] }

View File

@ -1,8 +0,0 @@
version: '3'
tasks:
build:
cmds:
- npm install typescript
- typeshare --lang typescript --output-file visions.ts ../server/src
- npx tsc

View File

@ -1,14 +0,0 @@
{
"name": "visions-types",
"version": "0.0.1",
"description": "Shared data types for Visions",
"main": "visions.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"typescript": "^5.7.3"
}
}

81
visions/types/src/lib.rs Normal file
View File

@ -0,0 +1,81 @@
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Deserialize, Serialize)]
#[serde(tag = "type", content = "content", rename_all = "kebab-case")]
pub enum AccountStatus {
Ok,
PasswordReset(String),
Locked,
}
#[derive(Deserialize, Serialize)]
pub struct AuthRequest {
pub username: String,
pub password: String,
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct SessionId(String);
impl SessionId {
pub fn new() -> Self {
Self(format!("{}", Uuid::new_v4().hyphenated()))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<&str> for SessionId {
fn from(s: &str) -> Self {
Self(s.to_owned())
}
}
impl From<String> for SessionId {
fn from(s: String) -> Self {
Self(s)
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct UserId(String);
impl UserId {
pub fn new() -> Self {
Self(format!("{}", Uuid::new_v4().hyphenated()))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<&str> for UserId {
fn from(s: &str) -> Self {
Self(s.to_owned())
}
}
impl From<String> for UserId {
fn from(s: String) -> Self {
Self(s)
}
}
#[derive(Deserialize, Serialize)]
pub struct UserOverview {
pub id: UserId,
pub name: String,
pub status: AccountStatus,
}
#[derive(Deserialize, Serialize)]
#[serde(tag = "type", content = "content", rename_all = "kebab-case")]
pub enum AuthResponse {
Success(SessionId),
PasswordReset(SessionId),
}

View File

@ -1,15 +0,0 @@
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
},
"include": ["./visions.ts"]
}

23
visions/ui/.gitignore vendored
View File

@ -1,23 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

17
visions/ui/Cargo.toml Normal file
View File

@ -0,0 +1,17 @@
[package]
name = "visions-client"
version = "0.1.0"
edition = "2021"
[dependencies]
visions-types = { path = "../types" }
gloo-console = "0.3.0"
gloo-net = "0.6.0"
serde = { version = "1.0.217", features = ["derive"] }
serde-wasm-bindgen = "0.6.5"
serde_json = "1.0.138"
wasm-bindgen = "0.2.100"
wasm-bindgen-futures = "0.4.50"
web-sys = "0.3.77"
yew = { git = "https://github.com/yewstack/yew/", features = ["csr"] }

View File

@ -1,14 +0,0 @@
version: '3'
tasks:
dev:
cmds:
- cd ../visions-types && task build
- npm install
- npm run start
test:
cmds:
- cd ../visions-types && task build
- npm install
- npm run test

3
visions/ui/Trunk.toml Normal file
View File

@ -0,0 +1,3 @@
[[proxy]]
backend = "http://localhost:8001/api"
insecure = true

36
visions/ui/design.css Normal file
View File

@ -0,0 +1,36 @@
:root {
--spacing-s: 4px;
--spacing-m: 8px;
--shadow-shallow: 2px 2px 1px;
}
body {
background-color: hsl(0, 0%, 95%);
font-family: Ariel, sans-serif;
}
.card {
display: flex;
flex-direction: column;
align-items: space-between;
border: 1px solid black;
box-shadow: var(--shadow-shallow);
border-radius: var(--spacing-s);
padding: var(--spacing-m);
}
.card > h1 {
margin: 0px;
}
.card > * {
margin-top: var(--spacing-s);
margin-bottom: var(--spacing-s);
}
.login-form {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}

9
visions/ui/index.html Normal file
View File

@ -0,0 +1,9 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Visions Client Demo</title>
<link data-trunk rel="css" href="design.css" />
</head>
<body></body>
</html>

View File

@ -1,51 +0,0 @@
{
"name": "ui",
"version": "0.1.0",
"private": true,
"dependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.2.0",
"@testing-library/user-event": "^14.6.1",
"@types/jest": "^27.5.2",
"@types/node": "^16.18.119",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@types/react-router": "^5.1.20",
"@types/react-router-dom": "^5.3.3",
"classnames": "^2.5.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router": "^6.28.0",
"react-router-dom": "^6.28.0",
"react-scripts": "5.0.1",
"react-use-websocket": "^4.11.1",
"typescript": "^4.9.5",
"visions-types": "../visions-types",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

44
visions/ui/src/client.rs Normal file
View File

@ -0,0 +1,44 @@
use std::future::Future;
use gloo_console::log;
use gloo_net::http::{Request, Response};
use visions_types::{AuthRequest, AuthResponse, SessionId, UserOverview};
pub enum ClientError {
Unauthorized,
Err(u16),
}
pub trait Client {
fn auth(&self, username: String, password: String) -> impl Future<Output = Result<AuthResponse, ClientError>>;
fn list_users(&self, session_id: SessionId) -> impl Future<Output = Result<Vec<UserOverview>, ClientError>>;
}
pub struct Connection;
impl Connection {
pub fn new() -> Self { Self }
}
impl Client for Connection {
async fn auth(&self, username: String, password: String) -> Result<AuthResponse, ClientError> {
log!("authenticating: ", &username, &password);
let response: Response = Request::post("/api/test/auth")
.header("Content-Type", "application/json")
.body(serde_wasm_bindgen::to_value(&serde_json::to_string(&AuthRequest{ username, password }).unwrap()).unwrap())
.unwrap()
.send()
.await
.unwrap();
if response.ok() {
Ok(serde_json::from_slice(&response.binary().await.unwrap()).unwrap())
} else {
Err(ClientError::Err(response.status()))
}
}
async fn list_users(&self, session_id: SessionId) -> Result<Vec<UserOverview>, ClientError> {
todo!()
}
}

128
visions/ui/src/main.rs Normal file
View File

@ -0,0 +1,128 @@
use std::rc::Rc;
use gloo_console::log;
use visions_types::AuthResponse;
use wasm_bindgen::JsCast;
use web_sys::HtmlInputElement;
use yew::prelude::*;
mod client;
use client::*;
struct AuthInfo {
session_id: Option<String>,
}
impl Default for AuthInfo {
fn default() -> Self {
Self { session_id: None }
}
}
enum AuthAction {
Auth(String),
Unauth,
}
impl Reducible for AuthInfo {
type Action = AuthAction;
fn reduce(self: Rc<Self>, action: Self::Action) -> Rc<Self> {
// log!("reduce", action);
match action {
AuthAction::Auth(session_id) => Self {
session_id: Some(session_id),
}
.into(),
AuthAction::Unauth => Self { session_id: None }.into(),
}
}
}
#[derive(Properties, PartialEq)]
struct LoginProps {
on_login: Callback<(String, String)>,
}
#[function_component]
fn Login(LoginProps { on_login }: &LoginProps) -> Html {
let username = use_state(|| "".to_owned());
let password = use_state(|| "".to_owned());
let on_click = {
let on_login = on_login.clone();
let username = username.clone();
let password = password.clone();
Callback::from(move |_| on_login.emit((username.to_string(), password.to_string())))
};
let on_username_changed = {
let username = username.clone();
Callback::from(move |event: Event| {
let input = event
.target()
.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
if let Some(input) = input {
username.set(input.value());
}
})
};
let on_password_changed = {
let password = password.clone();
Callback::from(move |event: Event| {
let input = event
.target()
.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
if let Some(input) = input {
password.set(input.value());
}
})
};
html! {
<div class="login-form">
<div class="card">
<h1>{"Welcome to Visions VTT"}</h1>
<input type="text" name="username" placeholder="username" onchange={on_username_changed} />
<input type="password" name="password" placeholder="password" onchange={on_password_changed} />
<button onclick={on_click}>{"Login"}</button>
</div>
</div>
}
}
#[function_component]
fn App() -> Html {
let auth_info = use_reducer(AuthInfo::default);
let on_login = {
let auth_info = auth_info.clone();
Callback::from(move |(username, password)| {
let auth_info = auth_info.clone();
wasm_bindgen_futures::spawn_local(async move {
let client = Connection::new();
match client.auth(username, password).await {
Ok(AuthResponse::Success(session_id)) => {
auth_info.dispatch(AuthAction::Auth(session_id.as_str().to_owned()))
}
Ok(AuthResponse::PasswordReset(session_id)) => {
auth_info.dispatch(AuthAction::Auth(session_id.as_str().to_owned()))
}
Err(ClientError::Unauthorized) => todo!(),
Err(ClientError::Err(status)) => todo!(),
};
})
})
};
if auth_info.session_id.is_some() {
html! { <p>{ "this is just a thing" }</p> }
} else {
html! { <Login on_login={on_login.clone()} /> }
}
}
fn main() {
yew::Renderer::<App>::new().render();
}

View File

@ -1,27 +0,0 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src",
"gen"
]
}

View File

@ -0,0 +1,11 @@
[package]
name = "yew-app"
version = "0.1.0"
edition = "2021"
[dependencies]
gloo-net = "0.6.0"
serde = { version = "1.0.217", features = ["derive"] }
wasm-bindgen-futures = "0.4.50"
yew = { git = "https://github.com/yewstack/yew/", features = ["csr"] }

View File

@ -0,0 +1,3 @@
body {
background-color: hsl(0, 0%, 50%);
}

View File

@ -0,0 +1,5 @@
<!doctype html>
<html lang="en">
<head></head>
<body></body>
</html>

126
visions/yew-app/src/main.rs Normal file
View File

@ -0,0 +1,126 @@
use yew::prelude::*;
use serde::Deserialize;
use gloo_net::http::Request;
#[derive(Clone, PartialEq, Deserialize)]
struct Video {
id: usize,
title: String,
speaker: String,
url: String,
}
#[derive(Properties, PartialEq)]
struct VideosListProps {
videos: Vec<Video>,
on_click: Callback<Video>,
}
/*
fn videos() -> Vec<Video> {
vec![
Video {
id: 1,
title: "Building and breaking things".to_string(),
speaker: "John Doe".to_string(),
url: "https://youtu.be/PsaFVLr8t4E".to_string(),
},
Video {
id: 2,
title: "The development process".to_string(),
speaker: "Jane Smith".to_string(),
url: "https://youtu.be/PsaFVLr8t4E".to_string(),
},
Video {
id: 3,
title: "The Web 7.0".to_string(),
speaker: "Matt Miller".to_string(),
url: "https://youtu.be/PsaFVLr8t4E".to_string(),
},
Video {
id: 4,
title: "Mouseless development".to_string(),
speaker: "Tom Jerry".to_string(),
url: "https://youtu.be/PsaFVLr8t4E".to_string(),
},
]
}
*/
#[function_component(VideosList)]
fn videos_list(VideosListProps { videos, on_click }: &VideosListProps) -> Html {
let on_click = on_click.clone();
videos
.iter()
.map(|video| {
let on_video_select = {
let on_click = on_click.clone();
let video = video.clone();
Callback::from(move |_| on_click.emit(video.clone()))
};
html! {
<p key={video.id} onclick={on_video_select}>{format!("{}: {}", video.speaker, video.title)}</p>
}
})
.collect()
}
#[derive(Properties, PartialEq)]
struct VideosDetailsProps {
video: Video,
}
#[function_component(VideoDetails)]
fn video_details(VideosDetailsProps { video }: &VideosDetailsProps) -> Html {
html! {
<div>
<h3>{video.title.clone()}</h3>
<img src="https://via.placeholder.com/640x360.png?text=Video+Player+Placeholder" alt="video thumbnail" />
</div>
}
}
#[function_component(App)]
fn app() -> Html {
let videos = use_state(|| vec![]);
{
let videos = videos.clone();
use_effect_with((), move |_| {
let videos = videos.clone();
wasm_bindgen_futures::spawn_local(async move {
let response = Request::get("/tutorial/data.json").send().await;
println!("response: {:?}", response);
let response = response.unwrap();
let fetched_videos: Vec<Video> = response.json().await.unwrap();
videos.set(fetched_videos);
});
|| ()
});
}
let selected_video = use_state(|| None);
let on_video_select = {
let selected_video = selected_video.clone();
Callback::from(move |video: Video| selected_video.set(Some(video)))
};
let details = selected_video.as_ref().map(|video| html! {
<VideoDetails video={video.clone()} />
});
html! {
<>
<h1>{ "RustConf Explorer" }</h1>
<div>
<h3>{"Videos to watch"}</h3>
<VideosList videos={(*videos).clone()} on_click={on_video_select.clone()} />
</div>
{ for details }
</>
}
}
fn main() {
yew::Renderer::<App>::new().render();
}