@OtKl@3KE~Hv09B+hWT*Hb3=|(+9}!>Ke3X{yZDu9=8ZZE*6K9DpU?+
z9Jq+m7!ovd4Lr6{Sse=*S*u(mU+)^=i%07HAw7&@TW@ArcS>%NPK`(Jw3+4ka^
z2ZsEh3(?G`>o|_{n2}>{17bY|3z|2;4~qHA@Nh+7r}-+86&jBGcy@pN&&wHd?|(@5
z7@>C*Ch)5!!7-!B-!XptvG^6M-Buzu{8X_;pM3L%ntLKM>NiOD1CXHL-teF+8He1?
zYiUrFh4|=444s9c
zD@wRwR?VN>i*3Fo$Kw~tYF}a?8hq)$m
zu~}g&oT=h{I31Cg-<|lLhzp97$?Wgzsn0X3obCU2Q#RvB7-fOTQ~*-8bze;mbIfFB
zSZ62hklO93;I2{qSpX}c>6phjC8+SUEZSA$gFnE9|cphpk3ZVY%TfE
zvudxU3cOsW=D;HR{zm1=PbG%uy~1x-$G8eJinPC-KdTGie0*EsjqutebvR+Z8*mXF
zY)jf~-(72@QPvg`_({l@-qvWGc~;0>ALjSW?-jz)~v8qi{mzB<#G8*pro?lB&Zs2caKw#mqOB)SvX{61aWE
zpKNJ!`UqLy|1R0b)^ok8X)T5itG_EMFX8C(A;jrfQ})(p!_68}%&E^*b##wBq)wRo8
z6iaiZ$&=`b!r#$aj+j^ArV7mWxA^KO;LD0r7@?Rcg2OpOBKp2bgS)IowVH@26MsR~
zoRucRs5*!vvlN>AEswMG*cTx0$rb=8{SK<>4p;j}=F7sNbgau&w(r;Q_G_O{2zNG}
zL%?z{0qoc9&i?B!zG#V^M|FJ|WQ^hDU>?M6H8SBZCy&FD+4tzB3nADiviP#zqA
zJ#T$ObY*!5ug!F!t*Nq|S?G-r#|GxSjSu&cN{EiLGld)4jKEqCT3od+Yko&{#a4t<
z7C#uQA2bdS%w2SCb|_c?_Af*dA=#Y~k_A&1>#|uEq5KeCuZt;N3%OQ+arB}6*Yg#Bfa`Z!5k}>&0`hp13*tt3N4;rlHMGTIS0CnT!=(O)*kCA)5F9)=I
zmc5U>TB!gjz5J)oGRb&31mKMx?|r3tC|4bnUUs0jbe15{`r}*VXi&)4=O|NQ&G6*u6A7K72ViS<)qt_x
z-ThkKsB3)(lH_^-su(B@8+|zX-uD--rkq1Re!v%Jvu4*A+l6cD5
zks3&gbk{`OQ!136xP-#Q%Y!nMCRNWeoA%&g1l^mOttSM5OvVacXM4(vpQ`4-Gl+$GYeZE8e7Yc#KYSos?)W<8func>Ze-yR
zow=E-hxrTbY!HAn>w84b2K4GN5IK@1L6s2lHn|!f=>^s2$(p~T4ticN$r@U4wKj2^D6>?N}c?Am^-3K
zQ)k0N3(jo>(@R)HNT<)Do*wIKlc`SwF@Grs4|JEA^)9D#Y00Hec6`O&AC-kFHjlTQ
z@6Q{{7HYHK$8fg%;wF|8DIW$l2*H@`cTStP>(I8@i`WPb#*gyocP!mWoCr4SPvD;l
z@A^i?SR^r~-^JrO##_2819xtVovC-U_Sl0JdnN(8O5g8}vOHiYPkHyFhrPqk#no?C
zB08Ng8ORvJxE66(Ig=x#=q1MVF$wr%;t(rZp1tt~lvS${4C(PX!YA$8w+7-!NgfM4
z7MiK3f0>EqFj6bLx$AJ@Lw=QH)t{x|U=T4D>CmuPA1b^>pEIY0kZ;o;p}&D+2BV0V
z+2oQa4%@1+Cu3KYe?jF2`Gy^7wEnyqfKrUQZVMs90qL*?A#y_2y9}w+K)w!`CGG>2
zQmj8JuyiXs1CKIEew4U|X_|ngOG5DOkc?G|-e!|xPt&lM*}o2GNO#NFJtC(1
zeqE^Bi(gbvRQ(|IYKyiD1lZau_z~I1YpGq5xN90Jo%0XN$z&Tc8e6G!^^l@ioPDas
z-n*Cc+?Ru4yQcV!gQzLLpL4a@!j)=Jv`%vk}6x&QW&XprbJPSit>E
z;AL>}y@~lB6Mb7~LZ~Q?lR)KDm5XZgyv%6$hBuKm7cgYVRU46-p1A90|0Hs-nu|7m
znbb?EaUpYe(#*c}<-2|MVsO!!vNW$VBs>$h{+(a*IBeqeeZra1~hbVT+lzk8t^a|
zwO44BJ-Rc4ySvWsIpsoDNXHH(sM_n6(`Xv@-9&dK#4}_9=r`8&-Q}i<(zsst6A%wr
z%cxSTwrdPbX!FhYwM3J38@k5mqs5A1mXqXkzXFww^uj|;mE+{6TnsWmnE>e5a-r}AnARA0qzis_`jyKX)_>HKqtt&~1PyEa31y_7WN5@*}Wuf~cgvEk!_?PG!
z1RulG-Tme*rJl*c#XNV_;$Y7vzpywyD6eJ{N+0b>I45N8-x}d75rfu$z{?FWYmfbB
zij#z6I3pDKD3XdO-Ik{8m&_50K`*X1wWI4qe6W*4OwUwC(OF34$%cQY<#338L71!~PH!;zqL
zYlKJ6t$T9w?fE!@dBb3*L5TmM?~i
zga*bKM!wl0^5RM5Vv5~iFz)HRG2KYGQyhi87EgLY_7$r7TSD&yr4fLN)2jD4oojgY
z;!)zX6}XbZ>}!#!udmHz!WWh0tB@vX?JxWQqlXcIlP|{A4a>tHul-n)F^SL*oVl
zLDt~in%RcB?zS_i9w-BnVDxs5AarwfMCY7Z$?KU?O}J^BC&ESDt-vz>SJ>=J#9EqQ
zyq9p{VLOJiyoQPP2JtLcRA`x61RnKuIs{{M?7vGmX#m^;obZfB(n2CW8_p952K)+UB0tv3&nEm3_pz+xRK}SKzSKswxXG=j26|;&-CsXtwDbbv@S&NmU%U8#-72!qI);
zUr0hV{Be%lQBgDn2ZTku$-os)K`yCr-9035T6;I^JQ4wSP8II<+9ygku69uS6$--1
zGktLCk`iSK1Rg8l2M@Dnx!0h-AjJuw9
zw>LhAnG(E}h0!HwP>n{pc%tPFF?f|h$_Ses85eb>AO;d_%B7&s`@Nf2q%bpnHSxAT
z(CH`ratf}soiT4+@vw{}zxrC!%dj{otr+%ka`3+bV3hBg?4hZ=W8+Ry#@
z0uq?3aPGrvRdTxLHGaOsaGL3ZekW1>O#}%O$)=+J4gyU|H4FKLw1m0W{PL|X+-LH+
z1b@7;d3qR{#wv&?sPwF0IoTx*Aq1`DV0E)~fsN=p8+aw<8@ESqUkA$efGF0P;sW^1+%FJRp$r*O4Y-*>w=RO(_pNac~-&ghhdP{)GWdk88Il!
zP^~{`VZn$_Na#G3Ouu=Art-;>#UF&W#t4K@lIO#*Bx>Tu0F(g
zUra8FGUgHXw@vxI*~lZZ4*Dm)%mUqg$dcL_pxx+**r!*DEgjy4;zm>X>o;-^kHtLu-`ac$putuEZC_-~fv
zzd5yFxe0XN6J>e<)n&A$c(C*iz1E!vb6j7n0Zh(Ml;mP?58n7G@f#hZ7mxXC=wULy
zYDZ0-?FfO>pk-E;M>8bhDinbKvU&0Lv=
zq7|@l04h0hU8iPyFBymBSBPhiVU*$*2``puF_^#KSNgdQa(RdIcEO#GSF8-n55|Ha
zhgk_p+1iAB=<}_2TJj7<)kTbc+CTYryxEvcYp&kW*-<;}A`9KM7%Db3Bsa0mk?3|g
zI{mDH*fMj@%irfR27({tM{crZ@rL;i?SZZF877AOwHSshGCbs;z5P!#RO)RmU@o%1
z`W!K}CQ-NoWm#rZggRYjLiayj6dkivLe>z7^JHFFd(csdmAzfi%a8gFK0MP0P4`nn~`f|rpOIv0?dw}Vm1WLGnE21~?lD&NE?VNF1-eLw8Ik$va
zI<~(yA;=0!ocey}$vv}Zz(-?pqmQkTS7_zwm&Hi0Ge9%pAyY$6$VS}kF(}S*>Kdt;
zf#S(^ef2S-0U5fx!2y%I$G}**!dFXFqhb$bxdl-%r$p~?I#BrV;DTr)(4j&2|G>>+
zs<(ZBoZh5La#je``pK_Fs?><|z(Wlng=h&KZE02=VGrTyaug95TYmZM>!MKY{Z}!U8TdlA#8^Au>zg0zT8bPJ|#`6E8
zyojc(fVQwGyzJ-oZ}tWJJgP-^OBprkw(nH+CRl1C(74bQkJsz@V#!%YO2lBUBtV~U
zy^!Ukl=7jW$(gdask=%!Bl^@r%IR5i%et^l$#0F<`A(-II9pHp{>dM2JT`SHUl^k9
zKwt1rW4~Xdt6p=!H?baZho{Xs_pb(iTPZr8)N}!X-eC9H>o4)Dy8@RFtY~hK^w6
zk63d@II2jBZ|!S=<s&wnt+p%5+JTv1!Y!6<-1
zig%ut#$c>GdJw;)A0VmyFJ@25d&^D?lg9RD>tb?~dc~->o0Or%ah&e&bLto`JFDby
zPc=s-mqa;L<;Nquo+g}(@N;l8k{2$eEU~a(d&W!;wzjrD=Elb*coPMY=G1;4
zMB|7CI_uj0VS`TknF<^rnxw7K&oXQyY(NaRutD9|@{byAq@U!4daG=l%Ap=d_;12j
z{KfOGg&&PBsOa1GDC{+Fng7*9%r;+^4VyvJAiDr))>=nK0%R>Z?@rQtI}u%iO(JE+
zMkE#q%+1d~|1hZ*oX$8#ZAOoohnBgubLAJ^W@@^v21@xM^F*aAFb$Z^=F5Is=B1?j
z$h1FEl4z7Q^{V&v5+1*rRbKJr7WxW1CHPW=fA5U6_~B(9iF{93$&O+tp0(zoW$l!P
z?3OOEoD8p;M+SMO9~SMs4^&QqVBpci*!|>_SjT2UaEK~a)-r+H1@$OcxCfZU5Xdy4
zWm-phX^kEjf5!w)6gx*fKBy_clU&Na24p*Z(EKq90KijL8)E*;3yM1mhH$j-4sa
z>CfwC?4W7=`|8N_;hBv}4-SU+y;eKomB?aP`!|D_4^HLl7E+)wSfD7Ig
zOEltXSBjQrp6cK+Nw~|r5hK>HI(@Nurt(i&XR>o
zOWNs{*&6u9%y+>t0iA&VQg0&(n^zBr>#faIC3nGZXW3Jw_?A6S(O=J;<%I6iH#7hB
zmi&y@$NlTx#1aQ&rdO8dMj5HO()3rSIOo5z@S!f;W}0wP!A^9rSeI52!5qb{k%EvC
zzM}1|zx(kRrYn6=PU&L(U7(yVVAq8e
zJ*xuC$#@CC(EA*%NLDb@?9lUa)8hGLHca1@yccDhNKODsj$APbRf0Pd)Ild&SfL`A
z{H5*#{3X8Q3efJ~TbU&X%p@5-%!gjebUji^@%$5FwF5F&UEP*XD3J3oxr`4oDR_OMn
zll2=N?^H!bpbD0VhD=ip^Zg3;pI6$bXxfU?Ln-@MvBtldo!|5wSQec8gGcKU=n^rv
z#mGbYtAu_fA_-604VTIL{GNp(RR$~wyxgB)J;cU5qai6Im4CO5uC1evn|*VIH9na(
z`k?a`698}{BKR~e+@JivWYR&OjqR?YCqks*1l1m^)>$eZV9y2QN-{3LbIq>Xq&Iij
z*0&tuDL3wiR6`GdhCxT5;a`u9H(}^Imn`xlfZn?+#d3&@gk!9|X0=9Hs%hZWmnXY{
z%vCz$QJz%=d=^B+Q76aQ`1h-c7|Z58L+?VXE_xUKYME#GzY)@|%C6F)K8$)@hBFFV
zp=|RjvA$HD1d6S{7rdLD7i;HS)HF7&T^YE(!Fl|OXtoJqlKo;8bxPKKQKK8!LF{Xr{xfi_H>iE;7?UmMxS
ze>_o@GPp=fYGA-7$q!|
z_?+5+VJ`U#s;{`44zNY49RajeUN*n=QT5ChtKVxKYZ*XxoY9>xeu?M~{S@-F3htbr
ze}0olaNZU+H{ca29BJEuK^kV6;s$OT54e|B6%K55EtLPw^~1lbW1h#Nay
z2Or8i#1~Z2Ql82Kc}_6-Kg$oGU9UB4E7YFO_9&6!iKJo8F^!U_x@2Dw4S=)@>&_9W
zd>Y&Z8yfCGoab6MD7!Z~boevHv?1W0?|h^ALC=6?H#e^lPvB-$~SFxGyRB8A4)_smnTmZn(
+ {% endfor %}
+ method: merge
+ delete_head_branch:
+ - name: Merge if approved, no conflicts are present and it's not a WIP
+ conditions:
+ - or:
+ - "-#approved-reviews-by=0"
+ - "label~=Approved"
+ - "label~=Able to merge"
+ - "-conflict"
+ - "-draft"
+ - "-locked"
+ - "#check-failure=0"
+ actions:
+ comment:
+ message: |
+ # Merging pull request
+
+ Checks:
+ | Name | Status |
+ |------|--------|
+ {% for check in check_success %}| {{check}} | :white_check_mark: |
+ {% endfor %}
+ merge:
+ commit_message_template: |
+ {{title}}
+
+ {{title}} in #{{number}} by @{{author}}, contributing to {{milestone}}
+
+ Changed files:
+ {% for file in files %}- '{{file}}'
+ {% endfor %}
+
+ Approved by: @{{ approved_reviews_by | join(', @') }}
+
+
+ {% for commit in commits %}Co-authored-by: {{commit.author}} <{{commit.email_author}}>
+ {% endfor %}
+ method: merge
+ delete_head_branch:
+ - name: Add review requested label and request review from ElBe
+ conditions:
+ - or:
+ - "#approved-reviews-by=0"
+ - "-label~=Approved"
+ - "-title~=^[WIP].*"
+ - "-label~=Declined"
+ - "-label~=Review requested"
+ - "-draft"
+ - "-locked"
+ - "-conflict"
+ actions:
+ label:
+ add:
+ - Review requested
+ request_reviews:
+ users:
+ - ElBe-Plaq
+ - name: Warn on conflicts and add label
+ conditions:
+ - conflict
+ actions:
+ comment:
+ message: "@{{author}} this pull request has one or more conflicts."
+ label:
+ add:
+ - Invalid
+ remove:
+ - Review requested
+ - name: Remove invalid label if not needed
+ conditions:
+ - -conflict
+ actions:
+ label:
+ add:
+ - Review requested
+ remove:
+ - Invalid
+ - name: Warn on failed checks
+ conditions:
+ - "-#check-failure=0"
+ actions:
+ comment:
+ message: |
+ # Checks failed
+
+ Checks:
+ | Name | Status |
+ |------|--------|
+ {% for check in check_success %}| {{check}} | :white_check_mark: |
+ {% endfor %}{% for check in check_failure %}| {{check}} | :x: |
+ {% endfor %}
diff --git a/.github/stale.yml b/.github/stale.yml
new file mode 100644
index 0000000..a4d7996
--- /dev/null
+++ b/.github/stale.yml
@@ -0,0 +1,11 @@
+---
+daysUntilStale: 60
+daysUntilClose: 7
+exemptLabels:
+ - Security
+staleLabel: stale
+markComment: >
+ This issue has been automatically marked as stale because it has not had
+ recent activity. It will be closed if no further activity occurs. Thank you
+ for your contributions.
+closeComment: false
diff --git a/.github/workflows/codecov_workflow.yml b/.github/workflows/codecov_workflow.yml
new file mode 100644
index 0000000..4a813bb
--- /dev/null
+++ b/.github/workflows/codecov_workflow.yml
@@ -0,0 +1,14 @@
+name: Codecov
+permissions: read-all
+
+on:
+ [push, pull_request]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Upload coverage reports to Codecov
+ uses: codecov/codecov-action@v3
+ env:
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml
new file mode 100644
index 0000000..c71a461
--- /dev/null
+++ b/.github/workflows/dependencies.yml
@@ -0,0 +1,46 @@
+---
+name: Dependencies
+permissions: read-all
+
+on:
+ push:
+ paths: ["dev-requirements.txt", "Cargo.toml"]
+
+ pull_request:
+ paths: ["dev-requirements.txt", "Cargo.toml"]
+
+jobs:
+ install-python-dependencies:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ python-version: ["3.8"]
+ steps:
+ - uses: actions/checkout@v4
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v4
+ with:
+ python-version: ${{ matrix.python-version }}
+ - name: Install dependencies from txt files
+ run: |
+ python -m pip install --upgrade pip
+ pip install -r dev-requirements.txt
+ install-rust-dependencies:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Install dependencies from Cargo.toml
+ run: cargo update
+ - name: Install dev-dependencies from Cargo.toml
+ run: cargo test --no-run
+ # ^ This is the only way to install dev-dependencies
+
+ dependency-review:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout Repository
+ uses: actions/checkout@v4
+ if: ${{ github.event_name != 'push' }}
+ - name: Dependency Review
+ if: ${{ github.event_name != 'push' }}
+ uses: actions/dependency-review-action@v3
diff --git a/.github/workflows/label.yml b/.github/workflows/label.yml
new file mode 100644
index 0000000..34a042d
--- /dev/null
+++ b/.github/workflows/label.yml
@@ -0,0 +1,25 @@
+# This workflow will triage pull requests and apply a label based on the
+# paths that are modified in the pull request.
+#
+# To use this workflow, you will need to set up a .github/labeler.yml
+# file with configuration. For more information, see:
+# https://github.com/actions/labeler
+
+---
+name: Labeler
+permissions:
+ pull-requests: write
+
+on: [pull_request_target]
+
+jobs:
+ label:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ pull-requests: write
+
+ steps:
+ - uses: actions/labeler@v4
+ with:
+ repo-token: "${{ secrets.GITHUB_TOKEN }}"
diff --git a/.github/workflows/megalinter.yml b/.github/workflows/megalinter.yml
new file mode 100644
index 0000000..280bede
--- /dev/null
+++ b/.github/workflows/megalinter.yml
@@ -0,0 +1,38 @@
+---
+name: MegaLinter
+permissions: read-all
+
+on:
+ push:
+ pull_request:
+ branches: [main]
+
+env:
+ APPLY_FIXES: VALIDATE_ALL_CODEBASE
+ APPLY_FIXES_EVENT: all
+ APPLY_FIXES_MODE: commit
+
+concurrency:
+ group: ${{ github.ref }}-${{ github.workflow }}
+ cancel-in-progress: true
+
+jobs:
+ build:
+ name: MegaLinter
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ issues: write
+ pull-requests: write
+ steps:
+ - name: Checkout Code
+ uses: actions/checkout@v4
+ with:
+ token: ${{ secrets.PAT || secrets.GITHUB_TOKEN }}
+ fetch-depth: 0
+ - name: MegaLinter
+ id: ml
+ uses: oxsecurity/megalinter@v7
+ env:
+ VALIDATE_ALL_CODEBASE: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
new file mode 100644
index 0000000..45c562c
--- /dev/null
+++ b/.github/workflows/rust.yml
@@ -0,0 +1,18 @@
+name: Rust
+permissions: read-all
+
+on:
+ [push, pull_request]
+
+env:
+ CARGO_TERM_COLOR: always
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Build
+ run: cargo build --verbose
+ - name: Run tests
+ run: cargo test --verbose
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..6985cf1
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,14 @@
+# Generated by Cargo
+# will have compiled files and executables
+debug/
+target/
+
+# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
+# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
+Cargo.lock
+
+# These are backup files generated by rustfmt
+**/*.rs.bk
+
+# MSVC Windows builds of rustc generate these, which store debugging information
+*.pdb
diff --git a/.mega-linter.yml b/.mega-linter.yml
new file mode 100644
index 0000000..b10c5b8
--- /dev/null
+++ b/.mega-linter.yml
@@ -0,0 +1,16 @@
+---
+DISABLE_ERRORS_LINTERS:
+ - SPELL_CSPELL
+ - REPOSITORY_DEVSKIM # cspell:disable-line
+ - MAKEFILE_CHECKMAKE # cspell:disable-line
+ - HTML_HTMLHINT # cspell:disable-line
+ - MARKDOWN_MARKDOWN_LINK_CHECK
+ - REPOSITORY_DUSTILOCK # cspell:disable-line
+ - JAVASCRIPT_STANDARD
+ - REPOSITORY_TRUFFLEHOG # cspell:disable-line
+ - SPELL_LYCHEE
+FILTER_REGEX_EXCLUDE: .*mypy.* # cspell:disable-line
+EDITORCONFIG_EDITORCONFIG_CHECKER_FILTER_REGEX_EXCLUDE: "Docs/acknowledgements.md|src/Installer/Linux/installer.bash"
+REPORT_OUTPUT_FOLDER: none
+SHOW_ELAPSED_TIME: true
+VALIDATE_ALL_CODEBASE: false
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..6c07b01
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,38 @@
+---
+repos:
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v4.4.0
+ hooks:
+ - id: check-added-large-files
+ - id: check-executables-have-shebangs
+ - id: check-json
+ - id: check-merge-conflict
+ - id: check-shebang-scripts-are-executable
+ - id: check-toml
+ - id: check-yaml
+ - id: pretty-format-json
+ args: [--autofix]
+ - id: requirements-txt-fixer
+ - id: trailing-whitespace
+ - repo: https://github.com/jorisroovers/gitlint
+ rev: v0.19.1
+ hooks:
+ - id: gitlint
+ args: [--config=.github\.gitlint, --msg-filename]
+ - repo: https://github.com/doublify/pre-commit-rust
+ rev: v1.0
+ hooks:
+ - id: fmt
+ - id: cargo-check
+ - repo: local
+ hooks:
+ - id: clippy
+ name: Run clippy linter
+ entry: cargo clippy -- -A non_snake_case
+ language: system
+ pass_filenames: false
+ci:
+ skip:
+ - fmt
+ - cargo-check
+ - clippy
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..51ffa65
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "logging-rs"
+description = "logging-rs helps you add logging to your projects using simple macros."
+version = "1.0.0"
+authors = [
+ "ElBe-Plaq "
+]
+edition = "2021"
+rust-version = "1.69"
+documentation = "https://docs.rs/logging_rs/"
+readme = ".github/README.md"
+repository = "https://github.com/ElBe-Development/logging-rs/"
+license = "MIT"
+keywords = ["log", "logger", "logging", "debug", "debugging"]
+categories = [
+ "development-tools::debugging",
+]
+publish = true
+
+[dependencies]
+# chrono = "0.4.31"
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..04e1350
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 ElBe Development
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/codecov.yml b/codecov.yml
new file mode 100644
index 0000000..b2a0e98
--- /dev/null
+++ b/codecov.yml
@@ -0,0 +1,10 @@
+coverage:
+ status:
+ project:
+ default:
+ target: 50%
+ threshold: 1%
+
+comment:
+ require_changes: true
+ require_ci_to_pass: false
diff --git a/dev-requirements.txt b/dev-requirements.txt
new file mode 100644
index 0000000..56ce91b
--- /dev/null
+++ b/dev-requirements.txt
@@ -0,0 +1 @@
+pre-commit==3.4.0; python_version>='3.8'
diff --git a/examples/main.rs b/examples/main.rs
new file mode 100644
index 0000000..cc5cb60
--- /dev/null
+++ b/examples/main.rs
@@ -0,0 +1,12 @@
+use logging_rs;
+
+fn main() {
+ let logger = logging_rs::Logger::default();
+
+ logging_rs::debug!(logger, "Debug message");
+ logging_rs::info!(logger, "Info");
+ logging_rs::warn!(logger, "Warning");
+ logging_rs::error!(logger, "Error!");
+ logging_rs::fatal!(logger, "Fatal error!");
+ logging_rs::log!(logger, "Log message");
+}
diff --git a/justfile b/justfile
new file mode 100644
index 0000000..a6550c9
--- /dev/null
+++ b/justfile
@@ -0,0 +1,25 @@
+alias b := build
+alias c := clean
+alias l := lint
+alias r := run
+alias t := test
+
+# Compiles the rust source files
+build *ARGUMENTS:
+ cargo build --release *ARGUMENTS
+
+# Removes temporary files
+clean:
+ cargo clean
+
+# Lints the rust source files
+lint *ARGUMENTS:
+ cargo check *ARGUMENTS
+
+# Compiles and executes the main.rs file
+run *ARGUMENTS:
+ cargo run *ARGUMENTS
+
+# Runs the unittests
+test *ARGUMENTS:
+ cargo test *ARGUMENTS
diff --git a/src/errors.rs b/src/errors.rs
new file mode 100644
index 0000000..3a6ef8d
--- /dev/null
+++ b/src/errors.rs
@@ -0,0 +1,159 @@
+#![doc = include_str!("../.github/errors.md")]
+// logging-rs errors
+// Version: 1.0.0
+
+// Copyright (c) 2023-present ElBe Development.
+
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the 'Software'),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+////////////////////////////////
+// IMPORTS AND USE STATEMENTS //
+////////////////////////////////
+
+use std::fmt;
+
+
+///////////
+// ERROR //
+///////////
+
+/// Error object.
+///
+/// Use [`Error::new()`] to create error objects instead of using this struct.
+///
+/// # Parameters
+///
+/// - `name`: The errors name.
+/// - `description`: The error description.
+/// - `exit_code`: The errors exit code.
+///
+/// # Returns
+///
+/// A new `Error` object with the specified name and description.
+///
+/// # Examples
+///
+/// ```rust
+/// # use logging_rs;
+/// logging_rs::errors::Error {
+/// name: "name".to_owned(),
+/// description: "description".to_owned(),
+/// exit_code: 1
+/// };
+/// ```
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Error {
+ /// The errors name.
+ pub name: String,
+ /// The error description.
+ pub description: String,
+ /// The errors exit code.
+ pub exit_code: i32,
+}
+
+/// Display implementation for the error object.
+impl fmt::Display for Error {
+ /// Format implementation for the error object.
+ ///
+ /// # Parameters
+ ///
+ /// - `self`: The error object.
+ /// - `f`: The [`fmt::Formatter`] to use.
+ ///
+ /// # Returns
+ ///
+ /// A [`fmt::Result`] containing the formatted error message.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// # use logging_rs;
+ /// # let error = logging_rs::errors::Error::new("name", "description", 1);
+ /// println!("{}", error);
+ /// ```
+ ///
+ /// # See also
+ ///
+ /// - [`fmt::Display`]
+ /// - [`Error`]
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "\x1b[31;1m{}\x1b[0m: {}", self.name, self.description)
+ }
+}
+
+impl Error {
+ /// Creates a new error object.
+ ///
+ /// # Parameters
+ ///
+ /// - `name`: The errors name.
+ /// - `description`: The error description.
+ /// - `exit_code`: The errors exit code.
+ ///
+ /// # Returns
+ ///
+ /// A new `Error` object with the specified name and description.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// # use logging_rs;
+ /// logging_rs::errors::Error::new("name", "description", 1);
+ /// ```
+ ///
+ /// # See also
+ ///
+ /// - [`Error`]
+ pub fn new(name: &str, description: &str, exit_code: i32) -> Error {
+ return Error {
+ name: name.to_owned(),
+ description: description.to_owned(),
+ exit_code: exit_code,
+ };
+ }
+
+ /// Raises the error and exits with the specified exit code.
+ ///
+ /// # Parameters
+ ///
+ /// - `self`: The error object.
+ /// - `details`: The error details.
+ ///
+ /// # Aborts
+ ///
+ /// Exits with the specified exit code.
+ ///
+ /// # Examples
+ ///
+ /// ```should_panic
+ /// # use logging_rs;
+ /// # let error: logging_rs::errors::Error = logging_rs::errors::Error::new("name", "description", 1);
+ /// error.raise("Something went very wrong");
+ /// ```
+ ///
+ /// # See also
+ ///
+ /// - [`Error`]
+ pub fn raise(&self, details: &str) {
+ eprintln!("{}", self);
+ eprintln!("{}", details);
+
+ std::process::exit(self.exit_code);
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..ad6dcd0
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,657 @@
+#![doc = include_str!("../.github/README.md")]
+// Logging-rs.
+// Version: 1.0.0
+
+// Copyright (c) 2023-present I Language Development.
+
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the 'Software'),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+/////////////
+// EXPORTS //
+/////////////
+
+pub mod errors;
+
+
+/////////////
+// IMPORTS //
+/////////////
+
+use std;
+use std::io::Write;
+
+// use chrono;
+
+
+////////////////
+// LOG LEVELS //
+////////////////
+
+/// Log levels
+#[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
+pub enum Level {
+ /// Debug log level. The default value
+ #[default]
+ DEBUG,
+ /// Info log level
+ INFO,
+ /// Warn log level
+ WARN,
+ /// Error log level
+ ERROR,
+ /// Fatal log level
+ FATAL,
+ /// Message log level
+ MESSAGE
+}
+
+
+//////////////////
+// HTTP METHODS //
+//////////////////
+
+// HTTP methods
+/*#[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
+pub enum HTTPMethod {
+ /// GET request. The default value
+ #[default]
+ GET,
+ /// POST request
+ POST,
+ /// PUT request
+ PUT,
+ /// PATCH request
+ PATCH
+}*/
+
+
+/////////////////
+// OUTPUT TYPE //
+/////////////////
+
+/// Output types
+#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
+pub enum Output {
+ /// Stdout. The default value
+ #[default]
+ STDOUT,
+ /// Stderr
+ STDERR,
+ /// File
+ FILE {
+ /// File path
+ path: String
+ },
+ // Web request (not currently implemented)
+ /*REQUEST {
+ method: HTTPMethod,
+ url: String
+ }*/
+}
+
+
+///////////////
+// FORMATTER //
+///////////////
+
+/// Logging formatter object.
+///
+/// Use [`Formatter::new()`] to create formatter objects instead of using this struct.
+///
+/// # Parameters
+///
+/// - `color_format_string`: Format string supporting special ASCII control characters
+/// - `format_string`: Format string *NOT* supporting special ASCII control characters
+/// - `timestamp_format`: Timestamp format string in strftime format
+///
+/// # Returns
+///
+/// A new `Formatter` object with the specified format strings.
+///
+/// # Examples
+///
+/// ```rust
+/// # use logging_rs;
+/// logging_rs::Formatter {
+/// color_format_string: "format string with color support".to_owned(),
+/// format_string: "format string".to_owned(),
+/// timestamp_format: "timestamp format".to_owned()
+/// };
+/// ```
+#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
+pub struct Formatter {
+ /// Format string supporting special ASCII control characters
+ pub color_format_string: String,
+ /// Format string *NOT* supporting special ASCII control characters
+ pub format_string: String,
+ /// Timestamp format string in strftime format
+ pub timestamp_format: String,
+}
+
+impl Default for Formatter {
+ fn default() -> Formatter {
+ return Formatter::new("[{{color.bright_blue}}{{timestamp}}{{end}}] [{{level}}] {{path}}: {{message}}", "[{{timestamp}}] [{{level}}] {{path}}: {{message}}", "%Y-%m-%d %H:%M:%S");
+ }
+}
+
+impl Formatter {
+ /// Creates a new formatter object.
+ ///
+ /// # Parameters
+ ///
+ /// - `color_format_string`: Format string supporting special ASCII control characters
+ /// - `format_string`: Format string *NOT* supporting special ASCII control characters
+ /// - `timestamp_format`: Timestamp format string in strftime format
+ ///
+ /// # Returns
+ ///
+ /// A new `Formatter` object with the specified format strings.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// # use logging_rs;
+ /// logging_rs::Formatter::new(
+ /// "[{{color.bright_blue}}{{timestamp}}{{end}}] [{{level}}] {{path}}: {{message}}",
+ /// "[{{timestamp}}] [{{level}}] {{path}}: {{message}}",
+ /// "%Y-%m-%d %H:%M:%S"
+ /// );
+ /// ```
+ ///
+ /// # See also
+ ///
+ /// - [`Formatter`]
+ pub fn new(color_format_string: &str, format_string: &str, timestamp_format: &str) -> Formatter {
+ Formatter {
+ color_format_string: color_format_string.to_owned(),
+ format_string: format_string.to_owned(),
+ timestamp_format: timestamp_format.to_owned()
+ }
+ }
+
+ /// Formats the given message.
+ ///
+ /// # Parameters
+ ///
+ /// - `self`: The formatter object
+ /// - `output`: The [`Output`] to write to
+ /// - `level`: The log [`Level`] to use for formatting
+ /// - `message`: The message to log
+ /// - `arguments`: A vector of additional formatting arguments
+ ///
+ /// # Returns
+ ///
+ /// A `String` containing the formatted message.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// # use logging_rs;
+ /// # let formatter: logging_rs::Formatter = logging_rs::Formatter::default();
+ /// formatter.format(
+ /// logging_rs::Output::default(),
+ /// logging_rs::Level::default(),
+ /// "Some message with an {{argument}}",
+ /// vec![("argument", "replaced value")]
+ /// );
+ /// ```
+ ///
+ /// # See also
+ ///
+ /// - [`Formatter`]
+ /// - [`Output`]
+ /// - [`Level`]
+ pub fn format<'a>(&self, output: Output, level: Level, message: &'a str, mut arguments: Vec<(&str, &'a str)>) -> String {
+ // TODO (ElBe): Make timestamps work. None of chrono, time or humantime have the things I want
+ let timestamp: &str = "TIMESTAMP";
+
+ // let time = chrono::Utc::now();
+ //let _timestamp = time.format("%Y-%m-%d %H:%M:%S").to_string(); //chrono::format::DelayedFormat>
+ //let parsed = chrono::NaiveDateTime::parse_from_str(&_timestamp.to_string(), "%Y-%m-%d %H:%M:%S").expect("Bad");
+
+ //let borrowed = &_timestamp;
+
+ //println!("{}", _timestamp);
+ //println!("{}", parsed.to_string().as_str());
+
+ // arguments.push(("timestamp", &time.format("%Y-%m-%d %H:%M:%S").to_string()));
+
+ let mut colors: Vec<(&str, &str)> = vec![
+ // Formatting codes
+ ("end", "\x1b[0m"),
+ ("bold", "\x1b[1m"),
+ ("italic", "\x1b[3m"),
+ ("underline", "\x1b[4m"),
+ ("overline", "\x1b[53m"),
+
+ // Foreground colors
+ ("color.black", "\x1b[30m"),
+ ("color.red", "\x1b[31m"),
+ ("color.green", "\x1b[32m"),
+ ("color.yellow", "\x1b[33m"),
+ ("color.blue", "\x1b[34m"),
+ ("color.magenta", "\x1b[35m"),
+ ("color.cyan", "\x1b[36m"),
+ ("color.white", "\x1b[37m"),
+
+ // Bright foreground colors
+ ("color.bright_black", "\x1b[90m"),
+ ("color.bright_red", "\x1b[91m"),
+ ("color.bright_green", "\x1b[92m"),
+ ("color.bright_yellow", "\x1b[93m"),
+ ("color.bright_blue", "\x1b[94m"),
+ ("color.bright_magenta", "\x1b[95m"),
+ ("color.bright_cyan", "\x1b[96m"),
+ ("color.bright_white", "\x1b[97m"),
+
+ // Background colors
+ ("back.black", "\x1b[40m"),
+ ("back.red", "\x1b[41m"),
+ ("back.green", "\x1b[42m"),
+ ("back.yellow", "\x1b[43m"),
+ ("back.blue", "\x1b[44m"),
+ ("back.magenta", "\x1b[45m"),
+ ("back.cyan", "\x1b[46m"),
+ ("back.white", "\x1b[47m"),
+
+ // Bright background colors
+ ("back.bright_black", "\x1b[100m"),
+ ("back.bright_red", "\x1b[101m"),
+ ("back.bright_green", "\x1b[102m"),
+ ("back.bright_yellow", "\x1b[103m"),
+ ("back.bright_blue", "\x1b[104m"),
+ ("back.bright_magenta", "\x1b[105m"),
+ ("back.bright_cyan", "\x1b[106m"),
+ ("back.bright_white", "\x1b[107m"),
+ ];
+
+ let level_string: (&str, &str) = ("level", match level {
+ Level::DEBUG => "DEBUG",
+ Level::INFO => "INFO",
+ Level::WARN => "WARNING",
+ Level::ERROR => "ERROR",
+ Level::FATAL => "FATAL",
+ Level::MESSAGE => "MESSAGE"
+ });
+ let colored_level_string: (&str, &str) = ("level", match level {
+ Level::DEBUG => "DEBUG",
+ Level::INFO => "{{color.blue}}INFO{{end}}",
+ Level::WARN => "{{color.yellow}}WARNING{{end}}",
+ Level::ERROR => "{{color.red}}ERROR{{end}}",
+ Level::FATAL => "{{color.red}}FATAL{{end}}",
+ Level::MESSAGE => "{{color.blue}}MESSAGE{{end}}"
+ });
+
+ arguments.push(("message", message));
+ arguments.push(("timestamp", timestamp));
+ //arguments.push(("timestamp", chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string().to_owned().as_str()));
+
+ let mut result: String = match output {
+ Output::STDOUT | Output::STDERR => {
+ arguments.push(colored_level_string);
+ self.color_format_string.to_owned()
+ },
+ _ => {
+ arguments.push(level_string);
+ self.format_string.to_owned()
+ }
+ };
+
+ arguments.append(&mut colors);
+
+ for (key, value) in arguments {
+ result = result.replace(("{{".to_owned() + key + "}}").as_str(), value);
+ }
+
+ return result.clone();
+ }
+}
+
+
+///////////////////
+// LOGGER STRUCT //
+///////////////////
+
+/// Logger object.
+///
+/// Use [`Logger::new()`] to create logger objects instead of using this struct.
+///
+/// # Parameters
+///
+/// - `formatter`: The [`Formatter`] to use for formatting messages
+/// - `writable_list`: A vector of [`Output`]s to write to
+///
+/// # Returns
+///
+/// A new `Logger` object with the specified formatter and writables.
+///
+/// # Examples
+///
+/// ```rust
+/// # use logging_rs;
+/// logging_rs::Logger {
+/// formatter: logging_rs::Formatter::default(),
+/// writable_list: vec![logging_rs::Output::default()]
+/// };
+/// ```
+#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
+pub struct Logger {
+ pub formatter: Formatter,
+ pub writable_list: Vec