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):
+
+![LaTeX sample](https://gitlab.compute.dtu.dk/tuhe/snipper/-/raw/main/docs/latex_nup.png)
 
 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`):
 
 ![LaTeX sample](https://gitlab.compute.dtu.dk/tuhe/snipper/-/raw/main/docs/index.png)
@@ -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