From 8c3deccac3b353e41bdc051b334c77de0e19fd98 Mon Sep 17 00:00:00 2001 From: Tue Herlau <tuhe@dtu.dk> Date: Sat, 18 Sep 2021 19:57:29 +0200 Subject: [PATCH] Bugfixes --- setup.py | 8 +-- src/codesnipper.egg-info/PKG-INFO | 60 +++++++++++------- src/codesnipper.egg-info/SOURCES.txt | 4 +- src/snipper/__init__.py | 3 +- .../__pycache__/__init__.cpython-38.pyc | Bin 221 -> 246 bytes .../__pycache__/block_parsing.cpython-38.pyc | Bin 3482 -> 3510 bytes src/snipper/__pycache__/fix_bf.cpython-38.pyc | Bin 2505 -> 3751 bytes src/snipper/__pycache__/legacy.cpython-38.pyc | Bin 1644 -> 2217 bytes .../__pycache__/snip_dir.cpython-38.pyc | Bin 2057 -> 2057 bytes .../__pycache__/snipper_main.cpython-38.pyc | Bin 3025 -> 2729 bytes src/snipper/block_parsing.py | 4 +- src/snipper/fix_bf.py | 45 +++++++++---- src/snipper/legacy.py | 32 ++++++++++ src/snipper/snip_dir.py | 37 ++--------- src/snipper/snipper_main.py | 56 ++++++++-------- src/snipper/version.py | 1 + 16 files changed, 146 insertions(+), 104 deletions(-) create mode 100644 src/snipper/version.py diff --git a/setup.py b/setup.py index a7fc926..5d605ec 100644 --- a/setup.py +++ b/setup.py @@ -5,16 +5,16 @@ import setuptools import pkg_resources -# with open("src/unitgrade2/version.py", "r", encoding="utf-8") as fh: -# __version__ = fh.read().split(" = ")[1].strip()[1:-1] -# long_description = fh.read() +with open("src/snipper/version.py", "r", encoding="utf-8") as fh: + __version__ = fh.read().split(" = ")[1].strip()[1:-1] + with open("README.md", "r", encoding="utf-8") as fh: long_description = fh.read() setuptools.setup( name="codesnipper", - version="0.1.1", + version=__version__, author="Tue Herlau", author_email="tuhe@dtu.dk", description="A lightweight framework for censoring student solutions files and extracting code + output", diff --git a/src/codesnipper.egg-info/PKG-INFO b/src/codesnipper.egg-info/PKG-INFO index 51473de..f95b07c 100644 --- a/src/codesnipper.egg-info/PKG-INFO +++ b/src/codesnipper.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: codesnipper -Version: 0.1.0 +Version: 0.1.7 Summary: A lightweight framework for censoring student solutions files and extracting code + output Home-page: https://lab.compute.dtu.dk/tuhe/snipper Author: Tue Herlau @@ -24,12 +24,14 @@ pip install codesnipper ## What it does This project address the following three challenges for administering a python-based course - - You need to maintain a (working) version for debugging as well as a version handed out to students (with code missing) - - You ideally want to make references in source code to course material *"(see equation 2.1 in exercise 5)"* but these tend to go out of date - - You want to include code snippets and code output in lectures notes/exercises/beamer slides - - You want to automatically create student solutions + - Maintain a (working) version for debugging as well as a version handed out to students (with code missing) + - Use LaTeX references in source code to link to course material (i.e. `\ref{mylabel}` -> *"(see equation 2.1 in exercise 5)"*) + - Including code snippets and console output in lectures notes/exercises/beamer slides + - Automatically create student solutions -This framework address these problems and allow you to maintain a **single**, working project repository. +This framework address these problems and allow you to maintain a **single**, working project repository. Below is an example of the snippets produced and included using simple `\inputminted{python}{...}` commands (see the `examples/` directory): + + The project is currently used in **02465** at DTU. An example of student code can be found at: - https://gitlab.gbar.dtu.dk/02465material/02465students/blob/master/irlc/ex02/dp.py @@ -45,9 +47,9 @@ examples/cs101_students # This directory contains the (processed) student fil examples/cs101_output # This contains automatically generated contents (snippets, etc.). ``` The basic functionality is you insert special comment tags in your source, such as `#!b` or `#!s` and the script then process -the sources based on the tags. The following will show most basic usages: +the sources based on the tags. The following will show most common usages: -## The #f!-tag +## The #!f-tag Let's start with the simplest example, blocking out a function (see `examples/cs101_instructor/f_tag.py`; actually it will work for any scope) You insert a comment like: `#!f <exception message>` like so: ```python @@ -71,7 +73,7 @@ def myfun(a,b): return sm ``` -## The #b!-tag +## The #!b-tag The #!b-tag allows you more control over what is cut out. The instructor file: ```python def primes_sieve(limit): @@ -104,7 +106,7 @@ This allows you to cut out text across scopes, but still allows you to insert ex -## The #s!-tag +## The #!s-tag The #!s-tag is useful for making examples to include in exercises and lecture notes. The #!s (snip) tag cuts out the text between tags and places it in files found in the output-directory. As an example, here is the instructor file: ```python @@ -161,7 +163,7 @@ and finally: I recommend using `\inputminted{filename}` to insert the cutouts in LaTeX. -## The #o!-tag +## The #!o-tag The #!o-tag allows you to capture output from the code, which can be useful when showing students the expected behavior of their scripts. Like the #!s-tag, the #!o-tags can be named. @@ -193,7 +195,7 @@ Area of square of width 2 and height 4 is: and that is a fact! ``` -## The #i!-tag +## The #!i-tag The #!i-tag allows you to create interactive python shell-snippets that can be imported using the minted `pycon` environment (`\inputminted{python}{input.shell}`). As an example, consider the instructor file @@ -227,8 +229,8 @@ Note that apparently there is no library for converting python code to shell sessions so I had to write it myself, which means it can properly get confused with multi-line statements (lists, etc.). On the plus-side, it will automatically insert newlines after the end of scopes. My parse is also known to be a bit confused if your code outputs `...` since it has to manually parse the interactive python session and this normally indicates a new line. -## References and citations (`\ref` and `\cite`) -One of the most annoying parts of maintaining student code is to constantly write "see equation on slide 41 bottom" only to have the reference go stale because slide 23 got removed. Well now anymore, now you can direcly refence anything with a bibtex or aux file! +# References and citations (`\ref` and `\cite`) +One of the most annoying parts of maintaining student code is to constantly write *"see equation on slide 41 bottom"* only to have the reference go stale because slide 23 got removed. Well not anymore, now you can direcly refence anything with a bibtex or aux file! Let's consider the following example of a simple document with a couple of references: (see `examples/latex/index.pdf`):  @@ -314,7 +316,7 @@ Since the aux/bibtex databases are just dictionaries it is easy to join them tog I have written reference tags to lecture and exercise material as well as my notes and it makes reference management very easy. -## Advanced block processing +# Partial solutions The default behavior for code removal (#!b and #!f-tags) is to simply remove the code and insert the number of missing lines. We can easily create more interesting behavior. The code for the following example can be found in `examples/b_example.py` and will deal with the following problem: ```python @@ -331,10 +333,10 @@ def primes_sieve(limit): return primes #!b # Example use: print(primes_sieve(42)) ``` -The example shows how we can easily define custom functions for processing the code which is to be removed. -All such a function need is to take a list of lines (to be obfuscated) and return a new list of lines (the obfuscated code). -A couple of short examples: -### Permute lines +The examples below shows how we can easily define custom functions for processing the code which is to be removed; I have not included the functions here for brevity, +but they are all just a few lines long and all they do is take a list of lines (to be obfuscated) and return a new list of lines (the obfuscated code). + +### Example 1: Permute lines This example simple randomly permutes the line and prefix them with a comment tag to ensure the code still compiles ```python import numpy as np @@ -352,7 +354,7 @@ def primes_sieve(limit): # Example use: print(primes_sieve(42)) raise NotImplementedError('Complete the above program') ``` -### Partial replacement +### Example 2: Partial replacement This example replaces non-keyword, non-special-symbol parts of the lines: ```python import numpy as np @@ -371,8 +373,8 @@ def primes_sieve(limit): raise NotImplementedError('Complete the above program') ``` -### Half of the solution -The final solution display half of the proposed solution: +### Example 3: Half of the solution +The final example displays half of the proposed solution: ```python import numpy as np # Implement a sieve here. @@ -390,3 +392,17 @@ def primes_sieve(limit): raise NotImplementedError('Complete the above program') ``` + +# Citing +```bibtex +@online{codesnipper, + title={Codesnipper (0.1.0): \texttt{pip install codesnipper}}, + url={https://lab.compute.dtu.dk/tuhe/snipper}, + urldate = {2021-09-07}, + month={9}, + publisher={Technical University of Denmark (DTU)}, + author={Tue Herlau}, + year={2021}, +} +``` + diff --git a/src/codesnipper.egg-info/SOURCES.txt b/src/codesnipper.egg-info/SOURCES.txt index aa653c4..3188345 100644 --- a/src/codesnipper.egg-info/SOURCES.txt +++ b/src/codesnipper.egg-info/SOURCES.txt @@ -14,8 +14,10 @@ src/snipper/fix_bf.py src/snipper/fix_cite.py src/snipper/fix_i.py src/snipper/fix_o.py +src/snipper/fix_r.py src/snipper/fix_s.py src/snipper/legacy.py src/snipper/load_citations.py src/snipper/snip_dir.py -src/snipper/snipper_main.py \ No newline at end of file +src/snipper/snipper_main.py +src/snipper/version.py \ No newline at end of file diff --git a/src/snipper/__init__.py b/src/snipper/__init__.py index 4e820bd..32d269e 100644 --- a/src/snipper/__init__.py +++ b/src/snipper/__init__.py @@ -1,3 +1,4 @@ -__version__ = "0.0.1" +from snipper.version import __version__ from snipper.snip_dir import snip_dir + diff --git a/src/snipper/__pycache__/__init__.cpython-38.pyc b/src/snipper/__pycache__/__init__.cpython-38.pyc index eae9fb89b94899b9aab163c73358f43efa116212..b69da3bc3256d708cd9d94a26ff0de84cbe7c47d 100644 GIT binary patch delta 155 zcmcc1_>GY_l$V!_0SIQjbx91F$SYef1LUMIq%h_%<T6GvGJ@DlIZV0CQOrO#b1;J@ z%S(`YO~zZ?@$qG;Ma7x<dGYZ;E=O@*W<h*PW|5yJOB6qdQ;=Gu2UStT1k`g&0454k XTEq+#Udd3z3Z%fquZa`9nRplh2RkWy delta 126 zcmeyyc$bkkl$V!_0SF{sSSC75<dsbl0di6pq8L&bQ<!oXbD5%;7#UKSgBdhgs#p#5 z4D<|Nf|P4A-r^|E%Pfdb$t?2IWRBvFk1tCtD$dN$i;s^I0Lc`j7U{uM6fps{tYj!+ P22w@L6PNlku`mJvRFogC diff --git a/src/snipper/__pycache__/block_parsing.cpython-38.pyc b/src/snipper/__pycache__/block_parsing.cpython-38.pyc index f863b8116bd2e1ce5093ddf2a92c11e93ae21392..cb029cce281298d5b7f761b09068fdbd689ab7af 100644 GIT binary patch delta 309 zcmbOwy-k`ol$V!_0SG)YToUs)@;+o^)SUdDZ4sl+<Voy(jJlH*IT9FECpU2{_elb( zs$p2b(7{l{n8GN|u#l;isfH<qDVw!Orv@li!q~ys3>2?nYGsmS$P+>qOJM+s)iOiX zhfR*;jM9l>DK1G&FJc3lTf_|_c!0z$_Qay}g8bqVe;}tAB*MTbz-Tb}KIeQ!qsjGL z8bJ06AjvrSBG+nOkeiB`z>13OCU<hr7fl0+fPKWs!N>!~MUIngd6uZ%V#~?QOD#4k yvIQ!+#gtM3u@r7|kv&j6Z!$ZtETjEoRbC@*J0QQv5k%NePU1CYb>d**;s5}@9YZ1j delta 281 zcmdlcJxiK5l$V!_0SNYISSR{!<bBA-s6F{T+agB2$&=Xo81*MBawIUSPj2E^Zr}z~ zRl~4=p@X4@F@;f_VIfm3Qw@+^!q~ys4CL1^wK7RE)H2sFr7&f)7FkU$<&0vyHTeRk zEMFi{YcUVV8b$#|k;%U~=QA2lp3SAfXgK)@kYt|xmTNU{3P=$XSW%Jv<fYv6MKeGm zVAnBnF!F$Lk<;Wzo+U0(Y&n^Esl`S`c0eV!m{KZ=xPc4^0kWsa0Z0@8aj^)HU|?hc mYUTnPAOL3bFcvvXcH}kUwg<`<Ie`d=$#uNOtS%fZTpR%B?l~g> diff --git a/src/snipper/__pycache__/fix_bf.cpython-38.pyc b/src/snipper/__pycache__/fix_bf.cpython-38.pyc index 08110df11cf8ba047f40b6a36f84bec56cdd98a5..d658b0d20470302c45daa0e3245fa0823a229ac8 100644 GIT binary patch literal 3751 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 delta 1304 zcmZWpO>Epm6rML8|IONNXxjXg1e#`Puo@MJ0;sU9DzTtSt*S_+2O_FKcD$?AZZ_Gm zgHSY9P`5x37os^Ls@)TU0~c;oiV*4riEBCd%!OOey}%o9R6y8z&(HIIzV~N;JOArs zbUO?=!Rx2in;-th52HC)JG`*EU<>CSS$9PsJmKGibx(wXiwa7<fLkPv4)3n+H>mME zz9M|uNETB13lP#GITesw%F2i$1L%;iKprWgtXYyX#fBj02y$E50y;M8zE=!oYnll1 zDV-%lIwHBFm>|kF@90B^mSQ6Sk}-_4ZwWR{W-;^^B(qidChP&NlRMXPHyazdMn8bu zQ?9W-PKBmN%FD<>nU>r$rwT0%<Nz=oCij5!ANG$mg3Tb#Q!SN$m}vVmBzJ`~bWBYq zxN=RNSd*_T<#s666lAV&2XGgZn_HQu`~kdAbDmc|Crv1O>nUDG(41oud<>3$Hx*S} z0D}6$8Nut=%hH46G(1%q@)4;g#w}2RF%uPpN2z8ibe@L!OgTL&f;>cBh*psbzb4<% z(l)FS1PM&&zV}aSO(}S0nKK;p3H_e+)+-E$I%x)b3Rw%gbtlN-fGC(IhqTXbk?)A~ zQ5DmIUVOQLhJIw-gy(dXE$J_<*Dj5fP}pfE+dDCpX6Fj%$^f6GQkwmdCy*^5r3TO; zl<4cMdfGm_xUhLPt`t@`=@qQm?g=@efAp@-$5CN-+r1<$Y>{m4whE_wTPdI|pV626 z)y3vIOQhTyW0YtHsIZ&uk2jjGfx4>i`^)nXz)6UZreFeT3OMD!K~~Y^9sR^VQ+<q* zSrpT5Y`mSpSpL^qEbAIygf;yEzwqh-u80|o-J#ORR9NK}y9&z924%^ia9W_8QzZ9# z*A$aK=%4v%c&LBp_0Y8YPxFC2fphD6sd6JUy_XZnVqebbPb$yu7p#*cJB5?Bu`!tp zQCHALqo7Gr(C*5(Xfv0xy9M9a>~1$Y8_nI`G9ISqkP$4rinoXN49Xrb2q;7D(DM-e z<uG{>t^V_=o3=SfdM>yRC-sBi-M39{v2{GwP5~eN54$GXjjXY~wR7b<+NNF^!yy3< zdGCyw#^#E?9lr6>5-Q}2hQ-KYCe11ob~{V9(lQlk{xY#E=SmxW6rK;ukviibb^SPe vo?S+zo{H8gW`1M0%eo&;E|#WVZEIWXc9Sdkn<A;%3J*&UV8ZfV;4^;%BKa0X diff --git a/src/snipper/__pycache__/legacy.cpython-38.pyc b/src/snipper/__pycache__/legacy.cpython-38.pyc index de81fc57ca76b392afb215f36ef762b6c4553601..5b42f09e6c1319e32d100c7978700b585efb237c 100644 GIT binary patch delta 1150 zcmZ8f&u<$=6rMLf)@#Q$Ek&(UNM*PnA8Oi4J&{JLk|GWy9FigwRE5pX#@S|fy`G&^ zPBbe^7$n50!T*4L?1c+|0C!HD=f<%WH?BzVW=&DUj^@pqZ@>BWoA>sQ=8LQC$E{X_ z;PddOt>Dj<i}q)eI__#XH*pqYAjFV4S&$M2q@*~RgpwQBB2UgsVZ<CXT|ns>Ptp^i zwBg0qk|Bf6nGpk60FWiic$#ZEg&Sl+k;|2GN~0eQm3);rw&PAJuw%IT{_=}a$FSF= zQAZ1na)wS(5Lf18afK<IF!ighNijt+NhL}AvSw=Y`hwuzleKS&p<84MC6)fXVHlcc zh8lK6cimMdm+tc%RE{8Gm)zHHkkZfia~S2u$GZ^Z_4SHuOyL`-ZcdD6{O{;0sqRlC z?%mE1<P(>nf-{Wv<qFo~6<Tf@Z%QXXMz7EhaejKOoljn3AIRRzzwtTW`XW+i-NQik zyPLx_jWT^ZiL!w{F1q1ykPV`KckehZx<{v3sN-SQ-G02g*UeQ_MCw`8U2;S0>3<K! zOyJzbSzO0(XMiT3v`diKc7yG^pd*e>z(NB9e>#k_9<{W8$3pmmsM@TAkMB3uleo~~ zFwHk^IVFz4Bb1!44iGH*>&Y+-l41js*F428{(3NJ+D4Hlu}<PFDl9*WGihs^pKotJ z+S#*SkmnHs42@e>oTiqgL2gB^;!GoOh9{zNtd;wmtv?!vQSPqYt6M0ns5JMvGhmsD z(tQzU2VrU<u@I|w+?f-2s@%`M48DUz<=80}pJE{31Fwb;jeqkdbiku+oE)yuCfq_C z2O`K2gf?)!iSHc$ZR9p_sI{Mehaad=3^ivN-)t&Z`<|yd4)}%N{pboNs)fM{<(jFk zIlwtlt}}>M<q?HeajxE)b;a6sci1u=3~c=nYkYEmtyk~PcEyKZs+;Z|m1FfDCi@~k z9Lh8GKIZBJ2XLL{2bKkDV7+Wve-Vwf3jSY_)>1iF!!Rm}`?KG~R}b%@!cLE=wv$_P d!69#)tg2cS)L&1B@-&Gy+<#a78Jzid{{gkZ9oql^ delta 529 zcmYjNv2GJV5Z#&Gy*nG9qaY<tA|(w)bR85_hz64`F$K^h1#-M+>tb_v*;}L_t%Zb> z6cGZU`+>NOL`O}>52Rh?2O?3^P{3^5Fsprf^XAQHcCVIywc4-axXCDPem?I1h`zRW zKM2YKx?Q@}CxQ{1f@PP?z#OJ%QNdX1(}iNupn(~5*c=sh*p%mZd}=v)n0{us;xqUN z7A}Ql7A-%+bv6fJmMO}8Mmkr_GwNmrA6Ocv#8_xyq>>hA2<%u`T49JUSrYVaS^XQI zJ`bE2X1Q`<_r>1cPVbfDlQegYos&UYnykoO&@aoB46@W&ZW7_3+82Z3ZRH?yaCF6V zT%Y5UM2@`ZpIxmHFc8>;2sRK2Be)N1(4wQ4zHf>P<PMtkx1ig;{Y#MqPEN8st?EC) zqlf<6v>)W8?&qUaH#`?dMV9MuA>XYmE2FbgNA*Ftxw_&D-1JATaX2Xk<2S>1xxQC_ z4!1WPOdP5c-6G4rEDsCyUau0@o%%Z5+KN5BgSMX$FWO684AQDvspIHiJE1}^;aYm@ Rk_WFWn*M9AzeMY>{10mUh8O?< diff --git a/src/snipper/__pycache__/snip_dir.cpython-38.pyc b/src/snipper/__pycache__/snip_dir.cpython-38.pyc index 4ce3da3636250429478c87ecb3bafc063b86f0dc..f19c4b3b4d60e3eb2edd09eecf507974dbf6878e 100644 GIT binary patch delta 178 zcmeAa=oH`!<>lpK0D_G3&WSU*H}d^tW?|9N(x1%BvXIes@_Lr}jQW%PSzqx+aTVv6 z7A2>~r(_mIO?G2bX1p~yi%p9$YVuUJ7;a9W3B_d~bGRq}VRJNPVqxTA)MI2}WCLOl zn+XE{u&^@yW@F=G6atEYAR8keBM*}hkSwyBT)-{~G_Q-@g3)gBVRlnN76wKzWPs8? E0XqL8T>t<8 delta 178 zcmeAa=oH`!<>lpK00Q$Q>qK{fjeI|u+1RwS^tJRRbF(aDw4S_{Wj<rfWM9@-ytlZD z^Gl18Q{z)Ii*8MJVN+&|nw-I=#dvG-WVRS?4xkyuB_NZyCjVk{H0ESs;bGKcWMN|C zW?^Ju<zr+5!M`jlOn=x|xfq2QdBBj3k&lswNr;h;smNh+0lOs7v@UiFMu*9V*-ZtR M85qHk0ZRV_08ZT_c>n+a diff --git a/src/snipper/__pycache__/snipper_main.cpython-38.pyc b/src/snipper/__pycache__/snipper_main.cpython-38.pyc index 44240208f1eec24853d05c9805203b70e188365e..bf692bfec5b10c0f396eee21b21d79be8ddc833f 100644 GIT binary patch delta 775 zcmZ`%&1(}u6rVSnWIvLvvF#?^sBIL3E`C%B6~s`%k4hCnttl;nR=cyQ+ost#yFrBw zBE1@08N3KWaw&KdJbMy6`*D#ydGcTIAc!*?sYUTU-kW*9ncvKtdGBNXm7&k;x`w!{ zZeP6PJ=1@tk~R0i(lQ%_&v1i%gL`1IMSOEM!}s{YA&g-FAA&3tfbQdQ)y4w;ElEBK zyTHvq${m3=4p4|f90I`|sf_~Q5d-P??frZ`N|5E&N`xEdBgt+B)Z|#-6~t=Vb{f7% zmu!zXc@FprcL)+N0E0je^0^)7FHkNyZfS1)>sd!CO0fC<&1>l?JPPumz5zsDOKbuC zA|jGA(H|t#yBf*&01!!b^-%9Y+gL!8=o*?sizrkHT2r?$MZrjDgz5%v8{%DLgqS4J zkv6mKbeQg%p&?G##T&YgtU_fvVq>Y75V5zZ5~h3DV|fS)?74gba_pTvo{AEyp4(vG z<+7=Z3fNxrA$3>o22t`pbpyxB(PM00In9gmL^0tA+g3(1qx_{2)FSZd0ro*DjE`}E z76pp^iT{0N&8<6v>CdrAb$V~U2pSAy4K$pEVW1WELcIhf_Eo)7itx!(^r|@Yud-<6 zy6XqFx89uc*o-!@a8_KZ`OUf;)ZK>TTbRxW-sOME^c=?;-2Aw4AdW4lT57e@*NSD( zGlH09pS0dB(PeR|X+_KgeOEqlW^u15<#ms=>dqAK9sXhN6i^_uqglvEN?MZ<?C1|| F_7@k<qbC3W delta 1075 zcmZuwOK;Rj5bkQ*Gak=4nrtTXa3C)M%18l1q(uk<2VRGT70R+v(1_rj*q+3TGmmZ~ z9%_q2I3WQ+ed0Db!j&`Ul@J%SH{baWD}h#9?F2T9wp&%Ms{ZQXs_xh0A8YoCZCeCa z-#gd->81U5KGz8!xa*5@6#j%%eu4owa+R)Kw-Llk3Q8%A&{-&h1MCp$zj{=e_m_cR z9qAGhKZ4AMgvLY=LAPl_Vi3fax;77o<tc6Kx`POou?dvYq8D^|kGWpY59%s{iHv{_ z7W@Eg7G*ChS6Oi>pCVG0?5W(*rZxIv?(JLcIXo7|4rS%ICMw(D=kMxD6S`0C7BYzp z>$@Zw7vn?2ukJ!p-5^QLule;SL{u)5ZAj{|F6!HGnXFZB)1;a-L~Uq^MhA8Q$PNtt z!r1n=*k_gIHyY@fWpgPzE9{}=I|}X_LtEIG##tpll4bG>xkW@_znJy7AskV__Bg6h z{)B(X*Pm%G%1KcahmOc19Pk^vNt%b{J7Wkfc_(+&Z0RA3JP+i+9D95d+rg{IHZauM zNCG|?5`i_pqyrrtfgQucCZziBy|CM)DLt{kCegGvg?~q3Ekz-Zz1~*;LQBO|L(LIN z9!>4fs1ruPiKVFLwIhB3g$&(5I4i4gYAz|<e`MT?JIBv7C*-T#aj3~ZawiRT63^t9 z+|q2`b$QHOH?`HZ^LqV<EZo?PQ{z4h<Dfag=H+t!^p&)9ASX`?>unym{dT;`=2RAo z1)YEeT`%BiDGC)fa5sat&!(~>Iw^2ADL>?$Ytv|8Gs<doy6vqXg|MabUc_4lkAnws zs-conzW9ARzTQmr*D47AO^%yar|2Y@V3|6M;Dace(|A!U)B;N@Lj~B1jLa)J)$qOP z$oJ;r6k9-@-B9emx1XI-HaSyRJg*v*TD%{HaTIm~-lXhjb$IQ6ye^_`fW)(B2<)QV xDO7Sx3OOp@6n2lON1DAIwxT43KahEE;<9HoW@rh%?lP1#vuK$FzU|M}@;@*+@dW?? diff --git a/src/snipper/block_parsing.py b/src/snipper/block_parsing.py index 23d4271..27ca1b9 100644 --- a/src/snipper/block_parsing.py +++ b/src/snipper/block_parsing.py @@ -31,8 +31,6 @@ def block_join(contents): def block_split(lines, tag): stag = tag[:2] # Start of any next tag. - - contents = {} i, j = f2(lines, tag) @@ -80,7 +78,7 @@ def block_split(lines, tag): def argpost(line, j): nx_tag = line.find(stag, j+1) - arg1 = line[j+len(tag):nx_tag] + arg1 = line[j+len(tag):nx_tag] if nx_tag >= 0 else line[j+len(tag):] if nx_tag >= 0: post = line[nx_tag:] else: diff --git a/src/snipper/fix_bf.py b/src/snipper/fix_bf.py index c4d5254..d06de2f 100644 --- a/src/snipper/fix_bf.py +++ b/src/snipper/fix_bf.py @@ -4,7 +4,7 @@ from snipper.block_parsing import indent from snipper.block_parsing import block_split, block_join -def fix_f(lines, debug): +def fix_f(lines, debug, keep=False): lines2 = [] i = 0 while i < len(lines): @@ -20,31 +20,54 @@ def fix_f(lines, debug): if j+1 == len(lines) or ( jid < len(id) and len(lines[j].strip() ) > 0): break - if len(lines[j-1].strip()) == 0: + if len(lines[j-1].strip()) == 0: # Visual aid? j = j - 1 funbody = "\n".join( lines[i+1:j] ) if i == j: raise Exception("Empty function body") i = j + # print(len(funbody)) + # print("fun body") + # print(funbody) comments, funrem = gcoms(funbody) - comments = [id + c for c in comments] - if len(comments) > 0: - lines2 += comments[0].split("\n") + # print(len(comments.splitlines()) + len(funrem)) + # comments = [id + c for c in comments] + for c in comments: + lines2 += c.split("\n") + # print(funrem) f = [id + l.strip() for l in funrem.splitlines()] f[0] = f[0] + "#!b" errm = l_head if len(l_head) > 0 else "Implement function body" - f[-1] = f[-1] + f' #!b {errm}' + + """ If function body is more than 1 line long and ends with a return we keep the return. """ + if keep or (len( funrem.strip().splitlines() ) == 1 or not f[-1].strip().startswith("return ")): + f[-1] = f[-1] + f' #!b {errm}' + else: + f[-2] = f[-2] + f' #!b {errm}' lines2 += f + i = len(lines2) else: lines2.append(l) i += 1 + + if len(lines2) != len(lines) and keep: + print("Very bad. The line length is changing.") + print(len(lines2), len(lines)) + for k in range(len(lines2)): + l2 = (lines2[k] +" "*1000)[:40] + l1 = (lines[k] + " " * 1000)[:40] + + print(l1 + " || " + l2) + assert False + return lines2 # stats = {'n': 0} def _block_fun(lines, start_extra, end_extra, keep=False, silent=False): id = indent(lines[0]) - lines = lines[1:] if len(lines[0].strip()) == 0 else lines - lines = lines[:-1] if len(lines[-1].strip()) == 0 else lines + if not keep: + lines = lines[1:] if len(lines[0].strip()) == 0 else lines + lines = lines[:-1] if len(lines[-1].strip()) == 0 else lines cc = len(lines) ee = end_extra.strip() if len(ee) >= 2 and ee[0] == '"': @@ -59,13 +82,9 @@ def _block_fun(lines, start_extra, end_extra, keep=False, silent=False): else: l2 = ([id + start_extra] if len(start_extra) > 0 else []) + [id + f"# TODO: {cc} lines missing.", id + f'raise NotImplementedError("{ee}")'] - - # stats['n'] += cc return l2, cc - - -def fix_b2(lines, keep=False): +def fix_b(lines, keep=False): cutout = [] n = 0 while True: diff --git a/src/snipper/legacy.py b/src/snipper/legacy.py index d52e5ab..6d3f4ec 100644 --- a/src/snipper/legacy.py +++ b/src/snipper/legacy.py @@ -2,6 +2,38 @@ COMMENT = '"""' def gcoms(s): + lines = s.splitlines() + coms = [] + rem = [] + in_cm = False + for l in lines: + i = l.find(COMMENT) + if i >= 0: + if not in_cm: + in_cm = True + coms.append( [l]) + if l.find(COMMENT, i+len(COMMENT)) > 0: + in_cm = False + else: + coms[-1].append(l) + in_cm = False + else: + if in_cm: + coms[-1].append(l) + else: + rem.append(l) + if sum( map(len, coms) ) + len(rem) != len(lines): + print("Very bad. Comment-lengths change. This function MUST preserve length") + import sys + sys.exit() + + coms = ["\n".join(c) for c in coms] + rem = "\n".join(rem) + return coms, rem + + + + coms = [] while True: i = s.find(COMMENT) diff --git a/src/snipper/snip_dir.py b/src/snipper/snip_dir.py index f264c59..40b084f 100644 --- a/src/snipper/snip_dir.py +++ b/src/snipper/snip_dir.py @@ -5,6 +5,7 @@ import time import fnmatch import tempfile + def snip_dir(source_dir, # Sources dest_dir=None, # Will write to this directory output_dir=None, # Where snippets are going to be stored @@ -15,6 +16,7 @@ def snip_dir(source_dir, # Sources license_head=None, ): + if dest_dir == None: dest_dir = tempfile.mkdtemp() print("[snipper]", "no destination dir was specified so using nonsense destination:", dest_dir) @@ -52,46 +54,22 @@ def snip_dir(source_dir, # Sources shutil.copytree(source_dir, dest_dir) time.sleep(0.1) - ls = list(Path(dest_dir).glob('**/*.*')) + ls = list(Path(dest_dir).glob('**/*')) acceptable = [] for l in ls: split = os.path.normpath(os.path.relpath(l, dest_dir)) m = [fnmatch.fnmatch(split, ex) for ex in exclude] acceptable.append( (l, not any(m) )) - # for f,ac in acceptable: - # if not ac: - # print(f) - - # print(acceptable) - # now we have acceptable files in list. - # run_out_dirs = ["./output"] n = 0 - # edirs = [os.path.join(out, f_) for f_ in hw['exclusion']] # Exclude directories on exclude list (speed) - # edirs = {os.path.normpath(os.path.dirname(f_) if not os.path.isdir(f_) else f_) for f_ in edirs} - # edirs.remove(os.path.normpath(out)) for f, accept in acceptable: - if os.path.isdir(f) or not str(f).endswith(".py") or str(f).endswith("_grade.py"): # We only touch .py files. + if os.path.isdir(f) or not str(f).endswith(".py") or str(f).endswith("_grade.py"): continue - # f_dir = os.path.normpath(f if os.path.isdir(f) else os.path.dirname(f)) if accept: - # slist = hw['solutions'] if not CE else [os.path.basename(f)[:-3]] - # base = None - # if students_irlc_tools is not None: - # base = os.path.relpath(str(f), students_irlc_tools + "/..") - # base = base.replace("\\", "/") - - # if "assignments" in str(f) and "_grade.py" in str(f): - # continue - # info = {'new_references': [], 'code_copyright': 'Example student code. This file is automatically generated from the files in the instructor-directory'} - # paths = {} solution_list = [] kwargs = {} - # cut_files = True - # copyright() - - # run_files = True - nrem = censor_file(f, run_files=run_files, run_out_dirs=output_dir, cut_files=cut_files, solution_list=solution_list, + nrem = censor_file(f, run_files=run_files, run_out_dirs=output_dir, cut_files=cut_files, + solution_list=solution_list, base_path=dest_dir, references=references, license_head=license_head, @@ -105,7 +83,6 @@ def snip_dir(source_dir, # Sources if os.path.isfile(rm_file): os.remove(rm_file) else: - if os.path.isdir(rm_file + "\\"): + if os.path.isdir(rm_file+"/"): shutil.rmtree(rm_file) - return n diff --git a/src/snipper/snipper_main.py b/src/snipper/snipper_main.py index b2f5803..762bfbc 100644 --- a/src/snipper/snipper_main.py +++ b/src/snipper/snipper_main.py @@ -1,12 +1,11 @@ import os import re - from snipper.block_parsing import full_strip from snipper.fix_i import run_i from snipper.fix_r import fix_r from snipper.fix_s import save_s from snipper.fix_cite import fix_citations -from snipper.fix_bf import fix_f, fix_b2 +from snipper.fix_bf import fix_f, fix_b from snipper.fix_o import run_o @@ -23,12 +22,11 @@ def rem_nonprintable_ctrl_chars(txt): def censor_code(lines, keep=True): dbug = True - lines = fix_f(lines, dbug) - lines, nB, cut = fix_b2(lines, keep=True) + lines = fix_f(lines, dbug, keep=keep) + lines, nB, cut = fix_b(lines, keep=keep) return lines - def censor_file(file, run_files=True, run_out_dirs=None, cut_files=True, solution_list=None, censor_files=True, base_path=None, @@ -66,36 +64,36 @@ def censor_file(file, run_files=True, run_out_dirs=None, cut_files=True, solutio run_o(lines, file=file, output=ofiles[0]) run_i(lines, file=file, output=ofiles[0]) if cut_files: - save_s(lines, file_path=os.path.relpath(file, base_path), output_dir=run_out_dirs) # save file snips to disk + save_s(lines, file_path=os.path.relpath(file, base_path), output_dir=run_out_dirs) lines = full_strip(lines, ["#!s", "#!o", '#!i']) if censor_files: lines = fix_f(lines, dbug) - lines, nB, cut = fix_b2(lines) + lines, nB, cut = fix_b(lines) else: nB = 0 lines = fix_r(lines) - if censor_files and len(cut) > 0 and solution_list is not None: - fname = file.__str__() - i = fname.find("irlc") - # wk = fname[i+5:fname.find("\\", i+6)] - # sp = paths['02450students'] +"/solutions/" - # if not os.path.exists(sp): - # os.mkdir(sp) - # sp = sp + wk - # if not os.path.exists(sp): - # os.mkdir(sp) - sols = [] - stext = ["\n".join(lines) for lines in cut] - for i,sol in enumerate(stext): - sols.append( (sol,) ) - # sout = sp + f"/{os.path.basename(fname)[:-3]}_TODO_{i+1}.py" - # wsol = any([True for s in solution_list if os.path.basename(sout).startswith(s)]) - # print(sout, "(published)" if wsol else "") - # if wsol: - # with open(sout, "w") as f: - # f.write(sol) + # if censor_files and len(cut) > 0 and solution_list is not None: + # # fname = file.__str__() + # # i = fname.find("irlc") + # # wk = fname[i+5:fname.find("\\", i+6)] + # # sp = paths['02450students'] +"/solutions/" + # # if not os.path.exists(sp): + # # os.mkdir(sp) + # # sp = sp + wk + # # if not os.path.exists(sp): + # # os.mkdir(sp) + # sols = [] + # stext = ["\n".join(lines) for lines in cut] + # # for i,sol in enumerate(stext): + # # # sols.append( (sol,) ) + # # # sout = sp + f"/{os.path.basename(fname)[:-3]}_TODO_{i+1}.py" + # # # wsol = any([True for s in solution_list if os.path.basename(sout).startswith(s)]) + # # # print(sout, "(published)" if wsol else "") + # # # if wsol: + # # # with open(sout, "w") as f: + # # # f.write(sol) if len(lines) > 0 and len(lines[-1])>0: lines.append("") @@ -104,7 +102,6 @@ def censor_file(file, run_files=True, run_out_dirs=None, cut_files=True, solutio if license_head is not None: s2 = fix_copyright(s2, license_head) - with open(file, 'w', encoding='utf-8') as f: f.write(s2) return nB @@ -112,5 +109,4 @@ def censor_file(file, run_files=True, run_out_dirs=None, cut_files=True, solutio def fix_copyright(s, license_head): return "\n".join( ["# " + l.strip() for l in license_head.splitlines()] ) +"\n" + s - -# lines: 294, 399, 420, 116 \ No newline at end of file +# lines: 294, 399, 420, 116 diff --git a/src/snipper/version.py b/src/snipper/version.py new file mode 100644 index 0000000..283b03a --- /dev/null +++ b/src/snipper/version.py @@ -0,0 +1 @@ +__version__ = "0.1.7" \ No newline at end of file -- GitLab