From fc898e9710080fa918ae90353abcc63c299e7ca0 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Sun, 12 May 2024 12:07:03 +0530 Subject: [PATCH] Add `packaged.toml` support (#3) --- README.md | 17 ++ example/minesweeper/Sprites/Grid.png | Bin 0 -> 599 bytes example/minesweeper/Sprites/empty.png | Bin 0 -> 361 bytes example/minesweeper/Sprites/flag.png | Bin 0 -> 1927 bytes example/minesweeper/Sprites/grid1.png | Bin 0 -> 2426 bytes example/minesweeper/Sprites/grid2.png | Bin 0 -> 2659 bytes example/minesweeper/Sprites/grid3.png | Bin 0 -> 2281 bytes example/minesweeper/Sprites/grid4.png | Bin 0 -> 2436 bytes example/minesweeper/Sprites/grid5.png | Bin 0 -> 2534 bytes example/minesweeper/Sprites/grid6.png | Bin 0 -> 2490 bytes example/minesweeper/Sprites/grid7.png | Bin 0 -> 970 bytes example/minesweeper/Sprites/grid8.png | Bin 0 -> 871 bytes example/minesweeper/Sprites/mine.png | Bin 0 -> 1277 bytes example/minesweeper/Sprites/mineClicked.png | Bin 0 -> 581 bytes example/minesweeper/Sprites/mineFalse.png | Bin 0 -> 1223 bytes example/minesweeper/minesweeper.py | 266 ++++++++++++++++++ example/minesweeper/packaged.toml | 3 + example/minesweeper/setup.cfg | 7 + example/minesweeper/setup.py | 2 + setup.cfg | 1 + src/packaged/cli.py | 73 +++-- src/packaged/config.py | 60 ++++ tests/end_to_end/packaged_test.py | 17 ++ .../test_packages/configtest/configtest.py | 14 + .../test_packages/configtest/packaged.toml | 3 + .../test_packages/configtest/setup.cfg | 7 + .../test_packages/configtest/setup.py | 2 + .../test_packages/configtest/testimage.jpg | Bin 0 -> 19394 bytes 28 files changed, 448 insertions(+), 24 deletions(-) create mode 100644 example/minesweeper/Sprites/Grid.png create mode 100644 example/minesweeper/Sprites/empty.png create mode 100644 example/minesweeper/Sprites/flag.png create mode 100644 example/minesweeper/Sprites/grid1.png create mode 100644 example/minesweeper/Sprites/grid2.png create mode 100644 example/minesweeper/Sprites/grid3.png create mode 100644 example/minesweeper/Sprites/grid4.png create mode 100644 example/minesweeper/Sprites/grid5.png create mode 100644 example/minesweeper/Sprites/grid6.png create mode 100644 example/minesweeper/Sprites/grid7.png create mode 100644 example/minesweeper/Sprites/grid8.png create mode 100644 example/minesweeper/Sprites/mine.png create mode 100644 example/minesweeper/Sprites/mineClicked.png create mode 100644 example/minesweeper/Sprites/mineFalse.png create mode 100644 example/minesweeper/minesweeper.py create mode 100644 example/minesweeper/packaged.toml create mode 100644 example/minesweeper/setup.cfg create mode 100644 example/minesweeper/setup.py create mode 100644 src/packaged/config.py create mode 100644 tests/end_to_end/test_packages/configtest/configtest.py create mode 100644 tests/end_to_end/test_packages/configtest/packaged.toml create mode 100644 tests/end_to_end/test_packages/configtest/setup.cfg create mode 100644 tests/end_to_end/test_packages/configtest/setup.py create mode 100644 tests/end_to_end/test_packages/configtest/testimage.jpg diff --git a/README.md b/README.md index 2290fc2..8cbe039 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,23 @@ This produces a `./curve.bin` binary with: That outputs an interactive graph GUI. +### Minesweeper (using `packaged.toml` for configuration) + +You can use a `packaged.toml` file and simply do `packaged path/to/project` to +create your package. For example, try the `minesweeper` project: + +```bash +packaged ./example/minesweeper +``` + +[This configuration](tests/end_to_end/test_packages/minesweeper/packaged.toml) +is used for building the package. The equivalent command to build the project +without `pyproject.toml` would be: + +```bash +packaged minesweeper.bin 'pip install .' 'python -m minesweeper' ./example/minesweeper +``` + ### Textual Demo Since the dependencies themselves contain all the source code needed, you can diff --git a/example/minesweeper/Sprites/Grid.png b/example/minesweeper/Sprites/Grid.png new file mode 100644 index 0000000000000000000000000000000000000000..534577ba4da6c4ac90a170f2071e150b76be7070 GIT binary patch literal 599 zcmV-d0;v6oP))##^G>?NC+`qo&SNHA!9fk(ssM0QmI6?ZQm5o`&6&juf6F~uh(OA zIvpdlS}l&~`FK3$%3ar`Fbv7IoJR#97m|V{WWt8YWMYKrbV~F2oO`}nttg+*lkfYi zKqlip-vLyQ#Mp>UaS%XJ_+GEqOdtpXCXq^|?gZqrKrUhfkWO6a)dqaje%UiYUu6@_k?LP2%yR=XsKplJRQg+4fQg6kS{!{`RWq8|!ud8tZu;eHKGJuHLl3Q&;jAxI> z$8$K{o;6LAd;Z@iJ{_N_*V@-P00000NkvXX Hu0mjf)u5g& literal 0 HcmV?d00001 diff --git a/example/minesweeper/Sprites/flag.png b/example/minesweeper/Sprites/flag.png new file mode 100644 index 0000000000000000000000000000000000000000..e903c707619758d161d6df24e51a2a919b8f7db2 GIT binary patch literal 1927 zcmV;22YC32P)2(j5P3v%)s@{$K9<+nIx#B6upW??`eK%n(8 z4+&KcA=om*%)MXEIX!(DNqI>@)mPoOr~90*zq8zn0VDwWz~G$FLrPCQLQ3n5B!#Rc zgEN}+XozTwVtf^X#IRR4xsGtN=jI(m}qx*VaUmpe(nUg}<#q#0;%Eb~97zQ#;dSYt*n>E-q z2Mxt+q>4I6)}HSo}@V9kCA%d+6sZFsc`tm+yn<~pi% zT7UR0+)5SIY8|#8;J35C!QA2uN~Hoyh2n?;8ypwb7A^)z8xYA9JIoKVjdyxb}{q$9F`v} zV`*s#zkl!t7|BSY@Sj7sYC0C$1d2s9iHj(wh zHXKro34}z<_kFZGq`<-)R?3P%x%edvPO1J#idD@9g2*QsZ3GU*)NkU5O6JeM{5e|F z)98M92Z!%Z;pFOjz<3VHn1QXSNqkSq)nLWK7&h&9=$uDn1K*X9FoDj-CgvBGP$-sB zEIbexspKG6d|$>&!-VUxj2^aK7e{_fsT<%i-T2hV0TVZXv1{lV2F?lTkidyoM~xI{ zL`)!7&OOQ<*Okj?0(0|=C=!8EL1CDLx|kBG07^VNL_|i8h{Rv+{z5swK8YlEjhyDi zIj)TXIYTKheH+J~gY`Iq9g_TkKfJk}K?rm=Ft;#I1d3QKtOyLL&w=hl37o2f$fmdl z9UM76zWVeN>`wj!$5*e?ZLeTJ1VFJzo+_A#B*r)%-MoW;lS;KHqMYG)51n%r5&VEc z8Mq?^*!dTDKro;?-LMQAHHy7U$>X%JMagv3^Z>ttgUPG-ksS8M^LWh)b z9U}1B3Y7FrAW+Vup>rp6Nf{r_O(t+ZBe0qg(EUB-DxmBST0+2~P;fo}GqxxRr?7eZ z18h<0J-L1pr|(^-+fM+b$m0)h#(haAC)vH$)g-fR7U`Crni!Eoo+kvF)Mk0)bv@^o&(o(*VUTmi;o+g2Ves#KmWhZW+{)=} zr_&Jv?6_1aDFS%wj|oT|sifElA7*Exb8Ynb^XKAxVq!w(HnnI?LX&4`B1~XmVL@Wg z1SAJWZ$C4;dq7Cxeix)2h?C|*bI8)&C^b{{&zLYb(m(THBheQ)tTwKhYX9U!z z(gguFz~IAL`Z$h7X6;ZTm&;*%d^~$*V*2LVj)o2aJ6>5?d6xhy!EZx-xYcUm;NU=R z{N%}#?8f=|}C1SN&%`*9a#r*#TC#F`Q+wEo{D3{Bs4B7eHAnp0L&ImY0{aOuY#*Xvms?(RGWL%$}A zWn#Cg-qja9dqw_@V(1LMKzWE_wpZ;hXO~%BEvXC9|AK5ocR~Ir`7bXe>FdjDN96zj N002ovPDHLkV1j@WqA36X literal 0 HcmV?d00001 diff --git a/example/minesweeper/Sprites/grid1.png b/example/minesweeper/Sprites/grid1.png new file mode 100644 index 0000000000000000000000000000000000000000..513e269dfa36854e2b523f82c2ca338ace7bbabd GIT binary patch literal 2426 zcmV-=35E8FP)Zh4Gcq#E+V35rJ!)fg)Wc-j#eCAo@nnKoXM$(5W6Wn0eji{m zKEiB#jHi=h9F6CgjwhH-Mwm^fZkynPe)JD-S42=IYg(sk8Zb*es@%k0e^SV z>vhm?AL^XLb{Fjq*LS*@42I|&_W6CN>$?3O4*Lh_^mx$r2;+kh=k+n@cVSI=qW0kc ze}4G}ag-xT*NB&E_)&~>l_O14EVG0oLdIv3t`KD_&dHE26Rh$K`7%Ym+8|C7Z1NcE zWq>T@`Z(eGC9-^pXcZ#PBP`P#c`V;z-aw7dxCB#(|#54-5@^1Qo%ESAr03 z#8XG7aLscB1Q5E{SgvodyadvhKz@D8bysi}V%$*>h6YXG=zcPS5`;%DC(Eq>I6;3f z(RhZ52irMC97b@@coJ{PjcA-6@aS^c$n##nv;Tx;WN?|k$FH9J4y~qxX5Gc!?r*W) z+!C~}LqNzd^s47Z^je6J@5T{;5z=JMEFge(XMiM&X~ZsK$4Rl1A+R$AAR#aeOXNEU zoEM1P46imf_~A9ssw`2n-l0*wRS???(1Yg;g~rm@#L22TZX{V=e%UZx`$(p$q{;K@8s}@#vL{&0D;A z1Jo-iPyUvMZwcTIR`mvrmV?a}dU^pN>ji*c0+549IMjwYV=5AWD3ypsmgNe-Bg1tF zc{tf$1PZ_jUtsm>C+t1>7PXxvDzzU`Z+u48y2Fn32^H%jnoWc421-2NEHrhE2^s%T z526`G&lJN|UHKUR&Fd0?vY0N5{gm;$K(_rC{`vz@Ypq~e|K)hEqe{a9K%T7ziMa+{ zY(Iby9>~Eo|4pGE_amj4S+gnSQbR!T@g2>5jh`@;jiVR!&=roZKYjuO;W?Xix=Pgxj- zB?rDB71fY@Y@S35pte6?_frf8T*zf zp$8-d845|q76Giq0})RgunzdA#mVhJ0f;DxS0yCI&sZ@p$N@G5Qo{v+{{SF*@P^2+ zsG(V;g`kk(>lHNREbEp4KER^!T8&xI3b5jTTJ0#=lIsCIAcaee7nxueJfn?_rC)_e zvS7tp9=L@Ps*rdkA)}(~>IU4%bqRoCQ{N?lc0LnC0l=at?LG=vRp|j!97a5$L=5ep zWrGeSVIopIK&QX2KeWPmZUI7-(A7}5ejzFW>%F*kMF6ecP>t7ifNJHF@}X9 zu1KGtWtT(x1mTkHdxbd3e%_+Ug5l9n+okLtfLrRe^978SPsYDM_^s;UVk@=axso`b2Sf#hL%xZXTYjmC0z`FSIG^LN*VS4eE|#hl%5aPd zig?2H*SLCli}j_X;YYk?EofLNS~R{(!wmuu5G`ws*M9>pn5rAj%{T9vJ%7yN0`zoy$4XznerQ(yA^-$ndMM5|KBalP^n z_5>Hqjx%P7k0+0Rk5%>)E8l|NT__>NhHyc`nWX`D??f0UxgvP2|IZq^& zu3CLZWADn;V+p1dD-o-o%DQk@mgXDsf?lcnxF#oJ>7dw0#3Z$Hf&fCcWJxSiJbs2w zdq8(Ys?Q4+4{1!rc)&2&=A7MY0l>1}m5jeBE8qvMf?`-|g2=MGizdj}ZMhEsjQc`0 zM6$?XXakcX33I6h$J*oPt60-Tdq1X;a6w2ZtlwV0MZNV6>P;8BdlB{?rP!tDJZeN* z0cGsfiy`mLrl&&n_|bRRtp0XJZJYJ>ZKwP6aeUf zz3&=LD5%|1XG7?Lw3s_H#B{_=SJ2Bo*Q+d8L;K z?v>6jq*s8E@eAG`7R-W(SrC_sn5`n|h`uHX#{{s_R9To8I1SDet?eaxTZ&9Z7lvhT zU)BiMR!_P2mlheOIGGrIaCIr^=k(l}$g>EX_&$(f|Me literal 0 HcmV?d00001 diff --git a/example/minesweeper/Sprites/grid2.png b/example/minesweeper/Sprites/grid2.png new file mode 100644 index 0000000000000000000000000000000000000000..b94df4f7e1ad172b6970dccc561f3dbc8c8d61e4 GIT binary patch literal 2659 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 RYs3Hm002ovPDHLkV1ip85=sC7 literal 0 HcmV?d00001 diff --git a/example/minesweeper/Sprites/grid3.png b/example/minesweeper/Sprites/grid3.png new file mode 100644 index 0000000000000000000000000000000000000000..600729df524b28151f82cc2d17b25a7bd33e73f8 GIT binary patch literal 2281 zcmV(gy5tU>+A&iM*TXJHCqR20V#J1j(+4?fW9h%Zwq+Pap%s zB#w6{Ih^h%O$KUehV4VwmELl@U-vnu`_^_o*v6A5+vs+7U>aL6O_S>ecG?|UPycUp zy4WzcxNc&jyAGqXj*hX3PFvm)+vGLA*WEz7w@x##F3)!4dK+eEUDtM-@0o3M%?>sU z{_pls+j-K%&wly=);GE^=)~;w(CeDmY*!`$7-qL}&OpSuISHWG-csurU3Fk|*BGE! zyK*l3(V=0kRYCB>Hcf-Z0Az3as%`Q{j}MFvkC5&kA}K~F$`P_6N0wzM^8#g&@mf+@ z!T9H#otI324MS&2N)`5yPkQ@)d7KQDEUefq2QjCH1e z10f%>;s~zq!3%tNQK;toKFx(6Se5x90*}UPfp60$*P{WA|ARp9$8KebA0zZ5grScx z@Q`?cS`>sR%M!IU*23s*BOi?s&|wt2x~T+V`vKfIR+|PCsQEM(ZsK5&Ik0GQFAV7{ zNOcY9EDRhZbQE$QeUHJ#{2jWE2AwmbwRN+Hb=EUwN^M)dppU~~fX^HU=az$0$5%VI z#ax^_HqD~-@yhGti(r@5*L#RMB6+-#y)schU_ncS=HA1p26HxD1; zxtGF^$FNuii;kz6bi)LBF+^>f2p6>H>5u{B@L1yuX5!=43f|OaXmhx!-NtqO4(&E> z>NB*)WSzm~OhfIuG4tiU>sp=Gz@HbE04K)tAj8n}V6!An6wrt@mMY6fs0ppQy*5iw zz#@3UD8t#Fh2!Qb9oO-;ev6LhaLUau=4Sc3hS#)9+VxBgH;swaYJ8Rf%Y6p(rf~;f z+?vO;yLa*HAHI*jyM2D3tGM!_R56j06}894BXqV+(l6IKNg2-562EM%;&|oz7%wej zxU|GHuh5pT-(0}K@;ukY%9h^4QR^N?OZV_8ot@m7!xe+MV)n$|%+~Z7gao!QCQc9# zq&~4Rrq5buYa5R#7R9iH>yiPUk5S*k%Q(a7z{Z(pd$_7s*#Ju;*(nykhjX8dA-*inRceqn{^_ETndZOMT971%3OZ+APF!?LvsP;R)N%5iJ9u_)5g$JI0bWLh z25TiYc7r|DgmO4$0MvmVn@lvyV5NQ~7{o%7M4GBfibZ-;ydX_~vAT*;tI022#(t|s zfKn4$_mM9z;ZFbXzQT#?hG#U@_Q&f-$)%UWMH=5R4HgEOJ#Tt(3v((`I&78z^* zg53W|O39QtzzV4NfMPl-int>;5MBTQ5ln*&6=|=!2aRHmkuK@Ql$_u_%-Oeq;yGpdQig?KbGR*@&Yab7f-1l+K!2_f~EdA=ScRb{bO`@I@*k+703Ltv!uYU*ogjL z&1k&%U4TQ5;7LF(nUpLKGigQ~vpXE8GY*P2sUbBLU>#9DU$dvnJRt4(tTC(Y_^R>W zwc{7k2|eP9C3b>%BE^L$p@|)i#{!6uNOd_dN?H&2N*+ABcb$!)++J*9dRfTA37CxCo^G0K*+&FqO{#=&X<6E-FOm| zm~I~FAwwL5L3RDB2NTix>A@r=sAK9x%vnR;iAmq-!9+pWHGTpKLIQA-Olu=R6vt8$ znd9|T!<4D_LSK$^k^xuEQ#wlvua0%LYJg-i z_B}lv$#|ER48V{hbH~8(C!exCj`biZhtV;I(gU6qBu(;QLdqmWZ8}V}cGAhp?2zlB z{uU(_P-UZpmMg-=ieX+IYKPSURS&wvn>>H;$ua%`9Ex`eD%N7P00000NkvXXu0mjf DgS=bf literal 0 HcmV?d00001 diff --git a/example/minesweeper/Sprites/grid4.png b/example/minesweeper/Sprites/grid4.png new file mode 100644 index 0000000000000000000000000000000000000000..162372da890c7cfa7e4f303d61cf315dd6c7bdfd GIT binary patch literal 2436 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 literal 0 HcmV?d00001 diff --git a/example/minesweeper/Sprites/grid6.png b/example/minesweeper/Sprites/grid6.png new file mode 100644 index 0000000000000000000000000000000000000000..fe74452c511ff88dd0dfb6c34222cc04dc50ff2c GIT binary patch literal 2490 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 Eg6yQZ#Q*>R literal 0 HcmV?d00001 diff --git a/example/minesweeper/Sprites/grid7.png b/example/minesweeper/Sprites/grid7.png new file mode 100644 index 0000000000000000000000000000000000000000..4550616ea100d815f61c0ab2f6e623f257315e8f GIT binary patch literal 970 zcmV;*12z1KP)JGiOfQ z=-^;4wA<}auQx)HG((!EW=Y%#X(O4>C83edZN6rf#`C$@_AXjt^Q)S65edF84P#H`)B%-Cc-40RWs`TwIuSIvsoH^z<~Got>>^=jZ1O z*C!_@**#zk!0zs@p~4|{d3k9YHvvFr>MYL20CRnP9U|456ya1TpP!$@)6-LUe0*Hn zIEQG-ECvupd`38LwOWRTw6pl|@DQRX3e{@W#!c8{GRY#x2c@R$5iEs~paB&N_xJZ< zXJ^MAtW+v%nF8TV&K3t4DSM<=<3{L6TiNLSJ~`C5wY8PqlT~ zzouBueAfItJjjze>jJcZn{Kxoj*gD(x-7wFvzbMQeSUssj(v8RW3RdkKV-p37{}kL zhcF;M`Sw}WW@0r5FC(w6S%U+BU=$G}={Q}WHZ1F0Km}X?JoyBKHTDp_o+W520UVGJ z0YGXfq^pz;qs=}(J~HRq+uI8x91u{><{9e(T96D-cQQbVq=sHbi%gNr

{-eYJJY zIzB!&ACmcnHl!uw@UIf=+t!m1AVf|kNxgznrmYLa0RSv>tj*GK4)gks;l#J2uRW43 zr(TG%608Csw(=k;$8#r>;vxWExyXR(2a%@JX;yEdCVGj` z*VmT`8r>!4`x0jy`2M^q&VU|L&+7n@Pwx$HIM-gKUFU1=2S9NK*huP#T=mx>Ub2ar zEMOv^fGUgPfih7F0Gp9#uYB|~4(NMnJ&rvGsJ&kAs=X(4)&+dQt^%MsDs`Uq`M!UD ze-AG&FTdK^tE&{Z6o7mwc#zuXBi1$G{QhO_EW!KHfnosp_ksZU*RKblzE_m0{j#8p z1N!+W0-)~%WdPmJr(b|r;~DDjUvB>mKrbc{5HZsC0-3-eBlqY6VAIMmXRz;Wfdxr7wx^fHE2Q2c_>UH`*pnp8x;=07*qoM6N<$g7&e{K>z>% literal 0 HcmV?d00001 diff --git a/example/minesweeper/Sprites/grid8.png b/example/minesweeper/Sprites/grid8.png new file mode 100644 index 0000000000000000000000000000000000000000..49302ce59ea2d30c5d3b0f0a5e47b9704df28538 GIT binary patch literal 871 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 l-@l(z_;FdjfB!jje*u@Y^OWlGwg>= 0 and self.xGrid + x < game_width: + for y in range(-1, 2): + if self.yGrid + y >= 0 and self.yGrid + y < game_height: + if not grid[self.yGrid + y][self.xGrid + x].clicked: + grid[self.yGrid + y][self.xGrid + x].revealGrid() + elif self.val == -1: + # Auto reveal all mines if it's a mine + for m in mines: + if not grid[m[1]][m[0]].clicked: + grid[m[1]][m[0]].revealGrid() + + def updateValue(self): + # Update the value when all grid is generated + if self.val != -1: + for x in range(-1, 2): + if self.xGrid + x >= 0 and self.xGrid + x < game_width: + for y in range(-1, 2): + if self.yGrid + y >= 0 and self.yGrid + y < game_height: + if grid[self.yGrid + y][self.xGrid + x].val == -1: + self.val += 1 + + +def gameLoop(): + gameState = "Playing" # Game state + mineLeft = numMine # Number of mine left + global grid # Access global var + grid = [] + global mines + t = 0 # Set time to 0 + + # Generating mines + mines = [[random.randrange(0, game_width), random.randrange(0, game_height)]] + + for c in range(numMine - 1): + pos = [random.randrange(0, game_width), random.randrange(0, game_height)] + same = True + while same: + for i in range(len(mines)): + if pos == mines[i]: + pos = [ + random.randrange(0, game_width), + random.randrange(0, game_height), + ] + break + if i == len(mines) - 1: + same = False + mines.append(pos) + + # Generating entire grid + for j in range(game_height): + line = [] + for i in range(game_width): + if [i, j] in mines: + line.append(Grid(i, j, -1)) + else: + line.append(Grid(i, j, 0)) + grid.append(line) + + # Update of the grid + for i in grid: + for j in i: + j.updateValue() + + # Main Loop + while gameState != "Exit": + # Reset screen + gameDisplay.fill(bg_color) + + # User inputs + for event in pygame.event.get(): + # Check if player close window + if event.type == pygame.QUIT: + gameState = "Exit" + # Check if play restart + if gameState == "Game Over" or gameState == "Win": + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_r: + gameState = "Exit" + gameLoop() + else: + if event.type == pygame.MOUSEBUTTONUP: + for i in grid: + for j in i: + if j.rect.collidepoint(event.pos): + if event.button == 1: + # If player left clicked of the grid + j.revealGrid() + # Toggle flag off + if j.flag: + mineLeft += 1 + j.flag = False + # If it's a mine + if j.val == -1: + gameState = "Game Over" + j.mineClicked = True + elif event.button == 3: + # If the player right clicked + if not j.clicked: + if j.flag: + j.flag = False + mineLeft += 1 + else: + j.flag = True + mineLeft -= 1 + + # Check if won + w = True + for i in grid: + for j in i: + j.drawGrid() + if j.val != -1 and not j.clicked: + w = False + if w and gameState != "Exit": + gameState = "Win" + + # Draw Texts + if gameState != "Game Over" and gameState != "Win": + t += 1 + elif gameState == "Game Over": + drawText("Game Over!", 50) + drawText("R to restart", 35, 50) + for i in grid: + for j in i: + if j.flag and j.val != -1: + j.mineFalse = True + else: + drawText("You WON!", 50) + drawText("R to restart", 35, 50) + # Draw time + s = str(t // 15) + screen_text = pygame.font.SysFont("Calibri", 50).render(s, True, (0, 0, 0)) + gameDisplay.blit(screen_text, (border, border)) + # Draw mine left + screen_text = pygame.font.SysFont("Calibri", 50).render( + mineLeft.__str__(), True, (0, 0, 0) + ) + gameDisplay.blit(screen_text, (display_width - border - 50, border)) + + pygame.display.update() # Update screen + + timer.tick(15) # Tick fps + + +gameLoop() +pygame.quit() +quit() diff --git a/example/minesweeper/packaged.toml b/example/minesweeper/packaged.toml new file mode 100644 index 0000000..112d8cf --- /dev/null +++ b/example/minesweeper/packaged.toml @@ -0,0 +1,3 @@ +output_path = "minesweeper.bin" +build_command = "pip install ." +startup_command = "python -m minesweeper" diff --git a/example/minesweeper/setup.cfg b/example/minesweeper/setup.cfg new file mode 100644 index 0000000..99b9cd5 --- /dev/null +++ b/example/minesweeper/setup.cfg @@ -0,0 +1,7 @@ +[metadata] +name = minesweeper + +[options] +packages = find: +install_requires = + pygame diff --git a/example/minesweeper/setup.py b/example/minesweeper/setup.py new file mode 100644 index 0000000..8bf1ba9 --- /dev/null +++ b/example/minesweeper/setup.py @@ -0,0 +1,2 @@ +from setuptools import setup +setup() diff --git a/setup.cfg b/setup.cfg index 4443b7f..1f6737c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -26,6 +26,7 @@ classifiers = packages = find: install_requires = yen>=0.4.2 + tomli>=1.1.0; python_version<'3.11' python_requires = >=3.8 package_dir = =src diff --git a/src/packaged/cli.py b/src/packaged/cli.py index 940de69..5b55e59 100644 --- a/src/packaged/cli.py +++ b/src/packaged/cli.py @@ -5,15 +5,15 @@ import argparse import os.path import platform +import sys from packaged import SourceDirectoryNotFound, ensure_makeself, create_package - - -class CLIArgs: - source_directory: str - output_path: str - build_command: str - startup_command: str +from packaged.config import ( + Config, + ConfigValidationError, + config_file_exists, + parse_config, +) def error(message: str) -> None: @@ -23,33 +23,58 @@ def error(message: str) -> None: def cli(argv: list[str] | None = None) -> int: """CLI interface.""" - parser = argparse.ArgumentParser() - parser.add_argument("output_path", help="Filename for the generated binary") - parser.add_argument( - "build_command", help="Python command to run when building the package" - ) - parser.add_argument("startup_command", help="Command to run when the script starts") - parser.add_argument( - "source_directory", - help="Folder containing your python source to package", - type=os.path.abspath, - nargs="?", - default=None, - ) - args = parser.parse_args(argv, namespace=CLIArgs) + # Manually set argv from sys.argv, as we need to check its length to + # choose to parse config instead. + if argv is None: + argv = sys.argv[1:] if platform.system() == "Windows": error("Sorry, Windows is not supported yet. Ask for it on GitHub!") return 2 - ensure_makeself() - try: - create_package( + if len(argv) == 1 and config_file_exists(argv[0]): + # Use values from config file instead + try: + config = parse_config(argv[0]) + except ConfigValidationError as exc: + error(f"Expected key {exc.key!r} in config") + return 3 + + source_directory, output_path, build_command, startup_command = ( + config.source_directory, + config.output_path, + config.build_command, + config.startup_command, + ) + + else: + parser = argparse.ArgumentParser() + parser.add_argument("output_path", help="Filename for the generated binary") + parser.add_argument( + "build_command", help="Python command to run when building the package" + ) + parser.add_argument( + "startup_command", help="Command to run when the script starts" + ) + parser.add_argument( + "source_directory", + help="Folder containing your python source to package", + type=os.path.abspath, + nargs="?", + default=None, + ) + args = parser.parse_args(argv, namespace=Config) + + source_directory, output_path, build_command, startup_command = ( args.source_directory, args.output_path, args.build_command, args.startup_command, ) + + ensure_makeself() + try: + create_package(source_directory, output_path, build_command, startup_command) except SourceDirectoryNotFound as exc: error(f"Folder {exc.directory_path!r} does not exist.") diff --git a/src/packaged/config.py b/src/packaged/config.py new file mode 100644 index 0000000..ce9ce9e --- /dev/null +++ b/src/packaged/config.py @@ -0,0 +1,60 @@ +from __future__ import annotations + +from dataclasses import dataclass +import os +import sys + +if sys.version_info < (3, 11): + import tomli as tomllib +else: + import tomllib + + +class ConfigValidationError(Exception): + """Raised when the toml config has some problem.""" + + def __init__(self, key: str) -> None: + super().__init__(key) + self.key = key + + +@dataclass +class Config: + source_directory: str | None + output_path: str + build_command: str + startup_command: str + + +CONFIG_NAME = "./packaged.toml" + + +def config_file_exists(source_directory: str) -> bool: + """Returns true if `packaged.toml` exists in current directory.""" + return os.path.isfile(os.path.join(source_directory, CONFIG_NAME)) + + +def parse_config(source_directory: str) -> Config: + """ + Parses the config file according to this format: + + source_directory = "." + output_path = "myproject.bin" + build_command = "pip install ." + startup_command = "python -m myproject" + """ + with open(os.path.join(source_directory, CONFIG_NAME), "rb") as config_file: + config_data = tomllib.load(config_file) + + try: + config = Config( + os.path.abspath(source_directory), + config_data["output_path"], + config_data["build_command"], + config_data["startup_command"], + ) + except KeyError as exc: + key = exc.args[0] + raise ConfigValidationError(key) + + return config diff --git a/tests/end_to_end/packaged_test.py b/tests/end_to_end/packaged_test.py index e2825dc..30fb4cd 100644 --- a/tests/end_to_end/packaged_test.py +++ b/tests/end_to_end/packaged_test.py @@ -6,6 +6,7 @@ from typing import Iterator import packaged +import packaged.config TEST_PACKAGES = os.path.join(os.path.dirname(__file__), "test_packages") @@ -31,6 +32,8 @@ def build_package( def get_output(path: str) -> str: """Runs the executable with `--nox11` so that it still works as a subprocess.""" + if not path.startswith((".", "/")): + path = "./" + path return subprocess.check_output([path, "--nox11"]).decode() @@ -53,3 +56,17 @@ def test_numpy_pandas() -> None: "python somefile.py", ): assert "0 -2.222222\ndtype: float64" in get_output(executable_path) + + +def test_config_parsing() -> None: + """Packages `configtest` to ensure config parsing works as expected.""" + package_path = os.path.join(TEST_PACKAGES, "configtest") + config = packaged.config.parse_config(package_path) + + with build_package( + config.source_directory, + config.output_path, + config.build_command, + config.startup_command, + ): + assert "('R', 'G', 'B')\n('C', 'M', 'Y', 'K')" in get_output(config.output_path) diff --git a/tests/end_to_end/test_packages/configtest/configtest.py b/tests/end_to_end/test_packages/configtest/configtest.py new file mode 100644 index 0000000..f0a9305 --- /dev/null +++ b/tests/end_to_end/test_packages/configtest/configtest.py @@ -0,0 +1,14 @@ +import os.path + +from PIL import Image + + +def main(): + with Image.open(os.path.join(os.path.dirname(__file__), "testimage.jpg")) as img: + print(img.getbands()) + cmyk_img = img.convert("CMYK") + print(cmyk_img.getbands()) + + +if __name__ == "__main__": + main() diff --git a/tests/end_to_end/test_packages/configtest/packaged.toml b/tests/end_to_end/test_packages/configtest/packaged.toml new file mode 100644 index 0000000..ba4dd98 --- /dev/null +++ b/tests/end_to_end/test_packages/configtest/packaged.toml @@ -0,0 +1,3 @@ +output_path = "configtest.bin" +build_command = "pip install ." +startup_command = "python -m configtest" diff --git a/tests/end_to_end/test_packages/configtest/setup.cfg b/tests/end_to_end/test_packages/configtest/setup.cfg new file mode 100644 index 0000000..e9d06bc --- /dev/null +++ b/tests/end_to_end/test_packages/configtest/setup.cfg @@ -0,0 +1,7 @@ +[metadata] +name = configtest + +[options] +packages = find: +install_requires = + pillow diff --git a/tests/end_to_end/test_packages/configtest/setup.py b/tests/end_to_end/test_packages/configtest/setup.py new file mode 100644 index 0000000..8bf1ba9 --- /dev/null +++ b/tests/end_to_end/test_packages/configtest/setup.py @@ -0,0 +1,2 @@ +from setuptools import setup +setup() diff --git a/tests/end_to_end/test_packages/configtest/testimage.jpg b/tests/end_to_end/test_packages/configtest/testimage.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e6dca2eb648b777fb383c6b34b3c404fec16fa11 GIT binary patch literal 19394 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