From 3ee76bb5e44eadf46cf63348772ad682919bb05a Mon Sep 17 00:00:00 2001 From: AngelikaShorokhova <63111642+AngelikaShorokhova@users.noreply.github.com> Date: Sun, 19 Feb 2023 19:53:06 +0500 Subject: [PATCH 1/2] + mla --- Makefile | 1 - README.md | 3 +- docs/source/index.rst | 5 +- media/template.xlsx | Bin 30422 -> 35701 bytes src/formatters/base.py | 4 +- src/formatters/models.py | 103 +++++++++++++++++++- src/formatters/styles/gost.py | 69 ++++++++++++-- src/formatters/styles/mla.py | 106 +++++++++++++++++++++ src/logger.py | 2 +- src/main.py | 47 +++++++++- src/readers/base.py | 2 +- src/readers/reader.py | 150 +++++++++++++++++++++++++++++- src/settings.py | 5 +- src/tests/formatters/test_gost.py | 6 +- src/tests/readers/test_readers.py | 6 +- src/tests/test_renderer.py | 2 +- 16 files changed, 477 insertions(+), 34 deletions(-) create mode 100644 src/formatters/styles/mla.py diff --git a/Makefile b/Makefile index f7b479c..32d9686 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,6 @@ # обновление сборки Docker-контейнера build: docker compose build - # генерация документации docs-html: docker compose run --workdir /docs app /bin/bash -c "make html" diff --git a/README.md b/README.md index 33c0486..cfee11a 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,8 @@ Console application for creating a bibliography list. The application allows you to automate the process of generating a bibliography list according to the specified citation standard. Supported citation styles: -- ГОСТ Р 7.0.5-2008 +- ГОСТ Р 7.0.5-2008 +- Modern Language Association 9th edition ## Installation diff --git a/docs/source/index.rst b/docs/source/index.rst index f552b0d..c6bb77c 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -7,6 +7,7 @@ Поддерживаемые стили цитирования: - ГОСТ Р 7.0.5-2008 + - Modern Language Association 9th edition Установка ========= @@ -85,8 +86,8 @@ docker compose run app python main.py - Запустится программа. Она считает исходный файл `media/input.xlsx`, выполнит обработку данных и сгенерирует выходной файл – `media/output.docx`. - Откройте этот файл и проверьте результат работы приложения. + Запустится программа. Она считает исходный файл `media/input.xlsx`, выполнит обработку данных и сгенерирует выходные файлы – `media/output_gost.docx` и media/output_mla.docx`. + Откройте эти файлы и проверьте результат работы приложения. .. note:: diff --git a/media/template.xlsx b/media/template.xlsx index 2bfee63f9c8f995ffc5d30c9772bacc7939f0ced..6b45ec10e0897f961f853d290cd0704ae131bdcc 100644 GIT binary patch delta 26115 zcma&NRahO(wl$2qy99T4ch}$$B)Ge~EZp7QA-D&3cMtAvK?1>H{Uq<%`|NYhck$o! zQ>(hVE~?jMoU;pwWWUbr8+98C?tlLo1)Y(@}7>s?#_I7 zQ2#QA)DorIL13{YBiP0GC6RW>Z7!hq6*eDA(I*NyYi^RzEy#E~xeb>&ARYkLs zkcPt(l!Glojo87A)^&=$`wUkdK%du$J8PG@HTM+`zJIv6TmWMr6(d0JU4krVO7q zKq3?993mjl&T!Le7cuf#j^9pa&v6+o?j_aZPJ8cW7A;QUS&*IYkK{t{?(}DsftOowP(2)0_x$e}T zAI6)f3?jP#_AsX6V!LAF&!nAOZmDNApvyJt{s8|*S zsM~42pB9(Qs{uOGp0qT2lc;6eo+|qqBFr45kmQzb|gklftm`gjG+wfX22cUg=!Ezn!wGh zj~h#mE9t*N(=HR>qMR?`{szt!g3ndGpic}eb1w^{p|eOS4&^`KzszvH5^xS%k1JKC z&Nj?UAh!cVoTZY&ao?lm4Hl(7HXX|%Dv#ns`eIPB*hL(3qZ9q~c^bMWFZdQj&Vva; zJgsfit7$dnbOgyQx~h@5hhr8B8-o(WNHC@)%krxc#2722p_IDx6A4-r8$^^{U;;we z;DEwj4B4kfT$mveBVi$U=}d-MQhW4Pn7IWy-A-6n#d637nz@G?V5 z!t;}lo6l5cO>ssmelY)(WvY&yI4GW+EOLGa@D*^sUCXc1WF6|Z-E71arX3N+ZwF_kyAQ_+%wQJnVKSjejSMd=Zq z$`l8FVhy{?m|e5AN_C}`-6k`iZPCl8?1Ium3X93WHd<~MxkV8$YtVcL;0(Qa()G)vK3LtFMmZ$X#foQfzZ+N zQ^%{X-$M#W^cHwd&o65OkE|Zh(%N4_5Z(E$Xuos{TGz&bTNQP|i}W(76owR{LV$yo zomC2VERfnL`vPFQQT3OqiTOsOH{W-tD(-@;y)0G(;YuT0wW@P#(C3nkn~KL855&pa z7SVUUIF?qA9AI0bY1vOXB0OLW`1`AN!=9ICOCZFVE-43>0fg1Wwi7BwEv;Xl`Eb`+ zaziFP#(BYPP{Slqi6DMOVG8~(4Pbp@Wtx+}fE3M-)ujYcN-QneZYusx5bGw5Jo^h=5`$5ZS5v7|1&)hHt90I_^vkK0~XuZm}YZ zNPSjwCfmNIM!wGJI_9vEsr)5JVcFqo%;_O4&%>7UU>=W*Gn6i5o6>Z9PqLbiNEZpo z9Q+oo-U(JS3~6R^<9QoP5cEo{bJys>Vjv`E)u1hN+ z6jj6)!97{U0U-)LV{2EF(t%87j~#x=n56I}n*z-5X}oUK*FBL^U%zkUJU_xR+B2*B;hJnglxP$>tCUTSz@x+-=n<8Y;2sMN2=K*nBh_Q^sFM7iz7Q~3S$wSMiR{p{gpggxIKOOOa5Y$^Ab_IN z16Kcag0qfdj!HI39AUJ&Q~c$6I~2?!r|?hw#KF8B`qK@8li(mrzJmK4kBbqlPoL{1 zz8enLv_;)237yg?dCOu5eM1J|RSPbXSRLpe65)g)TH+TV4WkRm>;vlsE^(Eh*B?)> zMZ7J5AB+e5=aQYeX%{GrBB>v84bmT&k3&xL^6+Um)58>>rP=qTfwbXvDpU4qa<$Rq z(M+@DGB#{L;wNKAsUW~d;@Fmcuk2+br~Q4|3{z^`sBPVT3jkOwSSM0Qv(0AwMQV4} z<-DEU8PmvphTWd4m(`?D^Ev0@N^$~G1aT}T8ZQ!`hl8Cfb&+|4y3DXmSuna7`8Yud z*j`I@V3IithqgD2hqfmP3F{~(Oy(jSppdTY=sP)&kZbUESdl49ynDc&Z7hW2*^Dxz zpLB3Pmknp(gbz^4pJ2@w&Qcf7c=pM%B2db=G*b&LQ<0v3p77*>lquAHXQORFE)0yY z1c!@`{*-d?UQ*FZh!9G*)v?*YR`iP;r{SvrY$*{I5Zv1{bVCqN9yaDm8e!kH z03;N;2fchPZVCwwXp;5YA#0Y#*kH1=0wGlg`|yJWDnJ?nE)()&tXo^ij1qdqwF(sWVp0+ojP}@@Lz&q$6RNeQS(7ebUy4hbdVe6G zEsKD)j#d7qvUQwuilCrBUFa4oZ^@YT$#7W;$-0c*vvF)L?rMlyB9^n5Muq`DwaC46 zZsOwh+o?=N&K9fLCt`M9DIMc`^qB>h35dCx4L~E58*MoR%eAU4f7h4jWDRmFrt7yA z^kA+N(eyHNoQ98>ASZ(y$Z-UwTtx}>YgwmzS>W@mxkF&trlC-w$^~fs5eL^5R^%rU zD*G%++J>5fOSV`T*J-#dP@n#-VLgBYS~hLDsp(f@@3d^9iJ8txvO8I?Dj*F)EdNk! zC%|?G5z~jBg1@^V5TE{)n7+GzZC@n`oXPo)>i$?lnZ8lESz2F8IhCTQNwYgPvaJVZ zM45Hu_E4WaEA?FBwt%N0ss^^9$&&c3Fhp$!^)XM(5B^3nx+pqFyTGWy~uQRbi-)kfEZwVSPBblR@( zfDQnPT*FpazWDzC-`}ZF-YhF7ChRVbKymtuBlcWnK@X5p-H?c;)}jqZpIi(`|9>~|E7a% zEc9tQW)y2kw#ilT5`HXZRATQPhoGw|t4e{%Hs_wp6kD16i<kiC^YG3nZk#|g(HQ-hW~~|=DyPP^5q*F*%u>K z2Au$dhtjUrl+J^cNFoa}raO&3+5ON<7CqnoG@gyW(C_vJBY}Oxu)BPrh zzv4+(1qrF*lqtH@RsZeq69>I&C4FsLMt!>r99E`SO}z;QC+If-7%h<7{i(#^ULL<@ z4b6~A7|FW4!lQ#`?!lgpODwj&xH?>kfo?A1gY#b=4ZW1Stnr4cz=M~P%j%oye70vp zWsLZL)MJ@hl~B|JuFJW)PuasCo%e?WMqM^fub@TBX)Rxlji%S0(AO&;FV2;o56my9wjnS=> zcJ7M$EB!5dW`|=a8`tpd=u`u@3sr690ebAML~1Z#)<)ZLWw)DFs(fc2Y{hwaCcPXS(8bq=!tvpM?w@0 zFCxo+1#%BzgAPU!%iAefn_WacR9-WM3njl`e?SxPUxc14@_|hhiP}IPf1Dr|5?1M< zZkR%8trGR<#QXc-JCq(!6~gXw`+dON`oc|59Le<<-J)8ehXZZ4KD38b-E5$1BK;Fu z!@1(swwuG71Fm%{;jciBcIXGhgG^KU8|3V7Ev51tJsg=2J+d8Y;-Q$0h0;|km=XX1 z%*#rlmlxLcm!RkuBAK@m5+@ZQYEO0u<@jfjgoWT@4E46HO#s=Y-@WQoV-U4oHR#sB zh?0r_&qz_(%>2P)rQ7IlzU%q?(+MSji{9-lKo0m8*>O4*2ZzNMj0RTpWY)E?ic!J9 zUrt1R*Xvhkqe{6#c7l+hVECXeVLt%5U*nY-|NcroH*bFHBrf_2hXBb5M*YxAg>8~7 z7f{eE33906?Ia@EhJgcT1EEwS{Q#lJDXWLlNX$t|l05^s7y9qa$R=Lb`Xk7N0wWIu z*;qy_WEpT`>Z1DI19NVIc*$5y@r{AL^#*h{T)VzVyJKVCfQue8+Y!Td`fvLJ&>Ahc zw4@j-1G6Thf?ODodG`*wr+f;P!6?J^k;b7SG7%Ca)QnoVG70`FJV23MeUgh{JwI$P z23f)c994Bkgv0|)X21by1c)$e{GL9^I;?LuyydtjZNVV9Is|w)+Ho=_4y`QaAuOo! zX7KJi(0TJzE^WSy?1%% z?6yqb`aE@K>MPY%p#_(3GJ>_`?tXh1vN4lf?RUPnq?wr>9{R=odpAZ4i5%EPF)Ny?-b+<;pZ2P44%q!ghL#jO@GFc4Hw1k07nq{DJ#hiWk;A`g7dssgHfPn z)+c~eP8fitP2dUusTqZSjdJ_k^OT zMG>YbKm2|`SXdD`fgqOXL>0=O7EYgjA2@(&MU2jW|G z#O2OU$f7bnH)sXTZ!fv0P2A>eERSZX@Qr5h+K40FMCyFapItkABwGO5 zQ0$djKP)mRMMzGzikCGY4fo<8dGd?X3QfLEn-Q2S3ysaU(JKZOSy+d;t0OuS1YDiv|OTUHh+0fpj}*kVgc?P{L68 zp{ByCl`|F`%TeCeS>=cO)e1Q%ENp$0KWzQG{X0+a*o4gDR=@{@Rf#XcMvebKcnR|P z$(RR+L=k^Jxj6O19mue}V|{zIbV|0WPV z1*n}1Uty4yphxhA_cOyo>>{?K44>hKR?#Y~iq3Z##Iy-xR7QXTy$0Odi(sD-6i~iz zUO}lv5)f;29vxlekD|N0Hro}GR^pur?glGe74H!AQC-frzPM*Ve3P}(!X+B*8@ly< z@{ETLzW)cq)&Px#boB4U^yh}nx>lXXANIiBF4}R?strol(TxEP zRbwB03;H|F(LMx`bvisbfG#1}F>RQ^t$@7Z`bwY-l%|^`v0L=TTbI9(yi~x7E4yzk zI!?2(@C8oY=K+3v!~(gv3D^zBxBu1v$xB)a10NRCZnd};NCcaU3LY#!6B%5>vzn4B z=8Ax$T=`W!(HYm>A@<&e8ttY}dTArhwAglzV0XiUAi&&{6M!`0`DEEL)o8zjuz&d` zC4b9JF!R#sQ@-ALCAr+)?veM!vzb2~yX($o6Em%wZn)=wA<@R7f1sGwl9Uv-C?cAi z_#=J= zoC1Li9)SC|fMZ@n#o$Bv+sHLsG%&hPOz!>Jb%Kxx!)b~4u!_;moZ&K03nG>ng*;Gn z76z)%OL&-_#`vrJFtGzf05K{?pd>TQ5;$w=XWV@wR;U0n7>I~`6!t)oI>LCC%48Ns zN@Eo6K;d{8I1w@&h;vZfMuq=asQj;$S^uw?I3T`-@ zIJ6O1efNeLW*o4<4>W08pc)PuyOBWg1L(;ASb?1XF!q1Sf*_s{6pV(oqx{W|inHA{ z7D~;&|B?kk0XD_bbnSm-!EKyCokx54I1funTNT?u2ceVZXVd9(-%vlFZ3VOs!amDZ zYcRuYXN}2C%@bmnRv~a1OLz2FI!8$PIHcjsRC$!2ZVW zKY8dyL+)~%!Ia|-#7p$i=*W3tRB;lB*GJ-9*UD|31W=yfWD}n`*ifoPLZR^KIXZt= zI)KUSzO*hMtqE*r@)Iv}R(61L2ob}3c_&HQQIlbS-v`5W&n243+vlOYlk}1>y2ixyx?jNF7V~JUL%d6BZ0yG8vlY55=5Io;jUNFIHp48k zHRp!tkEqsE2c_|+T<+ncjRuXdpM1IL-CiSnuK;9cV;*Q`q*VFcY$RWId0cKp1<~M@ zc8}e{@4w9Ve!Y0!dfOc!ZPHCXTIN{5Si}C>zT3nhYzn!$a|yHM7dGp6V6@*hfuXQD z7+1uID_})7v{SI`F1x~68N5RG&6z7R)X(}C0l`K550ML35tlT_k*3vO%@yu6%8CKp zN;?1`iwXi%HDq|uit|3Rbk##bd~2BSCt?H7VvnULlKZ_!;4%nYv#D?=kHbKMtHCdfkcE6Hck}d2gN#c>o3IRG(vYq3kWdAUEQ^r=8Zl%2JgzRGP2rZYm~yJvL=! zOAR;YX|?tIF4He8$?EcilqJ%Z?ateOw5cjcyYCp-#FjBgm2EYx|@yOw9Jmdre ze0!Y^vu`f{zwtZ_KHs%AzZoLq{-0Bu-7mkXmjBnJZ%auBe9|S%ClO+4h>(2LXz1b> zKRI`7%1zXN0~}e}{Y9t#eX^DLG`1HNB2Zij2Ct9RL%5x&#l_ulp5Wi%;l|%(rjgTFO@qoq7Uc!~|9*G5lh z^vWwB+HP?Rq%U#I*_T~p7rRm`7&%;KifW99R#&NDAYP;uD_Qz{)*hLhYTnM zGgcp})8?*DMl&4~{>5&`6Hq_FQmHH@eTWfC4JUb`9Z&Y{`z3LrhGq~zy+IO@U7>V` zJ2lMM7`+c)_(F5&t|NdnD(TKuA3nkzbJ6#POui~An9kpxyO?Fhp4CyZSgCLpDSmVM z*7nKS{nCgv(e6Ai_=fDuJi0aVH8`fvbfOTxRv$F|D>*%(p>2nW7r?|b^qt>!pLop! z|D`WZly60x%{zsh&O0@@fT%Xe<9h~BVnSkiCwRSrUM31b;OR8#ip0H*8T>A3uCZNP z=O_PVFn1IFU)UC6Qv5X0#OaR^T&Mt30dZTriiNWlZYR6-61Z+L3+<7i`vxhf@vNoR zqgf;8q0euAIn)P0K-96{dlg;nUaZ6B>vn&;DS-WnQS$VXNei|Q&zR$|ghyN$Xmf4t zxYQlS?DuH2_W|4CQBtWANFtSo1_h9BrVuFJBJA${+z4nruI^H>UCgrKU992RAIPq$ zAyD_4SaZ5mDa-|P%F3;KmhhYKqt_Lkh^evqYN9`W9WoevawuI1wu5*_U#d7K)eTLx6t%-gP68+P3 zr5|?}Fx(Mu_;#b=qNEC-wT!^{a@Nm4r)k6_uVJt2Xfv;~Ts)wEz!06x6LT$HoR5kc zp~g;1mOv0wCLK)aLSFw36*53fY}r_80SkNuj`?iI7-8>=S~X z=9=q_Z!Z-467!>=h9ihW7J{>M^KXf_zk)tN{zWapt?I%qI0#6>KWdr#pEBHkaxYIh z3X<}u6<1c7_m^s~R<~$a4V&zLJUL_HRq2H0Q&I!}U7#61Tdv@zlIwpwxm{vW!oQvz zs!>sSL_f0dAQ)vzFiGi8{hn ztvmONA_-Kd2d0~}pEbaidQv(Er$vQ?9_A3Tq6i?Od`lIrb{W<#pVdy zB}i(;@3Wr<&E?d~<~rltb)l^-(expWI|g>TL(FC>fViQM5BV1OuYXL+W2`#jft_A#J6EWu!TFnP%G9@A z?iaLeCAx0$$X)7smwB@asC4SUxM8JA*q1jtL1FcH~c)su$f+}K87aGiVy3T`&G6S)D zx6Zb15!f)hL$5AD&m2~s34TvMfDHr`C`ld6SCGN1lVsLZ30$xse-up!Mr)A5Dj6DM ztz;5r7?=Qw9WdhY*Ql5as62|8s>KjVXN;>%VpsUllBgHdPMo zB_@Wz3V`>gri)5$s$^IrH6j&7tw(hvX@fih0vvk&YFyZ=O1>k|57Mki(#OXG@cci} zM+8T0k}_5BzY|~I5r~Ea((2j5coOap4)qvr{KjTqMV1m7(Jb7(PZHHEo?Nwwd9pkc zlS3+Jxslq6%D^xiLVL#!e6@5#bE>tH{SFzdfmycHKB4<1A=9bq)7%^YOM{k#XX#q? z^6L+o$O*kce6H)x!Nc_LC$gg-99l*zc_JGR{;j%Ol-#xs3Rq#5gJ+p7-{(mG5H?L2 z= z4*sORXv1+|W#T5;{}jP}CKHLaHT(Y7yW09D-Ns&f$HCri8(cGkPy1uPR8Hzi%&qz9 zjm)^6Fm;;ZNuJx(zs4OkCW?f8R!=Qf55V$!|M3vdm-id|E^98S-Me{`-!5b&D+GIV zA5?TM6palRE7YM_ZjC(@V?Z8AP*yL}&UN&5JGyu4)kZus5{Am-DHz5%LBOH7vE}I(ZWh!&@k(HS^R^jptK(u%N(;M=?0r*4bk6{iu zTTRTAFU;%-8?W87^ij;}nWxQf+VmBWN%E+9>IIxQaBM`&%*zw)bD#TNb{xGv<>crn zN7_7}DJJ#sUW(tVj{9+U1J6E4a{;gsxmQvlv5b)HTjD^+NqWmgS({d4@k?&`U;wbk zpr8g#sO%`d#9xSAdL68V(~r)n(5t%9AN^)5efe;!nFt&_PZKxnE+(L-QQ*(>)@_PJ zcY^1OB%}Bg+9uK5=O|NVbftPi&ou-^*&0IVc4AOpRZs-wABA3wg`a|4xn`v@9uWK z>Trz}|0vg}9WVp+hyVSYK(Sg0b7kZ|f)|DnMB_WXO1s`dl?^#98bL$^!l2}yXq)~<0m7- zk#bvwis#E52ry}B7zQ|8swLo*(EixPA#q5f@tVUAEEsQ4Crvr^p>@{)zjx|Q8mDLq zTZv#Rt$^6c{IsWI#m26A`~_w+0^+7p;h68bmoK55@@V_zfY|SKs&otyAyQrUpTFUj zLnUDN;Xsr#!P?J7{B2bEAV@ihmc;M(5Pt~r{qq1H+x$O`DmHv+w5y;-Ly#>appl0`z>KZOe5Jrxvu3MLP`&Wf9pom&wV#w)qSmyamQh#r9DpBU3yNg0gWABtS zJPBYSSjnC3pr2BtrJMgHJB?fT-G>ulmTCI_$n59cZL5maf_!-s*a z!%X6j5lC=*EFQu^{04vPCh%**Fti6?`OS!96|O^RkE_-Od|PhER0NOGV&l2yULRMV z3+rA58M;LX;q%Ei>e^U2GwNuj*1N)DF5wh^8wYpts~#~NAt#)@_hg#LF7612yhytk zbte}GjXW0ziy)-EGP%4t5iux&L1kzxLh{HNuyAwGZoIE7#&TONJ0#$;xQU_wem2pI zU71t`s_QcuX|GV>Pch_akwk2-1*gFO$Y)djyO;$AiWd%2ezWo*j8R_6{#S-7&ifyC zM0sEf2x-|5OIsetS!!EohIV)P_)(-O9MWdY3|PALAl1@7O-~r|cLHbG;}$GW)$Uc) zD(nGLZrhrdiwYE%25xt2=C)ox*Ii!OVhfbnRpts*0!0f{#^ff2BpEUBX*=F3itS!q zq3!aI-Fq80`rOWKo2Q#lHtVz2P_ij_8X@@6$T! zTNu$HoBbfbk7_aT_;~tXf{gJ;`6v!sffrUq{-wWv-``liel?$LpcczVMF9)y^%740 zX*{lpW%^<9ivDRR32)#n{X>YtWW;|ONBpU+jsE9t{wSG_)j|ehUSbRsbd>WXZ-e%? zo;R>R9xwpyJb>5d!@YYV1_40_`P=a7Vqt1)%Jld8w`uG`Ys&5u7g{&;C?ET?U~bRl z6DmK_(Q+iiYAA=>4jIQlM(L0pl634rfFZGiLK`{Ej_!qNfC-Z~OdyOV6B<3|$`_^F zKL41QTNIiG?@}4WkyHYnYesTV6|NURrBQvwv$5;VFDYcBGXf(<7&&dfCzY`W2Ua8F zM0uU}oZbA{YXK5vR>@?F)Dyd7YgwwsTV2AUB`d$M;}c(YHs*JNzy zW5At2WNNSY0cRp_S<@wgZc+i)h=`p zqsLsowNfPV^0nc&)zj5y4(g%hrR(1+9oUNuZ+IBWiJz(pR)5t7Xl(oQ18+vmlpB`%c-p;>mF##W+50ivSRP%=6(Sds%Gy1RRLbWQ$bkn*w zuQMY|xr9Q%32floeS{dmJFCbSU67mwYYWlmVAG6+e%4TE>)4)>B^srkTnVC`pyGwK z!Ji8&yxoDorsJ9sddU&AcKS-E`BK7%F~a!hT-gTH9k+;JI0vb<=7^P_KbuPR;lRgk z&yLLHB(oz5jjs3Y3E?EIRn3jeCc`tTP`r>vq3&*x&CS2w*!2MbQ~%m7Cz%b)nXo0W z=ZfOkZX$hOEj}L7EQ8>v)rt_Tq|?3Hb?%DeTK!Q4wI%Pw3g?X`GO}vBq0N-T=uksa zmj4`fMU2}Py`giR5OEFcsJR99>)> z!&!>H*2t_U4xU$JWr)?5Q0HRpXfT5y=V?*)CqXm~uc&c!7atKH{YhQM;qAtWHEP{B zryIo*hskk8yJB5$ZjJL2jZ+v5o$14MvTdEYyJMsW7Ig#wb-VZpN_hfKdPOn1!<7nZB-Cgh z8m^6g0Lvz>WYBtoC0=!XOlqb7!^rvD#-N|&EwgZe$qzmj89klXY+CPrq0$RSZ02L7 z>sCx)u8KJTy~HMu)`4^P9eN^@Fq5i<`GWjB+g16;S2d1zps#x4scv_fDWg?Jl=krN z-@g^CGCF+%#wo9!PY9KKj<9BfmD;JWF|J@XAB2^wBm);q{z>G_uAUjDPMk@FTWgTL{w_9HwtFSuoeGdDDKMxqwJZ2>ScOtB(Y#9 zAy%#*YK1$LLf6}*uC{}|5rq7ZN(5yYhMVcER1>G3J#Ti)+D^-TjCcBZy4Va#c&uyE zpMU-{!1R3hu>40!xm&2TjwH^h8NOk$Aq3H|mRlyn(`~dX8*;g6uG7#j+tH~qt}Tfw z%<}9#sLXVE5%jCF!{rK+PlM63RfmfCK?rF#2u@OEwant*98}m6`F(e{L$%DljuN6N zpE-QyrNJK*H@Z|tYw`L1s?Iqp?i}7+{S{Qy17IIx4OZQ0KP)o)lX!m&BCZ(M%~;%% zw+k{K9@*qaHD$n(Wz+MZ0hzD`dA=@U9AH089ZgIv{ z1^}zFOn2dP>6Kj)SJgO|@vgL*2d_O_cfWYPobOganoAkHH@|5*JmIGAufRR;cK96?N@fd! z*=%F;PvjJ-RoDE#90W{SiMReb%5HiMD*@CIxX>B4C|L!+i7Fw=hDff|V67QyGCT}8 zl`og`2gXQ9I`YUXK0-Sbi(s$v^bqRvoyVnp<4lvJXJqva)?W-gZ+v$e;yqW4d+aLU z9eWS}E55DMz-6zTQmCG-YK9KUU$(@^+|>RyTj_e z5X67T+S$d^#?<*w%6##4%5I+%r5pQ%4+d^lOj#n)*3-83r%TRYFPJ4hJL9ZafPX4Y zmTttIip$bgq42}v0HOC)IdUC?Qohj2J!Uf3S^Qnx#w+J%|3VyyaEVB^Pe{jE1>{KU z-naCw(`AzxfE@y^4aBcuzciz9h}<`3pnPk0dD}>R#8vt%M|5X5HqX0X6!^YGCFjah zt0UFzc#h;ybJ#xl5h_|p{?cVB`E*GxY9=nC9Q)=&OM`ouNiFv#i?a9@&3vhiR88NF zm=rx~-dK(uWh0A$-;i7$vU|v@wc*8kB${XlS2#U@2Z+e=)J(%PXZ(_rz6SqhLZ3>C z9kB-4-7SrVwGg$(`Btj9he);K!=+$&0G(1@KXXXH^TS7GCOucMW&NnJDzAJaVknv} zecCm}_==yvbwm4y>qYTJ6l&S&qWAXdlYu$a3Syw@)eZK#gzrPwF9iO8-Al)4@7}fJ-DO*)!yb+pY3`7+Sg+h zPsZg;i3{+FSCCRy`=!@Bw$gJJH{8OQG>9I}NLd{g%b(%|UDv0rL+(RNLwQ3p$J^OG zfj!GZS3~wg_?jxXJupce+4X8HHC@G8wqFgdeQ)-k&Ts9bT7+NEBT=H8#4W(aAo~GC z`Nu6o_{2Wyugb4#l+Yav312=_>n{wfNZx)fw&e%YhsQJx<3mmC3Vtloy-atal{wS9 z>yHI4CQ%i1yy9t zJD~`*%degf4{0dal3$ea7RxR~uBQVS41yVf*86Mra-h{POxQ~@Ga7vDZS{8q-3J?n zjD~+C9P8E*qbJ5ffe(lD35(mtzV1Nqo9VZ=bt8iQ`=BuWjv(|#P=NOrhZ*Ecsea$D z*FSsv-t1eskRRAH7oi5hE>(x#x+cUiqTz9{|8D;ZIr-A-lL6FXi~~$_25wN7D3UXu zj-C%PjcVY{1y8DQXl`G$W}U~Kgio?t6-P#+(|qJe;5{8wPLFmy6CQFU>$v@`!3U$ZrJ?a`$$d}|kh%)eCWeppk{l!?or zlt5~W$jPCc?VAY zP7fEPh>q5nA8mPGb{*&509I})e?+#Do+l7N?YEJ9OGjtD?oT9=k2_{m$Q=iyq9-^7 zCF;O}^W<8OQ@i`@4ImE}O2b|t7T$c-DqpiGK=CxyawVgx8UxAuigd32Ww%~XH{8ng zG_|lHxk8%;0Xs?xUVRzasUIFcAaFCmm}d^=l?eZr$?MU|x3-A-Q9vG!D*AJq-RW|B zk^6Nm*`w6eS!`Rxaq1RJ6d2Gp-MZcS$mNTDH0I)N98cmLFu%ihsC5?7xW9mw_*LGKI!~6zF ziB0VKURHu?@`e0iM*y~V+DHP@%%EnXLJoIg$LIxL>hm~nCOD*ooEpGb;6dsoq?_+J ziJ5P`*6DlojCi}u$!oc)otF%3HU<4ss`h&kJ$|7x9~KitQgSxkVbwJS==YJ>LfmD> z4#DyLd`S2yAj>CJ2SbdixkmoOGgve)C&#sBR7De!?2zww=pkT@sa?VkCjBeb<3kbi z1Xy2FeCmq2^}ec8(2^2G&ygyxKj_SJ$fZh#eU^32ciR+I#Y&}h=4-opWI+aS#){%i z9BQ?Gu-~|z@4<6fVNp8Cw)AUgC!Yhcdi%7_r0~}WalADb`}!u51EOQS266Y6UTPVU z5#xL^g`5__j_LtzTO;Ul%-zxoMcNq*c8-<>J{?}jkX_RVVgVFt3A+chd)WiLM&xRg z*Cf>1!87nsgi}eNmh*jY8fcyL_N);_QnL7Fa~s6G`M+d`SRAOrq8r%daCx**VIt9F zAcrj>D33Bj%!$xd2gQe>+3GjLIJZAdC)mb1<$9x!5^4fWbgd9C@xek_ag!Dp3(u5H zGCVDhr?Sc5x=J{41!8eAETO(TyQBV&h`SDycXH4276#>bape8v9QIF6YJtWUZaK9i(JnJRl7O=t2+{*($(ykznHSA z7-!u>I)aRDon~tm4VH#Q>_T+-F*g+pA>{gGku5i%!@v(qzPrO0;9_Kq!) z9fUf=-n$BsSxaWTgbaT?5f}OA{Y#Q2$u2xr0&@^(OX)Iw)n&g!)~RFCQQ+)@4D~8c zsLq@3B$**{#!qJbt818%73XJg(QvTb3zQ5M+M_93Z6*+DG`6%)+K6B+$U_Y46X~+y z5G1>RQC$_*CDy{6H2F1{14Fa!Tmw2>0G0LiGtHK=YCIP6@*HT#Se3t~0q*og$PFGH z^im;x^Q;lU1;C$L6A|@`5t98qFHKQs-pCK(;t)9DC!$`udb@!#(z)@ymXZ9)Q~GL> z9JHR>1E{GWacHz}V!!55idxF}i@gkm@UY+kFLUJZo^09cb$y^zEG?<8(R&%#@!n|=^w~vLa_a|)@j&YbW-kIYoKhzO+(`wckqiy73y#@@EysQ6TOl&mjgFjrhlyEJ_pLqlFoe||~^h_}-X zPLqhi@~G)25a>D({qg1SZeq+SJgW5#pb?qWy=Oeg!)4Km86el*nvX|;TV|n_jk3+* zV=tL)_ee1@A?GK-xnQoBsG3k)^B(`t#=A;gp|_93T!c#b?ZSZ( zA!Ab1)9H!?|G-dF0+XDd#(h{&B4o}NB)YmRKY$)>M=$lY%&Eo@uFn7)W++?Ho;)|5 z8a1Qj4Hc>Y$g-D*L?Q1+WNj5G;v?@}(0e2Nqn*W`{$Ht)Waf;Llr4yxgebZQ;m>id znG`QbkyI-Tmqg2137VQT%S{MJ>zd?E_~FU2f+vA;5+%Jj!6mf`BLi$D#M^(CzzKLH zJcAgscHnaYQYV`>ySucaEDDGrPie@Ld~R96Lf#iA~;{O`YO@&8|P6T{)d^06Q>IwZvJv4=-INC(gs=NuVn1MHONy zoH(CMCbo1b1>01{i=L1^-?2#fdUihm%g3&Fugy`4)%2-czM8?aDqcR$oSDpH=lye% z(SmPDm)vhjQI7wdC?5A>67PSF3Ah%MP96T4C+1v)z{-D||8vryJV}IHxL_zLNjzL^ zpbSa>Ut3=p&{orgi@OCc1PJc##i3YnC~n1y1(!mR;980VFHqczTX85(k<#Ms6nBdD zrhUKrao>LT~a&{(R}D(r?!;IxQp9Lw7w}63=KVx-k@2@| zexQ`h(f%h|lopi;bF7#7QwB~i?kpuQdI?qRK>`l&Yv)+Z!q*b>uNQ4>j}9CdQee!y zkd#OsJPWnKfW#y0j)HL>2xckEq;AVD^6zhfVLOSaUI5zw!bXKwS`a}wJ&)4g?)Wou- z-N|C?q7kp7(R$9I_^!~;I)J?FGNxIeJs_a)Tcf`nB=9Gr$E_7r?_4;7O!LL(nqrWD zXIvHmp-(5Ruar8T7XGYrYK8(>!yma96EWmh5C=GHEt5EGG^4~{kZZnZDRX+n{YHx} zf|iN&I!8%E0Zr!Z%?iqA8Vn|C;#I?otHl{xgsO-2%U2!lB6#5r!PGYhKngQl`e|^mkAx2VQtJsE30X%J>ve9`Z*jkTKxY z_C=v_uc%=eUH)&TiiOxg?>JssJshF@LM}Y__aj_ z=Y2qDBp}cB4ts-YGogkaKpfN%K*^hu?lvlRH9C3H=8>hZmQboXYz|(hoo+}lHyE8* z1A~8ED$tr4)<;I`XC%q;G>k##+FzfZsX_V~{4?l@GdOmu+qhl{yJoy;Ryb`EXGu|n zSKG2m>PKmqOw-~lqsy9%rsMYB!kfOx6jjCa3FaCq`h`LlTehsL#K(SS1)k`)O-?_f z9Bw%Mv2$L|RHiyPW1>6NoZGmvw~OPIrCL_QvAu=Xnc*s~Ry0TvlPauzN_S8`D+T%b zD{|ART2l8ZVkLYn*~jKh-Y>17{G8ZgbyDPzPfTcO(h1M>FgX|#LdP7+BHR=XHBx9Z zvI;JEqQ@Wf(M)Odq7Zsp|q*=!imcFYXA7LvbamBb^$v z{9J*R$|c{v&?WH)+{U_QLRFw4Rt=D$=(+UdvhKGII9PsX;58A>otP(5ObCJWaut&DT_$8*MzPX;XQ<<4}^s;`Sa*|m4y4(*zbG;Wppq2I=iOkdU`o01%S81cBx z{p83I_JhZ#xJFl|d1#^Z>vuQWcgSlAZXx_?C8!GUiqb0ybsmZs?t%%4cu zBL$Opi=5*8x-uqjA{y;y*g4H!AkV~()6tBh5fo}6F@{}K$K{O&3!+16B4=#RC9Q%_ z+h(Cgk;WVL#MC8BvK5T?>R##Ut=nQv5)~ciVKHA<$Zt@keB{KQD3+ z_^{qEwtNCB7Zt9mUaXGnuZP<+Qjrt=>}c^PtH>}^%Pp?dEls+IBFhXC>P%sY{30(hUDCg>J_Is0HXJ~%~k#q}NS8&~R{|Kh-6(OI!} zwC%uZevp6isL<@W>6f$nA<DSaxIhG;|FF`i1gOkc6W)qMlUj1u?tMC{QG z9CVq4W8gM(=L^YBKqUNFBpen|RqhkkCnxCgwQVY{-@tPfxDdPA9}9dyv^>AdlWKD3 z9Tb0CR5}EU7s=TxpPW$Tgja;}K(eW3CNIymm;wF&H4#K4^U5m=e_;xDE zv^^IiAmJkIi!L>a$IQu#hAc^A6_D61BA|w$ZmGxs+UB#`2>99R4X*2e+rM@mrypt} zuXI#sBl1eu1`#B2hIj=qu^@pg&~g%Lf+!Y`RTRlnWtFNZZR$Jd!xqKr1%X~)BkylW zc$Zp?^Bh}3g^g>uy^2y;w|(YsDaaVxuMV12WWZI6@5ifLMtAE^Q}P-wk0&3ZW!(1< z#bamxG4C*%SFbTTVA6er^k>=~H${QGP;vYwNxDeuf;xO#V!dB2M!;WkN1&9%LR<%Y zxvPwiIZrU6Jolnla~P-kB|JIt0yRkc4!tSJ4Oz8Yh*=Tp85l=DecHO9z;zhXYFt(! zHuYx01(+1;-TOT&^>|M735Gr_R>gIrIC2A=-IuXf>TZOFSY$5iP)Yh!`+<{=2s&0o z5EpIN?e1cb7}~DqI;)fP8b`i9&OzJQhv8$|`9s3khS+K8K!Lh>@fB7YZP;g;?JK)y zGx2xjHhHt^-oFNk%0DR^_jX!B9CEV;N9)=kfKTGRdJf+*bRS}We;n6_`7 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 delta 21655 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 Template: ) def substitute(self) -> str: - logger.info('Форматирование книги "%s" ...', self.data.title) return self.template.substitute( @@ -64,7 +63,6 @@ def template(self) -> Template: ) def substitute(self) -> str: - logger.info('Форматирование интернет-ресурса "%s" ...', self.data.article) return self.template.substitute( @@ -89,7 +87,6 @@ def template(self) -> Template: ) def substitute(self) -> str: - logger.info('Форматирование сборника статей "%s" ...', self.data.article_title) return self.template.substitute( @@ -103,6 +100,62 @@ def substitute(self) -> str: ) +class GOSTMagazineArticle(BaseCitationStyle): + """ + Форматирование для статьи из журнала. + """ + + data: ArticleMagazineModel + + @property + def template(self) -> Template: + return Template( + "$authors $article_title // $magazine_title. – $year. – №$number. – С. $pages." + ) + + def substitute(self) -> str: + logger.info('Форматирование статьи из журнала "%s" ...', self.data.article_title) + + return self.template.substitute( + authors=self.data.authors, + article_title=self.data.article_title, + magazine_title=self.data.magazine_title, + year=self.data.year, + number=self.data.number, + pages=self.data.pages, + ) + + +class GOSTLaw(BaseCitationStyle): + """ + Форматирование для закона, нормативного актa и т.п. + """ + + data: LawModel + + @property + def template(self) -> Template: + return Template( + "$type \"$law_title\" от $passing_date № $number // $source. - $source_year г. - № $source_number. - Ст. " + "$article_number с изм. и допол. в ред. от $start_date." + ) + + def substitute(self) -> str: + logger.info('Форматирование закона, нормативного актa и т.п. "%s" ...', self.data.law_title) + + return self.template.substitute( + type=self.data.type, + law_title=self.data.law_title, + passing_date=self.data.passing_date, + number=self.data.number, + source=self.data.source, + source_year=self.data.source_year, + source_number=self.data.source_number, + article_number=self.data.article_number, + start_date=self.data.start_date, + ) + + class GOSTCitationFormatter: """ Базовый класс для итогового форматирования списка источников. @@ -112,6 +165,8 @@ class GOSTCitationFormatter: BookModel.__name__: GOSTBook, InternetResourceModel.__name__: GOSTInternetResource, ArticlesCollectionModel.__name__: GOSTCollectionArticle, + ArticleMagazineModel.__name__: GOSTMagazineArticle, + LawModel.__name__: GOSTLaw, } def __init__(self, models: list[BaseModel]) -> None: diff --git a/src/formatters/styles/mla.py b/src/formatters/styles/mla.py new file mode 100644 index 0000000..2c6ba89 --- /dev/null +++ b/src/formatters/styles/mla.py @@ -0,0 +1,106 @@ +""" +Стиль цитирования по Modern Language Association 9th edition. +""" +from string import Template + +from pydantic import BaseModel + +from src.formatters.models import MLABookModel, MLAInternetResourceModel +from src.formatters.styles.base import BaseCitationStyle +from src.logger import get_logger + +logger = get_logger(__name__) + + +class MLABook(BaseCitationStyle): + """ + Форматирование для книг. + """ + + data: MLABookModel + + @property + def template(self) -> Template: + return Template( + "$author_last_name, $author_first_name. $title. $edition$publisher, $year." + ) + + def substitute(self) -> str: + logger.info('Форматирование книги MLA "%s" ...', self.data.title) + + return self.template.substitute( + author_last_name=self.data.author_last_name, + author_first_name=self.data.author_first_name, + title=self.data.title, + edition=self.get_edition(), + publisher=self.data.publisher, + year=self.data.year, + ) + + def get_edition(self) -> str: + """ + Получение отформатированной информации об издании. + + :return: Информация об издании. + """ + + return f"{self.data.edition} ed. " if self.data.edition else "" + + +class MLAInternetResource(BaseCitationStyle): + """ + Форматирование для интернет-ресурсов MLA. + """ + + data: MLAInternetResourceModel + + @property + def template(self) -> Template: + return Template( + "$author_last_name, $author_first_name. “$title” $website, $publication_date, $url." + ) + + def substitute(self) -> str: + logger.info('Форматирование интернет-ресурса MLA "%s" ...', self.data.title) + + return self.template.substitute( + author_last_name=self.data.author_last_name, + author_first_name=self.data.author_first_name, + title=self.data.title, + website=self.data.website, + publication_date=self.data.publication_date, + url=self.data.url, + ) + + +class MLACitationFormatter: + """ + Базовый класс для итогового форматирования списка источников. + """ + + formatters_map = { + MLABookModel.__name__: MLABook, + MLAInternetResourceModel.__name__: MLAInternetResource + } + + def __init__(self, models: list[BaseModel]) -> None: + """ + Конструктор. + + :param models: Список объектов для форматирования + """ + + formatted_items = [] + for model in models: + formatted_items.append(self.formatters_map.get(type(model).__name__)(model)) # type: ignore + + self.formatted_items = formatted_items + + def format(self) -> list[BaseCitationStyle]: + """ + Форматирование списка источников. + + :return: + """ + + return sorted(self.formatted_items, key=lambda item: item.formatted) diff --git a/src/logger.py b/src/logger.py index dcd14b6..8af826c 100644 --- a/src/logger.py +++ b/src/logger.py @@ -3,7 +3,7 @@ """ import logging -from settings import LOGGING_FORMAT, LOGGING_LEVEL, LOGGING_PATH +from src.settings import LOGGING_FORMAT, LOGGING_LEVEL, LOGGING_PATH def get_logger( diff --git a/src/main.py b/src/main.py index 7a9fa8e..6bd2856 100644 --- a/src/main.py +++ b/src/main.py @@ -5,11 +5,13 @@ import click -from formatters.styles.gost import GOSTCitationFormatter +from src.formatters.styles.mla import MLACitationFormatter +from src.formatters.styles.gost import GOSTCitationFormatter + from logger import get_logger -from readers.reader import SourcesReader +from readers.reader import SourcesReader, MLASourcesReader from renderer import Renderer -from settings import INPUT_FILE_PATH, OUTPUT_FILE_PATH +from settings import INPUT_FILE_PATH, OUTPUT_FILE_PATH, OUTPUT_MLA_FILE_PATH logger = get_logger(__name__) @@ -53,7 +55,7 @@ class CitationEnum(Enum): show_default=True, help="Путь к выходному файлу", ) -def process_input( +def process_input_gost( citation: str = CitationEnum.GOST.name, path_input: str = INPUT_FILE_PATH, path_output: str = OUTPUT_FILE_PATH, @@ -87,10 +89,45 @@ def process_input( logger.info("Команда успешно завершена.") +def process_input_mla( + citation: str = CitationEnum.MLA.name, + path_input: str = INPUT_FILE_PATH, + path_output: str = OUTPUT_MLA_FILE_PATH, +) -> None: + """ + Генерация файла Word с оформленным библиографическим списком. + + :param str citation: Стиль цитирования + :param str path_input: Путь к входному файлу + :param str path_output: Путь к выходному файлу + """ + + logger.info( + """Обработка команды с параметрами: + - Стиль цитирования: %s. + - Путь к входному файлу: %s. + - Путь к выходному файлу: %s.""", + citation, + path_input, + path_output, + ) + + models = MLASourcesReader(path_input).read() + formatted_models = tuple( + str(item) for item in MLACitationFormatter(models).format() + ) + + logger.info("Генерация выходного файла ...") + Renderer(formatted_models).render(path_output) + + logger.info("Команда успешно завершена.") + + if __name__ == "__main__": try: # запуск обработки входного файла - process_input() + process_input_mla() + process_input_gost() except Exception as ex: logger.error("При обработке команды возникла ошибка: %s", ex) raise diff --git a/src/readers/base.py b/src/readers/base.py index 47485d3..70a547f 100644 --- a/src/readers/base.py +++ b/src/readers/base.py @@ -9,7 +9,7 @@ from openpyxl.workbook import Workbook from pydantic import BaseModel -from logger import get_logger +from src.logger import get_logger logger = get_logger(__name__) diff --git a/src/readers/reader.py b/src/readers/reader.py index 9007a80..0272a82 100644 --- a/src/readers/reader.py +++ b/src/readers/reader.py @@ -7,10 +7,10 @@ import openpyxl from openpyxl.workbook import Workbook -from formatters.models import BookModel, InternetResourceModel, ArticlesCollectionModel -from logger import get_logger -from readers.base import BaseReader - +from src.formatters.models import BookModel, InternetResourceModel, ArticlesCollectionModel, ArticleMagazineModel, \ + LawModel, MLABookModel, MLAInternetResourceModel +from src.logger import get_logger +from src.readers.base import BaseReader logger = get_logger(__name__) @@ -90,6 +90,109 @@ def attributes(self) -> dict: } +class ArticleMagazineReader(BaseReader): + """ + Чтение модели сборника из журнала. + """ + + @property + def model(self) -> Type[ArticleMagazineModel]: + return ArticleMagazineModel + + @property + def sheet(self) -> str: + return "Статья из журнала" + + @property + def attributes(self) -> dict: + return { + "authors": {0: str}, + "article_title": {1: str}, + "magazine_title": {2: str}, + "year": {3: int}, + "number": {4: int}, + "pages": {5: str}, + } + + +class LawReader(BaseReader): + """ + Чтение модели закона, нормативного актa и т.п.. + """ + + @property + def model(self) -> Type[LawModel]: + return LawModel + + @property + def sheet(self) -> str: + return " Закон, нормативный акт и т.п." + + @property + def attributes(self) -> dict: + return { + "type": {0: str}, + "law_title": {1: str}, + "passing_date": {2: date}, + "number": {3: str}, + "source": {4: str}, + "source_year": {5: int}, + "source_number": {6: int}, + "article_number": {7: int}, + "start_date": {8: date}, + } + + +class MLABookReader(BaseReader): + """ + Чтение модели книги MLA. + """ + + @property + def model(self) -> Type[MLABookModel]: + return MLABookModel + + @property + def sheet(self) -> str: + return "Книга MLA" + + @property + def attributes(self) -> dict: + return { + "author_last_name": {0: str}, + "author_first_name": {1: str}, + "title": {2: str}, + "edition": {3: str}, + "publisher": {4: str}, + "year": {5: int}, + } + + +class MLAInternetResourceReader(BaseReader): + """ + Чтение модели интернес-ресурса MLA. + """ + + @property + def model(self) -> Type[MLAInternetResourceModel]: + return MLAInternetResourceModel + + @property + def sheet(self) -> str: + return "Интернет-ресурс MLA" + + @property + def attributes(self) -> dict: + return { + "author_last_name": {0: str}, + "author_first_name": {1: str}, + "title": {2: str}, + "website": {3: str}, + "publication_date": {4: str}, + "url": {5: str}, + } + + class SourcesReader: """ Чтение из источника данных. @@ -100,6 +203,8 @@ class SourcesReader: BookReader, InternetResourceReader, ArticlesCollectionReader, + ArticleMagazineReader, + LawReader, ] def __init__(self, path: str) -> None: @@ -125,3 +230,40 @@ def read(self) -> list: items.extend(reader(self.workbook).read()) # type: ignore return items + + +class MLASourcesReader: + """ + Чтение из источника данных. + """ + + # зарегистрированные читатели + readers = [ + MLABookReader, + MLAInternetResourceReader, + ] + + def __init__(self, path: str) -> None: + """ + Конструктор. + + :param path: Путь к исходному файлу для чтения. + """ + + logger.info("Загрузка рабочей книги ...") + self.workbook: Workbook = openpyxl.load_workbook(path) + + def read(self) -> list: + """ + Чтение исходного файла. + + :return: Список прочитанных моделей (строк). + """ + + items = [] + for reader in self.readers: + logger.info("Чтение %s ...", reader) + items.extend(reader(self.workbook).read()) # type: ignore + + return items + diff --git a/src/settings.py b/src/settings.py index 62d2037..2b2aa97 100644 --- a/src/settings.py +++ b/src/settings.py @@ -9,8 +9,9 @@ # путь к входному файлу INPUT_FILE_PATH: str = os.getenv("INPUT_FILE_PATH", "../media/input.xlsx") -# путь к выходному файлу -OUTPUT_FILE_PATH: str = os.getenv("OUTPUT_FILE_PATH", "../media/output.docx") +# путь к выходным файлам +OUTPUT_FILE_PATH: str = os.getenv("OUTPUT_FILE_PATH", "../media/output_gost.docx") +OUTPUT_MLA_FILE_PATH: str = os.getenv("OUTPUT_MLA_FILE_PATH", "../media/output_mla.docx") # путь к директории для логирования LOGGING_PATH: str = os.getenv("LOGGING_PATH", "../logs") diff --git a/src/tests/formatters/test_gost.py b/src/tests/formatters/test_gost.py index c93e1e7..6153a14 100644 --- a/src/tests/formatters/test_gost.py +++ b/src/tests/formatters/test_gost.py @@ -2,9 +2,9 @@ Тестирование функций оформления списка источников по ГОСТ Р 7.0.5-2008. """ -from formatters.base import BaseCitationFormatter -from formatters.models import BookModel, InternetResourceModel, ArticlesCollectionModel -from formatters.styles.gost import GOSTBook, GOSTInternetResource, GOSTCollectionArticle +from src.formatters.base import BaseCitationFormatter +from src.formatters.models import BookModel, InternetResourceModel, ArticlesCollectionModel +from src.formatters.styles.gost import GOSTBook, GOSTInternetResource, GOSTCollectionArticle class TestGOST: diff --git a/src/tests/readers/test_readers.py b/src/tests/readers/test_readers.py index 67d863b..d407448 100644 --- a/src/tests/readers/test_readers.py +++ b/src/tests/readers/test_readers.py @@ -5,14 +5,14 @@ import pytest -from formatters.models import BookModel, InternetResourceModel, ArticlesCollectionModel -from readers.reader import ( +from src.formatters.models import BookModel, InternetResourceModel, ArticlesCollectionModel +from src.readers.reader import ( BookReader, SourcesReader, InternetResourceReader, ArticlesCollectionReader, ) -from settings import TEMPLATE_FILE_PATH +from src.settings import TEMPLATE_FILE_PATH class TestReaders: diff --git a/src/tests/test_renderer.py b/src/tests/test_renderer.py index d2bef8f..a77b97f 100644 --- a/src/tests/test_renderer.py +++ b/src/tests/test_renderer.py @@ -5,7 +5,7 @@ import pytest -from renderer import Renderer +from src.renderer import Renderer class TestRenderer: From a0362f952c3d19c2e71f49c44c116f0f74eb7080 Mon Sep 17 00:00:00 2001 From: AngelikaShorokhova <63111642+AngelikaShorokhova@users.noreply.github.com> Date: Mon, 20 Feb 2023 11:42:33 +0500 Subject: [PATCH 2/2] + tests --- src/formatters/models.py | 10 +-- src/settings.py | 2 +- src/tests/formatters/test_gost.py | 40 ++++++++++- src/tests/formatters/test_mla.py | 69 ++++++++++++++++++ src/tests/readers/test_readers.py | 116 +++++++++++++++++++++++++++++- 5 files changed, 227 insertions(+), 10 deletions(-) create mode 100644 src/tests/formatters/test_mla.py diff --git a/src/formatters/models.py b/src/formatters/models.py index 0787455..c77a072 100644 --- a/src/formatters/models.py +++ b/src/formatters/models.py @@ -142,9 +142,9 @@ class MLABookModel(BaseModel): MLABookModel( author_last_name="Smith", author_first_name="Thomas", - title="", - edition="", - publisher="", + title="The Citation Manual for Students: A Quick Guide", + edition="2nd", + publisher="Wiley", year=2020, ) """ @@ -166,8 +166,8 @@ class MLAInternetResourceModel(BaseModel): MLAInternetResourceModel( author_last_name="Smith", author_first_name="Thomas", - title: "Whales Likely Impacted by Great Pacific Garbage Patch." - website: "The Ocean Cleanup" + title="Whales Likely Impacted by Great Pacific Garbage Patch." + website="The Ocean Cleanup" publication_date="10 Apr. 2019", url="www.theoceancleanup.com/updates/whales-likely-impacted-by-great-pacific-garbage-patch", ) diff --git a/src/settings.py b/src/settings.py index 2b2aa97..b40e7e3 100644 --- a/src/settings.py +++ b/src/settings.py @@ -10,7 +10,7 @@ # путь к входному файлу INPUT_FILE_PATH: str = os.getenv("INPUT_FILE_PATH", "../media/input.xlsx") # путь к выходным файлам -OUTPUT_FILE_PATH: str = os.getenv("OUTPUT_FILE_PATH", "../media/output_gost.docx") +OUTPUT_FILE_PATH: str = os.getenv("OUTPUT_FILE_PATH", "../media/output.docx") OUTPUT_MLA_FILE_PATH: str = os.getenv("OUTPUT_MLA_FILE_PATH", "../media/output_mla.docx") # путь к директории для логирования diff --git a/src/tests/formatters/test_gost.py b/src/tests/formatters/test_gost.py index 6153a14..3b4531c 100644 --- a/src/tests/formatters/test_gost.py +++ b/src/tests/formatters/test_gost.py @@ -3,8 +3,10 @@ """ from src.formatters.base import BaseCitationFormatter -from src.formatters.models import BookModel, InternetResourceModel, ArticlesCollectionModel -from src.formatters.styles.gost import GOSTBook, GOSTInternetResource, GOSTCollectionArticle +from src.formatters.models import BookModel, InternetResourceModel, ArticlesCollectionModel, ArticleMagazineModel, \ + LawModel +from src.formatters.styles.gost import GOSTBook, GOSTInternetResource, GOSTCollectionArticle, GOSTMagazineArticle, \ + GOSTLaw class TestGOST: @@ -61,6 +63,40 @@ def test_articles_collection( == "Иванов И.М., Петров С.Н. Наука как искусство // Сборник научных трудов. – СПб.: АСТ, 2020. – С. 25-30." ) + def test_article_magazine( + self, article_magazine_model_fixture: ArticleMagazineModel + ) -> None: + """ + Тестирование форматирования статьи из журнала. + + :param ArticleMagazineModel article_magazine_model_fixture: Фикстура модели статьи из журнала + :return: + """ + + model = GOSTMagazineArticle(article_magazine_model_fixture) + + assert ( + model.formatted + == "Иванов И.М., Петров С.Н. Наука как искусство // Образование и наука. – 2020. – №10. – С. 25-30." + ) + + def test_law( + self, law_model_fixture: LawModel + ) -> None: + """ + Тестирование форматирования закона и т. д. + + :param LawModel law_model_fixture: Фикстура модели закона и т. д. + :return: + """ + + model = GOSTLaw(law_model_fixture) + + assert ( + model.formatted + == "Конституция Российской Федерации \"Наука как искусство\" от 01.01.2000 № 1234-56 // Парламентская газета. - 2020 г. - № 5. - Ст. 15 с изм. и допол. в ред. от 11.09.2002." + ) + def test_citation_formatter( self, book_model_fixture: BookModel, diff --git a/src/tests/formatters/test_mla.py b/src/tests/formatters/test_mla.py new file mode 100644 index 0000000..34cfac8 --- /dev/null +++ b/src/tests/formatters/test_mla.py @@ -0,0 +1,69 @@ +""" +Тестирование функций оформления списка источников по Modern Language Association 9th edition. +""" + +from src.formatters.base import BaseCitationFormatter +from src.formatters.models import MLABookModel, MLAInternetResourceModel +from src.formatters.styles.mla import MLABook, MLAInternetResource + + +class TestMLA: + """ + Тестирование оформления списка источников согласно Modern Language Association 9th edition. + """ + + def test_mla_book(self, book_model_fixture: MLABookModel) -> None: + """ + Тестирование форматирования книги. + + :param BookModel book_model_fixture: Фикстура модели книги MLA + :return: + """ + + model = MLABook(book_model_fixture) + + assert ( + model.formatted + == "Smith, Thomas. The Citation Manual for Students: A Quick Guide. 2nd ed. Wiley, 2020." + ) + + def test_mla_internet_resource( + self, internet_resource_model_fixture: MLAInternetResourceModel + ) -> None: + """ + Тестирование форматирования интернет-ресурса. + + :param InternetResourceModel internet_resource_model_fixture: Фикстура модели интернет-ресурса + :return: + """ + + model = MLAInternetResource(internet_resource_model_fixture) + + assert ( + model.formatted + == "Slat, Boyan. “Whales Likely Impacted by Great Pacific Garbage Patch.” The Ocean Cleanup, " + "10 Apr. 2019, www.theoceancleanup.com/updates/whales-likely-impacted-by-great-pacific-garbage-patch." + ) + + def test_citation_formatter( + self, + book_model_fixture: MLABookModel, + internet_resource_model_fixture: MLAInternetResourceModel, + ) -> None: + """ + Тестирование функции итогового форматирования списка источников. + + :param BookModel book_model_fixture: Фикстура модели книги + :param InternetResourceModel internet_resource_model_fixture: Фикстура модели интернет-ресурса + :return: + """ + + models = [ + MLABook(book_model_fixture), + MLAInternetResource(internet_resource_model_fixture), + ] + result = BaseCitationFormatter(models).format() + + # тестирование сортировки списка источников + assert result[0] == models[0] + assert result[1] == models[1] diff --git a/src/tests/readers/test_readers.py b/src/tests/readers/test_readers.py index d407448..2e10e77 100644 --- a/src/tests/readers/test_readers.py +++ b/src/tests/readers/test_readers.py @@ -5,12 +5,17 @@ import pytest -from src.formatters.models import BookModel, InternetResourceModel, ArticlesCollectionModel +from src.formatters.models import BookModel, InternetResourceModel, ArticlesCollectionModel, ArticleMagazineModel, \ + LawModel, MLABookModel, MLAInternetResourceModel from src.readers.reader import ( BookReader, SourcesReader, InternetResourceReader, ArticlesCollectionReader, + ArticleMagazineReader, + LawReader, + MLABookReader, + MLAInternetResourceReader ) from src.settings import TEMPLATE_FILE_PATH @@ -104,6 +109,109 @@ def test_articles_collection(self, workbook: Any) -> None: # проверка общего количества атрибутов assert len(model_type.schema().get("properties", {}).keys()) == 7 + def test_article_magazine(self, workbook: Any) -> None: + """ + Тестирование чтения статьи из журнала. + + :param workbook: Объект тестовой рабочей книги. + """ + + models = ArticleMagazineReader(workbook).read() + + assert len(models) == 1 + model = models[0] + + model_type = ArticleMagazineModel + + assert isinstance(model, model_type) + assert model.authors == "Иванов И.М., Петров С.Н." + assert model.article_title == "Наука как искусство" + assert model.magazine_title == "Сборник научных трудов" + assert model.year == 2020 + assert model.number == 10 + assert model.pages == "25-30" + + # проверка общего количества атрибутов + assert len(model_type.schema().get("properties", {}).keys()) == 6 + + def test_law(self, workbook: Any) -> None: + """ + Тестирование чтения закона т. д. + + :param workbook: Объект тестовой рабочей книги. + """ + + models = LawReader(workbook).read() + + assert len(models) == 1 + model = models[0] + + model_type = LawModel + + assert isinstance(model, model_type) + assert model.type == "Конституция Российской Федерации" + assert model.law_title == "Наука как искусство" + assert model.passing_date == "01.01.2000" + assert model.number == "1234-56" + assert model.source == "Парламентская газета" + assert model.source_year == 2020 + assert model.source_number == 5 + assert model.article_number == 15 + assert model.start_date == "11.09.2002" + + # проверка общего количества атрибутов + assert len(model_type.schema().get("properties", {}).keys()) == 9 + + def test_mla_book(self, workbook: Any) -> None: + """ + Тестирование чтения иностранной криги. + + :param workbook: Объект тестовой рабочей книги. + """ + + models = MLABookReader(workbook).read() + + assert len(models) == 2 + model = models[0] + + model_type = MLABookModel + + assert isinstance(model, model_type) + assert model.author_last_name == "Smith" + assert model.author_first_name == "Thomas" + assert model.title == "The Citation Manual for Students: A Quick Guide" + assert model.edition == "2nd" + assert model.publisher == "Wiley" + assert model.year == 2020 + + # проверка общего количества атрибутов + assert len(model_type.schema().get("properties", {}).keys()) == 6 + + def test_mla_internet_resource(self, workbook: Any) -> None: + """ + Тестирование чтения иностранной криги. + + :param workbook: Объект тестовой рабочей книги. + """ + + models = MLAInternetResourceReader(workbook).read() + + assert len(models) == 1 + model = models[0] + + model_type = MLAInternetResourceModel + + assert isinstance(model, model_type) + assert model.author_last_name == "Slat" + assert model.author_first_name == "Boyan" + assert model.title == "Whales Likely Impacted by Great Pacific Garbage Patch." + assert model.website == "The Ocean Cleanup" + assert model.publication_date == "10 Apr. 2019" + assert model.url == "www.theoceancleanup.com/updates/whales-likely-impacted-by-great-pacific-garbage-patch" + + # проверка общего количества атрибутов + assert len(model_type.schema().get("properties", {}).keys()) == 6 + def test_sources_reader(self) -> None: """ Тестирование функции чтения всех моделей из источника. @@ -111,7 +219,7 @@ def test_sources_reader(self) -> None: models = SourcesReader(TEMPLATE_FILE_PATH).read() # проверка общего считанного количества моделей - assert len(models) == 8 + assert len(models) == 10 # проверка наличия всех ожидаемых типов моделей среди типов считанных моделей model_types = {model.__class__.__name__ for model in models} @@ -119,4 +227,8 @@ def test_sources_reader(self) -> None: BookModel.__name__, InternetResourceModel.__name__, ArticlesCollectionModel.__name__, + ArticleMagazineModel.__name__, + LawModel.__name__, + MLABookModel.__name__, + MLAInternetResourceModel.__name__, }