From 9940f7140f8dd1891b7e7e232257fa6947b11736 Mon Sep 17 00:00:00 2001 From: Lucien Cartier-Tilet Date: Sun, 2 Mar 2025 16:07:30 +0100 Subject: [PATCH] feat: add foreign one_to_one relationships --- Cargo.lock | 111 +- README.md | 38 +- assets/logo.png | Bin 0 -> 65206 bytes assets/logo.svg | 1272 +++++++++++++++++ georm-macros/src/georm/ir.rs | 195 --- georm-macros/src/georm/ir/m2m_relationship.rs | 79 + georm-macros/src/georm/ir/mod.rs | 98 ++ .../src/georm/ir/simple_relationship.rs | 66 + georm-macros/src/georm/relationships.rs | 22 +- src/lib.rs | 47 +- tests/fixtures/simple_struct.sql | 3 +- tests/models.rs | 7 +- tests/o2o_relationship.rs | 21 + 13 files changed, 1666 insertions(+), 293 deletions(-) create mode 100644 assets/logo.png create mode 100644 assets/logo.svg delete mode 100644 georm-macros/src/georm/ir.rs create mode 100644 georm-macros/src/georm/ir/m2m_relationship.rs create mode 100644 georm-macros/src/georm/ir/mod.rs create mode 100644 georm-macros/src/georm/ir/simple_relationship.rs diff --git a/Cargo.lock b/Cargo.lock index cf64eb3..205286c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,9 +73,9 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bitflags" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" dependencies = [ "serde", ] @@ -97,9 +97,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" [[package]] name = "cfg-if" @@ -254,18 +254,18 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "either" -version = "1.13.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d" dependencies = [ "serde", ] [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" @@ -415,7 +415,7 @@ dependencies = [ [[package]] name = "georm" -version = "0.1.0" +version = "0.1.1" dependencies = [ "georm-macros", "rand 0.9.0", @@ -424,7 +424,7 @@ dependencies = [ [[package]] name = "georm-macros" -version = "0.1.0" +version = "0.1.1" dependencies = [ "deluxe", "proc-macro2", @@ -698,9 +698,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.169" +version = "0.2.170" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" [[package]] name = "libm" @@ -726,9 +726,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "litemap" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" [[package]] name = "lock_api" @@ -742,9 +742,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.25" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" [[package]] name = "md-5" @@ -764,9 +764,9 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "miniz_oxide" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" +checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" dependencies = [ "adler2", ] @@ -840,9 +840,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] name = "parking" @@ -982,8 +982,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.0", - "zerocopy 0.8.14", + "rand_core 0.9.3", + "zerocopy 0.8.21", ] [[package]] @@ -1003,7 +1003,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.0", + "rand_core 0.9.3", ] [[package]] @@ -1017,19 +1017,18 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.0" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ "getrandom 0.3.1", - "zerocopy 0.8.14", ] [[package]] name = "redox_syscall" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f" dependencies = [ "bitflags", ] @@ -1075,9 +1074,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" [[package]] name = "scopeguard" @@ -1087,18 +1086,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.217" +version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.217" +version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" dependencies = [ "proc-macro2", "quote", @@ -1107,9 +1106,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.137" +version = "1.0.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b" +checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" dependencies = [ "itoa", "memchr", @@ -1172,9 +1171,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" dependencies = [ "serde", ] @@ -1422,9 +1421,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.96" +version = "2.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" dependencies = [ "proc-macro2", "quote", @@ -1444,13 +1443,13 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.15.0" +version = "3.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" +checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230" dependencies = [ "cfg-if", "fastrand", - "getrandom 0.2.15", + "getrandom 0.3.1", "once_cell", "rustix", "windows-sys 0.59.0", @@ -1578,9 +1577,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unicode-bidi" @@ -1590,9 +1589,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11cd88e12b17c6494200a9c1b683a04fcac9573ed74cd1b62aeb2727c5592243" +checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" [[package]] name = "unicode-normalization" @@ -1889,11 +1888,11 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.14" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a367f292d93d4eab890745e75a778da40909cab4d6ff8173693812f79c4a2468" +checksum = "dcf01143b2dd5d134f11f545cf9f1431b13b749695cb33bcce051e7568f99478" dependencies = [ - "zerocopy-derive 0.8.14", + "zerocopy-derive 0.8.21", ] [[package]] @@ -1909,9 +1908,9 @@ dependencies = [ [[package]] name = "zerocopy-derive" -version = "0.8.14" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3931cb58c62c13adec22e38686b559c86a30565e16ad6e8510a337cedc611e1" +checksum = "712c8386f4f4299382c9abee219bee7084f78fb939d88b6840fcc1320d5f6da2" dependencies = [ "proc-macro2", "quote", @@ -1920,18 +1919,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", diff --git a/README.md b/README.md index 9b0159a..9c313e9 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,10 @@ A simple, opinionated SQLx ORM for PostgreSQL + + Georm logo + -
@@ -13,26 +15,22 @@ actions status - Crates.io version - + Crates.io version + - docs.rs docs + docs.rs docs +
-
-

What is Georm?

-
+## What is Georm? Georm is a quite simple ORM built around [SQLx](https://crates.io/crates/sqlx) that gives access to a few useful functions when interacting with a database, implementing automatically the most basic SQL interactions you’re tired of writing. -
-

Why is Georm?

-
+## Why is Georm? I wanted an ORM that’s easy and straightforward to use. I am aware some other projects exist, such as @@ -40,16 +38,12 @@ some other projects exist, such as my needs and/or my wants of a simple interface. I ended up writing the ORM I wanted to use. -
-

How is Georm?

-
+## How is Georm? I use it in a few projects, and I’m quite happy with it right now. But of course, I’m open to constructive criticism and suggestions! -
-

How can I use it?

-
+## How can I use it? Georm works with SQLx, but does not re-export it itself. To get started, install both Georm and SQLx in your Rust project: @@ -121,9 +115,7 @@ pub struct Author { Congratulations, your struct `Author` now has access to all the functions described in the `Georm` trait! -
-

Entity relationship

-
+## Entity relationship It is possible to implement one-to-one, one-to-many, and many-to-many relationships with Georm. This is a quick example of how a struct with @@ -132,8 +124,12 @@ several relationships of different types may be declared: #[derive(sqlx::FromRow, Georm)] #[georm( table = "books", + one_to_one = [ + { name = "draft", remote_id = "book_id", table = "drafts", entity = Draft } + ], one_to_many = [ - { name = "reviews", remote_id = "book_id", table = "reviews", entity = Review } + { name = "reviews", remote_id = "book_id", table = "reviews", entity = Review }, + { name = "reprints", remote_id = "book_id", table = "reprints", entity = Reprint } ], many_to_many = [{ name = "genres", diff --git a/assets/logo.png b/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..8a4239f915a4f2f8e4822c70d5880efbbf06b315 GIT binary patch literal 65206 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%0DQjs*)cYuu{O{}8KH>EM$8L2lH0ZYBjC2Y z?9Th}E0z=1S(99M8TVPqa8iBzYD$?v_jYkrv)#m%Vl>=X_PhM}EPyT~Pl<5Ne_n|P zImK*8$i54qm;(`9>il~MMGUT{1>(KMEVav^pOS_ylD2w!T5MKCQvj-Z8yNk+)YkS( z`Wcnw7zN?;w!=NPqB{T*Dm zZp1wpqFS}?i4+iA~JcYpD8 zy`TF5KT=os)Y!#|&~)yCu6LQouN#^eD*$c^G6O#u{cvVAJ6%w~(jHXv0drC{8VIq{ zWS43}U4v92;_|X~X_Z$)2M`CoAyFotorkF-ze=X;>NpmARvfyE2bLlH&1AmjI>L85 zj9Q?=Lvwv1=s~F0=!fapc>$O|w}kqJVI5mKg$a zt+QzciC-+jJy8`QmBV{mT)SXHBk1tlgkXspN$fiQWpcZ2)FJN_1#9M^?;763$;t0yGqRM&%-#3!)vZoI{pU5I{G@-qyt+m(n>uaq zN*^aY&pycJmmC~tq09k!M_VW%f!v4JL5|zq>^F;iSA`90Usu`A1;1MVRg(|Svnl2Y zz-;hU7q&BA{-)?^vH5DFBKMVJw)H6?g@)XhS`Vl!;I9O%1@_)09EanL57m0r+9FtR z7kT+HFM>Jx<~*W)VQqNbK-# z>u0yY-mv{v42sZKp?QjAg>1d&fm|G;ixW>!bpp?b?BDs;GhJzsj?&fxxS=8jpc4-mt%Ye?Am$=LZY1@Drk4;?B(iC!KyjeLkA zQ~$;QwaAy?l@RYiztJr|zS5vGIGj`uaUED$CW~qg!A zt#FUbY;`vAqH;`2A&AKKL08h8OWXulmT27=-d)tU<461alivfR#e~bFe5A)40^inM zpQdj%uHJFamNHR_3bQYBXmfsEN_4(&%KitUF;Qqa3-FK@03xfsny`9)mQj$VOz?Pp zM=@M|Cy@bdwwAtEv#g{0bc=#&4B zFzL2-dUKw%m@y*n2oC0r`=z#xdp8d9W-1b8ws{s;EOZX_KDDs2HJ5MJnz2@w-o?v^ zCf(AYc)v(Sq2G1IjgU{8vLfyN18{ga9Z|{`C+&{XcR?VD4g^=#oUQ2pg(ZvIZ3%k~ zEH>yoeyM(4eY$z>FO8EecE7+6MTs@-Maatt)xi{pv(3_FDuLQPTTLuAmvYVy-(QTXmG#RV52C#egElbutBE zB^@FAEP`SqR<{Rghn!+c>c>W|Wbx@(bJ z0(Tm;#o+AdGJF_6S=JJn-h1NV2G64$?(?0mFUJ;^XY2%B@L?2Za74J(bhxpDxzdL+ zkxff!z>4q;d%u%(Bx++hd3YP(aWwaD;tw%;41&m4E<{!G9X9WOw|CdSYW(n!kX(y< z0Kll!&WMVd*DpDqFFdAfb|*Eg5TxW^vpuMgg$~gt(tP-b6}V9uHofq)(;3e{z^mH- zW#;i1@;R`~`jEz5oqxSq4e$A3rr=7Yn7b5wV=}V6CATFx7aW2J*?T39N{8+Okpeeb z(=eA!c0tX5g{q;hkxL;Vy#fJ}3HgC~2$7a8R7Nu!(`XH~QR@>-n&os`*ZQ7fzq=ju zxMqV>sWP+JT~;ggQz}emaUI)aHH!+EyxHx(qoM8HBqs-_8@Hkjla-6SvGCCMr z2&(^(4&iJdLW1mj;OBqou()22`W#IC!Vg;+~ZW~R9g za;yF1%iD+2^xn%j^WfizJz3vhg(#UpV#FCCVI-fBm!AipX$l62d?9Eq1ODk=+k*eK zJ9SHcBZBIEd9(WlV&7m;1a1jT<=?A~P<9rUQg`N4eXu4;gwRs6`AG5&^NP-3-+Xo5 zF4MUWmScBcg?w?Ddu}~tmbjw5qw@&d(pEF*|I1C5t9@$a&(LM6-Jw+dzR8x&DAy|6 zp!)8wck>Z7=r^#52yc#i39p zQo#M1#TcmFs6#VF{0^^lyIxKx$}plTfxkkdW`L<>D>n7h{wA-|o+T;a7m?nxAJ;|* z7AFu+ zrLo0r%)kh38H%y_kO<+4rYs@}bvuFu&rOY+{;kw)Sa*Twg)`117oPzZ zBR(N@$r&V{>wknG-HC;zzt5n4}xb=~`Ez~hve zdDq&Y9q@W7P_@5g`SOR5(D&NcF!&J9gYsLBuJYOnypF}|r4a98t&t;lmJt?N2!@l6kn?&PJ}Y^T9uVgOEx zD3FiX1M&0qd`q^y+vwRCYQz6#kHGQbJI>B^Eb&*`Gj@w7+Mf3w5^;;LmB=NQ+u4kp zFriN*zt>me|FS9V^CnTYVcrn!p=sgy1Ib{JMu`{y)AcCG!*5|Vq1qnT7krm^=-Edw z>$Od3=goOKcyAeu;sg7h*i5@N9Q(Azl+bU|N{lfio9`?iPb)arP2L5u0Mu*4Wa#1?^T>$i^;oXHaA zLR7HPXqm*}oz;pjAD{o-8rL0KX}L;osJOHnhvr&m`GQFh)Q8J5Drxc{zX?mW9fb2Ct; z`Uj!71>wuZX2_cFgNb6g6E4^10~$7C-5JR~y84WBFf!FpmfuHNmh)jm@Bq`D#ph*5 z$ofa=>%Ye}Xr_9fsA2(S{cocNB;J8I27a*y@B8G)gnYagugiW{X!3s7oH?F53LlSu z*psem888na)MwUsL_sBl>=6~!o&ME&d@X+jIc-BBHYMm@y_+p6^IA+ouUOLz^u2hz zE;u?Rnn^0ZeLTSumlhP%OFSCYHM(!+@`Ne%q>YDI5KmKCWJ~LFcfpl!bhsUsSTi5| zB_W);`n&}hOzR{qUg*@rG8|vTDH8EHoD5kwt(J`x`;^P~p7U%ujN}p<-Xt9Sw`;1@ z-_>P1SFr*k4EBB#52A}%r~m(3fKO-e+;N%a;uTlGn?-5s)l1*^mBOyW0$;}_My_K3 z-je~&Q+Xyf%3y(4Y4N@PJ{$l$zP zjAbjAIf{TaBTx8QIW0952QA^(FLSmY&W*8y?0bQO-q?dG@BR$9d!v+n(t((E0YaJz z>ZB2R7wr&paa}StzzRN=Sus5+9X*k;5iqCxaaq3jpsut0MWfBRX^Fu=eukn2^r|n{<_4dvvMQ651*zW08CJmi5EvAH+3`_Ipms<|&r8vF4nU8oOZdPAF@Tf)&o$kuC1K|YV75?RNL9hB=*NaT#so#74 zkIT!xo8SKV{rl(ywO!!NA$O23^dMz>qeU-OEUD1>`tt`dqs~efUoysF!&-eht>O#sStBu77E2zz?9=p zJ+oL46HpBO#^{HE!AEh~m7GyV;7COBv3ixcXa-2eRxxeEU+x>?u{FYe-Yu>~^S5$m zQBl#M`T(G*P##+6%W|@|{TXbKF+T|7JFHY-5BLxXty-Ji=|I{-`}~b}-~XatfDp-{ z$04-p{?MT9?oQ@z*$c*P&4YYUmb*<QSSt`I(zF$`h2hs8(WQ8i#AoH6)^RrJ6YCZ&g0JSJ-iKhut9ra_=1wsh(; ztjUbz=$OTb;8m{&_&K$6%CE6oBp)sMd%j|r7&{Oigwe#{E>!?fCR4EpE*X30+w&_P zD=4UQFrt3P{uFY3eSP?|Zq((BU_1i5?D1&QcXZPC&5L#)M>?g}U=|0|xkt>J)JgL_$xb~t z>AP5)^*m{g!MH3_?isEwLbHcjkK{2dkWZE7USFd3uT zJqg_b6M~Zu&^w!XOwUdf&eWvze{@xJ#-UO9hVc%xhpAXQ&EMeYaij+xPDanuJda$! zo|8r=rRKgKizB7ID>Z2B>=_aPXh>pqbsb zYiSbH%XuS24H4+1Lh)d+y{6SKw7pIYW_dovdTi!Vg|D0_M;6IqxbPM8-Qy2tzq9Ik zmK~Lqdmt^GBS1Wed>hnt9R-maq3=lkWbW-NZJH4)m)$5*)O;WoQ|MUb1D*Q9<9wlt z>GcD)N50@_c5p9N1+2@!-JB0fD$9kn=6aK=`LYmdre$hckZ%(oepQ^9H7A{ZQ&6Qg zL>1e#L>;rwe)##Y8=6Z!*$ol6H{FPLZa)#&nZ1U{j`R3M_9qZogWIByZJR@{E1!t2 zaXI-Dy?sQ}{#r&8%)0}Bcu=BBP}@Aw(NAd(fR9HSC+h9u;eFILb>_bGiVj9$Z6Heu zsTJp0Ey{siXXR&Hi5&50565>ytSb9mitLhUlQ7H~xAt#!{;o`Zo?o#mxg^0dA%-cM zPMfZ{BZ=w4U)0PzozKW9uCJaU`0K$)yV0HV_VV>W#~$VV^>3@Q9uj1Ru-JaPX)!J2Qf+g;N!Hf1O>vm$pKv#3jS4@5iB(Jjb0ss+pTNZ9YvrW zAB|J~<6TTjW@m*?Hob5#1jkvwa2v`$-4Opeh&8XrFrSR|r#0-c4qC&toNSs+Hd|No zhh>C{)TL5C@-HH`?mgk0Mq&K*qN9GKX{GCP%2y0@fR*Qeb*z=*)1*r|5vLxHwDTTf zKF}VAMDnR)ay@-ABqDa21@-n@M=(DjV_)+JI0}0C>h1N@JJWAN|7!Y0pt$J!oq0*u z=B#P&4^9(Cb1?Resr{wL!H(x9+!=)8Y7@J7@0+&l83%b!qZ%q`X4*7Iq!IAjtHExA zUQg4hZA@|ddBcnR-e1WWT5eKb&Sm}nRU-5bcyy33t*P*a=iGSxQu2Bz*70O(Fv5|n zD5+A)AhLPJ1x=NW=In9X_7k20LLyV@W_ncX3MR&`i1$?0m|J zHY&90rWT2%j^Btm-rlRDBhg;3`BsmnXD^?0@gSzO@ov>eG<%(VcU+^tU(hhAC?DvK ze&XgwOkqzmRFBK*El>e?s6Ecw$Vw=tZv^Wplan@p0^jJT9pM8AxnF`dYUOz;F;ffH z3}M^DWiq(X7g?2O4IPTr%Mr=DdL$}Uc{pa~5mAFbL&=0+bG(kZGm;82JZL<(>kE8% z?8x;34l2ucj!Bki4wDjzLHI66d(P|kZ zh(P{HA}X6IoSsJz9GtT(*W-+8sJxT}wdiJq=cWqimb36rjbp++Vc>|vxtJ~UHR=cV zoCU(!Prh;LOg@ijqQpwOF zw}mj<}aE|heNqPlvQJg6m3{OwWvZrRMe{nT}%9rfL9!|vgf#8)iz^EW(zkaR=!^I2== zTkm-$hICy9tm?}<6%y|U7LGMl>e}rjxf@j)gDA|-b5cvihDm|1_%L04(z(tqq`<6gmkuZ6Z2NofyqU(4#iof@hDbqA7}DQJqH7 zxE~jV!q26s8HW^(addIn-I21%67hQm6idcn{i|urFOBcIFFW049&gKPS`P^H%Mycf z*@>!wb37LTk?MxpV_cW1rn6E7L1HkfEmRkT(LeF&ze!P}naVng04zR}ddC)NB#lU_ z>&%d@C#Sd`XB>@A*+L*i#F?p@<$8h$3Sp+Y;zneWJOF2v=8EtWH-cu~r0?kiCSJpbGS$UNB%&#doML!lDxCX+go}XAx!m?8(Q8Hpmtc4<_nGQ$Q$c@P(x1ZrfnGT<%?6V!PH;we&oY;W}G#?h6? z)PyN?SRtc#E*l|tP`upesN)L;`=B`nK%?Rh?#Qo=o22Fl`uggwG-Rfd?4LNr?&Qd? z_b)kkmDseVoD`LpgE@I*tQk2>=z<**(g<-8yqKb8J$(5-qw1aP1e+Na=}r3*#XR(= zNckVbO1c-5tkzTj$Pd0Sb&<%)#qGnQw{hKb7e$oI{r;#M;K?zsX{`Go| zq{33!M$!wPQ-dbAJ_d5Yq}WYs4upW!L{<2Vq?o z!JdOI&POZF{dYzdJ}C@Z5o|?EvVN4#-hXW>0J&YOU-W6zMjq=VCT%3mbdrTn<@vq zJr)EA%6PwgX89nZ7!%IgdjFQH}pu7;yVt?LS%>xgil~~O}hM^ zxO^a_4`zMGM@@l76?T23E4b2NSt@gI`e%7AXZ*bY(0}LGCtamJuu47VhLQ4Uz$c{& zBhJbzt41%NLq)59({q7pK!nT+SAW2fwZkYlDe!eNlf>i+L!G7TZD)|_bhy)DdCyEi zA?~pd60Xt@{Vuqdn?{YS?n7Ns=NmIMbn0~%elH$0r5G`7=8InD(PcGs{P`_JZYSk4G?-M$9T7mK0QzTw?pOo5s2V06*ibd zEE<2Tch+`qBu7U=SMX{3E@eTKy8k73V~1-~LOGsg-$o{lkvLA$_ntW%(|bx z7iy{IIqSZcl|OQKcPj`v~$tgE*_nRD2z59IetYaIxw&qO38B$mh-tn9cs z9OmuR`~kV>0wpuNo|;>^AL7QE-stHkZ*cXl_@ePi*@V(~UH1HbOiP4psh{D&}VpVeLx#&4JRues!aK=fR)Dnt8g)`A8}PIdA-ym081GTd}=YCn;pBCS#+y z;DYP1)js7}u;`DRpQCPrE!SxEuk1)q^@q>`|K>)(X|rD-Z-zF4De*gMG~&lx{X0`3 z+RA1zoS`)6>6$IjWnLxjme^@B(}IL$9~u)OZ@JBb7Q#6BhDkvj77vvUX z-C@HW-e_z!)BdFS2Q$9#oHedr=CaFVghNWztH$TEK^I!YV3?WdA+*cT9~f?u@l1cP z5!3ViwbmjdTMi7j|Kiuy#0!XgPSM9!dZ=dTM(KR49k;bBKp^Gv_=^Q}|7+`e5!f47 zbIKRf-yf{)GTsaToW_14;4iI59h?za&$&b&Rih)r;#e0OL@$1E7E;na^`%q2Uh@<9 z9#I5^+e)>qY|VFCxot`_C~43@FPp-oH<>*dO5y`-#yt#bQAX*!g8YHcnI6JVXZM$1jT{?CTG_3J+IFZR^@QqiVM>q4R|a3i9}_>wRRy z|9VoU%IhXQyX>^fMC`dM2Wq}EAu+GC?O<+H@NV%T#+V@3mKQASA*-^Qr zaMm#-PyC0!$9C@fk7kGmh&=|;I|xE=ZF1DGE1e_N#?5_Y#6NX}a+CFxrXf8?yDNo4 z#hxl~=`?#P{9e4^CT9{p^SGRJxo~O^``mAgRT)5QJ=@Pj>~LkdJ}CmOUpBaIE-ov4 zxKhMwJg#wUn|I`d3`Dsoe0D^>;I^?HOY~`92Jm$EsRw9{9Ur#t@R>X@n}|Wryz~1) zttSgS^JQ|_PHd((7`UP@J3ifTugCZ6s++6OVe7;jM*Tt;47~aiJtyK;cVp1t(eboT z;CXY{Q$^Z~0ktvH*Z%sL&C}5!CG-*g-9U0I!fnirP(ZLhK0pD%Z%5s0G%C-7NinDk%*q zCZZZnD)v4&-ZlPIT}kM(37)rFtg5i4g^wb@%`_$0ty`2kyQR8)(9vNEUGx63i;maW zE+TdP-W7Nx;g5;&a);`6y$+vadPBHM6Z}YY_;ZH!vcd!56?TZ;poo@+MhFCVHcWyI|ys>{XDx3LK#)K7uz7y z9#(XzZFu+g^doaWbiE=T1RD{##AzGazC38l;~{iLQRPV-wVn$1(Op*+c>G<)eY}ML zHGZ|Ey^ViQ02D*#cBuAy8SUXLZ{D3}3<3|-Rgb|P0cS*S3{RZswOW;Ldj+I3fqIKI zch1CHR_|wGd0)Yfqmm#F#O4Q|bSy+>S9Df|AU2z#9C3zOD#N6LsD~>>%I0>PB?zOV(?49(z z6Faj(R{^G--f zXwM`;)$k!QeS%W3>?@23od@0+>^z-LN0@~?pm%KY#5ty+=hw&Ahh;_vqcMJRCDkP; zP01;jk$dHOJ48WwCjV)QgQ}C#~l=I+U-9GC~QtyIL3xg!ZFfF?<(rTxbt*q{$)laF)j%^~(h~@%#6& z7yP~GB09^Is*{Y8V^rum7HGHg-VKr$6uXW$h}it;>z)Y*I%n&0Ju*DO zfApqA&*oIPW=>exbX6Uh?^<23Q62wB>+fQJK?0f)y{O&zDlBy~K zk4UarJSC$vvVhwe*k5nm9$lP}9k}Z2M?#xYY4Pt{`J~+Z%WqkA;^nz!)OQ9$pM^+6 zYbVl2xDT>K6bbAr?P9xHENh!k3n%I}wvcE>V0Et8_>f!{7!H|S!4Mt%ojlle7qc3KUItIZo- z5{2PIV#~gEQN`ED=D~GZi^g>TBZfL0%lesZ1r$~INN|VR4%M;K9^wrmwR|f%TKY{c z@HG7>!@Z9taw__>=97GHh~<)|Y-9^7AIk9`m{qa>-Qb8Gy3=oB(ZqcP@@pfr@@?Hd z@Aq8se}_Pw#*)ac@uepetzJx#MZsu7_S?hC?k{?7PT-_oG!`!s*ywNvMw&qSc@*lS zc;Y{N>bj&F%GCBQu9{j~_F+zw0x@QKEt$ffS3B!bsm!Xbr)m@Fh`bsF@VE2-M5Fb@ z1Zn*e41{+auTN#Q+^s5irlwNb>&dVzwf%VSDNG#+@rPWkkIut0mQL+nZaXoZuL1WP z>Ek4JzH#H34Rv><^;XuO_;{-WY=`lXkdc};^J~*Q|5gWO(a9d%Ik1;s*G|^v>D{nd zmtt6#q2JKWV1?G$goybWob%!hkB1RN zXitd19bk{pqqOOvc=kM43%Izr{<&C4HEeNs0nK&Uml&O9#*EESUcpv} zl<c}Y>E?0LsXKh>e{vpfha)Z zA_J#-eT(Kox{U1X`?m8%MIxz5A2x>`LBQjokxaTb;Lw{4U63u`LC2o&0^lS&^)+aB zE-^-bg6e&HFg0uCtWOr%$p8E#`BXH01=3lTo<0OK0qi;ZZuA$?46VYU6ciwVQVi`3 z3o4PNDrM6|)+>aOvg#oXeSY_ZUexn4sO1%|kTrcM9?y1nZQ0*T#jPsHry(j?I@wv# zHmX@{tlz>@Lx;7ZfuW^^iKn|{DA#FC_Ty_$Xf$&zsVOd8UTxj&xqjGF>HOj4G51qg zRs=ITBcC6L@y7QFSmL9&D0cfRN6&{i7JT0sB)pv6IQ<1bB~u@kZ&qD!60JEc>-t#l zC4nzAIhfqDHp03Yb$%X0YkjNS779X`W`$(ERau zoc{*6wfsoXm>RHfXi7Y`T#`LWcGft#`1MY$tcabV&;I}MT z+vTkig6^)6<+n;D$1{xxF`eI4Fo$<)HL^W+t4-*%Iy#z5R`s=E%V-TWVCUGF?kg*z zVH?G~IlU>I_*!N%CI$2+3MPM6o4b%Gn2wk^Rj#cUh&k=g)rK#a=PR;)chR}fjxM19 z!mlKn6E!aInP?07$Af&NeZWicFIRuBXY3m)6*(^(x&5%c6 z4{;~r#+w#=t&<2kI!WSC^_-%{XU26<#J%uy(~R)*8dq(`t0yZ9X6Z`WcY! z?O`9aBGfB%72~U`1F7#lk7J;;>D9HUH8i#qW_xTOOh!2_mN2azA3FKMn}R#2;G7Tx za%6u<4A-ma1Rvv!PymyazANFMj<6B53_z($;97>?430Ds z1g2&tVi`NW?1k9tpgR?0ay9&T6au|0c^DdImB@&fGpoj+>&z1E9|Uu~pX_8f14#42 zTTE_|TQTS~!O5Y-)Mf*NkyAA7v-=}H_fJoj1&qs5((X`ywAHJ74RnfRp4r-LR%zU~ z_KMmrT;UtkoMkbt9yZiCxr-gfcFB*_XQCy3_F=5oo%bwCC1-`7~(ox_F$`#Lc{ z$<>Ff`gxf=F$mEb)Pid<|LrVU(Qel(D#hOn8Q0lveB8otYW(>7mva4zs9TWA*Nc@G zP_V~iwKIyg&mOnzUkaq2$4{s0BwIE@zrXvEIVO}AkSbyBC(5^6YznAWxevFP>P3bH zU?=Zm51W3oGhi!T0na#wR_{3(w>DR86pX_wQiFyQtEs==1t?xusSsu~XGlxd;4KNnJA;FN>6 z6y`-ZU+y<5_qM1wS)N;gO}bqBs`40)?;XnQ$J~3r@IhP_`UUsO3O_}1fUgR^-rA3# z>#ohkx7WXqsx7X9R5Ci(FBa*#U-t!d!zu%J24bvNV23d9Q+92!|H~&>vP{w|`JhDP?D=jd-C3Pl2~ba-xEz-a8L90x#G;YyxcI6gc!`A8fBf-fX1RZ&i z9oFEg?2;V40V~#)s)8D)nz&~gf=^!y;*stSt3%%TDj5slTx@5UKa;Rf0H5>NpwM%I zu&-erZn0jvf0w<0(O_w+Hhpm0lqiK6?B}9LI9ntKlj+&4Z6X>L{pww;i@2mUkLGJr z(_@O+dt%84fIj}|Vui3H%I=^(L_FFx^i6K4EsH`l1X7uXa_H~Z%_#vLuTry)Sw5|2 za*T{U+bGD>HLaZmEjtX0D3^~XAn#6nY;mHks)LIT>PEE!nJ8~@XdK01I895^)Byz? z?4;HJ&|?JXmhMSO8FMc%R`u$&2I&3=H;zdGsodd0bfG|`d@DiuX{Nf#LWp>(Rqedc znK1s#a z^f4tmsAnzwv;oJ&q+jNp^X9xUj#}Asrrk=s>ytXiVpnlk{ZMAWLRGV-67lrS_c^T4 z9bAVq@VqyGD(8T)O&LsbJuYqjolr+l8H$KgV01G2gwgz$2nJMwrnE^x;fbV1F4TkT5(o8ka|TS=v8K0H(*EB?G}x>mLGMg7q*+b8lLr0% z^DU_Fnr((XJ1@x>u8m1Kz);8k{0Jw8*<+zn{=FuRD6)mQrIetIgVDgMKF~Sh*rwH6 z6qne$G=ljVWCSJ+T*h*-uZUZe7Uuhi{!Lw(`a7c4_tgL zQ~spu1efCv%~-GN;&H(&gzzruFZ=yw%1_D+I&m>f0>T*INF&hQE7*cW6sU#?>(I*8 zvzu4oQ^4Jj(zv2rw-v`MYv2|}=o{`G0-c+`!!RvsTj>BY9xWIdeDaIx!=(3M|IgK# z&(@4q%?TAgttj~=TD{x1kzo!8XDI=s>&ydP`Ti9bf;c6zAcv0#UGh60Uc5A{ zf^x>snm2>H9N2*{TdhmzU$Hkc%mjmAdi+(&I0|3WSi^Nt`3OqAd@3NQFj7*>zZaD_ zDS{Mz5b0z%gxQIqZ7tVAt^n$CViPj-#r6FgN22W368Ezz^q|ak(rhdh1=e&L*K?p^Jk$L2yC2~9Y*@<; z43I6_cxbE>7e6S6f-1;#zr1qR_awb&PA46 zt~791A9t>P<~2s+dA=e4GlB(-j{C1)aZETJq3Dj^NHq&C$r7ONfL$NG)^!=)!DBztTj^5}AId@fWQ;mzttIql6t88YCc=#9$v;kq zQ{lTpNR|^j0Y~Ld`{cU8z{!1>n6mtf+*t%cNK$}ck?`*r)oS}!jHJVlLrDaV1s4;e z0k%a}>CWC%LPW>0y^Rz$gY6*oc6(MmZxsYxW@b|Px`B^%V$`X`!N*PJmJk=5&6MFV zKGc`v-{bns>rIxYS(Y~p6^#jX6mZjpIZRr(sR=I$1Wj=%8Zx+%y0ihkPO{io`7?wy z9%Jf&;>8wIh4l^gye0?J6Vbnf7!ck~M}elo~VkVCQ{evt53}P-|Ir+bg0IBdP;V8I8f%c_ZE$ z+!2GV)wmVZG9XZ1UoCrl?S7X>BNnqf(Ghynmw0M6Jm##}?7_<_5RzfJb2}C8hn3AF zDq{pb4lP-1XCNAGaxmh09aH|o?1*~N{=>x{Mznw5_a~BblDbVWMQGezNtFi_BYqe+ z*XQr>g=~SETc`?l&DY&^+0s_iUsJsZZkS9t>p^BZ&})!n@hYN|D)4$bsqHt8!i;?n zz!VmVP#0x9r7gdMkunE}?}&BB=L&zo7tgRYcw%M!PLD66C{zsb5^d} z=hiLE>3IBo@0w^7f{5ejSon80auY%(y7sf%{Cu()9#0G^c|QLc;x#vodlBmKwj$)E z_5+2Um7h_VzP}eAB)1s7Y%%zBwVy@yPrA4eD5>>SxgJGoTElS+=8JLN}VTayYI%>ijiOB;{%jo8IE-CoP_Go zd||o$@6O$R!{jlIJ#G^~Ue#aLWhH(KT^@e5f?b<21iVc8DJiS;AC&;3qka+07iSgg zNhwj74%5-3yRs!${SB%?tLNf}3 zprDKvh}iCF0iCW)2H44eYuE&xyG@bU$$2x)eN>y*bQn3<4k?3*LVVy75e#xNKv_jGhcN<@e2G9_X;tqZZ;o0Cba-6zti`#@7RpU0 zQuV6LFSjXe{Jb7rb$!oa*Iypk?>3Hq!~ib7c-|hOJP`lVf8+g4=Wr@7Ubg zr5czBUSv7D)lt+@8TUG$+zdX@Go2qP#-{YVK6Uee{ElXtG2uzr{C$0%XKKaeX=rHG zTWyIpyg5@VJJVNv!N=6q8fVL4kYx>Mw40(#wq2a#EW64U7RzO|n>89BY)5>zMD&tr z6aC6hHVnI=Zg9R@A<*8^M5kUGb|oR6>wE0f-RMD=(HJdE<#p2G#@JTp8sMv*7aX2) zzB!9(wWO%1Zs!en=)_%{c-V@MYF%u!T^q2f>=+(p>=5(t*ja3nZyvWWNkvo#eB zV!iqBs#C1dkO{R?Fi-eCJp7g+^q{O(nS@s66nz1NRUQXDcv@#hFZoG?e3^FRTBT`Z z@jK-~yEU_>H347bOoF?Tem`QRxZ19?wXjWnk^gm*@BO-LstP*!(DKy;(jXl^3Aaf6 zl81r6-EV*GSkq|>m2vPEzoEF@WO2#DYB~Jh&}LT43lv&{6bN3MAyh}#Etj`2HkMe~ zkuCOJn+$ZTRY5K$P^8B>Ee%GkyXm0?pT5*(NK&~9A7HaN+NBHTYjiq8J9T`As}zPL znTys1{G*B;FgY)4-NvRWw=SyyIHI}e)d-ePn2ox3{uRtKxXm43@|;O9*H_n z*jkHdTB9YYiY+JFty-K`2Su#-yY|FK%tofG+;t1rUQQy?T*p~gQ=h`$AnMC=3mq!{ z&-=dj`6h&1fSx2oXwCCndisEI$4NN|pWz6Fj&+jndIGLw&Djj8Tn(w~TkMr1_CjHz zZKhZ6G$^rk|E0m&gjc@ALwpMwS@^x1dgnYWl?nX_oG1zZr zyxwj*{K@rk?gYZX7p;*`4G77rQ;@%D)KtOSKJ1>CaCWR06%xQFIkVb6IKq=g{I${2 z`Q~xyAo}iKmhPA?!c9BG6mXrr4l~cpkR0x#nR)sBC8aKQvUX^&GGYi>&2C5?}XwyEz{GnTFBYF?WD$#OS|WS>YRIb|&&uD+KnK zmI`zL!NndX#@9WMC#?1q1fvVp?!7ocCoQvgITHxidu9abju{TUjS?*0F4#G~O@S4w z7$te}SR{jfjQhF*vn`8G;UQo*S7Oiof6O?dC1cJ?6}2@=?+p9-w z@G+5>bqO*C>apXb6y#PXtT^ek0ae5k?IC<LO{3uMczWqP}x=>A_QZbV!Q&%Zk^R zSF+?Wt=b@bl@?`Mv)2~Dn~NRxa}1-=NuI2pmk!c+N^)lUwZ@l4`sLqoRwzKN0l|fK z+iGJXNG|6V922Tkfm%@T^XAitd!@FHj;iEb&}1BZb-P#&oj)o4Udjn+8CkRF<*Gi|Po;V92~t+nXdmTJnQyf< zl4OJob4rXUyy%dJ>^m18ea_QycHn+q7exn=1x8lhpUh>>$hD_+T7F!$MapqOCZhdb;yKu36Ok(^HM)z;MKoLRqN@IgmGKMlWt-yVsU>@L1Px&9ISt;0C@IB(_w@EoLEpkMlRlX&;1(fXi0 zwSn17mDHU~aOiUFDR=J~@^SnmJ(E4;Q zVyRR~Z)d?>KDw5G2_xw+Tx}=&wSuhw;F9A5nMo#>sm}e}s)V(0?a0vCLInvVyN_wL zr$v~HyWSzR0(a*Ra;;+PZ-`2lYbr3!sa#HPyBa})rS;nGGHP(Q^=B@l6CQW{jqW>Z z>4Z)1<~%7$Rqo4D9WS1~vr;-*8PV7`GSh_wVAJN3K#IsFIYf1V!1j-qw_jaAXB?LO zn_BMas^TGE=Iu~N%0?jN3@q@ldG=0=BgFoSlu^J1f*t)Xqv=dFA=H?zcAN3)bPB2+ zft>5<%PXM<|5ye1(poO*5o&7(1JsqcDN2tIU-7lYIBpNR9oQkfRWK)|m6!0I=3Ivecg8%;=}OdN(O<1#^1C$hq}DzJ(y zziXU+V5u3-dEVX_W2t#M3yX$_Cz#aL8+;U}GOlbCm$&c& zP(^-&{m&@$=%WHRUzI_urV{bl-xT&7ex)4L!W*dX8Cl~W+@@D^q+-;#V z(NS)9;~*odPfew7W-0m4M3Fd@hw z(u179lV7D0(ahtrSLz%tCLqUGvX%CqGtWa@)t?AVbc?yB{QRJhPV? zV_FxLeSth3Kpx-qrUy@HhFP8}XS7zANG7{^A)59x=KCjDaTk*dTVWaU`L5`Cmn2z+ z`dv5(DvKx=J1i-k9g*pNVyE^6?T`_?v*n4LSih+t(vD>~yBO7OLt?1~=#yge@C1ep$(~?zu*}+-@mfOih60&Vx zRpD8WpxpPdj!>Yafddu4&#AvER|~j5#kaoB3#NXw(ehoT?5?F$hf;O%vCS57sl~)N zQ9vGqx-rF4^IHJ*?w@CDDd^-KA^_WpV_qKOi<%-Ah8!PT!Bbrxu} zo*d)wlS%*cfL1EzQNGJ@cQ6mJ-eR~O8S~xR#e=6R!|b1-?DD&n-G6^x^F+0^AFzeA zt>pV9^N;4l{DSL+G#%MLpgg#Eobt=Y+q<3A#8}x6SXGfduz9(2OtyER52JS&;?i>( z4cXfiGRDqQ(G|^%SnQkf0auZu9}f;zi4Iz$?Q%89e8VnsHC2PFFsDkX@zKgwadDd? z^2?Na9F`~;BE6$Sl*ATSevVPve0I_k2LA?^fD)8dB%Pq*zRTX^LFEtSxxD3Je(>Bm z&R?G-AP(d_&8U$no7x2(bE{3RCOAQyv(77lrQ68iv;_nywyv?xZVM-m2Nn}o4zaMe z@zL~RQ84~mldli3{TAo|1)_ea%PnpR=~0Mj5vT~Z9Y+6W{D8teWyvV3MaS$#+eAp2 zgBd7~r)TGlx4dgZf#mg+Vj*iVaPYa=b3G-i_dkcT1i3n%XgxC@WDhPX%NZcA^lh(D z8(!3FA5zqf_OVpxN0jYa`zQZfRjU}3ZpHmS;9a>?mo|+TTGYs3L!X9JqDq-O%1brY z>8e(FT6Cu&xoQp){}d$}QB{Gz>dp_ItxhV zQ68;Z2=7I9I^X;Inp$>XW1$!3=@$;ELY2*An@sGaPANeO!LGf3sQS%o&>bGl%0!@5&M!4cE7b3EsFAaR19vbNNi_;@y5}VvM=p zv$!4t36~UChy%(@2YoSx#$~H1^oQiU;|SBQrPSI7lB*KVy;Nu490(u@Cld!TFvzo{ zYTkxC=|?HJEzj|`jlMY)`{onmjm5IO@Xm3|0`6aWbb?i}wFl2Nn-f3>vD++F1LmzY zxrf%*QZIdO5fHHP+d)+TkeZEy8WbJJ(s9)o4Q2V zw+O*X+*z>UszgNPxp9UTXg%Qnyw(%P$g0_l@t_KY}z(FHEbAA;N>&F9wm#oSAW$hKFa*)f?-HM+?oULS1S! zQm8t;3MFY!(=4$+vOBYuMDdn$Wu;T&Z3gGeW0U(8!9Y=GwBRhCyL5;!_9uVFCE_GM zYc@8mYQr6=CLMhQB>hG*n@|0~WhguNe=Wdg-rWz77swPoZpV@ba{XLNRR!Mg$fpQ(hYxTT%ldvq~ho)JgG;Apl<1B zSCmYQS6T7ZY0H+^c}cD!65bNF?9(JeNd^o;iAEM7LQZMMwlTAuak=N$hUZ^zgq}km zqwrW0)?ANeQ)Buis7uRB^|xkB#vTnBNPJ$tL0(bRuv62~L(D6!i7wMxj7W#M&`Ji|g5tp6xp~=i3XJ@748c`fGoi zsSbSPH}n$uoE;*Kli*<0ex+$_>H&l==xpCBS0rSe#jS{qTJ??J<4@T2e-?CHgU9?Z zZ}aJ$MUM7%F=Gy%tb7G^b(uj_Q$R6fe;kY>Em|RjLMF0Z3I8O@L}?_W|DowCquP9) zwp*mQTX8Gy?(Rbm3XJt1U?%O*MKDOTulpnCyML*yu_xb@3$>ec9D$GN!R$ ze^aZ#=KPU?`EWR=_HR?lN^eMvd#I#b2abG;yI}CSP^6pH%4@(CK9(!NY-gO3j4;<` zMT$q$au@jg3uimjPBKf3Jbfn=o;b1b*-!7iKqWumCvaOz1K=%U-F`5VF6{ zmml-|=fOAUD=|w|%k^;Zq#4!V0X;TIsP~a53fb&ipkn>oCipwEHZui_d#N-kW$z`= zawo2~j_K%e1%-l)IaT50qiBNRufyXtFB-(?BsXo7nFMZVDxmcW@9^D!Q0_(AEg;&E z_znh^$$+Y%o?fIWJeqy?J%e`L{bmz6+oAteY2aUg=f=QfrmT@X2WVS^zWtr89 zxT%@23?#}aH1}W(!X)iWs%yt8J27vik?esMq%x0kZ5@IG#;wP`*WK^swi>m1tZJ?s zDExi^qAd77b>iI5S&{Jm2G%p0rAF6RIlB6MK2rhTj-)5M;IMDCW#kq-p%ex~4>i|o zOj;9_sIy1-w6s8@V=-RyDonGDO-);OaDOnElr@B>q`tTA1_E#J&G4Nr%;=7E)V1Aw zE?1WpHSl~OB=Lj1xeQTSf`@pdah+W|yuuTh%g z2AbJvhEngXBU3k)c$!4aI0qg2ZarKe8l>0R&zKJ~W4500xBi`gT9)Rx8+2kdua#~^ z!*h%!JC2(Zhe##Kz=SHQt_a&xL!tYiIft}|ZLXceOxWPhs5ll5jxwdB3-Icj*w_1& zswR6J>|Afu7>%57?jO`L*#{xOHujVmrOW54*mTtb(voR}Mj~`rCsu|#2uBVn9aL4~=yC(=v3T{tOX(0a-;V{eq z_eQU_A~{i!HR?uMBurqxmymh4lRe~AkI}mpR1m;!5iC&n8rmM)t)%|bqHq^WwD$av z{K07}Xz~SSZCV$gqQ%$qnzb5d?{z0r0f~@E`tZTzB3Xsf!p)R)RkU!SC10+P9hN~} z*p``2zAO+xuIgwv^_cgmQPO^NmKJU!IKnsev5~&`_S-d=uFwCzs|lY@F8J?HwLZYs zkxhMf%jD^D9L~&1P#wFp3-D15X(@jcG%Tj`yZYBO7Yfm7kFXHcwO$74X=Zt%diCT1 zt+l%54vmo%31)Ig+s82KbCyI|2KH9r#~Fk@uws2l6d;yG~lwK_%suOU z71k7fKsgh$yF5@_A`_CG#3&1aD(%qDeQ*03v<6jH5uX{FYTn1O5kM`Qx>Sv@PLWrT z#U5)=(kL>Vxu`bjWwBoAyMA32yA-bce&!~n-x`FE$!&xCp-MiMj#Xy(t4Os7ot;om zXRq0&rRf5r--?+TkEQ1OkKtL;dfJ}Tq+@b<8&HF8z+!i6&>J6FEv=%08GDMrayO05 z7T0{yZBdZkWkD1vOap`%rNvppkcnYwppMf}RY z06TnL1aW{O1aWUrx}>OfoX4y&ON=Q=;;E(4ay6~=EXgqXXI5-9wG@C?2(u?g#^uRi zbz5|l{i(n|;BLj4R}69ozDydqK1oAcT1F}3=d%l*h$r2iuT*2=?D%IIohKg2oMMEw z+a}>q%{GhXL90r-Ut_8<6zj$jK!r-EidtfTChbUJA&ECc@+Z?GnCvxpuleMWnY}q{ zt#pJkbaD1OO$8=-JSazn71KSUZ3Tbybo@-niDR}vx~k3e7*F}4rvZ96F;vH$r6zYN zc^rH{mfCeQ-92gY9MDV#k#D(J9g(Ac&X>{(jjwqS`+eiaF#gZ(r6hy}W{KA0-4rxs zs@^fapWs%({R|WPy_7{gdXirCa9Z+u(7~qE;CpB+ZX3SUl*pB`=alx)N)qp*1qWVL zsB8Z+82QQu2y<>J#{UpR1c<%@K4YyE7`i}RD>sdBHg#MRFM{K%pgDLuKyadN#Wr_%!Po#E8b(9f_Z)2!1s^{6yzl{~f};C}NpW;TqZ z&Z7y*R%b0u@NFTZqWsRo{yKzz0MCk?QDmrVdb(EWdW-m>xmLCHSBO3Q{L_wo zM;!X6$cE8bpzDzvzpOw0WF^t&CS|b ziE~@^WEkY0AT1w@ZM6>I1I*Wn_foubh;EK3+dmeYrnjmJ7fDr8TGp~ ztyE!w`P;|eL)DcE`$_k+zn(A*aMDT=c6~Kib8q&QelYs;3lod5!|11e8a$F0kvVKI z=lY!L`c`F#hX1$NU3m&rjPD~*0>laY$sptVw`6FFW&t~@3EBuVh%AJxpVxH87yIwJ zXoe`&uWK!Mi8T}h15e!A@F$J=avx4XUUMEGaB2Z41x%lQ&N>*z`y2*rGGLl$b=$jG zQQ`|a+;h56d2lZ!DAz-$-Dlc9zUL99HO@`i*tXN5`lUSZH$j~m38(h*(! zW5-A&53+%=svIOVw|dzFX@IH-sn{!CoDWS#vBNCd5oko*1vjrI<2$^c+i$e zk;6yeY|9?68CHlNjST1vd3aZX3$kL1i5#pDq$JShmzY_!W%nGhY8mmt&b+RY_82E#(QC3pgCp)+Gy2>BQzmy;9(hDmXC9HmbV5M(Y!_kx9 z2^jyPq7Z4}qBtZ0z++ED7V`o;FABvSN>|IYrsYz$uRAWc)$kCajnK^zsK>(;BJQ3$ z&Uh{8b#)ssPsvT8a)frHlw1g_IE5alfe}&*jx&SPYr)G={Pv}6QsWDd zI8J0o)NwbHd*n>tqYKT4Zeu4Y!T&tY5l5bzW(xX@2vU3Cb<)bME15|F8HA$$vcW|oi8GhjniT(XKH5VO4qPO&0uU2qG zc&8lT65WRzZrd4vAk#jpmx&8Pv8A3 zM)hV}c6j*DlQuIo4j`GgTKj|hfRgb17r)#R+Nl}DeRA!`?KLiTYetck1?Cm`9u$F% zbPLGZLiutt3yU*m&`5?s-Zn_Kv|3EgR|{qilXy_MAcNX3pR+tQ6bU5M8FC$+DZ!|< zH2n!yq@0>KXU~5n=6OBRVw4SY0mSZHqKkOVs{H#0qqTXon;j?X!A# z18iX8aw*mbPwm!H;)YE9)*criHDOq7Iefz4{bhTqv_z&2<^5Sc3X%iTV8|#sp0{3cLaTePLfP>ERliTNpt&!)|WR0xp#yR!AXtH1Vf%6jCjmfkKCCy zpDSh2qo$e*&h)+0rMUK|=GO+!D5jrf;pGWl_fs>qpe{Zw$$x%x-%71o7*`gA&4YI< z`_ud-hKz)eQ@MTKh$oynMeb*c-&GBwS(UP-k})x}vL0pgSnGQBW^}~f&T!z=gC9k%z7aj9nWuZ!W|_|>4>&lexB zAujEPYQqihHg&Afudg3&FE3?9d44*EJG>W9!+oLBYCu-La+UXQi!)$+YZySLvXjr& zqG3qr`ULL&zAu*FXKnZ#quZHpK<@=*`@X!ExeG9J;SF}DTgwK(L{jZE)E>6$R6D%C z0NF~Y4|h8ODueGiyFgv%3Ue^|5d?*<_47-kK=cdM~&bJqv& zuS~i{9a+2vgw@$i*6xYHl@~kjh%DC)o_eqjk7(rHFT6nMZ1CpzzbQ`j6DC?a>0-q8 zf|v)yP5QW~NlH)d54piJawuDY$JPcjTLwp=i)vLs4LP^{3kdbjw>&|8_lhMTY~bZN z=sNZJs2jzoTg(T!2lT~g7#Z+H3GhGmxQ;{Dz4NQeX=>uXXamDL3}8HgP~mb4!@$XW zsDLVAjI{**Tc*7vAC$;%h9CK#(lRI{Z;Y)@L8U2J?=kY9ry1kEh5B@;2|8g zKVK&$afdvz&(j^7ZoKMDOWZy4R#Z_g{3cH_eSA-Fl(b)yT}J>pQM?fC`_933zjO-N zsotva((jEM)2uRN05Aywq+WAw>=Hyv#6MyF+sqHT?25oXA8-9<^5-)aRQ#Hnn9EE- zQxiK$;+b1SghQiju*fKdmxXd1I+?y~N699mOj=;ZRoaJF)C_b3yb6_TR+)rFWw0J7 zAt^i0>JG`oh%EAWdii+_*0g%URzdE!wuX2rT7&wb{GIbhzm%_RRR~Z)!a3jQ-RW>+ zQF~x_CNS7vf`f}KMCn+QNoPLu&m+IN+8zI$dwX9eJbQfZU9a0ufoLgx z`$NrTqGviuFw--gWw<>InaluQY<80R0MGLPcdMo$59y^!ydDw>pzlaHru&wWEG#^$ zG|b5B2AJ-P+D;-sn8x_{r*b z%qxrVLABFm{7=!zzvNHxk0uKz8o7D~Ixa=HJkO__pO5p!M7lxQroJ2cwwi{{OTPpF z!cg-b;|FA}P?6}ay_d1BO{cQ@(r@+^Jwp|0Jv(<+LbJ`EK&s8HmJ7xg0T34ATC?m@ zs%1EF>8(8MCfCGq<5zH4s%}rt7$V?>+N9-N_@7@vg}?m$UQOjr;4k$-A($BWD0K$9q>9$26vAc?oX>&T8L^TYtQoa>hNz-dX2 ze-+<+S%@>V@0x5n)MX9u6NvK>TZXJRK)LU;2-=S$tH#D0v};eHBqZFAjW|x{u?|x5 z>-5H+Gj4ACLt!u@mJ*X5uN7AqtdPVXu$M&Q&%(}nkWFH~1OJNPyc?KjE1A>zVhg`y z=zq@f-+|7p7yNRK+H80*NVo>PPM7V!RDLpwaIMr4blV(g1Dm8QZ{3Bg2iy;$2=^?Y zZMe^C{I~g8>4Pp6u=ue2ci?9dtwugK`}s#bf~=M^HoKZ^7n}Rq!Prsi&?cgsu{b-A z8FH=U&I;GD9z5Xg_rkp4I@iwV$@o7c)bqxWekEQ1`v&{JopsMn(|70V_m+D@o)-2E zA26I}Ihs>Qt9tEr%aKh3o(1yeef+Z*mL6L!wV;9$0rp2mBQpT%vXew0Av`f=FBq?3 zN-9-qEWv*0NnaO&6Vhlw&aaVKF07Uo&i`p{09GiyrOJa%aM@O>K?O0d6I}`uzkk^i zt1bO^!&y$T$OA6u79LCx>B0@jrD=MQHp?tx^v&!mT2ja@LdV;S}R)nYyvX7H+81hog+ED?yIXHpGGB5tV+ynKQ;J=8#k$7hg&ryE z@zz_b0C6ya^>*PG^j;%& zA0!i&X`PC{ZO(|$uBV`a?8gsL47PP~n_V&(p?(*I_p8Uf#WHrAzcDOUe(1QbBHMF= z6PPAkCYeGWiR{gM$=!OO$pS|iqA~CY?}J?{;#8Ub{*y40Nl;> zyPbsNI&RxzCg1rwD~io_3FvkJ3h<5_UH#MR<(FqyHt5TtO3!2YMc$-+1I0J^Jy3L_ zAbj)L`Fi``N{`eEPJ1@vZEo6eN8s^oyVYL~Owp4MBkBR)If726k3To3-`w!Kb{u_n z?YY38|7ly;VmhwYy0-VW&cu^Y+?uw}FRlrSq)3_F(9p+h%Gwzl5Z%opl*1l}8npg9mb8g1tp`s=d$T|> zt=^j!_>l}^pB9^7i;17}IKupos`H1j(-Cd1 zSO4=PX-nS<3m%-0YI3JAbd?&}>Mn?!RRrd~4c;1>_RY9<*W8& zC{%sC5sgN<=!lr6Ok!X0nJ}!`zfgirJS$98{pSGA2gtnRkVo0JX1sJ^z#F24_abM+ z#sW@o3xUs`Z#yX^7p{fG#y>m*f?J)fiJ(mvKg^`!EH;7}49z{UbuvN#ac~Ap4Z#s+IH3~t_tnV8%H)ii@Z54hF3p-1U2J&srlO<N1wWY$jE+uogue8@=0llW$zXhc_!_I~W13@%V2`Cjh0Luy zg*V5B@ohJPTlKdq92wL@f&YjnaEh9bAPm%eelP5`B94TJqSO}Ur;)gM?l-uJF55A! z<-Ox+!-aYtTF#tGey%fx002sN2ep3fsoQ81*!I*u z^-F@}#FN+NZ0;zFm)ZT7TKJSPpr1%AkFH|lIXC*iLYau%YyGSmOGJn|nbW{y%`5t8KL$O*;gtOs84#Z(L z(RluPF6ImF+b*=b23Z(>oMF%C7*1;UhnpwdcvEU9J1AzSjtGnTzQjV&X^C#wpu7nM(k4Y z0&q-A%B(SD>AI4AgF9(Lt1ClB35NyJ3~aen%4F-jh6%Y{ABV>ZNFMzJT)M&|_PO_k zcFTR(*Tbr1Ffax-5k7op8W6{@3eQg(K4gbP1(Cjt?N1s#Fsxv9$Cj^7jeDCJ7!zmW z^c2AIQNVllS`7iva@GsS2S@E*DyiN>^jc20m)oEC1)&cv1%WUQlRrzJE9u}C`}@}_ zc4is6QGF;U!2i}Tz7RrHa5o-o1~O3cnjr!l?h(||oyfB-#Z*sXlJSq0e;oY6%R65j z@8}z|m(~yDf!zYzTk>vKdb7KKKx!Y##@Jfn7F@|&9b;b7@bRKKAHOvVZWaTrY`uFO zRzeHK3ah6BN&kW*UqrDEhG&!-zbyRZHOow0__m-)IXT;p_;2;x;bcV%=z)}oL`!R+ zH`!EY57{ksRDTNOM7I4q=sMSh=J$HUs7WsPOFaa2bhA08R~&|Lsc~Ozy7GxVqNY^_uBL#;7Y|jfG_l3Wu-*)8nnHwIpb=pONapXFaw)7Qe!vSkO`ms;m?)g|EV#H zo!=HGjBnTN=3=(6DrQSr^JT=gqgKXFP7$j&V!s1(?~r9C#iA=IhN>--ld=<^?OmAw z@6T9yWb|3tTr=|GWSt@gW)56c`qI}>T3p$yzRgy?`$);>$6A2LDVCJgqCUxRywo}0kx0JXNK zT_&SuU+ebT#zm}oI>zPsw!r?0fR-#4-|IXw107xrf7>1C3+k$hUMTI9 z7k*!-lB1%J>Q3=vKpZtOo(4JXEFu*IwSETbGP5*PO8Vrd=Vp4$oW25&yJvR0!ZKl& z9mf0C6nfD`rOLbQ_=jQFY_Aw^$KPr>mNI$JWp#TexXn&&bc%13Y8{140A8R>puK1H*wr68k>NTbS@&HGHW`!$g=jW7x@l{#&dQ==p%n;bf zYPcQN2PO*rLh@<hb1e*n;YwAQL|7UPOc3D(^#>f!(4r%MY?9?l3LcETY;s^M8;kaHvP8 z=(%iOVZp>)Cf40WqKV5n`X(8CUWeX^4)Yo1SlE(*iz+eEW+iEa5#$7IRLn_jBga7? z)G%*p<+rlg9*0~sgC4~3Vv~L>Z>$*Yk5Rkis_9!@W)A(JFS!7TTh@n%mM_j`O3H9S z3#JC4@L6nI-6~oW(kzW;^x?)x1AzScUxZlz)V|~tNg&lAhmfiK;Je#wNiVzt!@CYl zVmUiS!Gu31Lx7!cWvE9EQ?(8q(=*$o9yKy`2)ulJ?UzY@p65h{$?4e%rw`^Syz^%0|ep5jec&X?@j9^o~@;+!2BH}qHPt5|$6Ar_{O3d~yi(kpa%km&;N@+{tDx(o@ z*}N4urxGg=3w`2-CwFRdOjebeWH$DYq!{Xh#o~%^-+=)2;+MCjQ=&S#D)RUNE` z17X*&vui82=lT=S#0_@>MbqXHkI@_PLg9V>uM%rf zWo*mtPp=Y_Ci{}KM{DPWgJHf(E&G7iXQl)%c;}FQwHjpQZuObb;_K+tp z#bcw*v&6w$N&>F*FBw7pOaD$);TN3v#YIyEkWgH1}( zM!XW#PguP2WBPQ-v>vE3U(Hw&D#CAs8V43p6Gb~X3MzC$doxaoKP{BQcQEFoyoXuX zY4~-dlzn!<1VlOrAx)|_{0uGr^Fbh3B;K795eq`}%*-;I?hgiP-HRYVo?dQl4KeucrQ>h>B6|R!7_^+O< z*u!RL8}J~tM!ovS__DheuK)P=5Nd#yqM~3!)`(Bkr@6o|DE({!YB%i+G%vn_VKTxp z%^wnqRIt1&hg<83gSP`qiV0OY&9D{ASe=w+p9UXgV#&J zw?|PRx%fTSKjp0J6!wIPtek9^*HPw6XUMrEjcdmvGvutL`c*wmUtttpdN_?U2k%nS zu)D7;J(qg=1r^1wCez(+h*X`$_dI_az?=+@{q^f+*%o#i;mnJEne78vWp^q0=~&85 zf@3nGf5$O(qGcZ#@9~+>wZ=KK`fWXz&1T{SW)KaJuR;py7z0#e_BEVGM_Hfx*)!Hu z8p}0A?1x0!coN77Mh5RlLU&u@r96WGo|RVXbtBA|#(4jF^` znTfwLg6h;<15*I8QWoy zhNx@3+`~8^Wb)&=FszNLve!e+DY@ulv1tHJaSWBP6imKK{G8ll+w$q1g|qg*Svfxf z=n`HT6Kd&W0LiiOfT4w?+VdWAW<7G;IvO51+;p==y_vaGYn^`fW+f&MvbjW{pk} zwYshlBYm_xth|@oLm6VBw73o2MckD_?O#sCX7q7bC`VRmaSSWF_+x)* z-eeGp1^=yWjf>HGi(ou|h$Oh+)ZU=2XZJ3WXA3QQ9n#IJMVijSe+{edbSu;_shP<0 zc|3N=^Z)9)BYqzwvZht0KLl!(M`1v{N)Y4eHz>K<7YL5|up9KR{)1~7#{KO~U2PA} zPi(lMAy-=TQn@5Lkd&h!i}c4H%kN6xGH4Txg4ae*(UFa#ablSIu->)z$_m;W0}p5J zx5`lux!;-w_g7ZF;3q-H*Gt-Q_S^{(#~AV|^yzP{EGP@wGsk)phkdZia@(GdgFc}Z9S z&jF&6wR`hVZ^QD@njAOt@)WUj{GmSZ$}HQc3L;AOO%qMH1jSiTM1io8!a#w|Px(WJ zYGaNFo&vX5;DcV+iO{4G^3(kl7(%Nwi8Ow43*?u!9kSGZw;sr3v5qa~r^ zHo|v$0|;{#@$cSkVIdR}#=rWnK`+)#oTPMAja!|_6H%UDFW<^ovB`^%a?Gt`V|Ia7 zB&F2|&e*=KNTPPZcEmgc7Md#6Y#nb-=0pw)<;>vtEA;s~3n{52Lm+etjKi+1y-5J= zh*JFSYU+w5#XvUW~B*JmU;?4we) z-aO}>N@^Rg5UvS-IYy^X?e8x)_+->#+Xw98hiCK+Vef-3UuJYeax)0$f7@bE;BgQk zWwtd?+O6PnDY<;`!_%6$wGN9fHrx$<2UaK%m41s3UGfpv=gks&q6qS4l?oS)68J|{ zdX9IlU2tU!z7O?120XbfuW|jB7U!5kJjV}Il!C@7?t$?4;OQjwqX#nhu#p_G}i~#siI8hLk@f37I_ul=3VkgR$9!+=d zNmPNwmk|g_QY?YcT2cW?_!ISGoy4Ek#D!m0a=pt@%#=_$?R_apzkb>yzTTr~&;#V- z50+h&o51(!JXyWsL`fp(rBE6v3GSO3V*!mrCGCp(cirL2OedEp!}dCSv?J89S2C!q z{m5*~LxJm7TNPt?*jW7KDVe9eQfZaqlt;f}D(PU`%4I@>y27gFeWc_#()Ic>{u zLM?tFF`W4T*@mo~93$6bLb=_TJD)t@Ax4V?W?=iQ6&RL@Ey!Gp503*@_$5C=Bd=&@ zk&;jQSp}+(YvsF&A7mwliBU@(W0sLwP||6n!!l!<1h=k?YkdM`Bl5;;Ta-rV|@7~WTnT9HlEtf2hQA}(5(8P8CM&BR}qhWO_l~c z;aN43;!G+VoyfO61(#8s=*6^0F)hyFq9`3VA_!=tBn9C$>%Qez1iwt+TPJgOzMYV! zc!4>S1}lIvmfIi&<9iFR;w zu~%NW+28Y=Us{Gi-~U9_>3#)SiU3y*dN@^ge!E=wM{}$3qgN`)x%LI{W#it=XDf#x z3eE>ANkMHz7{r}S`9lKlH#iiX9A+hHbG+E;dOT=Z= zXr9Mt$NZ;@)!X&h#}1Lyjuhp;kstbFgR;?*N5iTK-$f(!s}^7*o`pYalzEHOOk4E7 zi*byc?@W|JZSTA*fA*@^qi%2SY&*1NGWgA$ zGRR{hK*&SBQBYpoopL)OgHD|uLpg66ALBjpae}J#h6|F8gLrKAFC_Ug>Z_xy5T#=w z{2>%HytG6CuN*~gxBlzJ5L_S3|034pYGZQZz#M)Vku2$^8LF96SZ>zE+sM=^RA@Z^ z0N+Xg{uTen?O|x#AYGn+Lkfo0wsZhJXgnAGTZ^JSN_{v+CIz%R7uN->&AF^qd8 zV<||A5>)n0Ay2L?uB}1;h~On^bRB#)tA}w0_@>2l=)N2ah5%YqLD$lw$NB9qOWw200#W0wNuMB0xGl zh0YvT<+R>-W1=phaaAVq#Cht-)zn>0gm* z!eKofmsWD7-?vE=F$*dKZ|9W+lj+VVUyj7*tu%ggWL~UNULInqp>+9P_hilB-q)Zv z^3TEE=WZnEwC>3A6nspoZ&71?<5fw&tg^YxGeik`$Xjn`o2FAMcRVC0xtCWRp`7*aNsa|=!Y}U<3CRd3Mym%ct$uFLzqNMjVy@^G|IpW3|Jg97l zIQrMr6nJwSL`f-%X>~Wrb8un$F`YUJUb3{ZvA6ZQbu{z}d|i+YczJSfb@-Vd{s@Du z64JR&!mn)j&y9LS?b7Zy`{9xHEr`ukA*2@MK-nCXkN4@vbp4SmNOfJJiQP|khPzBU zPtPNS%k(q_#zV7yzOp)yka`|zkl#vr=*R%*@ zLF8`hwV&GWo}kj?hu%AjyHH;D+4EBSzv$}3YGz94{bSu)N3j;LOulz{u#yo9>0#-! zIeEJK;qQ}`q4&3FvH{{ZMthEJU_DbQ2;=A^xX~{hf{@1BOVYENjBCKV0e7meiJ=U~ zg~Pkix3g?x9k=B7!E5$;;x=uoLIY<>Y%?~FWX)o&9@?-Cny~x)(5w;jI1Ol>Ez`*Kv@mHR;%!70-9yoN z1m3gcx4(+B`8C1q*9(e*BV~a(VVWP7jel{O1(G~XndO~$ zX4C|0hHKiwFNq4w-kHqZ^W-XGH5Ucc{`xH@NTG$q^lAjF>m;HcW|z+o;(kJw@3J50 z+$r!3M#Sqy%X>SG0?l>7te&F3+TSvgY}TnVMICK=Q1d;u3$ZQPt5G=5rplX}Q~5nV zt{f1Z(dC4(43pvs>+Ie$eOBr9_I>6?@^ZPyd}nOnUt0FiIx9mH`!LrA-oCnsRLub0 z*xuuTo+CJw3S$X157!!v*jdV~tFXm`7v!Gz_6q0f&98}%wkLCksW01;@LT(Y{;eh9 z&7*l`ex#yN7G4Zz*xGz61Tz(or zv2ZS4nrg5sS)tJQ>~L>2nJp2WFZLy!F|LeJm#c)zFwawj{#~N<_`!pXiJ!~iF;1b~ z^7ZN)Q@{j=xaYQnw}T%cU3q-u1@OV4Y%b+$RJWIk7C(%5EsY3%f(cVXb(C)#jL>ji z4p~OD9{C+ZTHog_*Ub3XNN}+>{bnLcNd8&()wQ4ej^v_9*iOrwm8~HKpE2YaZSGf* zQfsZ%VrLrI;Ulevt+12;0d1s(ml*6&K0byzR(3IrQmoYj$aQ%%QP+zs&6Y&Do>}kl zvSMR*G+S?#t9mO&--rkIKV>q5Eob9qD(rR>qsw+{l&eY6SSHlhehduA>=xip--B!_ z*~7YEF;$nP_CWKpl9fT1W9sA$9I&$%3!{2rAAKDxDq&qHT8fK>#cJRnX>4L$t#C`? zn|#TLN^&TJFiUbG&%A7oh`O+1fzW!w+N2e>JUreb5wjcjX#s9gj<`%yBDD^#==kV! z6<0a-q;Nm8a4R|cDlGVf!lu7DOWd$ZvkYU@b&bcQO74MLH)XfYU#5zY3KSoR(wq2i z<03u?9;+E7E(+s?5y1TroZB9uAQYytMG4)dRr5YNbg_QSe6`V-pt?B+Ag*FzOT=cP z(ZZ{V(#EZl8MYIWHphF=kK;A|LBw$kEKP2!iMccF*|p~#jLn--RV3GoC?%^+)(g-c z`;Ew0b=VCbXXpLrX$c5BZ{x}Sqj{jagPyh@;;vG?TB;;SeyU~vPrBOuHXqj??`-`y zRrt=cRX;X1OfMcFa{P`dgUFyf;B4x<1)9#-*f+QQygxR1-qOkDbTiPJ!JA+wV)mJ? zA4qg#(~FnyT596%9R}Qvy%!yC&U{Fdl@+6&&gFUkZS6Br1RTUp=D&!Ww63A*#g&OF zC)v60?m*fdMu+9|4>VTVpF9HsuGxYmp%C8g-d}B5B<3OMndX3eDzi?H>)BM{7JIH7 zepkhz*hiK&MOP9BbacqydMgP&yQzbHEh+m;Nt0+%B6!^r0ggz7oMCWX!M|A=vD__* zAP>Dv&ZaMO;ggf>s-Jn#(XUtA90}AiIBduM*tWSoNLZJd!l!+YmSb(tS3wV(oTb`y zUZ!1Vztb{p4X-wqY;9;9JB0KK(l(bPUgjqfj*vpV;Cdf8bqLymHeuZ*ZqC6R-Up#+OZA zsaT2T54j^EpYnVRu#=U=(OL2FFe%r5oUb=lHP~ld(o5t_BQxdCC`544k;O9BY;HdJ zLH3lmcx+*WxpW4=hIieGC0M|vf&aq-qyfhdQ~x%BO#&%R0!Vv8ec7ThJ5#4}LEL`J zkOR=AvW^zVm`&f;ma_N&x{xP96%RLdsgbWGPhR`0Li5!h8{vD*k`%kGez2rvUQ*OX z<9KfD#JOBV%}7XmwwHUXRsY|%-H?Ae<`<$L^s`@`$En7&Ky=(F-GahuKbG7O-oXj& zo4#y>lh1S{_bA4~^7eQ2IA z?jYAdF1~n<{1S8Oltzr>&M(_1ngzTc24F5fd+F zu;>-%etJMkn=6~_)}AZH#jG%&FwU~~V%XqPjt~-G2=YFk%p_oL|8gl?bKdOW;+Q_^ z|6{vv)Nz2AW|&f1V9efJ$cUB8eI1{iHyfg@vu@TMAfOe(os*t>b{rccCiP{O&Cfll zECb#|t^=2mBtKC#9FtlFQnDMv?KTbhvh~H*e_~fdUNleSO1)P6P?dL{wZPGnCO{#e z3%p9Mp2hRl_Rt3dh3p0u+}K&eBUP=vHhM+AGPCnn($K&>dd78HMJ@qT%Iz-h;Lt_w zvl~aonoJV*X8ZA3uETn@=ZPVmv2%|RcRCM&g{2#8tF+m<@`Ykeu65+OzU-F+&bB%r zT%by_MC&z~-^ttN|4W$zwLBbGVgGc)S!1RzBrqn@JiS%{cb-+vGO7_*1e437m^NLPu{kzTUZszlbz@>^W zYxh{29Tw`Kl4<4;8*7oVai@X4eYcX3nEuJ^7xAaN(9EGNWA-IA=-q#UlRK_=u^!x# ztmgJI!BzHz5&p~{nzpwmel#5r=5ty;SNEz+CUk6Cd1u`8ubC&e_os^%i+lUu%*`M8?>v=j|$6-c^p_OA?D<)9lHcys0j2HlbkK>*m@9 z<#TP(9#_tI<%^)d%VHhu$6^Ul}y#< zl`aLp>G!)6R~XxU39AnNYEvblc7mB$osoFJ1m)jdJq&&rT1kNDfT|;g8jXqF7&uB& z)yn2R$YCXv8+h0AJwE;&kt6VT)?*HrPD#O}pnrP4>EhM;0aCdha zpt!q-;uLo)?(S~IwRmxdBE_w^yL+LOFYm|mQ|P>?j0Ao#r;^k?vE=)XnjBR{XF!K(|=TY)->JmAb=}TR=CLi zwDyZI*EZonbxv{Zi%R)yd*SQF^R^-P|HBpS5 z`=&)X6ikLWZchNDp|SNyk_chTOHO{A9h49+hV5ckjAy5_{c(LpEYN!)x-SfW?sH-#GKAJ%hgN7!-2KV_nAojP+3+lJ8d92*sh*}c3SUeM4a@T zo9XtvxbtRYimo22HdP2Q{}DX!zuIr%j>qcVIpxa|zn8wd3qP7;!VgSqX*d{$aX02q zUUiN3U8&nFxRxr95^MZWoDZj(z6xX!Rl>AiUqG;DF-p$vlt-v}aztF_kS|9JshKuetgo;=U36r+p1Z@zko)1OX$jAOslI_=5s}Jj zsATq#bND;8LohP8dEQNr&-z!a<@%?(IFaO)i=yUeFYmu|b}RL;0MWy6z-Sl>%1DxI z0~dXp0e|S0h`*?+GuJANv#toGrIl+r_qBKiI_uToG_2F_ENXPW+y?ZzeHbbS25HB!IYAQmgDEAk}~l-Y?}J>*Q^ z{bJ9%+QM>>QENHZyo!{Xv^_U_GFLX$+~nYa)o2+{xiof#S75%Y6+^_4w2|vg*StP- zig){8ME)4CFx06``w5#kt7he>2<@Fg*M~ghtoISUI_?(lXPuQbq)4W26JD@LeMV&E zztpjW)70t@7KJ)A`mZnDoqLe7=#%PKJUA11>}eQ>#!$s@j=7lHmM1HfmMk|5s8zx8Ofx zdL4Yq;;uZPNn4gVa&)m?W(UTTyhi=RuV5eYKaWBa|^qNkc_HVPN`&P>`dqyS% zrwmT|^pU5O5rqu!4O=FBn^0M#O+UWu8h!kk=2Zcbx%5-nqj9n!mDaeePmMIf@RtM#>&5$2rC=6#7y!L>Z0l2~(sJ0)$BsnzG-$x#0wK;WZ*K>Iv+GVM9*ZyRWOH|s1I$k~bqPhw5C)XH z((g}uK3{Zd)3t9dXW_#HT*Lr9ylP|Ix6xjn1W$QNWAI7(pHvZN4 z!V-z!v!P6v+|R2JPG4h~r>>4&xrOAN`*7L}eju5-i-7T|W{Z`6YvH$<+cT!+{3dsn zv>PlZ*rC5rBq(N5#zTrWF!FeqkcH97BkHU)HaF&)J)jQ4NR66rw2vcshrxSOQ zY$7aI5d|FLHd@WBR_i~~_`Mj)S84e%E*L2(`GnSGkP+N;&sQ?e4l5Y z{r1lDDC&GRk%nj=7q6`943`)onAK|WOz}T7(iG3CYND6;v5JCe4l0)7#I)OlR)BmT z*GXFJ^&v}UlQo_1$$u3&H&BT+XPPdbk+G?{-lGGg_xHfdTw3#uHW2$DUyxS&YLopZy!;(Jk}NSmmnNvV>LNT;mhQogSmJX?DYO? zh&EC28g(->NV4N4np5NBa8dIjsHPe3alI^D(Yg#Rw<{0WfY`?h^b+?AxNAB)X+w`L zA=x|MoNQwICl`$;auDxA{qQre$!0zKzZHSeq({}thtJ+txh6j_*|*+# zUGQYs%eb4*N2oS^S>&xaS2E@dwxkms-|!{oGOhUYnU=_v)4evzZp5CrquF9f3R5AW43MF@&A&s zc&8Z$oapL(_annyCa?EQV5-sWw4eH+>4d*{kL+(P9>JaTJ(1J>RTaUS*_A|@f@avS zwrnqp{zhg4eW8D1ddxFc(#H*TK{Z5Cr2DB>bUVD`b!8Vw7nH)7uVh_*FIK6)Id`O> zMv*t2P!q18NeN=Y8M~WKJ$ey64EYpi-FjcFY~D#&Z&P{eC4a|~m2sGKHyyax>GhfSo6s2<)H0^xn54wb-(d!IxxEvR!n4X8@9!^~=-(V%*naA%K_n=&I z_-!DleWHeK*7@R^Py9jNbiqsFMP+@N0Td*IXWcgpsbQt65%rRSad;bim=8>3TvOXF zDSdO;Jhhts?i?x;XSE)VXX$44y8%GN#yx<>Db-Fx$bz zKTGiEo4xy*j+U#lL5>N15jN{DddwBHOpT@$OK6L7B~it*98&n(DA+@u26;@gP*h4nbVcaD%(To;V;;zod zM7N#F>J(y7!7(M(kmXXh>fVYgog zcBaUqREu2;=Vf7edfpJtN4D)4e@lvzzt`wvKNk&G32tWy4yv>MqyK3qrA8LJ2w$6| z(a|(wpgHg!K2^-Ph=URg|3^n7+-Optkv!t^*LRCI`|R#zyI@x7l@9vcduSoD$%t!w3{+0=lS*drSoxt zA6lze)jZE~VxGEjf+xwWt%p}EAN5#;#pW}#;rK>^^}hYL40%;_l)KL#!JguuxJYq$ z^37O!D2VVU=6fdf^HON&9>g$GkMn%rkr(!XN&Dws#G;?o?@l54bidxD67m9!T_TKg zy(Y8yH;2o$Z@cX$K5wt*ev_Rtvq$E#^=A5!*iZrLzCdKTh;bzN^wOr1(x#>=+12qc z4#zeQZ0BE0@1=EF{H9lZUUu;05CSz(AmY}Pd$Nw7@MJ07$>MUu%|>MHMi+I6WN-6a zU` z+PVU@#?t16HLo13-+r)kZ>;82YQfdRv@tUJ*Gdg;_k z_cm*CLw#keZNeU55U6N%Pvp{??^|9i4R$;1(50j!FysrMASD>W91J!8%bcGy)ZlEw z!S8uRNg{Cb@%{V1%q@0p*D-h>WT^!m*WG#oe`Rk*t#@4Z&d*66x22&4slC?{%a7-zs0I&)+FMSqqw z^iT!csfiCkmIRDbMPo|uH>_yKdTY>y?f{Zs-wcHX1ooFwDtbQ!a*H7^lB2A)QZH^V zKcVxU+3PmO#FO_UVE$vXTk{mDZ9DiFk=F#C-GN;-*D!8B81}(oaBgZXbRe8uH_Z$O z5EbMrn)tlS$(&2%MD$#8*2?-}lX9Qa3NnO2)WYP|lMQy~Y0TScm;EF>9N}0dd)nuZ7?HgwHWzN&3Y$eS|PD$PIo_MVrTd zufLRQn$kJQ?Cu>~BUC*p2u%KEuCnr~O#WaV2mB)(q}1ecXlUbnld7XA!)3Pz<^Li5 z{vWcNw!`uOx(r!pgn%ubMZJL)5X5WWNv3W58XYl|rn2Lj2Xtx68JL^$b(>`O1!wll ze0|}&YH97pWe(5+Fo@fV3k0~sZ!d21h9)B#&fVd>D8JqU|6Qni^xwV?i;|pg~nYe<5A{u<*I#1O(t=;g+&aGbP|X1CgP8W&XM)v!NRA) zEZZ;ZI)p^efGMR2gaeQ(pkUQrUC5mhx^r%g@))#WPSN(Eck%nL8SJT*l?%La5(rq< zZuP<6uInu=yo;%1e3o|4VqbhN8Hb8hW~_4A|ufO zUxFC-@dJkk&X$$U54Ug>2s~KQGeSMttMe`WjShoMFep$F;c3BRJ*Ydb``Kxta6=x) z71a0bg=`3){0Uqt_ecFJ8{RlkQP(}#?##A!yWk;di`P`ZB*~kAiYk;;NTuU`QU7ov zEYo=U)YRj%bEFpEj4scba+H4@B00t?4wC?hXt$5o)K93c)Mw7@dcT)nM`yxAoP}jT zR};bao2!2CZ|6OK1%0(l(kNzP0q4LJJkK7^S59>8o~ zBUqW*KPgB$R#<*0IQH>%&&TPU2js~U%kz7c@mW8Ak~_0J%@fjVjdD~yb=-%ok%-GTY1##S9I%9&4C6;F5Y zR)`8VYH!=viq^HMW~mYBqvi(__L!M`c=TXS?(fF!ua%J;nzB`$2t@CWWaes6GF>-) z4Rx6D0Oghgn|_a%+e7%5p*f}HhMgOI4^>phh-N6F2*dAUKm_@{VZC6W zstJf4fGmM{e~uN=3YcvY;a-E^YG@rwI1zr8(u5$!3%{WFKhE_;`}ljtBSV`5Wr&mURrsn{j0b4UvktaHy zA6call867LlJD6qc`MYuo;fM5r90DQDiA^LD8V8NM)U;)k3HPZlhtWznCXwe;Ex{> z)H*ni>2x#;RVWfk7=)+SY8&@=)M-a_nA2!sbm?;KRqSdf`Y{MU% zS+c*6Q@%KF56e9R-*YEz>Y0(j1ze5R4=pdD3AY4D?i$T$vd4Y(&a^S_WM?9&F7MQ=jqz=(C#*DN#<0N zEXr*?vM~V31W@jfu2S4~F@a<n=rUI&Ts$ap8KLh{f(R}1OEu=J2BCysk8nmEDWe^a0LTuhwP*fu zT4gq@6=3Vh#Q012`EuOM2=jy(bG(xwXnju5w*jB4+>Wp0Qx`9S8Gd#?hQ*_*@8X0Y zSya1<_U*dMQYYJ9wc+~}c{7u6zn0yj}E^Lt^X=ZULchuLK(n=#Mtf%wPah8RSKxc8o#pH~7zSUZJ_iE50zd&JH1 z4;dIdY-=WIiuug`!ICVdB3l}2pnlY~A%=K#;^D~57o_aFKXo-)*wY_bfqi)AQ?H{% z+_lbyu7oRdG*K+}%kYpW8FR~bL96~B==RR)SJGr3_oJ!tv+_4L=0-Zi8f{ouu zKCk4uyv9jnJp@1d|9cR68wrEmvtV98vmh{@`%PTMBl^JULoOeN=J>u7*N;yg7|asW zQWQe&0lQAS0^QoY(I&+J0O_SHL_z~fgDp3s8sS^lX6Ug>6Ma4lt!M7-JvG9o?w6&g zHH{EmBYAHo+r>@%U}@oEL@wRJcUdeP<7sN!*nZ@lI=>!in= zRx-EBGnb2fM9Cq{3{K{8AG^*gX>&1Qz##42`p*Ey*1}U7qesFo7CDiZ$SNqS)I~L) zM!pPp3^|wHIjqbBjUA6tPQUWjB=L%bDFt5fN**JsD;JqzPpYR=+$rH`)uAAEv7Uql zS2}}d9Z$Cjgy5E)M~4od^uOz9k+U4zQ$uSM_31Hxwr)cemK4b96-w5e_LJ5GzC$E1 z4W0+i+V{ic>73%=Z%kY|+%j2Qv{M^&n4?Q+*3kyALIzGp?Fwle+z;m_^|IvJ)YuPL z1K1_{tsSsFw?>gQDeZ9lInU*tpDz5MwKw4NIj z2{~yUQgj(TC~!Jm%W%+|iaPXG%gJ=#F7)AZYh|^POX7m>x!5AE?aE`v=XaL?r{)~e zGvkdzk0$IO+_S=$Bmziod;uJowDCHf@OC%pCzFA5z`N;^kzLqf#gyKh;42jIO*{Lg zg%wPhj$1dgdL1Q+5;bcDI{k>@y1bKG!*ZjWW}^?2D9eY0gqN{{0#p(ix!D63f!Uad zA~~G+KmMr|{^*4@wn__i%ti5Tw>Zc|o#E5@3}G5=larIMc$r-na3l)C2r)a}l|D_2 z8sMafQUV^qQIepoTJ0BA{ZcY()KN-b`-~9a8*-LQnDW zzW(mGhzlH$fHv5}@j#|zT}Ah6L4L$}z0h(5Puph$1}6&2OL$g1W(zk1h{6y#r6*0c zI(3%R5{ni-7rh`>_}|UwYb!*zHrG0lI?mDxX*zz*PTCUUw9RDoKacC)?CPt{jRZY` z#keU&Dph4=m7@x(nA&pKm9WNr$5`KEtsU-XzsE8^l%Bbv-dw!dNzG`rjm$j%m6KNR?mZenU8}v)fUd4t z5RSzsC28F>Bq734^$?d=ViGFwA?Cpv>htAY*u&W4_>9@W)nCpg zT6$dM?jSif2)Ee@5wP8rd(f!=`qJ?%2Xz>8WZ`3(Q9f*61&VJ&WPC{b$O=F8=;XTh zlrv&euXA8&C?mErGk=FHp$h3{PMFb)r6t)LALzOY3v<7L+O$lK5c0bMDC~x`uoLq%oR3YlI_5*y z(w}t%dldlcRL37sq=X%JVy$iOi?{*X7cXR?m#gNgENLacSn8QM4yOE=qf@!<1mG|= zEs|JU?MnXmRRxxb@(>}a_tC6v^Pykvthx3zb%8YH69(ctA|cAq?l`8itYf@!tUoJ! zJ*XupdQ1rz=wk}$0lBAiQW@?m2?hQ_K)g%|s!sbDafRvO@8X|M)HjN)$)4{zAM0S1 z7?YE$P~8uRg%4jSO0xNSKG|JhZ>xgx=BM}nZj4N1w!3V_ka|0IEq8TH4WbX`uWLWIY%3vtRwh!b}Io2bX)l6{iH!5l9LXwLI>WowqICxkNoA`s+0C}^XtK! zqoKI9xDZ+~3M0wK5!VMH3z0C&KgUr{vpQ6{A9&DyMid^?Xbd#C#11ZQ9n*>a`TeZH zA3fASfOXA#=V~NqMjYJz<)0oOSA$n=PG;EHk7CUW^#ebFw(t6KuM zHI$FrMbn3KM)=Q*V#P-*oaq zc2P)JVj%)*fkjgU$_^NPOEum{i@|M2ea|QTm7pkmI2;@t`XBTt;%MdbV^6{HZ@6j4 z-e&dHsJwUfxvCquL0}0#y{&i%PHd_#qo+~lV!qE+4EA=GF0TYWsBhXZNACWKR<+_u zBU?i*_IX$sQ|)cmAf{c!55!r~y}!~t)NsO}UHSC1MFBtd%o0g#{qTakWVcwO3T$8AH^C!K`qGUt9Fp5E|Qox_8RxcJP$ z#_uRSq^%BnWXkxih1F55xyxa1gWw&7ggtCQbsmGpFUm<*mHLqXnE;gxr|8Yfes#%7 z0GfE`e6a!5T_6$kB?$g{O-EtA)gG{`#$H=n!Fq*ggOi_FYOYzLYRH-s73RIbAB483 zUq#4QQOi3flw3tF?hl5u6qjZ#soZmi zd#=CKk3TwE5|{)j>wK{pyBmo__>58y9MW4Ltb5&numLRy5%H3tIlg1PPFioWvPi60 z=Y2e!#e2*2h6W; zVg~|f(LgSn%i6pG@Gt41oRbHC1Lhn{B_+!eF4)>oO%4Zv0ZS*&kW|Edml4!Q=>r zU!vhBa?Cj%6etP461ENmiwhu@oa~UJiJ{^k%Av-QPC*aD{fWi?&Zr;0aKl#nkiq>2 z(Y4Zpv3w4!954A`#X5rUhT$>>g(gk43NrHG;D(&Shhe9jn$P&)={VBXW8ZeZSuMu0 zuJQ|uj99cgGLENT2Nkt#{i-A)riVZDnB^rHLi1lTAGbe>Jxb7%239QDVA{4e0wMgL zT(D95uPG9sRy74ph=o4k1zf=v=y94;5?mm$nq*47oWHQ^B6zUeyb?e$;h@%tEC zgL_Pt`!IH}pdgGl!gl5>w(&YioTyN|{leKrZR1zM4bOup;(EA3>1jlWDbk7V;d{)K zk<&ElfH$tDyEGHueWA&G{s#|?94U!+Cq<(C zpAcSJ1iRV{a;S{5P3x3(9;1^s>9#757lWvN`(@CR3E7Uo0@J<4$O*~*>AH36UfU@0 zB93(s8aeD!jMJ%y`@4SE^Wx9p^-oN)fs3-dAP!%$_+@CDqqXgEUuk!hC2JDm?^EJu z`I`L%U|6a^$3_WSsM#dqi>#W`Fo!QFWkYgH46mRb zC!2lu>WB=73ET2M118#o)X?g6*lV?GQcZoCHt@!s6XZTS9*5alE?JAcpZUr-CI)bi zZ@1?tLsQ~NJ-H4K6a8MTK7X*o{JCw4x*D7gLk%klft`8U*nTIV5pq=zG9_^fP09EA z7n{LvEyC494J7PbawTB7lA`~XGcrFx^rw4FU+g%@&3?XVUSUSp#kr6qtBVT&Z}+Q^ zTY;8_(}9lu&q{F?Wu3yaG^T8H31&t6NCba`fKjmsY8brk#%AgI&9J4H@hB=X#oeS> zhY&5LIW5NeEeD=)bmqy3Du#5{0-Rq;3-TR#xzs;?=U_{`gIC>bMvpP|WGElU-;DEpX*bBFg=^(-heYVkT6I$Z@V29&Tw>KJ9OEI0C=;@P0O$rtQu&Oig6#BK zNjb9VEn#{y%|!vc;hn@q7XbH17_x2)A-(=!zH6>fq9&3W2=^3xC@~S}!v5vU1}UGS zBUbH z`c%=;+-Gao$qcQRZB?mJux!fjaJsda%q~EX@LJ9l3O^|#i`7)IPK`L$l01cbR;Io) z1%gd|@P-}TdsswaT7WsNQBGeTllQ)xtT+o_pHg{nO{wT|PDdAtkz*@X;`u02r>cvrv( zek(`mh{>R;<^M+FR0_}Wbr5rrSL`bkBZTt&HUcHYD#GI;#F*Z|IPkE~!_bsobr6#{ zK=uk0aB_SRa?v8cRgNM(Y}D)Z@85sl-XKQ-f)7y&{I?6`6`)7zOEN*t7ZDn>sh0xSrtmrf4nf(;hrFEnSALsLAP#<)&GXZXTR1#e_k@?@)2BUrvvhKy^b$ax{9B8}S0 z9qHmdV}pd({}L`mnz~#P&*T}28Mpr5RVQS9PNjbM?z+)M3ZN*X?P4Q7>SFl~#+Tcn zmiTf%>$#bjyhS-5P%?q{HWWnu#`X0{yy=IpP-n)tz)8hbRt5%P9w|%ir9-al!9W1L z+RJpL_V z8Sk_<@1bqpp9&pSn0+E50wh+tyR^Sy+7tZ=xuL3&*5G66!7OqII-&;)wD4Y=ZBwh6(>cXVSBLrVGfo55j;mHoT(5Q)xq5ZSCjfW>_D zFt{E%X5N|`TdfWuj@(;9ayDdF_RE6ugn#?}TJBYH>$>-UKSza7$r`M6e5im}EDFH~ zkOd#D%HA?v{c?)q-$kq3vXrSbG}NCu#wxe$N1{CHZ7RQf=c<3dZ}ol#QWC}s?`Bus=?Mxx+cP~~gkh!m2T2N74?AgxLmG+_%N|4cy>V(-`S8H7idJ84xk=|d z{<;B@2`diAJ5~xw3pXRWBtN<6}ddZzl#CIkMyLvneebBodK& z^8HFueVD=1n#IA3LD4>~lw?9)PHZ5-a&A$oi75uE5&9^O-8qeZkNazlc`wI1wie=KHo_6k6!h2j1H|WMtb|S<^U2NH2)V8(^2mP(6_kf=tzyZR9Loe zn-`^BeC=cP1{JlKyg2mXu*wKDGK^_bnIb|S?(iQNm>2P^$40la<(n}Q0VF^WV>-@9 z2B$beDmrf2+LU6+I_(M)!IV3P_Tzr^`l*8;JXebMedztnPYkUj*C55gD*KfmrG^1*rIFhIS z+YmwKmO0|*`3G&b1g}m9JzWF|1-~H!p-O!EV}y8caO+HK)>`|3|E$?UW%Fhs#Et7( zbMQHsqRsUnJUCp<9BX96Sh5fr`X8=oaaKT(yK?mtjf;@d>{@m5Js2PUt33zQhDvpaF71f}r~9htDKQ+vJ>7egpG|J;KC zqcuO3$fUbdhEASrTVR$#CkGeMv|+51W+%$!oc%=jOj7(WH6UD^0Le;W;csLE8&dA< zzGt6jAk!urQLs~@wDAoXq6NoaIHTVDkzyE+UF>8OQs?&tu@h)B}j&&JR|;1hTOOZ z9Q-T=_^N+o6^k+WM_<`;lJg(4z=qCH3+gR>>pBPX3vwDa!UvMU@2$cU!sT$(&>w0G z7-KV-r_^FR_Mc9fdp5_UPK{LJ-X%D0JWuB8qXy*V_zv;VlHGFCK(e{T36ZSvXZ-@& zA_H3CK25D>7wBXTKFxy^(&9cYD|x{Vt)}fm5~X38S8!!lK<;@q+y-`3YFmH%6RJr!-U`=nX;=-zVre)ws zUQ}w*jBH9S$d25E+d2m<{#wd6C08HX&0r>zDddjRBihs zeW_jX5=Ro?Hy?N&MulBOkwViO9@cbFwDu0tZYoSdj$l-;Ts<396G~uOlNra4tlp>S z$Neb!W)M3fmEDA_hnPZHv(oo3oA6EvrDcL=on?@ZtW<7j!O`RLVVr3 ze*fU8kw@e>L-~0KzY;5*?lgQbFPu4~LQ!syj^YPKuWv;vAZW-WDpgh?wc#hejOs1Z z;ZZd#%*@TP-F83G2_-7IQ{I{PpB;T@tNQUv5``?%+vulorG(g>#BQ7k7C4#g z8_+GM2%8I6xyD!D$s+$~6%KWIYDR@l@ zDoogmaDOdO4AHmL0A=k9h``Si_)G#ROzP#HUK%z?p8oe|c?JLZuekwVm?w9vCv_;K z=)oECa{LPb19(Lh;cXDosKPlK!+=D`w!$V@G|p2oKTE3$lga+D{gax6sX?@7 z=Kj65+?SSFSmcx#$5_>w&ns#Qtw}bKM0;aBh9nm=(5P4=HK921)wg3LQ~@J=e6NKN z-}{tn+?IPv&3AGN*9B!6haIMG6L&De0!o*Ve6C?AW}H^WMDE_wQCx9xPWAVu`t4-_cH4sX%}P_|Wjfp|tCA)%lZDRAr@K^HQj zX3C{OHL#sK-yWN>eW$sFW=$GY+ zcy`{WPD4!jmOr|_=Xi|wU+t#u^C6qT?Z+g-@rhTURzvJ_S* zA4uIt-E93s#8t^77?)y$6yNuA(BlTH!~~QFa3T7EOuP|~;LEYakrUnr@JH+b%ah{< zqW&E7>RME1unCp9`O3c~;NFH(21uzOs~}U#1UYFDP$gE2lLi-2ef}(oqWN9plPryA zEt90tg60fkO6BvFZ5(Eu2DY;6#^I4gPmN=Y3?Jlo)YA>jHiQ|ZX;T)D>MiJ%Oz5e~ zkCg<9R@zeBF-|5s7#)tk>6TP!`;lvD%$#yMg~)=lwxht>1gsPoI3xtc5#^cz zctO;VWN<0umAwcc0MuPi&!EBBuDM{_eb4_|IE_0ot6cd9!>i_19|WKP1^5Cqk;Q|O z)S!@ZW;xo_Z3bE#?nF%5d|Y9y>zJUjLL25v=^Y>D%2KH%-?9S+sywQ8>Gp&4riYTf zr*pxR=qK9zc*mAg+>5cGj`o%)-0S@zv}evaphv;>M&s|-l%DGJcH^qb$dtO-WNF2d za`)74q?)r?BsB$Yy325l-#%GK?21>$!CQk%{J=FPO`LI@5IqJ}yMh!fnxap(w5B!& z$q>(bqkKFbiRp$1ooBHauI{=vafg^s41t&gPZVmB)RNU9xb;}-6K@EI#fAKvOZk6wbL4)ZH5Tamf)sA77(1>2_YT0cV~N0YWSMCZ zh)MES6yCi^cW->3dw-5+K{ZyL4X2{vhD58DE{ZCPL>Z@1 z=1dUBRB^)Ju=ktEl5rQT6hS8qsU+t)v;wV!RF#K{=R879n&hLwoU}pyax`PyQ`C4I z9mv=T_AY7D2DO4Yc5VWKs%gG5A-f~Dob8lj$M|f?wYAlN-X?^1)dBf_)dl%x@PY5L zr2K)s-G2J0;uLp(j4JQqr?u>)3LRr$q26B_TaM>4uPdw8EGX5yhzJ*;9SZ_H86~bv zy&mf@TVlRoKA&-lzRtTPb0YV~Y9Ru^^+^yqy*)@Z{mf@+$0yvu-SZ^5`=9=2GR$*K z$M`bERko*1jtJt(o(SLJ{p0F`x+VE#kKh2UAmm`n;0!!J3gEwFpEfu^!c>nHX*&#O z6I`DgN~3=baBZN65}^zdiF-2GV14ODdmP(&Luf+Solp;bR0qKRWTliKwUQ=5{{v{R Bx*-4n literal 0 HcmV?d00001 diff --git a/assets/logo.svg b/assets/logo.svg new file mode 100644 index 0000000..61b5e0a --- /dev/null +++ b/assets/logo.svg @@ -0,0 +1,1272 @@ + + + + diff --git a/georm-macros/src/georm/ir.rs b/georm-macros/src/georm/ir.rs deleted file mode 100644 index 9045db2..0000000 --- a/georm-macros/src/georm/ir.rs +++ /dev/null @@ -1,195 +0,0 @@ -use quote::quote; - -#[derive(deluxe::ExtractAttributes)] -#[deluxe(attributes(georm))] -pub struct GeormStructAttributes { - pub table: String, - #[deluxe(default = Vec::new())] - pub one_to_many: Vec, - #[deluxe(default = Vec::new())] - pub many_to_many: Vec, -} - -#[derive(deluxe::ParseMetaItem)] -pub struct O2MRelationship { - pub name: String, - pub remote_id: String, - pub table: String, - pub entity: syn::Type, -} - -impl From<&O2MRelationship> for proc_macro2::TokenStream { - fn from(value: &O2MRelationship) -> Self { - let query = format!( - "SELECT * FROM {} WHERE {} = $1", - value.table, value.remote_id - ); - let entity = &value.entity; - let function = syn::Ident::new( - &format!("get_{}", value.name), - proc_macro2::Span::call_site(), - ); - quote! { - pub async fn #function(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result> { - ::sqlx::query_as!(#entity, #query, self.get_id()).fetch_all(pool).await - } - } - } -} - -#[derive(deluxe::ParseMetaItem, Clone)] -pub struct M2MLink { - pub table: String, - pub from: String, - pub to: String, -} - -#[derive(deluxe::ParseMetaItem)] -pub struct M2MRelationship { - pub name: String, - pub entity: syn::Type, - pub table: String, - #[deluxe(default = String::from("id"))] - pub remote_id: String, - pub link: M2MLink, -} - -pub struct Identifier { - pub table: String, - pub id: String, -} - -pub struct M2MRelationshipComplete { - pub name: String, - pub entity: syn::Type, - pub local: Identifier, - pub remote: Identifier, - pub link: M2MLink, -} - -impl M2MRelationshipComplete { - pub fn new(other: &M2MRelationship, local_table: &String, local_id: String) -> Self { - Self { - name: other.name.clone(), - entity: other.entity.clone(), - link: other.link.clone(), - local: Identifier { - table: local_table.to_string(), - id: local_id, - }, - remote: Identifier { - table: other.table.clone(), - id: other.remote_id.clone(), - }, - } - } -} - -impl From<&M2MRelationshipComplete> for proc_macro2::TokenStream { - fn from(value: &M2MRelationshipComplete) -> Self { - let function = syn::Ident::new( - &format!("get_{}", value.name), - proc_macro2::Span::call_site(), - ); - let entity = &value.entity; - let query = format!( - "SELECT remote.* -FROM {} local -JOIN {} link ON link.{} = local.{} -JOIN {} remote ON link.{} = remote.{} -WHERE local.{} = $1", - value.local.table, - value.link.table, - value.link.from, - value.local.id, - value.remote.table, - value.link.to, - value.remote.id, - value.local.id - ); - quote! { - pub async fn #function(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result> { - ::sqlx::query_as!(#entity, #query, self.get_id()).fetch_all(pool).await - } - } - } -} - -#[derive(deluxe::ExtractAttributes, Clone)] -#[deluxe(attributes(georm))] -struct GeormFieldAttributes { - #[deluxe(default = false)] - pub id: bool, - #[deluxe(default = None)] - pub relation: Option, -} - -#[derive(deluxe::ParseMetaItem, Clone, Debug)] -pub struct O2ORelationship { - pub entity: syn::Type, - pub table: String, - #[deluxe(default = String::from("id"))] - pub remote_id: String, - #[deluxe(default = false)] - pub nullable: bool, - pub name: String, -} - -#[derive(Clone, Debug)] -pub struct GeormField { - pub ident: syn::Ident, - pub field: syn::Field, - pub ty: syn::Type, - pub id: bool, - pub relation: Option, -} - -impl GeormField { - pub fn new(field: &mut syn::Field) -> Self { - let ident = field.clone().ident.unwrap(); - let ty = field.clone().ty; - let attrs: GeormFieldAttributes = - deluxe::extract_attributes(field).expect("Could not extract attributes from field"); - let GeormFieldAttributes { id, relation } = attrs; - Self { - ident, - field: field.to_owned(), - id, - ty, - relation, - } - } -} - -impl From<&GeormField> for proc_macro2::TokenStream { - fn from(value: &GeormField) -> Self { - let Some(relation) = value.relation.clone() else { - return quote! {}; - }; - let function = syn::Ident::new( - &format!("get_{}", relation.name), - proc_macro2::Span::call_site(), - ); - let entity = &relation.entity; - let return_type = if relation.nullable { - quote! { Option<#entity> } - } else { - quote! { #entity } - }; - let query = format!( - "SELECT * FROM {} WHERE {} = $1", - relation.table, relation.remote_id - ); - let local_ident = &value.field.ident; - let fetch = if relation.nullable { - quote! { fetch_optional } - } else { - quote! { fetch_one } - }; - quote! { - pub async fn #function(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result<#return_type> { - ::sqlx::query_as!(#entity, #query, self.#local_ident).#fetch(pool).await - } - } - } -} diff --git a/georm-macros/src/georm/ir/m2m_relationship.rs b/georm-macros/src/georm/ir/m2m_relationship.rs new file mode 100644 index 0000000..36091d5 --- /dev/null +++ b/georm-macros/src/georm/ir/m2m_relationship.rs @@ -0,0 +1,79 @@ +use quote::quote; + +#[derive(deluxe::ParseMetaItem, Clone)] +pub struct M2MLink { + pub table: String, + pub from: String, + pub to: String, +} + +#[derive(deluxe::ParseMetaItem)] +pub struct M2MRelationship { + pub name: String, + pub entity: syn::Type, + pub table: String, + #[deluxe(default = String::from("id"))] + pub remote_id: String, + pub link: M2MLink, +} + +pub struct Identifier { + pub table: String, + pub id: String, +} + +pub struct M2MRelationshipComplete { + pub name: String, + pub entity: syn::Type, + pub local: Identifier, + pub remote: Identifier, + pub link: M2MLink, +} + +impl M2MRelationshipComplete { + pub fn new(other: &M2MRelationship, local_table: &String, local_id: String) -> Self { + Self { + name: other.name.clone(), + entity: other.entity.clone(), + link: other.link.clone(), + local: Identifier { + table: local_table.to_string(), + id: local_id, + }, + remote: Identifier { + table: other.table.clone(), + id: other.remote_id.clone(), + }, + } + } +} + +impl From<&M2MRelationshipComplete> for proc_macro2::TokenStream { + fn from(value: &M2MRelationshipComplete) -> Self { + let function = syn::Ident::new( + &format!("get_{}", value.name), + proc_macro2::Span::call_site(), + ); + let entity = &value.entity; + let query = format!( + "SELECT remote.* +FROM {} local +JOIN {} link ON link.{} = local.{} +JOIN {} remote ON link.{} = remote.{} +WHERE local.{} = $1", + value.local.table, + value.link.table, + value.link.from, + value.local.id, + value.remote.table, + value.link.to, + value.remote.id, + value.local.id + ); + quote! { + pub async fn #function(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result> { + ::sqlx::query_as!(#entity, #query, self.get_id()).fetch_all(pool).await + } + } + } +} diff --git a/georm-macros/src/georm/ir/mod.rs b/georm-macros/src/georm/ir/mod.rs new file mode 100644 index 0000000..84f61e8 --- /dev/null +++ b/georm-macros/src/georm/ir/mod.rs @@ -0,0 +1,98 @@ +use quote::quote; + +pub mod simple_relationship; +use simple_relationship::{OneToMany, OneToOne, SimpleRelationship}; + +pub mod m2m_relationship; +use m2m_relationship::M2MRelationship; + +#[derive(deluxe::ExtractAttributes)] +#[deluxe(attributes(georm))] +pub struct GeormStructAttributes { + pub table: String, + #[deluxe(default = Vec::new())] + pub one_to_one: Vec>, + #[deluxe(default = Vec::new())] + pub one_to_many: Vec>, + #[deluxe(default = Vec::new())] + pub many_to_many: Vec, +} + +#[derive(deluxe::ExtractAttributes, Clone)] +#[deluxe(attributes(georm))] +struct GeormFieldAttributes { + #[deluxe(default = false)] + pub id: bool, + #[deluxe(default = None)] + pub relation: Option, +} + +#[derive(deluxe::ParseMetaItem, Clone, Debug)] +pub struct O2ORelationship { + pub entity: syn::Type, + pub table: String, + #[deluxe(default = String::from("id"))] + pub remote_id: String, + #[deluxe(default = false)] + pub nullable: bool, + pub name: String, +} + +#[derive(Clone, Debug)] +pub struct GeormField { + pub ident: syn::Ident, + pub field: syn::Field, + pub ty: syn::Type, + pub id: bool, + pub relation: Option, +} + +impl GeormField { + pub fn new(field: &mut syn::Field) -> Self { + let ident = field.clone().ident.unwrap(); + let ty = field.clone().ty; + let attrs: GeormFieldAttributes = + deluxe::extract_attributes(field).expect("Could not extract attributes from field"); + let GeormFieldAttributes { id, relation } = attrs; + Self { + ident, + field: field.to_owned(), + id, + ty, + relation, + } + } +} + +impl From<&GeormField> for proc_macro2::TokenStream { + fn from(value: &GeormField) -> Self { + let Some(relation) = value.relation.clone() else { + return quote! {}; + }; + let function = syn::Ident::new( + &format!("get_{}", relation.name), + proc_macro2::Span::call_site(), + ); + let entity = &relation.entity; + let return_type = if relation.nullable { + quote! { Option<#entity> } + } else { + quote! { #entity } + }; + let query = format!( + "SELECT * FROM {} WHERE {} = $1", + relation.table, relation.remote_id + ); + let local_ident = &value.field.ident; + let fetch = if relation.nullable { + quote! { fetch_optional } + } else { + quote! { fetch_one } + }; + quote! { + pub async fn #function(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result<#return_type> { + ::sqlx::query_as!(#entity, #query, self.#local_ident).#fetch(pool).await + } + } + } +} diff --git a/georm-macros/src/georm/ir/simple_relationship.rs b/georm-macros/src/georm/ir/simple_relationship.rs new file mode 100644 index 0000000..5046068 --- /dev/null +++ b/georm-macros/src/georm/ir/simple_relationship.rs @@ -0,0 +1,66 @@ +use quote::quote; + +pub trait SimpleRelationshipType {} + +#[derive(deluxe::ParseMetaItem, Default)] +pub struct OneToOne; +impl SimpleRelationshipType for OneToOne {} + +#[derive(deluxe::ParseMetaItem, Default)] +pub struct OneToMany; +impl SimpleRelationshipType for OneToMany {} + +#[derive(deluxe::ParseMetaItem)] +pub struct SimpleRelationship +where + T: SimpleRelationshipType + deluxe::ParseMetaItem + Default, +{ + pub name: String, + pub remote_id: String, + pub table: String, + pub entity: syn::Type, + #[deluxe(default = T::default())] + _phantom: T, +} + +impl SimpleRelationship +where + T: SimpleRelationshipType + deluxe::ParseMetaItem + Default, +{ + pub fn make_query(&self) -> String { + format!("SELECT * FROM {} WHERE {} = $1", self.table, self.remote_id) + } + + pub fn make_function_name(&self) -> syn::Ident { + syn::Ident::new( + &format!("get_{}", self.name), + proc_macro2::Span::call_site(), + ) + } +} + +impl From<&SimpleRelationship> for proc_macro2::TokenStream { + fn from(value: &SimpleRelationship) -> Self { + let query = value.make_query(); + let entity = &value.entity; + let function = value.make_function_name(); + quote! { + pub async fn #function(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result> { + ::sqlx::query_as!(#entity, #query, self.get_id()).fetch_optional(pool).await + } + } + } +} + +impl From<&SimpleRelationship> for proc_macro2::TokenStream { + fn from(value: &SimpleRelationship) -> Self { + let query = value.make_query(); + let entity = &value.entity; + let function = value.make_function_name(); + quote! { + pub async fn #function(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result> { + ::sqlx::query_as!(#entity, #query, self.get_id()).fetch_all(pool).await + } + } + } +} diff --git a/georm-macros/src/georm/relationships.rs b/georm-macros/src/georm/relationships.rs index f51c47b..0ac318c 100644 --- a/georm-macros/src/georm/relationships.rs +++ b/georm-macros/src/georm/relationships.rs @@ -1,6 +1,6 @@ use std::str::FromStr; -use crate::georm::ir::M2MRelationshipComplete; +use crate::georm::ir::m2m_relationship::M2MRelationshipComplete; use super::ir::GeormField; use proc_macro2::TokenStream; @@ -15,16 +15,12 @@ fn join_token_streams(token_streams: &[TokenStream]) -> TokenStream { .collect() } -fn derive(relationships: &[T], condition: P) -> TokenStream +fn derive(relationships: &[T]) -> TokenStream where for<'a> &'a T: Into, - P: FnMut(&&T) -> bool, { - let implementations: Vec = relationships - .iter() - .filter(condition) - .map(std::convert::Into::into) - .collect(); + let implementations: Vec = + relationships.iter().map(std::convert::Into::into).collect(); join_token_streams(&implementations) } @@ -35,18 +31,20 @@ pub fn derive_relationships( id: &GeormField, ) -> TokenStream { let struct_name = &ast.ident; - let one_to_one = derive(fields, |field| field.relation.is_some()); - let one_to_many = derive(&struct_attrs.one_to_many, |_| true); + let one_to_one_local = derive(fields); + let one_to_one_remote = derive(&struct_attrs.one_to_one); + let one_to_many = derive(&struct_attrs.one_to_many); let many_to_many: Vec = struct_attrs .many_to_many .iter() .map(|v| M2MRelationshipComplete::new(v, &struct_attrs.table, id.ident.to_string())) .collect(); - let many_to_many = derive(&many_to_many, |_| true); + let many_to_many = derive(&many_to_many); quote! { impl #struct_name { - #one_to_one + #one_to_one_local + #one_to_one_remote #one_to_many #many_to_many } diff --git a/src/lib.rs b/src/lib.rs index 3a24a72..caa6b30 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -58,13 +58,13 @@ //! //! Here is an explanation of what these different values mean: //! -//! | Value Name | Explanation | Default value | -//! |------------|-----------------------------------------------------------------------------------------|---------------| -//! | entity | Rust type of the entity found in the database | N/A | +//! | Value Name | Explanation | Default value | +//! |------------|------------------------------------------------------------------------------------------|---------------| +//! | entity | Rust type of the entity found in the database | N/A | //! | name | Name of the remote entity within the local entity; generates a method named `get_{name}` | N/A | -//! | table | Database table where the entity is stored | N/A | -//! | remote_id | Name of the column serving as the identifier of the entity | `"id"` | -//! | nullable | Whether the relationship can be broken | `false` | +//! | table | Database table where the entity is stored | N/A | +//! | remote_id | Name of the column serving as the identifier of the entity | `"id"` | +//! | nullable | Whether the relationship can be broken | `false` | //! //! Note that in this instance, the `remote_id` and `nullable` values can be //! omitted as this is their default value. This below is a strict equivalent: @@ -81,6 +81,39 @@ //! } //! ``` //! +//! But what if I have a one-to-one relationship with another entity and +//! my current entity holds no data to reference that other identity? No +//! worries, there is another way to declare such relationships. +//! +//! ```ignore +//! #[georm( +//! one_to_one = [{ +//! name = "profile", +//! remote_id = "user_id", +//! table = "profiles", +//! entity = User +//! }] +//! )] +//! struct User { +//! #[georm(id)] +//! id: i32, +//! username: String, +//! hashed_password: String, +//! } +//! ``` +//! +//! We now have access to the method `User::get_profile(&self, &pool: +//! sqlx::PgPool) -> Option`. +//! +//! Here is an explanation of the values of `one_to_many`: +//! +//! | Value Name | Explanaion | Default Value | +//! |------------|------------------------------------------------------------------------------------------|---------------| +//! | entity | Rust type of the entity found in the database | N/A | +//! | name | Name of the remote entity within the local entity; generates a method named `get_{name}` | N/A | +//! | table | Database table where the entity is stored | N/A | +//! | remote_id | Name of the column serving as the identifier of the entity | `"id"` | +//! //! ## One-to-many relationships //! //! Sometimes, our entity is the one being referenced to by multiple entities, @@ -105,7 +138,7 @@ //! entity = Post, //! name = "posts", //! table = "posts", -//! remote_id = "id" +//! remote_id = "author_id" //! }] //! )] //! struct User { diff --git a/tests/fixtures/simple_struct.sql b/tests/fixtures/simple_struct.sql index e280e38..3da6929 100644 --- a/tests/fixtures/simple_struct.sql +++ b/tests/fixtures/simple_struct.sql @@ -1,6 +1,7 @@ INSERT INTO biographies (content) VALUES ('Some text'), - ('Some other text'); + ('Some other text'), + ('Biography for no one'); INSERT INTO authors (name, biography_id) VALUES ('J.R.R. Tolkien', 2), diff --git a/tests/models.rs b/tests/models.rs index 3fb7288..0e128cb 100644 --- a/tests/models.rs +++ b/tests/models.rs @@ -1,7 +1,12 @@ use georm::Georm; #[derive(Debug, sqlx::FromRow, Georm, PartialEq, Eq, Default)] -#[georm(table = "biographies")] +#[georm( + table = "biographies", + one_to_one = [{ + name = "author", remote_id = "biography_id", table = "authors", entity = Author + }] +)] pub struct Biography { #[georm(id)] pub id: i32, diff --git a/tests/o2o_relationship.rs b/tests/o2o_relationship.rs index ba74c05..426c36c 100644 --- a/tests/o2o_relationship.rs +++ b/tests/o2o_relationship.rs @@ -53,3 +53,24 @@ async fn books_are_found_despite_nonstandard_id_name(pool: sqlx::PgPool) -> sqlx assert_eq!(tolkien, book.get_author(&pool).await?); Ok(()) } + +#[sqlx::test(fixtures("simple_struct"))] +async fn biographies_should_find_remote_o2o_author(pool: sqlx::PgPool) -> sqlx::Result<()> { + let london = Author::find(&pool, &3).await?.unwrap(); + let london_biography = Biography::find(&pool, &1).await?.unwrap(); + let result = london_biography.get_author(&pool).await; + assert!(result.is_ok()); + let result = result.unwrap(); + assert!(result.is_some()); + let result = result.unwrap(); + assert_eq!(london, result); + Ok(()) +} + +#[sqlx::test(fixtures("simple_struct"))] +async fn biographies_may_not_have_corresponding_author(pool: sqlx::PgPool) -> sqlx::Result<()> { + let biography = Biography::find(&pool, &3).await?.unwrap(); + let result = biography.get_author(&pool).await?; + assert!(result.is_none()); + Ok(()) +}