From 8a5cb5c964c72bd1c3071349182422d49bb39ad3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Henrik=20tom=20W=C3=B6rden?= <h.tomwoerden@indiscale.com>
Date: Tue, 3 Nov 2020 13:18:01 +0000
Subject: [PATCH] F xls readme

---
 CHANGELOG.md                                  |   3 +
 integrationtests/crawl.py                     |   4 +-
 .../2019-02-03_something/README.md            |  24 --
 .../2019-02-03_something/README.xlsx          | Bin 0 -> 5303 bytes
 .../2019-02-03_V1.0-rc1/README.xlsx           | Bin 0 -> 5255 bytes
 .../2019-02-03_V1.0-rc1/analyse.py            |   1 +
 .../2019-02-03_V1.0-rc1/calc.py               |   1 +
 .../2019-02-03_v0.1/README.md                 |  15 ++
 .../2019-02-03_v0.1/example.deb               |   1 +
 .../2010_TestSoftware/2019-02-03_v0.1/plot.py |   0
 .../2020NewProject0X/2020-02-03/README.md     |  15 ++
 .../2020NewProject0X/2020-02-03/calc.py       |   0
 .../2020NewProject0X/2020-02-03/plot.py       |   0
 .../2020-02-03_second/README.xlsx             | Bin 0 -> 5168 bytes
 .../2020-02-03_second/analyse.py              |   0
 .../2020-02-03_second/release.deb             |   1 +
 .../2020NewProject0X/2020-02-04/README.md     |  15 ++
 .../2020NewProject0X/2020-02-04/example.deb   |   1 +
 .../2020NewProject0X/2020-02-04/plot.py       |   0
 integrationtests/filldb.sh                    |   1 +
 integrationtests/insert_model.py              |   6 +
 integrationtests/model.yml                    |   8 +
 integrationtests/test_cache.py                |  71 +++---
 integrationtests/test_crawler_with_cfoods.py  | 212 ++++++++++++++++++
 src/caosadvancedtools/cfood.py                |  49 +++-
 src/caosadvancedtools/crawler.py              |   5 +-
 src/caosadvancedtools/utils.py                |  19 +-
 27 files changed, 380 insertions(+), 72 deletions(-)
 delete mode 100644 integrationtests/extroot/DataAnalysis/2010_TestProject/2019-02-03_something/README.md
 create mode 100644 integrationtests/extroot/DataAnalysis/2010_TestProject/2019-02-03_something/README.xlsx
 create mode 100644 integrationtests/extroot/Software/2010_TestSoftware/2019-02-03_V1.0-rc1/README.xlsx
 create mode 100644 integrationtests/extroot/Software/2010_TestSoftware/2019-02-03_V1.0-rc1/analyse.py
 create mode 100644 integrationtests/extroot/Software/2010_TestSoftware/2019-02-03_V1.0-rc1/calc.py
 create mode 100644 integrationtests/extroot/Software/2010_TestSoftware/2019-02-03_v0.1/README.md
 create mode 100644 integrationtests/extroot/Software/2010_TestSoftware/2019-02-03_v0.1/example.deb
 create mode 100644 integrationtests/extroot/Software/2010_TestSoftware/2019-02-03_v0.1/plot.py
 create mode 100644 integrationtests/extroot/Software/2020NewProject0X/2020-02-03/README.md
 create mode 100644 integrationtests/extroot/Software/2020NewProject0X/2020-02-03/calc.py
 create mode 100644 integrationtests/extroot/Software/2020NewProject0X/2020-02-03/plot.py
 create mode 100644 integrationtests/extroot/Software/2020NewProject0X/2020-02-03_second/README.xlsx
 create mode 100644 integrationtests/extroot/Software/2020NewProject0X/2020-02-03_second/analyse.py
 create mode 100644 integrationtests/extroot/Software/2020NewProject0X/2020-02-03_second/release.deb
 create mode 100644 integrationtests/extroot/Software/2020NewProject0X/2020-02-04/README.md
 create mode 100644 integrationtests/extroot/Software/2020NewProject0X/2020-02-04/example.deb
 create mode 100644 integrationtests/extroot/Software/2020NewProject0X/2020-02-04/plot.py

diff --git a/CHANGELOG.md b/CHANGELOG.md
index cb8662e1..7c458aed 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
   output
 - New class for collecting information for exporting tables, e.g., to
   metadata repositories
+- new name parsing
+- new test for software folder structure
+- new assure_name_is function
 
 ### Changed ###
 
diff --git a/integrationtests/crawl.py b/integrationtests/crawl.py
index e439510f..2bec8b0a 100755
--- a/integrationtests/crawl.py
+++ b/integrationtests/crawl.py
@@ -33,7 +33,7 @@ from caosadvancedtools.cfood import fileguide
 from caosadvancedtools.crawler import FileCrawler
 from caosadvancedtools.guard import INSERT, UPDATE
 from scifolder import (AnalysisCFood, ExperimentCFood, ProjectCFood,
-                       PublicationCFood, SimulationCFood)
+                       PublicationCFood, SimulationCFood, SoftwareCFood)
 
 try:
     from sss_helper import get_argument_parser, print_success
@@ -87,7 +87,7 @@ if __name__ == "__main__":
     c = FileCrawler(files=files, use_cache=True,
                     interactive=False, hideKnown=False,
                     cfood_types=[ProjectCFood,
-                                 ExperimentCFood, AnalysisCFood,
+                                 ExperimentCFood, AnalysisCFood, SoftwareCFood,
                                  PublicationCFood, SimulationCFood,
                                  ])
 
diff --git a/integrationtests/extroot/DataAnalysis/2010_TestProject/2019-02-03_something/README.md b/integrationtests/extroot/DataAnalysis/2010_TestProject/2019-02-03_something/README.md
deleted file mode 100644
index 4470e62b..00000000
--- a/integrationtests/extroot/DataAnalysis/2010_TestProject/2019-02-03_something/README.md
+++ /dev/null
@@ -1,24 +0,0 @@
----
-responsible:	
-- First Person
-description: 	A description of an example analysis.
-
-results:
-- file:	"images/*.png"
-  description:  an example reference to a results file
-
-sources:
-- file: /ExperimentalData/2010_TestProject/2019-02-03_something/
-  description:  an example reference to an experiment
-
-revisionOf:
-- ../2019-02-03
-
-scripts:
-- analyse.py
-
-tags:
-- collagen
-- time sweep
-- frequency sweep
-...
diff --git a/integrationtests/extroot/DataAnalysis/2010_TestProject/2019-02-03_something/README.xlsx b/integrationtests/extroot/DataAnalysis/2010_TestProject/2019-02-03_something/README.xlsx
new file mode 100644
index 0000000000000000000000000000000000000000..94afaa4f0af396997ec5582bc377245524817e00
GIT binary patch
literal 5303
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

literal 0
HcmV?d00001

diff --git a/integrationtests/extroot/Software/2010_TestSoftware/2019-02-03_V1.0-rc1/README.xlsx b/integrationtests/extroot/Software/2010_TestSoftware/2019-02-03_V1.0-rc1/README.xlsx
new file mode 100644
index 0000000000000000000000000000000000000000..37024543a36f17e717d472a63576c017a138a1de
GIT binary patch
literal 5255
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
E1MkP#u>b%7

literal 0
HcmV?d00001

diff --git a/integrationtests/extroot/Software/2010_TestSoftware/2019-02-03_V1.0-rc1/analyse.py b/integrationtests/extroot/Software/2010_TestSoftware/2019-02-03_V1.0-rc1/analyse.py
new file mode 100644
index 00000000..02e78c5b
--- /dev/null
+++ b/integrationtests/extroot/Software/2010_TestSoftware/2019-02-03_V1.0-rc1/analyse.py
@@ -0,0 +1 @@
+sdkflj2435j4'4rg
diff --git a/integrationtests/extroot/Software/2010_TestSoftware/2019-02-03_V1.0-rc1/calc.py b/integrationtests/extroot/Software/2010_TestSoftware/2019-02-03_V1.0-rc1/calc.py
new file mode 100644
index 00000000..ec7f06eb
--- /dev/null
+++ b/integrationtests/extroot/Software/2010_TestSoftware/2019-02-03_V1.0-rc1/calc.py
@@ -0,0 +1 @@
+sldfkjsldfjk
diff --git a/integrationtests/extroot/Software/2010_TestSoftware/2019-02-03_v0.1/README.md b/integrationtests/extroot/Software/2010_TestSoftware/2019-02-03_v0.1/README.md
new file mode 100644
index 00000000..d844a2dd
--- /dev/null
+++ b/integrationtests/extroot/Software/2010_TestSoftware/2019-02-03_v0.1/README.md
@@ -0,0 +1,15 @@
+---
+responsible: Responsible, Only
+description: 	A description of this example analysis.
+
+sources:
+- file:	"/ExperimentalData/2010_TestProject/2019-02-03/*.dat"
+  description:  an example reference to a results file
+
+sourceCode:
+- file: plot.py
+  description: a plotting script
+binaries:
+- file: example.deb
+  description: the binary file
+...
diff --git a/integrationtests/extroot/Software/2010_TestSoftware/2019-02-03_v0.1/example.deb b/integrationtests/extroot/Software/2010_TestSoftware/2019-02-03_v0.1/example.deb
new file mode 100644
index 00000000..685dc927
--- /dev/null
+++ b/integrationtests/extroot/Software/2010_TestSoftware/2019-02-03_v0.1/example.deb
@@ -0,0 +1 @@
+binary file
diff --git a/integrationtests/extroot/Software/2010_TestSoftware/2019-02-03_v0.1/plot.py b/integrationtests/extroot/Software/2010_TestSoftware/2019-02-03_v0.1/plot.py
new file mode 100644
index 00000000..e69de29b
diff --git a/integrationtests/extroot/Software/2020NewProject0X/2020-02-03/README.md b/integrationtests/extroot/Software/2020NewProject0X/2020-02-03/README.md
new file mode 100644
index 00000000..a47ea6e1
--- /dev/null
+++ b/integrationtests/extroot/Software/2020NewProject0X/2020-02-03/README.md
@@ -0,0 +1,15 @@
+---
+responsible:	
+- Only Responsible MPI DS
+description: 	A description of this example analysis.
+
+sources:
+- file:	"/ExperimentalData/2010_TestProject/2019-02-03/*.dat"
+  description:  an example reference to a results file
+
+sourceCode:
+- file: plot.py
+  description: a plotting script
+- file: calc.py
+  description: a calc script
+...
diff --git a/integrationtests/extroot/Software/2020NewProject0X/2020-02-03/calc.py b/integrationtests/extroot/Software/2020NewProject0X/2020-02-03/calc.py
new file mode 100644
index 00000000..e69de29b
diff --git a/integrationtests/extroot/Software/2020NewProject0X/2020-02-03/plot.py b/integrationtests/extroot/Software/2020NewProject0X/2020-02-03/plot.py
new file mode 100644
index 00000000..e69de29b
diff --git a/integrationtests/extroot/Software/2020NewProject0X/2020-02-03_second/README.xlsx b/integrationtests/extroot/Software/2020NewProject0X/2020-02-03_second/README.xlsx
new file mode 100644
index 0000000000000000000000000000000000000000..1a2262719f155893c8d88580ed0556f684a4c1b7
GIT binary patch
literal 5168
zcmaJ_1ys}T`X{6jP(}$ziz7!!38JD%GrD0kQiCBaA^k&Iq(c~pASqIl?ie8<<!BHG
zIA9_ob%)+_<?sDpKRah}&i8rVy-$3e0IL#S0N|05lH!3aEVS^>fdcp40%q&%AuM$E
zUY0bhLMTELi3x~K@b|8zyJu6~6g85m6cC(-Jh7Ba2QhgD_=l1j7Z;MmD+gi+B^5GI
zm+khVvmQh%LQ9PKW8#h&{o%P(`y0$nP6mTqL;5P*)Ykna<DJf5*^^xH-!OxwOtsn#
z&1IG#g)9w`HYgkaFc&im!c&{|Z&AhH<{ah@WA!8V!%{1YHxwhiSIh|%rQa_Z<R=L3
zUsn_sYrLd6d{0O3#TAWr&cfaY{u-p$KbTZ4M7d|z>sH64wRp-lM8dM=X+MRPpLj^u
zlPboaG$yHk9&ngweb4J4j|WyIA+;+BsawEZ`z8S%?)U#P3^ncvAKV#Gn3EM0>Lldj
z;vB2f=Q<0bY1-$%Fy|s@?#P@%Xb_ixhoUL!OHWdycZvq|?xf_O&Rw!C1kNZ>OGs19
zH>Ud<&t*A%W^FUf(?hmfw#tGVgu-UJHfOD6(nN+i4Tw^W?;wRB(}QT#Zo%NKF+a?E
ze_LyP4hf3+I$xq2bqc89e5GRb!9=xctc!IpLoGq8@eeH;wc`wmE}5|K12#&t6wb)l
zA?h2~H*Zra#dXf<=T|w`2!w|fm2<>P4%(a3ZiNkG9lSP}v4~CGc^u@R-5+PvS4iae
zcC<)%+PgPVW=UJLVu5r~mT?~#blagojZb0d!w&a?O;C@?7{}a0F7vl{h3XpKYj;x)
zE*i&1<@cV_P1lC{t)y>df3nXb4~S+eTi;y^giYFOLGDvz#n~;-d6QTfSR(BfWv#C6
zZsxJ-BCp}g@rPHfM@s=TvRR&>Yv^yRw0!Q}DwUU8B}SV^2<pjTC`vXTg#cv8`9+mu
z2JeDfKQbgLY+AlTWvvO*P_FX+coB7X3ML8;93{F_O15<+$ZTk0F8H1XcjO(K+Df<X
zczTqtUefrZQ&n|s8)BJbDWC`jat|5Un5#HuBN0pdUWVmj`?i<pchD70;?FJ_qYFvu
zqMAWTr7Y->K;;MX`FYS4{Qj+Qeg7}qwO+}O`_U(?Vl4)pXoj<xdFms!wU3X7=Suju
zn7Q~XW;`DGI@@}jMa-7r9q24OV-v=J{mbj@arN0g_X*Gbw*$Im#o`ZVo=i<Cc0uqT
zE}#4eVC<=yeW_FJsCpw8L_N2BfKSCCYeL6@(k@H~=*Y@!i{x{rkkL*9EQakdnxN5$
zs4zF+I>118xq98~m3r4_J&oc!FWA2VK6NLv$!hjU3r5{a;Gr8yQqiL!)O+rW&`su^
zNh1Fe8xXWa7e>YsZe@fdhieI>wg<=3-!1>1geOG?g25O>@%eL$UqriY-p`vPy=TeS
zrTB^TCOi3_9D;}C-s4%bgb>7!1}hB?9-`&vggx|89{J*p+3<-_sbba($8vCC(@-r{
z$60fcEJWp4s}!`zGKQ5VoJS;rPb8GwG3S#WnFkQ!E}f2;hx*w^>%w=mBEbxxFLz5W
z(0h1uNR#r!*l5!PnrXPJ)W56`ztwlMiWr_SHXdAQW#p>Lr8*qV$VK5}=&?DX(GU>4
zXx)GC9K|$0HaL68j!<E%u+cJrbQoa<;X00d1p>Y|p6M6l@R2+&efRZ+$B?Mj2nnn3
zb8&Hrz3%eG>)ac0*HfcWp>MNWOQ)5--GmrAbL=-WG9g6g3pg~0WKlrvA$(^A8_r^q
z2#IX|Yp!ds?istNVrLh2p=IWbQuQ?fe8-}qIu$nVRVeJrR))?p))H)1pE>Tw)W|Bf
z+=iqVyNM}lLqhC8VrfjsO{;5CAB!ydA#V*_Lz-Y>bG(v8r&BSLa?S5L9)DtC+w_aE
zD2&hcK0eCZtMht^>7s8GsGZE*=BO#sp54Xlbmg_>uG!OiEFU$sn%CbvO>J#lqn&K1
zcaZm{{dgy_36QZ=@$Flzx2TB#lcZXG#(tU`$J3C~!}vJAu#U0|Upr2xldj2)t|*Uc
z1NoLpR$2C?+B~x640+||c61t5ORUmDQ(!ajdj4YdH1M=(vSQ0mt}J}CKHG%jNf)}m
z#XaM1uJmxQwSD9v{PRQPECsp@)IDY;aHMJA=F?kzN&f2=xW68TvgxjpO&LReB~E-1
zB|-|qyKZ!{XNJe!mEgGa`E4B4<p#s%fJ6F(ocw)FY0qzlO6}9_7m1rUAoSR<N>uyt
zk=wD?nv6C_3VT13dHn+c)c$})J|RQAXosORUh7KxU^1VOtY$aUP$J7VftaCkLvLb;
zNfp+H1EhjfklwpHpX(#diL4=ISmI%nRacIw@G)IW15$8;?vt+%!~otX`m30SrIJ1h
z)_1xk7;WHH*hZl!*O=8AqMB232Z@a~ZW(BhJbgJV8%S2g@=wnjABt8Y@j9O5SMbGk
zAuhr|-mGMBco%O@tEu)qo`@QPt!?7h!{J5-)JIeVdvEGu8qtfk$utSzc+@2+v-;Gw
zF2|Q*YVElf*ladt5}D*p30smljXKg7*h07^!z`otwDjBj0}b7O;D-fgPndtF2(|LO
zonS^x33V&Eq7+`stma<oHX7(^#z%f%qUZ6zL&Agwz;oS)<lwlcGR-926sZrTfwy<>
zrW#tRcxIN<ZhVXnmS<`h68m&;O%ZU9{BetCkkeI+VojFhdM_@~YD%a(Qm_wLN9&uf
zCVjp;V8R~Y#z$Ws%8uxv%hWoYyM^wp@M4FLHHy0(BCZbBOpAcfDrDeA3T0j5#*j8{
z7Rk$Pw{JlNB}>ByrN)LqQ8L=f!)4u)KJma6W}z*{;Mot2P)A{|5uFV0GJ*Ty<JGTk
zFKhGV7!+(i5>4Vx%NBHUTw5nIZ0EIH(<y<cQ>{B&wnIQU4(=zjN!2AbQ^{X5lb%69
zb=b<mnF`W^5z)&>3#1Ce^tA}_8jU1xrKwE}vUMC&Lt?f8^J7!hp}U$wgAzXOoplP)
z>xN2nKy5FmN05{<qw|zmyqI4VgudQ+1UkSE$~=PosI{BoWbj&-EX{~n^!*{XbXvXJ
zk+Tb@+OyO=*zrZfb6*_2dJr_4Cyxfo%V`E3_4mj7kK41NmveTp*l}L@I|s_pVG@+s
z&CkkgM;&?D&2PSq<$Sy2lQX^fJbmy7GJ@5}{<xxjDBUbu`SVrdioG%m()Mu#sClff
zbv@WXmI;pP19);lhB92biG0`8I1mJ4Pw#I?QnEB~x(BsYQ(<T?-y{exuF^Px$YjRx
z%X=b-dMsIvrLrW1*I@&6uL4|o!~(Z2B*4mz_sy4u1_;#DpL9jYc2umeBs(#^N+vCS
ztBw%F3f8v7bpP{Vr~u(|6t&?nRRgM_4TMlL**SsnB%ZLd2fOc6)Ggd4yVrWx1sKgH
zY_!Y6RU%SH<#Ujd7}EwW^C>^QrnE)G)T#1M#k)-eyq=Gg<u*!5E>wqmji}A8;t9Qe
znG-zmS%P@Dw21AtW-@wPMmcakxAsVMN!e;^XZK?NQ*VCk$(mE&#5)qu+GqpOSirDH
zXJAv$O*d4#P(~xWI9qC>cgXvw6l9CSc!t#4kD*q5!^Fuiw62-*fdei0XQf#QzVOlP
z0v?_$)!!)U$}fug)#{#a>VCDoiRw-rAj$~LF`eRmmokZUj8s$1or>e{_}yAdBb^dS
zA8Jkmg_upqZ8t}rjZ9*jmG;o$vFNBy1xn(Q{0rf2U0$S3PmaHAEi&m3=FcrS@WfZL
zU4Gq2I~B~JENonjl`;~tYEQUnR?w%SQ9Rj)2;I^W9@teL%fcU|(<w6{eCx^&e&^AC
zT4_Cbk;($@p9|`}NHz$&nke>OJKk!6Us#mntINTv+^Re$>l9aiMQE<Jgg4oQh22$*
z9|e?bwhSD&dBly~txLaORxyWqOmJoy&FurfQQW<G{@UmQ{@t`VSi)>=bRNMTy4wHj
zU7kN1(C7fs+~0p3zAEQwsg=j49G-v(Cwko4nO?v(ZN2PHzPjP=Tg<<*c~tNHCChzu
zbFN9}f!f_CCIQvPPT|fSw`FU7tojB86g{_Dz&C|B?~&cdAYLnSzqYb{xX1CS{)XZv
zzFaTgy%m^P#0p2MOGZp*7^oLt#NXa^awB%<cIoqrVxKO*d+%uHm%X!ydEpd>SmU?h
zzvSE}q<3NLlBWT^NF^9kDbnDCG&+nA)GYpbs0$)AU?K)&Ubib5pMOmeNe(CAu|z@>
zw!IO0p4`a36d<LiY3qVU$ZH>CLo-1l0ofI}!k)|P-LIVqq=hP3BOHKa{KNZQ49~*c
zxm6d&HLYyDAIQirEgSL-gC?||MHV9gyzyCi8Ep}}W@G`T;?RD!>42Zs##GM}2;f@8
z;lH+smvGj$fm(xMP&W@@Ybeb2=U}0&r5OsM3EVeGXjTBKRntKJ8Io)0ycg45H#28Q
zr!wZ%aqLe)Dd9<dEuj9exndr*mm$=Q?w<{n$WLVAyb~pxP`Y?Gg~?Ay|AfW2Tf6V!
zgi`MaKBe)B0Z@m`Nn94K_@tH~=2J|8a$5h$2jk}u=P7zeM}ne+xR2t9mmdldozZVJ
zjS1q~w}~eqz@n6;p%H^;Tr$Qm38RwiazDZ$kS;j%E%CG9g=Zf^$_KkB<UC%MhRa>A
zzs}mHd~Dmv?K~^tuxyV0!Ta6wczZk^O))J;N0?mHqj>q{6XS&(F{KC?7xwbJ;8VJO
zIEXx8X{eMPyW*FF%&!BSBIcQ>+~L?bIUb=kNE&@Vf|Y88x^<QA`Rb6I&rqAi+<p=n
zTHi>^deZeXGyc)H1z918^@F`jhdoLnK&A0mK#x6FEOCJCMgRB5O<A1*6F|(2#JhcJ
zh*#P}6;tQ%GDvXG_HZQf7hPP#E5&obx#{CylY-{Fn=IYj&b*YJps9+Rjk_>`(L7%f
znf2?n$<EQ7IpltZU9*>;O-m#NaaR`}e^U&vwTEJTI-qNE&C)U^tn9Y5k2PJH75TRL
z9G1Q1e3^4L=!!*EDhe-N^@D||Ni0RQyrkjCdtyo#GAuXE)qSJTXsfO7BVd-np1e<4
zKy=F??HYUm_-gvegBj5eT2h=o7ZGvx><3)s)ZZ;$QK3Jz9No&YS|qy_{8XQlR6}@~
z20YUyMYFZrEI4F@jbtp-x~vMca=Jy^cx0Y85OKq0OsKYdxM#<DA{J%lSp!M|SiDFO
zt@X)@o$y>V?~rN&@HYP>*4Mq}b(=Ul1mLXozvpCH+!H1WP}fJcu8%Bqecfz5%+8F}
zq<d2n#~yQs{G1iny#|75RA!#^kUqaICc48T63g}mHG1Md1k}|d14c#ly}>l6YI;e`
zzPe@<iXC0gQd>{senG)iP!8LDTdykI=Pc7tZ$fda&3wy4I}%dCh`oO?+TT&Yp)|Q0
zc-3u#&(61Mg{~62^F4%4kz|D9Vq&OTHS_g@60*K6;&^WaDR19ixv<J=n8|E!2#G+o
zp>4JlNh}7xyhaL^8Yl&ia`t~LSSEjH-iAgi-7ZNJ$noYf7ks2JLtu4v8<7o2KsHe9
zn_3e3$0;+iN@=E!zO;mP3tGe&jh;ARz+$#Q3YwROz%VeB)osx!_{Ei(a$Wag&Go}d
zmsaYsxug?zUyAKgvulKzQ`p??D(3w~mXn8Ho7bp1t=+yiljOE?c;`}g4rf<g4I(CK
zk=#kYnXmKo-a^PG+p+nj;?t}vOC}8L&5rS+9|Dudyw8X|{@bdA5zZ?HL}#ACCjj96
zUhbUVZk#Q5{x8n0dj1qVKjNM(KYoJ=chn!NkUxdb&k1K!@NcN&=nyCTdnW!<^nCq3
z8zg??KJkCf{jag&Pc7$b-C3>wjdk*$TK-4n|5ML-7C)<uzd=Cx>mq;F$v-umCzvy~
z{|z^sW&Vr%{}ex;b!RN}8w@zF{73xXob;#k`N%lS%-^_!Q}+MlDOi;VXJtG*GTciZ
LM~SmP|MI^8u4cIx

literal 0
HcmV?d00001

diff --git a/integrationtests/extroot/Software/2020NewProject0X/2020-02-03_second/analyse.py b/integrationtests/extroot/Software/2020NewProject0X/2020-02-03_second/analyse.py
new file mode 100644
index 00000000..e69de29b
diff --git a/integrationtests/extroot/Software/2020NewProject0X/2020-02-03_second/release.deb b/integrationtests/extroot/Software/2020NewProject0X/2020-02-03_second/release.deb
new file mode 100644
index 00000000..92861e91
--- /dev/null
+++ b/integrationtests/extroot/Software/2020NewProject0X/2020-02-03_second/release.deb
@@ -0,0 +1 @@
+kdjf
diff --git a/integrationtests/extroot/Software/2020NewProject0X/2020-02-04/README.md b/integrationtests/extroot/Software/2020NewProject0X/2020-02-04/README.md
new file mode 100644
index 00000000..97b7137a
--- /dev/null
+++ b/integrationtests/extroot/Software/2020NewProject0X/2020-02-04/README.md
@@ -0,0 +1,15 @@
+---
+responsible:	
+- Some Responsible
+- Responsible, No, MPI DS
+description: 	A description of this example analysis.
+
+sources:
+- file:	"/ExperimentalData/2010_TestProject/2019-02-03/*.dat"
+  description:  an example reference to a results file
+
+sourceCode: plot.py
+binaries:
+- file: example.deb
+  description: the binary file
+...
diff --git a/integrationtests/extroot/Software/2020NewProject0X/2020-02-04/example.deb b/integrationtests/extroot/Software/2020NewProject0X/2020-02-04/example.deb
new file mode 100644
index 00000000..685dc927
--- /dev/null
+++ b/integrationtests/extroot/Software/2020NewProject0X/2020-02-04/example.deb
@@ -0,0 +1 @@
+binary file
diff --git a/integrationtests/extroot/Software/2020NewProject0X/2020-02-04/plot.py b/integrationtests/extroot/Software/2020NewProject0X/2020-02-04/plot.py
new file mode 100644
index 00000000..e69de29b
diff --git a/integrationtests/filldb.sh b/integrationtests/filldb.sh
index da1abc70..98d22347 100755
--- a/integrationtests/filldb.sh
+++ b/integrationtests/filldb.sh
@@ -5,5 +5,6 @@ python3 -m caosadvancedtools.loadFiles /opt/caosdb/mnt/extroot/ExperimentalData
 python3 -m caosadvancedtools.loadFiles /opt/caosdb/mnt/extroot/DataAnalysis
 python3 -m caosadvancedtools.loadFiles /opt/caosdb/mnt/extroot/SimulationData
 python3 -m caosadvancedtools.loadFiles /opt/caosdb/mnt/extroot/Publications
+python3 -m caosadvancedtools.loadFiles /opt/caosdb/mnt/extroot/Software
 python3 insert_model.py 
 python3 crawl.py /
diff --git a/integrationtests/insert_model.py b/integrationtests/insert_model.py
index 1dcf5bdb..2289f72e 100755
--- a/integrationtests/insert_model.py
+++ b/integrationtests/insert_model.py
@@ -1,5 +1,11 @@
 #!/usr/bin/env python3
+import caosdb as db
 from caosmodels.parser import parse_model_from_yaml
 
 model = parse_model_from_yaml("model.yml")
 model.sync_data_model(noquestion=True)
+
+if len(db.execute_query("FIND Property alias")) == 0:
+    al = db.Property(name="alias")
+    al.add_parent(name="name")
+    al.insert()
diff --git a/integrationtests/model.yml b/integrationtests/model.yml
index 35f5ef24..39659c00 100644
--- a/integrationtests/model.yml
+++ b/integrationtests/model.yml
@@ -10,6 +10,14 @@ Experiment:
   #recommended_properties:
     responsible:
 Project:
+SoftwareVersion:
+  recommended_properties:
+    version:
+      datatype: TEXT 
+      description: 'First name of a Person.'
+    binaries:
+    sourceCode:
+    Software:
 Person:
   obligatory_properties:
     firstName:
diff --git a/integrationtests/test_cache.py b/integrationtests/test_cache.py
index cb9c174e..4b0a6ced 100644
--- a/integrationtests/test_cache.py
+++ b/integrationtests/test_cache.py
@@ -22,45 +22,54 @@
 #
 # ** end header
 
+import os
+import unittest
+from tempfile import NamedTemporaryFile
+
 import caosdb as db
 from caosadvancedtools.cache import UpdateCache
 
 
-def setup():
-    try:
-        db.execute_query("FIND Test*").delete()
-    except Exception:
-        pass
-
+class CacheTest(unittest.TestCase):
+    def empty_db(self):
+        try:
+            db.execute_query("FIND Test*").delete()
+        except Exception:
+            pass
 
-def teardown():
-    setup()
+    def setUp(self):
+        self.cache = NamedTemporaryFile(delete=False).name
+        os.remove(self.cache)
+        self.empty_db()
 
+    def tearDown(self):
+        self.empty_db()
+        os.remove(self.cache)
 
-def test_same_old_different_new():
-    """Formerly, inserting two containers with different changes to the
-    same entity into the update cache would result in an
-    IntegrityException.
+    def test_same_old_different_new(self):
+        """Formerly, inserting two containers with different changes to the
+        same entity into the update cache would result in an
+        IntegrityException.
 
-    """
-    rt = db.RecordType(name="TestType").insert()
-    db.Property(name="TestProp1", datatype=db.TEXT).insert()
-    db.Property(name="TestProp2", datatype=db.TEXT).insert()
-    rec = db.Record(name="TestRecord").add_parent(rt).insert()
+        """
+        rt = db.RecordType(name="TestType").insert()
+        db.Property(name="TestProp1", datatype=db.TEXT).insert()
+        db.Property(name="TestProp2", datatype=db.TEXT).insert()
+        rec = db.Record(name="TestRecord").add_parent(rt).insert()
 
-    # add TestProp1 to TestRecord
-    rec.add_property(name="TestProp1", value="blub")
-    cont = db.Container().append(rec)
+        # add TestProp1 to TestRecord
+        rec.add_property(name="TestProp1", value="blub")
+        cont = db.Container().append(rec)
 
-    update = UpdateCache()
-    run_id = "a"
-    update.insert(cont, run_id)
-    assert len(update.get_updates(run_id)) == 1
+        update = UpdateCache(db_file=self.cache)
+        run_id = "a"
+        update.insert(cont, run_id)
+        assert len(update.get_updates(run_id)) == 1
 
-    # duplicate and add TestProp2 to TestRecord
-    rec = db.execute_query("FIND Record TestRecord", unique=True)
-    rec.add_property(name="TestProp2", value="bla")
-    cont = db.Container().append(rec)
-    # same old digest, different new digest
-    update.insert(cont, run_id)
-    assert len(update.get_updates(run_id)) == 2
+        # duplicate and add TestProp2 to TestRecord
+        rec = db.execute_query("FIND Record TestRecord", unique=True)
+        rec.add_property(name="TestProp2", value="bla")
+        cont = db.Container().append(rec)
+        # same old digest, different new digest
+        update.insert(cont, run_id)
+        assert len(update.get_updates(run_id)) == 2
diff --git a/integrationtests/test_crawler_with_cfoods.py b/integrationtests/test_crawler_with_cfoods.py
index cc6f0677..c39c3fc6 100755
--- a/integrationtests/test_crawler_with_cfoods.py
+++ b/integrationtests/test_crawler_with_cfoods.py
@@ -266,3 +266,215 @@ class CrawlerTest(unittest.TestCase):
 
         # Test type
         self.assertEqual(pub.parents[0].name, "Thesis")
+
+    def test_software(self):
+        ########################
+        # # overall software # #
+        ########################
+
+        sw = db.execute_query("FIND Record Software")
+        assert len(sw) == 5
+
+        ps = db.execute_query("FIND RecordType Software with name!='Software'")
+        assert len(ps) == 2
+
+        ##############################
+        # # first software version # #
+        ##############################
+        ana = db.execute_query(
+            "FIND Software with version='V1.0-rc1'", unique=True)
+
+        sw = db.execute_query(
+            "FIND Software with name='2010_TestSoftware'", unique=True)
+        assert sw.get_property("alias").value == "TestSoftware"
+
+        # The software record should inherit from the correct software
+        assert sw.id == ana.get_parents()[0].id
+        assert ana.name == "TestSoftware_V1.0-rc1"
+
+        # There should not be a file as binary
+        self.assertIsNone(ana.get_property("binaries"))
+
+        # There should be a file as script attached with path plot.py
+        datfile = get_entity_with_id(ana.get_property("sourceCode").value[0])
+        datfile2 = get_entity_with_id(ana.get_property("sourceCode").value[1])
+
+        for d in [datfile, datfile2]:
+            if datfile.path.endswith("analyse.py"):
+                assert datfile.description == "a simple script"
+            elif datfile.path.endswith("calc.py"):
+                assert datfile.description == "some calculation"
+            else:
+                raise Exception("unkown file")
+
+        # Should have two responsible person
+        self.assertIsNotNone(ana.get_property("responsible"))
+        person = db.Record(id=ana.get_property("responsible").value[0])
+        person.retrieve()
+        person2 = db.Record(id=ana.get_property("responsible").value[1])
+        person2.retrieve()
+
+        for fn in ["Second", "First"]:
+            found = False
+
+            for p in [person, person2]:
+                if p.get_property("firstname").value == fn:
+                    found = True
+
+            if not found:
+                raise Exception("Did not find person")
+
+        # Should have a description
+        self.assertIsNotNone(ana.description)
+
+        #######################
+        # # second software version # #
+        #######################
+        ana = db.execute_query(
+            "FIND Software with version='v0.1'", unique=True)
+
+        sw = db.execute_query(
+            "FIND Software with name='2010_TestSoftware'", unique=True)
+
+        # The software record should inherit from the correct software
+        assert sw.id == ana.get_parents()[0].id
+        # The software should have the date
+        assert "2019-02-03" == ana.get_property("date").value
+
+        # There should be a file as binary attached with path release.deb
+        datfile_id = ana.get_property("binaries").value[0]
+        datfile = get_entity_with_id(datfile_id)
+        assert os.path.basename(datfile.path) == "example.deb"
+
+        # There should be a file as script attached with path plot.py
+        datfile_id = ana.get_property("sourceCode").value[0]
+        datfile = get_entity_with_id(datfile_id)
+        assert os.path.basename(datfile.path) == "plot.py"
+
+        # Should have a responsible person
+        self.assertIsNotNone(ana.get_property("responsible"))
+        person = db.Record(id=ana.get_property("responsible").value[0])
+        person.retrieve()
+        self.assertEqual("Only", person.get_property("firstname").value)
+
+        # Should have a description
+        assert "example" in ana.description
+
+        #######################
+        # # third software version # #
+        #######################
+        ana = db.execute_query(
+            "FIND Software with date='2020-02-04' and not version",
+            unique=True)
+
+        sw = db.execute_query(
+            "FIND Software with name='2020NewProject0X'", unique=True)
+
+        # The software record should inherit from the correct software
+        assert sw.id == ana.get_parents()[0].id
+        # The software should have the date
+        assert "2020-02-04" == ana.get_property("date").value
+
+        # There should be a file as binary attached with path release.deb
+        datfile_id = ana.get_property("binaries").value[0]
+        datfile = get_entity_with_id(datfile_id)
+        assert os.path.basename(datfile.path) == "example.deb"
+
+        # There should be a file as script attached with path plot.py
+        datfile_id = ana.get_property("sourceCode").value[0]
+        datfile = get_entity_with_id(datfile_id)
+        assert os.path.basename(datfile.path) == "plot.py"
+
+        # Should have two responsible person
+        self.assertIsNotNone(ana.get_property("responsible"))
+        person = db.Record(id=ana.get_property("responsible").value[0])
+        person.retrieve()
+        person2 = db.Record(id=ana.get_property("responsible").value[1])
+        person2.retrieve()
+
+        for fn in ["Some", "No"]:
+            found = False
+
+            for p in [person, person2]:
+                if p.get_property("firstname").value == fn:
+                    found = True
+
+            if not found:
+                raise Exception("Did not find person")
+
+        # Should have a description
+        assert "example" in ana.description
+
+        #######################
+        # # fourth software version # #
+        #######################
+        ana = db.execute_query(
+            "FIND Software with date='2020-02-03' and not version",
+            unique=True)
+
+        sw = db.execute_query(
+            "FIND Software with name='2020NewProject0X'", unique=True)
+        assert sw.get_property("alias").value == "NewProject0X"
+
+        # The software record should inherit from the correct software
+        assert sw.id == ana.get_parents()[0].id
+        # The software should have the date
+        assert "2020-02-03" == ana.get_property("date").value
+
+        # There should not be a file as binary
+        self.assertIsNone(ana.get_property("binaries"))
+
+        # There should be a file as script attached with path plot.py
+        datfile = get_entity_with_id(ana.get_property("sourceCode").value[0])
+        datfile2 = get_entity_with_id(ana.get_property("sourceCode").value[1])
+
+        for d in [datfile, datfile2]:
+            if datfile.path.endswith("plot.py"):
+                assert datfile.description == "a plotting script"
+            elif datfile.path.endswith("calc.py"):
+                assert datfile.description == "a calc script"
+            else:
+                raise Exception("unkown file")
+
+        # Should have a responsible person
+        self.assertIsNotNone(ana.get_property("responsible"))
+        person = db.Record(id=ana.get_property("responsible").value[0])
+        person.retrieve()
+        self.assertEqual("Only", person.get_property("firstname").value)
+        self.assertEqual("Responsible", person.get_property("lastname").value)
+
+        # Should have a description
+        assert "example" in ana.description
+
+        ##############################
+        # # fifth software version # #
+        ##############################
+        ana = db.execute_query(
+            "FIND Software with version='second'", unique=True)
+
+        sw = db.execute_query(
+            "FIND Software with name='2020NewProject0X'", unique=True)
+        assert sw.get_property("alias").value == "NewProject0X"
+
+        # The software record should inherit from the correct software
+        assert sw.id == ana.get_parents()[0].id
+        assert ana.name == "NewProject0X_second"
+
+        # There should be a file as binary attached with path release.deb
+        datfile_id = ana.get_property("binaries").value[0]
+        datfile = get_entity_with_id(datfile_id)
+        assert os.path.basename(datfile.path) == "release.deb"
+
+        # There should be a file as script attached with path plot.py
+        datfile_id = ana.get_property("sourceCode").value[0]
+        datfile = get_entity_with_id(datfile_id)
+        assert os.path.basename(datfile.path) == "analyse.py"
+
+        # Should have a responsible person
+        self.assertIsNotNone(ana.get_property("responsible"))
+        person = db.Record(id=ana.get_property("responsible").value[0])
+        person.retrieve()
+        self.assertEqual("First", person.get_property("firstname").value)
+
+        # Should have a description
+        self.assertIsNotNone(ana.description)
diff --git a/src/caosadvancedtools/cfood.py b/src/caosadvancedtools/cfood.py
index 341594cf..54a6b809 100644
--- a/src/caosadvancedtools/cfood.py
+++ b/src/caosadvancedtools/cfood.py
@@ -412,24 +412,24 @@ def assure_object_is_in_list(obj, containing_object, property_name,
         to_be_updated.append(containing_object)
 
 
-# TOOD rename to is
-# switch arugments and check for old sequence
-def assure_has_description(entity, description, to_be_updated=None,
-                           force=False):
+def assure_special_is(entity, value, kind, to_be_updated=None, force=False):
     """
-    Checks whether `entity` has the description that is passed.
+    Checks whether `entity` has the name or description that is passed.
 
     If this is the case this function ends. Otherwise the entity is assigned
-    a new description. The list to_be_updated is supplied, the entity is added to
+    a new name. The list to_be_updated is supplied, the entity is added to
     the list in order to indicate, that the entity `entity` should be updated.
     Otherwise it is directly updated
     """
 
-    if entity.description == description:
+    if kind not in ["name", "description"]:
+        raise RuntimeError("Function cannot be used to set {}".format(kind))
+
+    if entity.__getattribute__(kind) == value:
         return
 
-    logger.debug("UPDATE: set description of entity {}".format(entity.id))
-    entity.description = description
+    logger.debug("UPDATE: set {} of entity {}".format(kind, entity.id))
+    entity.__setattr__(kind,  value)
 
     if to_be_updated is None:
         if force:
@@ -440,6 +440,37 @@ def assure_has_description(entity, description, to_be_updated=None,
         to_be_updated.append(entity)
 
 
+def assure_name_is(entity, name, to_be_updated=None, force=False):
+    """
+    Checks whether `entity` has the name that is passed.
+
+    If this is the case this function ends. Otherwise the entity is assigned
+    a new name. The list to_be_updated is supplied, the entity is added to
+    the list in order to indicate, that the entity `entity` should be updated.
+    Otherwise it is directly updated
+    """
+
+    assure_special_is(entity, name, "name", to_be_updated=to_be_updated,
+                      force=force)
+
+
+# TOOD rename to is
+# switch arugments and check for old sequence
+def assure_has_description(entity, description, to_be_updated=None,
+                           force=False):
+    """
+    Checks whether `entity` has the description that is passed.
+
+    If this is the case this function ends. Otherwise the entity is assigned
+    a new description. The list to_be_updated is supplied, the entity is added to
+    the list in order to indicate, that the entity `entity` should be updated.
+    Otherwise it is directly updated
+    """
+
+    assure_special_is(entity, description, "description",
+                      to_be_updated=to_be_updated, force=force)
+
+
 def assure_has_parent(entity, parent, to_be_updated=None,
                       force=False, unique=True):
     """
diff --git a/src/caosadvancedtools/crawler.py b/src/caosadvancedtools/crawler.py
index e5bb738c..6d706bb3 100644
--- a/src/caosadvancedtools/crawler.py
+++ b/src/caosadvancedtools/crawler.py
@@ -45,10 +45,10 @@ import subprocess
 import traceback
 import uuid
 from datetime import datetime
+from sqlite3 import IntegrityError
 
 import caosdb as db
 from caosdb.exceptions import TransactionError
-from sqlite3 import IntegrityError
 
 from .cache import Cache, UpdateCache, get_pretty_xml
 from .cfood import RowCFood, add_files, get_ids_for_entities_with_names
@@ -293,7 +293,7 @@ class Crawler(object):
             except Exception as e:
                 try:
                     DataModelProblems.evaluate_exception(e)
-                except BaseException:
+                except Exception:
                     pass
                 logger.info("Failed during execution of {}!".format(
                     cfood.__class__.__name__))
@@ -365,6 +365,7 @@ ____________________\n""".format(i+1, len(pending_changes)) + str(el[3]))
 
         """
         from xml.sax.saxutils import escape
+
         # TODO move path related stuff to sss_helper
         form = """
 <html>
diff --git a/src/caosadvancedtools/utils.py b/src/caosadvancedtools/utils.py
index cb790a51..2504f569 100644
--- a/src/caosadvancedtools/utils.py
+++ b/src/caosadvancedtools/utils.py
@@ -4,7 +4,8 @@
 # ** header v3.0
 # This file is a part of the CaosDB Project.
 #
-# Copyright (C) 2019 Henrik tom Wörden
+# Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+# Copyright (C) 2020 Henrik tom Wörden <h.tomwoerden@indiscale.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Affero General Public License as
@@ -58,10 +59,20 @@ def string_to_person(person):
     """
     Creates a Person Record from a string.
 
-    Currently only the format <Firstname> <Lastname> <*> is supported.
+    The following formats are supported:
+    - `<Firstname> <Lastname> <*>`
+    - `<Lastname(s)>,<Firstname(s)>,<*>`
+
+    The part after the name can be used for an affiliation for example.
     """
-    firstname = person.split(" ")[0]
-    lastname = person.split(" ")[1]
+
+    if "," in person:
+        firstname = person.split(",")[1].strip()
+        lastname = person.split(",")[0].strip()
+    else:
+        firstname = person.split(" ")[0].strip()
+        lastname = person.split(" ")[1].strip()
+
     pr = db.Record()
     pr.add_parent("Person")
     pr.add_property("lastname", lastname)
-- 
GitLab