101 Commits

Author SHA1 Message Date
e70b30112b refactor: do not use format!() 2024-01-25 12:20:26 +01:00
10f737cb41 fix: do not use as mut variable 2024-01-24 13:07:35 +01:00
a6657842c4 feat: show current file name in progress bar 2024-01-24 13:05:07 +01:00
adcce76f5c docs: ad some information about sorting and OBS files 2024-01-24 12:34:25 +01:00
f904f62d45 do not propagate version when using sub commands 2024-01-17 09:36:13 +01:00
47be1c7caf build: update dependencies 2024-01-17 09:28:41 +01:00
bf5a3a697f refactor: Cleanup command handling 2024-01-17 09:25:38 +01:00
0ec9c85e3a docs: add applications help output 2024-01-16 09:44:28 +01:00
221b2c6a2f feat: add shortcut methods to read OSC and Profile files 2023-12-31 18:34:42 +01:00
dc62454a74 feat: read YAML files containing profile using new file io 2023-12-31 17:09:57 +01:00
ee600436f9 refactor: reorganize features in file handling and errors 2023-12-31 16:57:32 +01:00
f858ecb9d4 fix: add missing feature flag 2023-12-31 16:44:00 +01:00
8c44e623f6 refactor: extract cli command handling 2023-12-30 07:54:49 +01:00
42d1151a6c feat: add note for OSB files within tree subcommand 2023-12-30 07:40:36 +01:00
2d2fb0dc61 Merge pull request #25 from CCC-MF/issue_24
feat #24: List content of OSB files
2023-12-30 07:35:52 +01:00
036dc80ad0 feat #24: List content of OSB files 2023-12-30 07:30:44 +01:00
fccfe4a880 chore: update dependencies 2023-12-28 13:50:06 +01:00
9ab00ce192 feat: remove error messages from list subcommand 2023-12-06 12:01:18 +01:00
70562f99ca style: code cleanup in file extension check 2023-12-06 12:00:28 +01:00
dcfc50878e feat: check file extension before reading OSC files 2023-12-06 11:44:49 +01:00
2358f6410f build: update dependencies 2023-12-06 11:25:36 +01:00
8f6bbc1692 feat: add script code to Klinik/Anamnese-MTB in ukw profile 2023-12-06 11:24:00 +01:00
0a3ca817c6 Code cleanup: Remove needless borrowed reference 2023-11-23 10:26:46 +01:00
172a2cc692 Code cleanup: Entry implements Display trait - remove 'to_string()' 2023-11-23 10:16:12 +01:00
7021f94891 Code cleanup: Use 'matches!()' macro 2023-11-23 10:13:55 +01:00
6589349b8d Implement Display trait for Requirement 2023-11-23 10:10:07 +01:00
2808cc3556 Code cleanup 2023-11-23 10:07:26 +01:00
f3bbef5d22 Bump version 2023-11-10 17:12:56 +01:00
ef8f6ab1b5 Change description and arguments depending on selected features 2023-11-10 16:41:54 +01:00
a1e5133516 Merge pull request #23 from CCC-MF/issue_22
Check form references to previously undefined forms in OSC files
2023-11-10 12:38:39 +01:00
13bcd74f6c Issue #22: Check form references to previously undefined forms in OSC files 2023-11-10 12:36:53 +01:00
6fdbe06106 Add information about the reason 2023-11-10 12:36:04 +01:00
31c162e977 Update profile file for UKM 2023-11-09 13:15:18 +01:00
2413e4b9b8 Improve check output 2023-11-09 13:00:34 +01:00
c9ecb8e944 Add progress bar to OSB file check 2023-11-09 12:11:13 +01:00
774b57d78e Fix required argument conflicts in check sub command 2023-11-09 11:21:24 +01:00
a52eb9742e Merge pull request #21 from CCC-MF/issue_14
Issue #14: Embed existing DNPM profile files
2023-11-09 10:58:28 +01:00
4c76504000 Issue #14: Embed existing DNPM profile files 2023-11-09 10:53:38 +01:00
4676a63c69 Fix warnings for disabled features 2023-11-09 10:42:54 +01:00
e250e330a5 Merge pull request #20 from CCC-MF/issue_19
Check OSC- und OSB-Files for known issues
2023-11-09 09:27:16 +01:00
349b571e09 Issue #19: Do not allow check listing in combination with file check 2023-11-08 16:49:23 +01:00
90423b5b4e Issue #19: Check OSC files within OSB file 2023-11-08 14:38:16 +01:00
d45ded939e Issue #19: Extract method to check content of OSC file 2023-11-08 14:38:02 +01:00
7ab5523f3c Add more checks shown before in list subcommand 2023-11-08 12:35:58 +01:00
f02ea9b065 Show issue counts ahead of issue list 2023-11-08 12:10:42 +01:00
a08abd7688 Make version entries and categories optional in property catalogue 2023-11-08 11:56:07 +01:00
bf7b8cd9ba Add 'PunkteKategorie' to model 2023-11-07 12:00:21 +01:00
821b30e452 Add information about 'check' sub command 2023-11-07 08:13:45 +01:00
6a0a356120 Update issue code and description 2023-11-07 08:10:50 +01:00
352f5e23fc Merge pull request #18 from CCC-MF/issue_15
Checks and fixes for known issues in OSC files
2023-11-06 14:02:32 +01:00
7b13251d34 Issue #15: Add flag '--fix' to modify sub command 2023-11-06 14:00:20 +01:00
6da1c48c28 Issue #15: Show list of available checks 2023-11-06 13:59:08 +01:00
e2d5eedd02 Issue #15: Check OSC files for known issues 2023-11-06 13:31:34 +01:00
a55db66e57 Issue #15: Implement check subcommand cli 2023-11-06 11:52:57 +01:00
dce2a5cdda Merge pull request #17 from CCC-MF/issue_16
Issue #16: Add missing model entries and add optional declaration
2023-11-06 11:46:34 +01:00
93981f7709 Issue #16: Add missing model entries and add optional declaration 2023-11-06 11:44:41 +01:00
b6694b9e53 Add package metadata to generate rpm packages 2023-11-04 11:45:47 +01:00
0e690cbb85 Update quick-qml dependency 2023-11-04 11:45:15 +01:00
0d6525c398 Update dependencies 2023-11-03 21:19:30 +01:00
df643b5e60 Recompile if c header file changed 2023-11-03 20:59:31 +01:00
1746026af8 Declare use within feature flag related part 2023-11-03 20:58:51 +01:00
7cdfe0068f Add information about optional params for unzip-osb command 2023-11-02 21:02:32 +01:00
1f5ec80cc6 Add optional destination dir for OSB file extraction 2023-11-02 20:56:57 +01:00
f851e9c424 Add optional OSB alternative password parameter 2023-11-01 13:29:30 +01:00
7ef1638b58 Update dependencies 2023-10-31 15:09:03 +01:00
4745a75f2e Initial WXS file to build MSI installer package 2023-10-24 12:29:28 +02:00
f089ad0b32 Update Cargo.lock file 2023-10-24 12:28:53 +02:00
3948cdd697 Pump version to 0.6.0 2023-10-23 12:49:48 +02:00
5a04571a16 Merge pull request #13 from CCC-MF/issue_12
Issue 12: Unzip OSB files
2023-10-23 12:47:21 +02:00
f5dc366488 Issue #12: Compile with feature unzip-osb in make file 2023-10-23 12:44:40 +02:00
8c11c6d891 Issue #12: Added information about subcommand unzip-os 2023-10-23 12:12:38 +02:00
8a23d5b71b Issue #12: Add command to unzip osb files 2023-10-23 12:06:16 +02:00
5d293555f8 Update dependencies 2023-10-09 22:10:52 +02:00
f1c8a002fa Add information for interactive modification 2023-10-06 12:54:46 +02:00
86894632b6 Merge pull request #11 from CCC-MF/issue_10
Issue #10: Add interactive mode
2023-10-04 11:06:39 +02:00
afb5a2e17a Issue #10: Add interactive mode 2023-10-04 10:14:45 +02:00
f456e278cb Update dependencies and bump version 2023-10-02 12:15:52 +02:00
f28ab1afcd Do not sort by form name in first place for now 2023-10-02 12:09:54 +02:00
ae6b8d48e5 Merge pull request #9 from CCC-MF/issue_8
Issue #8: Add sorting by depency for required form references and subforms
2023-09-30 16:27:13 +02:00
1302963b56 Issue #8: Use system library content as equal content
Since system library content is already present in OS database, it is no dependencies to be imported.
2023-09-30 16:09:36 +02:00
6be11a8951 Issue #8: Use field ReferencedDataForm instead of Name 2023-09-30 15:37:09 +02:00
416204b30a Issue #8: Add initial sorting based on form dependencies 2023-09-30 15:06:24 +02:00
86e988c50e Add information about filter option 2023-09-30 13:48:06 +02:00
76de86b685 Add information about dependency and requirement symbols 2023-09-30 13:34:41 +02:00
d0de76770f Sort dependencies and requirements 2023-09-30 13:26:25 +02:00
8cae6ce706 Cleanup Requirement::to_string() 2023-09-30 13:04:47 +02:00
81a0c6204b Move common implementation into Requires trait 2023-09-30 12:11:14 +02:00
21e31f35d1 Change requirement string mapping to match Unterformular implementation 2023-09-30 12:04:45 +02:00
108aae329c Change symbol for subform 2023-09-30 11:58:21 +02:00
1f51d3e994 Show subform entries in tree subcommand 2023-09-28 17:19:14 +02:00
c514005182 Update YAML file for UKM 2023-09-28 02:25:26 +02:00
a127133ba2 Add YAML file for master profile 2023-09-28 00:00:43 +02:00
e2d4d71063 Bump version 2023-09-19 17:38:57 +02:00
629cc8aba9 Merge pull request #7 from CCC-MF/issue_5
Add all required and optional assets into deb package
2023-09-19 17:34:57 +02:00
298a142586 Merge pull request #6 from CCC-MF/issue_4
Skip serializing None option
2023-09-19 17:34:19 +02:00
9b9a0b4622 Add all required and optional assets into deb package 2023-09-19 17:32:49 +02:00
9ffc0783ff Skip serializing None option 2023-09-19 17:31:28 +02:00
771d99fa27 Add extended description for deb packages 2023-09-19 16:54:48 +02:00
eadf9326d0 Update dependencies and mark as v0.4.0 2023-09-19 16:54:48 +02:00
bf63d93efa Add optional field 'Kontaktliste' to forms 2023-09-19 16:54:48 +02:00
ce52f92a7f Fix build error if dir "completion" does not exist 2023-09-11 18:32:31 +02:00
30 changed files with 2626 additions and 474 deletions

598
Cargo.lock generated
View File

@@ -18,16 +18,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]] [[package]]
name = "anstyle" name = "aes"
version = "1.0.2" version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea" checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
]
[[package]]
name = "anstyle"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
[[package]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.73" version = "0.1.77"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -49,6 +60,24 @@ dependencies = [
"rustc-demangle", "rustc-demangle",
] ]
[[package]]
name = "base64ct"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
[[package]] [[package]]
name = "block-buffer" name = "block-buffer"
version = "0.10.4" version = "0.10.4"
@@ -59,10 +88,37 @@ dependencies = [
] ]
[[package]] [[package]]
name = "bytes" name = "byteorder"
version = "1.4.0" version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
[[package]]
name = "bzip2"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8"
dependencies = [
"bzip2-sys",
"libc",
]
[[package]]
name = "bzip2-sys"
version = "0.1.11+1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
dependencies = [
"cc",
"libc",
"pkg-config",
]
[[package]] [[package]]
name = "cc" name = "cc"
@@ -70,6 +126,7 @@ version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
dependencies = [ dependencies = [
"jobserver",
"libc", "libc",
] ]
@@ -80,10 +137,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "clap" name = "cipher"
version = "4.4.2" version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a13b88d2c62ff462f88e4a121f17a82c1af05693a2f192b5c38d14de73c19f6" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
]
[[package]]
name = "clap"
version = "4.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@@ -91,9 +158,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.4.2" version = "4.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08" checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"clap_lex", "clap_lex",
@@ -101,18 +168,18 @@ dependencies = [
[[package]] [[package]]
name = "clap_complete" name = "clap_complete"
version = "4.4.0" version = "4.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "586a385f7ef2f8b4d86bddaa0c094794e7ccbfe5ffef1f434fe928143fc783a5" checksum = "dfb0d4825b75ff281318c393e8e1b80c4da9fb75a6b1d98547d389d6fe1f48d2"
dependencies = [ dependencies = [
"clap", "clap",
] ]
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.4.2" version = "4.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
@@ -122,15 +189,15 @@ dependencies = [
[[package]] [[package]]
name = "clap_lex" name = "clap_lex"
version = "0.5.1" version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
[[package]] [[package]]
name = "console" name = "console"
version = "0.15.7" version = "0.15.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb"
dependencies = [ dependencies = [
"encode_unicode", "encode_unicode",
"lazy_static", "lazy_static",
@@ -140,14 +207,35 @@ dependencies = [
] ]
[[package]] [[package]]
name = "cpufeatures" name = "constant_time_eq"
version = "0.2.9" version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]]
name = "cpufeatures"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
dependencies = [ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "crc32fast"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
version = "0.1.6" version = "0.1.6"
@@ -158,6 +246,35 @@ dependencies = [
"typenum", "typenum",
] ]
[[package]]
name = "deob"
version = "0.1.0"
dependencies = [
"cc",
]
[[package]]
name = "deranged"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
dependencies = [
"powerfmt",
]
[[package]]
name = "dialoguer"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de"
dependencies = [
"console",
"shell-words",
"tempfile",
"thiserror",
"zeroize",
]
[[package]] [[package]]
name = "digest" name = "digest"
version = "0.10.7" version = "0.10.7"
@@ -166,6 +283,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [ dependencies = [
"block-buffer", "block-buffer",
"crypto-common", "crypto-common",
"subtle",
] ]
[[package]] [[package]]
@@ -180,6 +298,32 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "fastrand"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
[[package]]
name = "flate2"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]] [[package]]
name = "generic-array" name = "generic-array"
version = "0.14.7" version = "0.14.7"
@@ -192,15 +336,15 @@ dependencies = [
[[package]] [[package]]
name = "gimli" name = "gimli"
version = "0.28.0" version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.14.0" version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
[[package]] [[package]]
name = "heck" name = "heck"
@@ -215,20 +359,69 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]] [[package]]
name = "indexmap" name = "hmac"
version = "2.0.0" version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
"digest",
]
[[package]]
name = "indexmap"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown", "hashbrown",
] ]
[[package]] [[package]]
name = "itoa" name = "indicatif"
version = "1.0.9" version = "0.17.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25"
dependencies = [
"console",
"instant",
"number_prefix",
"portable-atomic",
"unicode-width",
]
[[package]]
name = "inout"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
dependencies = [
"generic-array",
]
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]]
name = "itoa"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]]
name = "jobserver"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
@@ -238,15 +431,21 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.147" version = "0.2.152"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
[[package]]
name = "linux-raw-sys"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.6.3" version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
@@ -258,25 +457,59 @@ dependencies = [
] ]
[[package]] [[package]]
name = "object" name = "number_prefix"
version = "0.32.1" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
[[package]]
name = "object"
version = "0.32.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
[[package]] [[package]]
name = "osc-variant" name = "osc-variant"
version = "0.4.0" version = "0.7.0"
dependencies = [ dependencies = [
"bytes",
"clap", "clap",
"clap_complete", "clap_complete",
"console", "console",
"deob",
"dialoguer",
"indicatif",
"quick-xml", "quick-xml",
"serde", "serde",
"serde_yaml", "serde_yaml",
"sha256", "sha256",
"zip",
]
[[package]]
name = "password-hash"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700"
dependencies = [
"base64ct",
"rand_core",
"subtle",
]
[[package]]
name = "pbkdf2"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917"
dependencies = [
"digest",
"hmac",
"password-hash",
"sha2",
] ]
[[package]] [[package]]
@@ -286,19 +519,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
[[package]] [[package]]
name = "proc-macro2" name = "pkg-config"
version = "1.0.66" version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a"
[[package]]
name = "portable-atomic"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0"
[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "proc-macro2"
version = "1.0.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]] [[package]]
name = "quick-xml" name = "quick-xml"
version = "0.30.0" version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
dependencies = [ dependencies = [
"memchr", "memchr",
"serde", "serde",
@@ -306,13 +557,28 @@ dependencies = [
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.33" version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
[[package]]
name = "redox_syscall"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
dependencies = [
"bitflags 1.3.2",
]
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.23" version = "0.1.23"
@@ -320,25 +586,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]] [[package]]
name = "ryu" name = "rustix"
version = "1.0.15" version = "0.38.30"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca"
dependencies = [
"bitflags 2.4.2",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "ryu"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.188" version = "1.0.195"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.188" version = "1.0.195"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -347,9 +626,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_yaml" name = "serde_yaml"
version = "0.9.25" version = "0.9.30"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574" checksum = "b1bf28c79a99f70ee1f1d83d10c875d2e70618417fda01ad1785e027579d9d38"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"itoa", "itoa",
@@ -359,10 +638,21 @@ dependencies = [
] ]
[[package]] [[package]]
name = "sha2" name = "sha1"
version = "0.10.7" version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "sha2"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cpufeatures", "cpufeatures",
@@ -371,9 +661,9 @@ dependencies = [
[[package]] [[package]]
name = "sha256" name = "sha256"
version = "1.4.0" version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7895c8ae88588ccead14ff438b939b0c569cd619116f14b4d13fdff7b8333386" checksum = "18278f6a914fa3070aa316493f7d2ddfb9ac86ebc06fa3b83bffda487e9065b0"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"bytes", "bytes",
@@ -383,10 +673,22 @@ dependencies = [
] ]
[[package]] [[package]]
name = "syn" name = "shell-words"
version = "2.0.31" version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398" checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
[[package]]
name = "subtle"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
[[package]]
name = "syn"
version = "2.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -394,10 +696,61 @@ dependencies = [
] ]
[[package]] [[package]]
name = "tokio" name = "tempfile"
version = "1.32.0" version = "3.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa"
dependencies = [
"cfg-if",
"fastrand",
"redox_syscall",
"rustix",
"windows-sys",
]
[[package]]
name = "thiserror"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "time"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e"
dependencies = [
"deranged",
"powerfmt",
"serde",
"time-core",
]
[[package]]
name = "time-core"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "tokio"
version = "1.35.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes",
@@ -406,27 +759,27 @@ dependencies = [
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.16.0" version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.11" version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]] [[package]]
name = "unicode-width" name = "unicode-width"
version = "0.1.10" version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
[[package]] [[package]]
name = "unsafe-libyaml" name = "unsafe-libyaml"
version = "0.2.9" version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b"
[[package]] [[package]]
name = "version_check" name = "version_check"
@@ -436,18 +789,18 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.45.0" version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [ dependencies = [
"windows-targets", "windows-targets",
] ]
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.42.2" version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
dependencies = [ dependencies = [
"windows_aarch64_gnullvm", "windows_aarch64_gnullvm",
"windows_aarch64_msvc", "windows_aarch64_msvc",
@@ -460,42 +813,97 @@ dependencies = [
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.42.2" version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.42.2" version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.42.2" version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.42.2" version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.42.2" version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.42.2" version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.42.2" version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]]
name = "zeroize"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
[[package]]
name = "zip"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
dependencies = [
"aes",
"byteorder",
"bzip2",
"constant_time_eq",
"crc32fast",
"crossbeam-utils",
"flate2",
"hmac",
"pbkdf2",
"sha1",
"time",
"zstd",
]
[[package]]
name = "zstd"
version = "0.11.2+zstd.1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4"
dependencies = [
"zstd-safe",
]
[[package]]
name = "zstd-safe"
version = "5.0.2+zstd.1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db"
dependencies = [
"libc",
"zstd-sys",
]
[[package]]
name = "zstd-sys"
version = "2.0.9+zstd.1.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656"
dependencies = [
"cc",
"pkg-config",
]

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "osc-variant" name = "osc-variant"
version = "0.4.0" version = "0.7.0"
edition = "2021" edition = "2021"
authors = ["Paul-Christian Volkmer <volkmer_p@ukw.de>"] authors = ["Paul-Christian Volkmer <volkmer_p@ukw.de>"]
description = "Anwendung zum Anpassen einer OSC-Datei an einen Standort" description = "Anwendung zum Anpassen einer OSC-Datei an einen Standort"
@@ -9,13 +9,26 @@ readme = "README.md"
build = "build.rs" build = "build.rs"
[workspace]
members = ["libs/deob"]
[dependencies] [dependencies]
clap = { version = "4.4", features = ["std", "help", "usage", "derive", "error-context"], default-features = false } clap = { version = "4.4", features = ["std", "help", "usage", "derive", "error-context"], default-features = false }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_yaml = "0.9" serde_yaml = "0.9"
quick-xml = { version = "0.30", features = ["escape-html", "serialize"], default-features = false } quick-xml = { version = "0.31", features = ["escape-html", "serialize"], default-features = false }
console = "0.15" console = "0.15"
sha256 = "1.4" sha256 = "1.4"
dialoguer = "0.11"
indicatif = "0.17"
deob = { path = "./libs/deob", optional = true }
zip = { version = "0.6", optional = true }
bytes = "1.5.0"
[features]
# Requires env var OSB_KEY to be present at build time
unzip-osb = ["dep:deob", "dep:zip"]
[build-dependencies] [build-dependencies]
clap = { version = "4.4", features = ["std", "help", "usage", "derive", "error-context"], default-features = false } clap = { version = "4.4", features = ["std", "help", "usage", "derive", "error-context"], default-features = false }
@@ -30,6 +43,14 @@ panic = "abort"
[package.metadata.deb] [package.metadata.deb]
copyright = "Copyright (c) 2023 Comprehensive Cancer Center Mainfranken" copyright = "Copyright (c) 2023 Comprehensive Cancer Center Mainfranken"
extended-description = "Anwendung zum Anpassen einer OSC-Datei an einen Standort."
assets = [ assets = [
["target/release/osc-variant", "usr/bin/", "755"],
["completion/osc-variant.bash", "etc/bash_completion.d/", "644"] ["completion/osc-variant.bash", "etc/bash_completion.d/", "644"]
] ]
[package.metadata.generate-rpm]
assets = [
{ source = "target/release/osc-variant", dest = "/usr/bin/", mode = "755" },
{ source = "completion/osc-variant.bash", dest = "/etc/bash_completion.d/", mode = "644" }
]

View File

@@ -32,11 +32,11 @@ binary-all: win-binary-x86_64 linux-binary-x86_64
.PHONY: win-binary-x86_64 .PHONY: win-binary-x86_64
win-binary-x86_64: win-binary-x86_64:
cargo build --release --target=x86_64-pc-windows-gnu cargo build --release --target=x86_64-pc-windows-gnu --features unzip-osb
.PHONY: linux-binary-x86_64 .PHONY: linux-binary-x86_64
linux-binary-x86_64: linux-binary-x86_64:
cargo build --release --target=x86_64-unknown-linux-gnu cargo build --release --target=x86_64-unknown-linux-gnu --features unzip-osb
.PHONY: install .PHONY: install
install: install:

View File

@@ -16,6 +16,29 @@ unvollständigen Ausgabedateien zu erzeugen.
### Beispiele ### Beispiele
Die folgenden Unterbefehle sind verfügbar
```
Anwendung zum Anpassen einer OSC-Datei an einen Standort
Usage: osc-variant <COMMAND>
Commands:
sha256sum Berechne SHA256 Prüfsumme für die angegebene Datei
list Zeigt alle enthaltenen Kataloge und Formulare mit Revision an.
tree Zeigt Kataloge und Formulare mit Revision und Abhängigkeiten an.
modify Modifiziert die angegebene Datei anhand der Profildatei
diff Vergleiche zwei Dateien anhand der Revision der enthaltenen Inhalte
check Prüfe eine OSC-Datei auf bekannte Problemen
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
-V, --version Print version
```
#### Unterbefehl `sha256sum`
Das Berechnen der SHA256 Prüfsumme ist mit dem Unterbefehl `sha256sum` auch unter Windows einfach möglich Das Berechnen der SHA256 Prüfsumme ist mit dem Unterbefehl `sha256sum` auch unter Windows einfach möglich
und erzeugt eine Ausgabe analog dem Befehl auf Linux-Systemen: und erzeugt eine Ausgabe analog dem Befehl auf Linux-Systemen:
@@ -23,21 +46,42 @@ und erzeugt eine Ausgabe analog dem Befehl auf Linux-Systemen:
osc-variant sha256sum meine-beispieldatei.osc osc-variant sha256sum meine-beispieldatei.osc
``` ```
#### Unterbefehl `list`
Zum Auflisten der Inhalte einer Datei wird folgender Befehl verwendet: Zum Auflisten der Inhalte einer Datei wird folgender Befehl verwendet:
``` ```
osc-variant list meine-beispieldatei.osc osc-variant list meine-beispieldatei.osc
``` ```
Zum Auflisten der Inhalte mit allen Abhängigkeiten, z.B. Daten- und Merkmalkataloge und bei Formularen wird der Befehl Mit der Option `--filter` kann die Ausgabe eingeschränkt werden.
*Bei Verwendung der OSB-Funktionalität kann die Eingabe eines Passworts erforderlich sein.*
#### Unterbefehl `tree`
Zum Auflisten der Inhalte mit allen Abhängigkeiten, z.B. Daten- und Merkmalskataloge und bei Formularen wird der Befehl
`tree` verwendet: `tree` verwendet:
``` ```
osc-variant tree meine-beispieldatei.osc osc-variant tree meine-beispieldatei.osc
``` ```
Abhängigkeiten werden je nach Art gekennzeichnet:
* `+`: Datenkatalog
* `-`: Merkmalskatalog
* `>`: Formularverweis
* `*`: Unterformular
Für Formularverweise und Unterformulare werden dabei die verwendeten Datenkataloge nicht erneut ausgegeben.
Achtung! Dies erzeugt eine sehr umfangreiche Ausgabe. Achtung! Dies erzeugt eine sehr umfangreiche Ausgabe.
Mit der Option `--filter` kann auch hier die Ausgabe eingeschränkt werden.
#### Unterbefehl `diff`
Zum Vergleich zweier OSC-Dateien wird der Unterbefehl `diff` verwendet. Zum Vergleich zweier OSC-Dateien wird der Unterbefehl `diff` verwendet.
Der optionale Parameter `--strict` vergleicht auch den Inhalt der OSC-Datei. Der optionale Parameter `--strict` vergleicht auch den Inhalt der OSC-Datei.
Ohne diesen wird nur das Vorhandensein von Inhalten und die Revision verglichen. Ohne diesen wird nur das Vorhandensein von Inhalten und die Revision verglichen.
@@ -52,6 +96,8 @@ bzw.
osc-variant diff meine-beispieldatei.osc andere-beispieldatei.osc --strict osc-variant diff meine-beispieldatei.osc andere-beispieldatei.osc --strict
``` ```
#### Unterbefehl `modify`
Zum Anpassen des Inhalts einer Datei: Zum Anpassen des Inhalts einer Datei:
``` ```
@@ -59,21 +105,53 @@ osc-variant modify meine-beispieldatei.osc --profile ukw-profil.yml --output ukw
``` ```
Die Parameter `--profile` und `--output` sind optional. Die Parameter `--profile` und `--output` sind optional.
Mit dem ebenfalls optionalen Parameter `--interactve` oder `-i` können die Parameter zur kompakten Ausgabe, zum Sortieren
und dem Entfernen von Inhalten der Systembibliothek interaktiv gesetzt werden.
Ohne Profildatei wird die Datei lediglich eingelesen, Leerzeichen am Ende eines XML-Tags entfernt und wieder ausgegeben. Ohne Profildatei wird die Datei lediglich eingelesen, Leerzeichen am Ende eines XML-Tags entfernt und wieder ausgegeben.
Ohne eine Angabe der Ausgabedatei wird auf die Standardausgabe ausgegeben. Ohne eine Angabe der Ausgabedatei wird auf die Standardausgabe ausgegeben.
##### Enthaltene Profile
Die im Ordner [`examples/`](/examples) enthaltenen Profile für Standorte sind in der ausführbaren Anwendung enthalten
und die Dateien müssen nicht explizit als Datei vorliegen:
* `--profile examples/dnpm-ukm.yml` => `--profile UKM`
* `--profile examples/dnpm-ukw.yml` => `--profile UKW`
* `--profile examples/dnpm-umg.yml` => `--profile UMG`
#### Unterbefehl `unzip-osb`
Ab Version 0.6.0 ist die Anwendung zudem in der Lage, die für eine Aktualisierung der OS-Bibliothek genutzten OSB-Dateien zu entpacken:
```
osc-variant unzip-osb OSBIB-6.10.osb
```
Dieser Befehl kennt die beiden optionalen Parameter
* `-d`: Optionale Angabe des Zielverzeichnisses. Wenn keine Angabe vorhanden ist, wird das aktuelle Verzeichnis verwendet.
* `-p`/`--password`: Optionale Angabe des Passworts zum Entpacken der OSB-Datei.
#### Unterbefehl `check`
Der Unterbefehl `check` prüft eine OSC-Datei auf bekannte Probleme und gibt eine Liste mit erkannten Problemen aus.
Eine Liste mit bekannten Problemen wird mit `check --list` ausgegeben.
*Bei Verwendung der OSB-Funktionalität kann die Eingabe eines Passworts erforderlich sein.*
#### Kompakte Ausgabe #### Kompakte Ausgabe
OSC-Dateien sind XML-Dateien. Diese Anwendung ermöglicht optional die Ausgabe als kompaktere XML-Datei ohne Zeilenumbrüche. OSC-Dateien sind XML-Dateien. Diese Anwendung ermöglicht optional die Ausgabe als kompaktere XML-Datei ohne Zeilenumbrüche.
Hierzu ist die Option `--compact` vorgesehen. Es können, je nach Datei, bis zu 30% eingespart werden. Hierzu ist die Option `--compact` vorgesehen. Es können, je nach Datei, bis zu 30 % eingespart werden.
#### Filter #### Filter
Bei der Auflistung von Inhalten ist es möglich, die Ausgaben anhand des Namens zu filtern. Bei der Auflistung von Inhalten ist es möglich, die Anzeige für die Unterbefehle `list` und `tree` anhand des Namens zu filtern.
Hierzu ist die Option `--filter=` vorgesehen. Hierzu ist die Option `--filter=` vorgesehen.
Wird diese angewendet, werden nur Inhalte ausgegeben, deren Name die angegebene Zeichenkette beinhalten. Wird diese angewendet, werden nur Inhalte angezeigt, deren Name die angegebene Zeichenkette beinhalten.
#### Sortierung #### Sortierung
@@ -81,9 +159,15 @@ Bei der Auflistung der Inhalte, kann die Option `--sorted` dazu verwendet werden
Die Sortierung erfolgt dabei nach Namen des Katalogs oder des Formulars. Die Sortierung erfolgt dabei nach Namen des Katalogs oder des Formulars.
Beim Modifizieren der Inhalte kann ebenfalls die Option `--sorted` dazu verwendet werden, die Einträge im Anschluss an die Modifikation Beim Modifizieren der Inhalte kann ebenfalls die Option `--sorted` dazu verwendet werden, die Einträge im Anschluss an die Modifikation
nach Namen zu sortieren. nach Namen und für Formulare der Abhängigkeit von Formularverweisen und Unterformularen zu sortieren.
Formulare, die von anderen Formularen in einem Formularverweis oder als Unterformular verwendet werden, werden dabei weiter oben angeordnet,
da Onkostar einen Formularimport sequenziell, ohne Berücksichtigung von Abhängigkeiten, durchführt.
Dies erlaubt eine konsistente Reihenfolge der Einträge, wodurch ein direkter Vergleich mit Vorversionen ermöglicht wird. Dies erlaubt eine konsistente Reihenfolge der Einträge, wodurch ein direkter Vergleich mit Vorversionen ermöglicht wird.
*Die Einteilung in Formualre und Unterformualare wird hierdurch nicht angepasst.*
##### Entfernen von Inhalten der Systembibliothek bei Modifikation ##### Entfernen von Inhalten der Systembibliothek bei Modifikation
Mit der die experimentelle Option `--strip` ist es möglich, die in der OSC-Datei enthaltenen und beim Import nicht genutzten Inhalte aus der Systembibliothek zu entfernen. Mit der die experimentelle Option `--strip` ist es möglich, die in der OSC-Datei enthaltenen und beim Import nicht genutzten Inhalte aus der Systembibliothek zu entfernen.

View File

@@ -35,7 +35,7 @@ fn main() -> Result<(), Error> {
let package_name = std::env::var("CARGO_CRATE_NAME").unwrap_or("osc-variant".to_string()); let package_name = std::env::var("CARGO_CRATE_NAME").unwrap_or("osc-variant".to_string());
fs::remove_dir_all("completion")?; fs::remove_dir_all("completion").unwrap_or_default();
fs::create_dir("completion")?; fs::create_dir("completion")?;
generate_to(Bash, &mut cmd, package_name.as_str(), "completion")?; generate_to(Bash, &mut cmd, package_name.as_str(), "completion")?;

27
examples/dnpm-master.yml Normal file
View File

@@ -0,0 +1,27 @@
forms:
- name: 'DNPM Klinik/Anamnese'
form_references:
- name: MTB
referenced_data_form: 'OS.Tumorkonferenz'
anzeige_auswahl: 'MTB vom {Datum}'
- name: 'DNPM Therapieplan'
form_references:
- name: referstemtb
referenced_data_form: 'OS.Tumorkonferenz'
anzeige_auswahl: 'MTB vom {Datum}'
- name: reftkhumangenber
referenced_data_form: 'OS.Tumorkonferenz'
anzeige_auswahl: 'MTB vom {Datum}'
- name: reftkreevaluation
referenced_data_form: 'OS.Tumorkonferenz'
anzeige_auswahl: 'MTB vom {Datum}'
- name: 'DNPM UF Einzelempfehlung'
form_references:
- name: mtb
referenced_data_form: 'OS.Tumorkonferenz'
anzeige_auswahl: 'MTB vom {Datum}'
- name: 'DNPM UF Rebiopsie'
form_references:
- name: reftumorkonferenz
referenced_data_form: 'OS.Tumorkonferenz'
anzeige_auswahl: 'MTB vom {Datum}'

View File

@@ -4,6 +4,10 @@ forms:
- name: MTB - name: MTB
referenced_data_form: 'MR.MTB_Anmeldung' referenced_data_form: 'MR.MTB_Anmeldung'
anzeige_auswahl: 'MTB Anmeldung vom {Anmeldedatum}' anzeige_auswahl: 'MTB Anmeldung vom {Anmeldedatum}'
scripts_code: |
setFieldValue('AnmeldedatumMTB', getFieldValue('MTB').MTBTermin);
setFieldValue('WHOGrad', getFieldValue('MTB').WHOGrad);
setFieldValue('Leitlinienstatus', getFieldValue('MTB').Leitlinienstatus);
- name: 'DNPM Therapieplan' - name: 'DNPM Therapieplan'
form_references: form_references:
- name: referstemtb - name: referstemtb

View File

@@ -4,6 +4,13 @@ forms:
- name: MTB - name: MTB
referenced_data_form: 'OS.Tumorkonferenz.VarianteUKW' referenced_data_form: 'OS.Tumorkonferenz.VarianteUKW'
anzeige_auswahl: 'MTB vom {Datum}' anzeige_auswahl: 'MTB vom {Datum}'
script_code: |
// Keine Übernahme des Datums des MTBs bei Verwendung
// des Formulars 'OS.Tumorkonferenz'.
// Gewollt ist das Datum, an der die Anmeldung stattgefunden hat.
//setFieldValue('AnmeldedatumMTB', getFieldValue('MTB').Datum);
setFieldValue('WHOGrad', getFieldValue('MTB').WHOGrad);
- name: 'DNPM Therapieplan' - name: 'DNPM Therapieplan'
form_references: form_references:
- name: referstemtb - name: referstemtb

7
libs/deob/Cargo.toml Normal file
View File

@@ -0,0 +1,7 @@
[package]
name = "deob"
version = "0.1.0"
edition = "2021"
[build-dependencies]
cc = "1.0"

32
libs/deob/build.rs Normal file
View File

@@ -0,0 +1,32 @@
/*
* MIT License
*
* Copyright (c) 2023 Comprehensive Cancer Center Mainfranken
*
* 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.
*/
use std::io::Error;
fn main() -> Result<(), Error> {
cc::Build::new().file("src/deob.c").compile("deob");
println!("cargo:rerun-if-changed=src/deob.c");
println!("cargo:rerun-if-changed=src/deob.h");
Ok(())
}

37
libs/deob/src/deob.c Normal file
View File

@@ -0,0 +1,37 @@
/*
* MIT License
*
* Copyright (c) 2023 Comprehensive Cancer Center Mainfranken
*
* 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.
*/
#include "deob.h"
void deob(char * in) {
const long long s[2] = { S0, S1 };
char d[] = "OSTAR.password$OSB";
for (size_t i = 0; i < DL; i++) d[i] = (CS)[i];
size_t l = strlen(in) / 2;
for (size_t i = 0; i < l; i++) {
for (size_t j = 0; j < DL; j++) { DLT(0); DLT(1); }
DLS(i);
}
INZ(l);
}

43
libs/deob/src/deob.h Normal file
View File

@@ -0,0 +1,43 @@
/*
* MIT License
*
* Copyright (c) 2023 Comprehensive Cancer Center Mainfranken
*
* 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.
*/
#ifndef OSC_VARIANT_DEOB_H
#define OSC_VARIANT_DEOB_H
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define S0 8373972096940928081
#define S1 7378413942531504450
#define CS (char*)&s
#define DL sizeof(d) / sizeof(d[0]) - 2
#define I2 i*2
#define INZ(var) in[var] = 0
#define DLS(idx) in[idx] = (d[DL+1]<<4)|d[DL]
#define DLT(idx) d[DL+idx] = (in[I2+idx] == d[j]) ? (char)j : d[DL+idx]
void deob(char * in);
#endif //OSC_VARIANT_DEOB_H

38
libs/deob/src/lib.rs Normal file
View File

@@ -0,0 +1,38 @@
/*
* MIT License
*
* Copyright (c) 2023 Comprehensive Cancer Center Mainfranken
*
* 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.
*/
use std::ffi::{c_char, CStr, CString};
#[link(name = "deob")]
extern "C" {
fn deob(key: *const c_char);
}
pub fn deobfuscate(s: &str) -> String {
let key = CString::new(s).unwrap_or_default().into_raw();
unsafe {
deob(key);
String::from_utf8_lossy(CStr::from_ptr(key).to_bytes()).to_string()
}
}

30
osc-variant.wxs Normal file
View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*"
Language="1033"
Manufacturer="CCC Mainfranken"
Name="OSC-Variant"
UpgradeCode="{83088581-5db3-49a2-8932-27da356818c7}"
Version="0.6.0">
<Package InstallScope="perMachine" Compressed="yes" />
<MediaTemplate EmbedCab="yes" />
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFiles64Folder">
<Directory Id="INSTALLFOLDER" Name="osc-variant">
<Component Id="MainExecutable" Guid="*">
<File Id="OscVariantExe" Name="osc-variant.exe" KeyPath="yes" Source="target/x86_64-pc-windows-gnu/release/osc-variant.exe" />
<File Id="LicenseTxt" Name="LICENSE.txt" Source="LICENSE.txt" />
</Component>
</Directory>
</Directory>
</Directory>
<Feature Id="Complete">
<ComponentRef Id="MainExecutable" />
</Feature>
</Product>
</Wix>

245
src/checks/mod.rs Normal file
View File

@@ -0,0 +1,245 @@
/*
* MIT License
*
* Copyright (c) 2023 Comprehensive Cancer Center Mainfranken
*
* 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.
*/
use std::fmt::{Display, Formatter};
use std::path::Path;
use console::style;
#[cfg(feature = "unzip-osb")]
pub mod osb;
pub mod osc;
#[allow(dead_code)]
pub enum CheckNotice {
/// This will result in Error if importing file and has a support code
ErrorWithCode {
code: String,
description: String,
line: Option<usize>,
example: Option<String>,
},
/// This will result in Error if importing file
Error {
description: String,
line: Option<usize>,
},
/// Other known issues
Warning {
description: String,
line: Option<usize>,
},
/// Other known issues
Info {
description: String,
line: Option<usize>,
},
/// Ok
Ok(String),
}
impl Display for CheckNotice {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
CheckNotice::ErrorWithCode {
code,
description,
line,
example,
} => match line {
Some(line) => write!(
f,
"{: <7} ({}) at Line {}: {}{}",
style("ERROR").red().bold(),
code,
line,
description,
match example {
Some(example) => format!("\n 🔥 '{}'", style(example).dim()),
_ => String::new(),
}
),
None => write!(
f,
"{: <7} ({}): {}{}",
style("ERROR").red().bold(),
code,
description,
match example {
Some(example) => format!("\n 🔥 '{}'", style(example).dim()),
_ => String::new(),
}
),
},
CheckNotice::Error { description, line } => match line {
Some(line) => write!(
f,
"{: <7} at Line {}: {}",
style("ERROR").red().bold(),
line,
description
),
None => write!(f, "{: <7} {}", style("ERROR").red().bold(), description),
},
CheckNotice::Warning { description, line } => match line {
Some(line) => write!(
f,
"{: <7} at Line {}: {}",
style("WARNING").yellow().bold(),
line,
description
),
None => write!(
f,
"{: <7} {}",
style("WARNING").yellow().bold(),
description
),
},
CheckNotice::Info { description, line } => match line {
Some(line) => write!(
f,
"{: <7} at Line {}: {}",
style("INFO").blue().bold(),
line,
description
),
None => write!(f, "{: <7} {}", style("INFO").blue().bold(), description),
},
CheckNotice::Ok(msg) => write!(f, "{: <7} {}", style("OK").green(), msg),
}
}
}
pub trait Checkable {
fn check(&self) -> Vec<CheckNotice>;
}
pub trait Fixable {
fn fix(&mut self) -> bool;
}
#[allow(unused_variables)]
pub fn check_file(file: &Path, password: Option<String>) -> Result<Vec<CheckNotice>, CheckNotice> {
match file.extension() {
Some(ex) => match ex.to_str() {
#[cfg(feature = "unzip-osb")]
Some("osb") => match password {
Some(password) => osb::check_file(file, password.as_str()),
None => {
use deob::deobfuscate;
osb::check_file(file, deobfuscate(env!("OSB_KEY").trim()).as_str())
}
},
Some("osc") => osc::check_file(file),
_ => Err(CheckNotice::Error {
description: "Keine prüfbare Datei".to_string(),
line: None,
}),
},
_ => Err(CheckNotice::Error {
description: "Keine prüfbare Datei".to_string(),
line: None,
}),
}
}
pub fn print_checks() {
println!(
"{}",
style("Die folgenden Probleme sind bekannt\n")
.yellow()
.bold()
);
struct Problem<'a> {
code: &'a str,
name: &'a str,
description: &'a str,
fixable: bool,
}
impl<'a> Display for Problem<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{} {} {}\n\n{}",
style(self.code).bold(),
style(self.name).underlined(),
match self.fixable {
true => style("(Behebbar)").green(),
false => style("(Nicht behebbar)").red(),
},
self.description
)
}
}
vec![
Problem {
code: "2023-0001",
name: "Unterformular mit Markierung 'hat Unterformulare'",
description: " Aktuell gibt es keine Unterformulare in Unterformularen, daher\n \
sollte dies nicht vorkommen.\n\n \
Eine mögliche Ursache ist die Speicherung eines Unterformulars als Formular.",
fixable: false,
},
Problem {
code: "2023-0002",
name: "Formular hat keine Angabe zum Prozedurdatum",
description: " Formulare benötigen die Angabe des Prozedurdatums, anderenfalls\n \
führt dies zu Problemen in Onkostar.\n\n \
Unterformulare können ein Prozedurdatum haben, müssen es aber nicht.\n\n \
Eine mögliche Ursache ist die Speicherung eines Formulars als Unterformular.",
fixable: false,
},
Problem {
code: "2023-0003",
name: "Leerzeichen am Ende der Plausibilitätsregel-Bezeichnung (OSTARSUPP-13334)",
description:
" Treten Leerzeichen am Ende der Plausibilitätsregel-Bezeichnung auf,\n \
führt dies zu Fehlern beim Import der OSC-Datei.\n\n \
Das Problem wird beim Verwenden des Unterbefehls 'modify' automatisch\n \
behoben und Leerzeichen entfernt.
",
fixable: true,
},
Problem {
code: "2023-0004",
name: "Verweis auf noch nicht definiertes Formular (OSTARSUPP-13212)",
description: " Wenn ein Formular einen Verweis auf ein anderes Formular enthält,\n \
das nicht vor diesem Formular in der OSC-Datei definiert ist, wird der\n \
Formularverweis beim Import der OSC-Datei nicht übernommen.\n\n \
Dies kann bei wechselseitiger Abhängigkeit zwischen zwei (Unter-)Formularen\n \
auftreten.\n\n \
In diesem Fall kann ein erneuter/zweiter Import helfen, da das Onkostar in\n \
diesem Fall alle Formulare importiert hat und der Formularverweis dann \n \
gespeichert werden kann.
",
fixable: false,
},
]
.iter()
.for_each(|problem| println!("{}\n", problem))
}

113
src/checks/osb.rs Normal file
View File

@@ -0,0 +1,113 @@
/*
* MIT License
*
* Copyright (c) 2024 Comprehensive Cancer Center Mainfranken
*
* 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.
*/
use std::fs;
use std::io::Read;
use std::path::Path;
use indicatif::{ProgressBar, ProgressStyle};
use crate::checks::{osc, CheckNotice};
#[cfg(feature = "unzip-osb")]
pub fn check_file(file: &Path, password: &str) -> Result<Vec<CheckNotice>, CheckNotice> {
let file = match fs::File::open(file) {
Ok(file) => file,
Err(err) => {
return Err(CheckNotice::Error {
description: format!("Kann Datei nicht lesen: {}", err),
line: None,
});
}
};
let mut archive = match zip::ZipArchive::new(file) {
Ok(file) => file,
Err(err) => {
return Err(CheckNotice::Error {
description: format!("Kann Datei nicht lesen: {}", err),
line: None,
});
}
};
let mut result = vec![];
let progress_bar = ProgressBar::new(archive.len() as u64).with_style(
ProgressStyle::default_bar()
.template("{wide_bar} {msg:32} {pos}/{len}")
.unwrap(),
);
for i in 0..archive.len() {
progress_bar.inc(1);
if let Ok(Ok(mut zip_file)) = archive.by_index_decrypt(i, password.as_bytes()) {
progress_bar.set_message(zip_file.name().to_string());
if zip_file.is_file() && zip_file.name().ends_with(".osc") {
let mut buf = String::new();
let _ = zip_file.read_to_string(&mut buf);
match osc::check(buf) {
Ok(ref mut check_result) => {
result.push(CheckNotice::Info {
description: format!("Prüfe Eintrag '{}'", zip_file.name()),
line: None,
});
if check_result.is_empty() {
result.push(CheckNotice::Ok(format!(
"Keine Probleme in '{}' erkannt",
zip_file.name()
)))
}
result.append(check_result)
}
Err(_) => result.push(CheckNotice::Warning {
description: format!(
"Überspringe Eintrag '{}': Inhalt kann nicht geprüft werden",
zip_file.name(),
),
line: None,
}),
};
continue;
}
if zip_file.is_file() {
result.push(CheckNotice::Warning {
description: format!(
"Überspringe Eintrag '{}': Keine OSC-Datei",
zip_file.name()
),
line: None,
})
}
} else {
return Err(CheckNotice::Error {
description: "Kann Datei nicht lesen".to_string(),
line: None,
});
}
}
progress_bar.finish_and_clear();
Ok(result)
}

78
src/checks/osc.rs Normal file
View File

@@ -0,0 +1,78 @@
/*
* MIT License
*
* Copyright (c) 2023 Comprehensive Cancer Center Mainfranken
*
* 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.
*/
use std::fs;
use std::path::Path;
use std::str::FromStr;
use crate::checks::{CheckNotice, Checkable};
use crate::model::onkostar_editor::OnkostarEditor;
pub fn check_file(file: &Path) -> Result<Vec<CheckNotice>, CheckNotice> {
match fs::read_to_string(file) {
Ok(content) => check(content),
_ => Err(CheckNotice::Error {
description: "Kann Datei nicht lesen".to_string(),
line: None,
}),
}
}
pub fn check(content: String) -> Result<Vec<CheckNotice>, CheckNotice> {
let mut result = content
.lines()
.enumerate()
.flat_map(|(line, content)| check_line(line, content.to_string()))
.collect::<Vec<_>>();
let inner_checks = &mut match OnkostarEditor::from_str(content.as_str()) {
Ok(data) => data.check(),
Err(err) => {
return Err(CheckNotice::Error {
description: format!("Interner Fehler: {}", err),
line: None,
})
}
};
result.append(inner_checks);
Ok(result)
}
fn check_line(line: usize, content: String) -> Vec<CheckNotice> {
let mut result = vec![];
if content.contains(" </Bezeichnung>") {
result.append(&mut vec![CheckNotice::ErrorWithCode {
code: "2023-0003".to_string(),
description:
"Leerzeichen am Ende der Plausibilitätsregel-Bezeichnung (OSTARSUPP-13334)"
.to_string(),
line: Some(line),
example: Some(content.trim().to_string()),
}])
}
result
}

View File

@@ -1,7 +1,7 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2023 Comprehensive Cancer Center Mainfranken * Copyright (c) 2024 Comprehensive Cancer Center Mainfranken
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@@ -30,8 +30,8 @@ fn build_cli() -> Command {
} }
#[derive(Parser)] #[derive(Parser)]
#[command(author, version, about, long_about = None)] #[command(author, version, about)]
#[command(propagate_version = true, arg_required_else_help(true))] #[command(arg_required_else_help(true))]
pub struct Cli { pub struct Cli {
#[command(subcommand)] #[command(subcommand)]
pub cmd: SubCommand, pub cmd: SubCommand,
@@ -78,7 +78,7 @@ pub enum SubCommand {
#[arg( #[arg(
long = "sorted", long = "sorted",
alias = "x-sorted", alias = "x-sorted",
help = "Sortiere Kataloge und Formulare nach Name (Optional)." help = "Sortiere Kataloge und Formulare nach Name und Abhängigkeiten (Optional)."
)] )]
sorted: bool, sorted: bool,
#[arg( #[arg(
@@ -87,6 +87,17 @@ pub enum SubCommand {
help = "Entferne Einträge aus der Systembibliothek die nicht importiert werden (Optional)." help = "Entferne Einträge aus der Systembibliothek die nicht importiert werden (Optional)."
)] )]
strip: bool, strip: bool,
#[arg(
short = 'i',
long = "interactive",
help = "Starte interaktiven Dialog zum Modifizieren von OSC-Dateien"
)]
interactive: bool,
#[arg(
long = "fix",
help = "Erweiterte Problembehandlung und Reparatur der OSC-Datei"
)]
fix: bool,
}, },
#[command(about = "Vergleiche zwei Dateien anhand der Revision der enthaltenen Inhalte")] #[command(about = "Vergleiche zwei Dateien anhand der Revision der enthaltenen Inhalte")]
Diff { Diff {
@@ -95,4 +106,36 @@ pub enum SubCommand {
#[arg(long = "strict", help = "Strikter Vergleich des Inhalts")] #[arg(long = "strict", help = "Strikter Vergleich des Inhalts")]
strict: bool, strict: bool,
}, },
#[command(about = if cfg!(feature = "unzip-osb") { "Prüfe eine OSB- oder OSC-Datei auf bekannte Problemen" } else { "Prüfe eine OSC-Datei auf bekannte Problemen" })]
Check {
#[arg(help = "Die zu prüfende Datei", group = "check-file", required = true)]
file: Option<String>,
#[arg(
short = 'p',
long = "password",
help = "Passwort der OSB-Datei (Optional - für OSB-Dateien)",
requires = "check-file",
hide = !cfg!(feature = "unzip-osb")
)]
password: Option<String>,
#[arg(
long = "list",
help = "Prüfe nicht und zeige Liste mit Checks auf bekannte Problemen",
conflicts_with = "check-file"
)]
list: bool,
},
#[cfg(feature = "unzip-osb")]
#[command(about = "Entpackt eine OSB-Datei")]
UnzipOsb {
file: String,
#[arg(
short = 'p',
long = "password",
help = "Passwort der OSB-Datei (Optional)"
)]
password: Option<String>,
#[arg(short = 'd', help = "Zielverzeichnis (Optional)")]
dir: Option<String>,
},
} }

316
src/commands.rs Normal file
View File

@@ -0,0 +1,316 @@
/*
* MIT License
*
* Copyright (c) 2024 Comprehensive Cancer Center Mainfranken
*
* 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.
*/
use std::error::Error;
use std::fs;
use std::fs::OpenOptions;
use std::io::Write;
use std::ops::Add;
use std::path::{Path, PathBuf};
use console::style;
use dialoguer::Confirm;
use quick_xml::se::Serializer;
use serde::Serialize;
use sha256::digest;
use crate::checks::{check_file, print_checks, CheckNotice};
use crate::cli::SubCommand;
use crate::file_io::{FileError, FileReader, InputFile};
use crate::model::onkostar_editor::OnkostarEditor;
use crate::profile::Profile;
fn write_outputfile(filename: String, content: &String) -> Result<(), FileError> {
OpenOptions::new()
.read(false)
.write(true)
.create(true)
.truncate(true)
.open(filename.clone())
.map_err(|err| FileError::Writing(filename.clone(), err.to_string()))?
.write_all(content.as_bytes())
.map_err(|err| FileError::Writing(filename, err.to_string()))?;
Ok(())
}
pub fn handle(command: SubCommand) -> Result<(), Box<dyn Error>> {
match command {
SubCommand::List {
inputfile,
sorted,
filter,
} => match InputFile::read(inputfile, None)? {
osc @ InputFile::Osc { .. } => {
let mut content: OnkostarEditor = osc.try_into()?;
if sorted {
content.sorted()
}
if let Some(name) = filter {
OnkostarEditor::print_list_filtered(&mut content, name.as_str());
return Ok(());
}
content.print_list();
}
InputFile::Osb { content, .. } => {
for file in content {
match file {
InputFile::Osc { .. } => {
println!(
"{}{}",
style("OSB-Paketinhalt: ").bold().yellow(),
style(file.filename()).bold()
);
let mut content: OnkostarEditor = match file.try_into() {
Ok(oe) => oe,
Err(err) => {
println!("{}", err);
continue;
}
};
if sorted {
content.sorted()
}
if let Some(name) = filter {
OnkostarEditor::print_list_filtered(&mut content, name.as_str());
return Ok(());
}
content.print_list();
println!()
}
_ => {
println!(
"{}{}{}",
style("OSB-Paketinhalt: ").bold().yellow(),
style(file.filename()).bold(),
style(" ignoriert").yellow()
);
}
}
}
}
InputFile::Yaml { filename, .. } | InputFile::Other { filename, .. } => {
return Err(Box::new(FileError::Reading(
filename,
"Nur OSB- und OSC-Dateien werden unterstützt".to_string(),
)))
}
},
SubCommand::Tree {
inputfile,
sorted,
filter,
} => match InputFile::read(inputfile, None)? {
osc @ InputFile::Osc { .. } => {
let mut content: OnkostarEditor = osc.try_into()?;
if sorted {
content.sorted()
}
if let Some(name) = filter {
OnkostarEditor::print_tree_filtered(&mut content, name.as_str());
return Ok(());
}
OnkostarEditor::print_tree(&content);
}
InputFile::Osb { filename, .. } => return Err(Box::new(FileError::Reading(
filename,
"Nur OSC-Dateien werden unterstützt. OSB-Dateien erzeugen eine zu lange Ausgabe."
.to_string(),
))),
InputFile::Yaml { filename, .. } | InputFile::Other { filename, .. } => {
return Err(Box::new(FileError::Reading(
filename,
"Nur OSC-Dateien werden unterstützt".to_string(),
)))
}
},
SubCommand::Modify {
inputfile,
profile,
outputfile,
compact,
sorted,
strip,
interactive,
fix,
} => {
let mut data: OnkostarEditor = InputFile::read(inputfile, None)?.try_into()?;
if let Some(profile) = profile {
let profile = if profile.contains('.') {
FileReader::<Profile>::read(profile)?
} else {
Profile::embedded_profile(profile.as_str())?
};
data.apply_profile(&profile);
}
let mut compact = compact;
let mut sorted = sorted;
let mut strip = strip;
if interactive {
compact = Confirm::new()
.with_prompt("Kompakte Ausgabe, ohne Einrücken?")
.default(compact)
.interact()
.unwrap();
sorted = Confirm::new()
.with_prompt("Sortiere Kataloge und Formulare nach Name und Abhängigkeiten?")
.default(sorted)
.interact()
.unwrap();
strip = Confirm::new()
.with_prompt(
"Entferne Einträge aus der Systembibliothek die nicht importiert werden?",
)
.default(strip)
.interact()
.unwrap();
}
if fix {
// No operation as of now
}
if sorted {
data.sorted();
}
if strip {
data.strip_system_library_content();
}
let mut buf = String::new();
let mut serializer = Serializer::new(&mut buf);
if !compact {
serializer.indent(' ', 2);
}
data.serialize(serializer).expect("Generated XML");
let output = &"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
.to_string()
.add(
buf
// Replace &apos; and &quot; as used in original file
.replace("&apos;", "'")
.replace("&quot;", "\"")
.as_str(),
);
match outputfile {
Some(filename) => write_outputfile(filename, output)?,
None => {
println!("{}", output)
}
}
}
SubCommand::Diff {
inputfile_a,
inputfile_b,
strict,
} => {
println!(
"Vergleiche Datei A ({}) mit Datei B ({})",
style(&inputfile_a).yellow(),
style(&inputfile_b).yellow()
);
let data_a = &mut FileReader::<OnkostarEditor>::read(inputfile_a)?;
let data_b = &mut FileReader::<OnkostarEditor>::read(inputfile_b)?;
data_a.print_diff(data_b, strict);
}
SubCommand::Sha256Sum { inputfile } => match fs::read_to_string(inputfile.clone()) {
Ok(content) => {
println!(
"{} {}",
digest(content).as_str(),
PathBuf::from(inputfile.clone())
.canonicalize()
.unwrap_or_default()
.to_str()
.unwrap_or_default()
)
}
Err(err) => {
eprintln!("{}", FileError::Reading(inputfile, err.to_string()));
}
},
SubCommand::Check {
file,
list,
password,
} => {
if list {
print_checks();
} else {
match check_file(Path::new(file.unwrap_or_default().as_str()), password) {
Ok(notices) => {
println!(
"Es wurden {} Probleme gefunden\n",
notices
.iter()
.filter(|notice| matches!(
notice,
CheckNotice::ErrorWithCode { .. } | CheckNotice::Error { .. }
))
.count()
);
notices
.iter()
.for_each(|check_notice| println!("{}", check_notice));
}
Err(err) => {
println!("{}", err)
}
}
}
}
#[cfg(feature = "unzip-osb")]
SubCommand::UnzipOsb {
file,
password,
dir,
} => {
use crate::unzip_osb::{unzip_osb, unzip_osb_using_password};
match password {
Some(password) => unzip_osb_using_password(
file.as_str(),
dir.unwrap_or_default().as_str(),
password.as_str(),
),
None => unzip_osb(file.as_str(), dir.unwrap_or_default().as_str()),
}
}
}
Ok(())
}

226
src/file_io.rs Normal file
View File

@@ -0,0 +1,226 @@
/*
* MIT License
*
* Copyright (c) 2024 Comprehensive Cancer Center Mainfranken
*
* 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.
*/
use std::error::Error;
use std::fmt::{Debug, Display, Formatter};
use std::fs;
use std::marker::PhantomData;
use std::path::Path;
use std::str::FromStr;
use crate::model::onkostar_editor::OnkostarEditor;
use crate::profile::Profile;
pub enum FileError {
Reading(String, String),
Writing(String, String),
Parsing(String, String),
}
impl Error for FileError {}
impl Debug for FileError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(self, f)
}
}
impl Display for FileError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match &self {
FileError::Reading(filename, err) => format!("Kann Datei '{}' nicht lesen: {}", filename, err),
FileError::Writing(filename, err) => format!("Kann Datei '{}' nicht schreiben: {}", filename, err),
FileError::Parsing(filename, err) => format!(
"Die Datei '{}' wird nicht unterstützt, ist fehlerhaft oder enthält zusätzliche Inhalte\n{}",
filename,
err
)
}
)
}
}
#[allow(dead_code)]
pub enum InputFile {
Osc {
filename: String,
content: String,
},
Osb {
filename: String,
content: Vec<InputFile>,
},
Yaml {
filename: String,
content: String,
},
Other {
filename: String,
content: Vec<u8>,
},
}
impl InputFile {
pub fn filename(&self) -> String {
match self {
InputFile::Osc { filename, .. } => filename,
InputFile::Osb { filename, .. } => filename,
InputFile::Yaml { filename, .. } => filename,
InputFile::Other { filename, .. } => filename,
}
.to_string()
}
pub fn read(filename: String, _password: Option<String>) -> Result<Self, FileError> {
if let Some(extension) = Path::new(filename.as_str()).extension() {
return match extension.to_str() {
Some("osc") => match fs::read_to_string(filename.clone()) {
Ok(content) => Ok(InputFile::Osc { filename, content }),
Err(err) => Err(FileError::Reading(filename, err.to_string())),
},
#[cfg(feature = "unzip-osb")]
Some("osb") => {
use bytes::BytesMut;
use deob::deobfuscate;
use std::io::Read;
let file = match fs::File::open(filename.clone()) {
Ok(file) => file,
Err(err) => return Err(FileError::Reading(filename, err.to_string())),
};
let mut archive = match zip::ZipArchive::new(file) {
Ok(file) => file,
Err(err) => return Err(FileError::Reading(filename, err.to_string())),
};
let mut result = vec![];
let password = _password.unwrap_or_else(|| deobfuscate(env!("OSB_KEY").trim()));
for i in 0..archive.len() {
if let Ok(Ok(mut zip_file)) =
archive.by_index_decrypt(i, password.as_bytes())
{
if zip_file.is_file() && zip_file.name().ends_with(".osc") {
let mut buf = String::new();
let _ = zip_file.read_to_string(&mut buf);
result.push(InputFile::Osc {
filename: zip_file.name().to_string(),
content: buf,
})
} else {
let mut buf = BytesMut::new();
let _ = zip_file.read(&mut buf);
result.push(InputFile::Other {
filename: zip_file.name().to_string(),
content: buf.to_vec(),
})
}
} else {
return Err(FileError::Parsing(
filename.into(),
"Kann OSB-Datei nicht lesen".to_string(),
));
}
}
Ok(InputFile::Osb {
filename,
content: result,
})
}
Some("yml") | Some("yaml") => match fs::read_to_string(filename.clone()) {
Ok(content) => Ok(InputFile::Yaml { filename, content }),
Err(err) => Err(FileError::Reading(filename, err.to_string())),
},
_ => Err(FileError::Parsing(
filename,
"Kein unterstütztes Dateiformat".to_string(),
)),
};
}
Err(FileError::Reading(filename, String::new()))
}
}
impl TryFrom<InputFile> for OnkostarEditor {
type Error = FileError;
fn try_from(value: InputFile) -> Result<Self, Self::Error> {
return match value {
InputFile::Osc {
filename, content, ..
} => match OnkostarEditor::from_str(content.as_str()) {
Ok(data) => Ok(data),
Err(err) => Err(FileError::Parsing(filename, err)),
},
InputFile::Osb { filename, .. }
| InputFile::Yaml { filename, .. }
| InputFile::Other { filename, .. } => {
Err(FileError::Parsing(filename, "Keine OSC-Datei".to_string()))
}
};
}
}
impl TryFrom<InputFile> for Profile {
type Error = FileError;
fn try_from(value: InputFile) -> Result<Self, Self::Error> {
match value {
InputFile::Yaml { filename, content } => match Profile::from_str(&content) {
Ok(profile) => Ok(profile),
Err(err) => Err(FileError::Parsing(filename, err)),
},
InputFile::Osc { filename, .. }
| InputFile::Osb { filename, .. }
| InputFile::Other { filename, .. } => Err(FileError::Parsing(
filename,
"Keine Profildatei".to_string(),
)),
}
}
}
/// Shortcut methods for OSC and Profile files
pub struct FileReader<FileType> {
file_type: PhantomData<FileType>,
}
impl FileReader<OnkostarEditor> {
pub fn read(filename: String) -> Result<OnkostarEditor, FileError> {
TryInto::<OnkostarEditor>::try_into(InputFile::read(filename.to_string(), None)?)
}
}
impl FileReader<Profile> {
pub fn read(filename: String) -> Result<Profile, FileError> {
TryInto::<Profile>::try_into(InputFile::read(filename.to_string(), None)?)
}
}

View File

@@ -22,212 +22,22 @@
* SOFTWARE. * SOFTWARE.
*/ */
use std::error::Error; use crate::cli::Cli;
use std::fmt::{Debug, Display, Formatter}; use crate::commands::handle;
use std::fs;
use std::fs::OpenOptions;
use std::io::Write;
use std::ops::Add;
use std::path::PathBuf;
use std::str::FromStr;
use clap::Parser; use clap::Parser;
use console::style; use std::error::Error;
use quick_xml::se::Serializer;
use serde::Serialize;
use sha256::digest;
use crate::cli::{Cli, SubCommand};
use crate::model::onkostar_editor::OnkostarEditor;
use crate::profile::Profile;
mod checks;
mod cli; mod cli;
mod commands;
mod file_io;
mod model; mod model;
mod profile; mod profile;
#[cfg(feature = "unzip-osb")]
enum FileError { mod unzip_osb;
Reading(String, String),
Writing(String, String),
Parsing(String, String),
}
impl Error for FileError {}
impl Debug for FileError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(self, f)
}
}
impl Display for FileError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match &self {
FileError::Reading(filename, err) => format!("Kann Datei '{}' nicht lesen: {}", filename, err),
FileError::Writing(filename, err) => format!("Kann Datei '{}' nicht schreiben: {}", filename, err),
FileError::Parsing(filename, err) => format!(
"Die Datei '{}' ist entweder keine OSC-Datei, fehlerhaft oder enthält zusätzliche Inhalte\n{}",
filename,
err
),
}
)
}
}
fn read_inputfile(inputfile: String) -> Result<OnkostarEditor, FileError> {
return match fs::read_to_string(inputfile.clone()) {
Ok(content) => match OnkostarEditor::from_str(content.as_str()) {
Ok(data) => Ok(data),
Err(err) => Err(FileError::Parsing(inputfile, err)),
},
Err(err) => Err(FileError::Reading(inputfile, err.to_string())),
};
}
fn write_outputfile(filename: String, content: &String) -> Result<(), FileError> {
OpenOptions::new()
.read(false)
.write(true)
.create(true)
.truncate(true)
.open(filename.clone())
.map_err(|err| FileError::Writing(filename.clone(), err.to_string()))?
.write_all(content.as_bytes())
.map_err(|err| FileError::Writing(filename, err.to_string()))?;
Ok(())
}
fn read_profile(filename: String) -> Result<Profile, FileError> {
let profile = fs::read_to_string(filename.clone())
.map_err(|err| FileError::Reading(filename.clone(), err.to_string()))?;
let profile =
Profile::from_str(profile.as_str()).map_err(|err| FileError::Reading(filename, err))?;
Ok(profile)
}
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
let cli = Cli::parse(); let cli = Cli::parse();
handle(cli.cmd)?;
match cli.cmd {
SubCommand::List {
inputfile,
sorted,
filter,
} => {
let mut data = read_inputfile(inputfile)?;
if sorted {
data.sorted()
}
if let Some(name) = filter {
OnkostarEditor::print_list_filtered(&mut data, name.as_str());
return Ok(());
}
data.print_list();
}
SubCommand::Tree {
inputfile,
sorted,
filter,
} => {
let mut data = read_inputfile(inputfile)?;
if sorted {
data.sorted()
}
if let Some(name) = filter {
OnkostarEditor::print_tree_filtered(&mut data, name.as_str());
return Ok(());
}
OnkostarEditor::print_tree(&data);
}
SubCommand::Modify {
inputfile,
profile,
outputfile,
compact,
sorted,
strip,
} => {
let data = &mut read_inputfile(inputfile)?;
if let Some(profile) = profile {
let profile = read_profile(profile.clone()).map_err(|_| {
FileError::Reading(profile, "Kann Profildatei nicht lesen!".into())
})?;
data.apply_profile(&profile);
}
if sorted {
data.sorted();
}
if strip {
data.strip_system_library_content();
}
let mut buf = String::new();
let mut serializer = Serializer::new(&mut buf);
if !compact {
serializer.indent(' ', 2);
}
data.serialize(serializer).expect("Generated XML");
let output = &"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
.to_string()
.add(
buf
// Replace &apos; and &quot; as used in original file
.replace("&apos;", "'")
.replace("&quot;", "\"")
.as_str(),
);
match outputfile {
Some(filename) => write_outputfile(filename, output)?,
None => {
println!("{}", output)
}
}
}
SubCommand::Diff {
inputfile_a,
inputfile_b,
strict,
} => {
println!(
"Vergleiche Datei A ({}) mit Datei B ({})",
style(&inputfile_a).yellow(),
style(&inputfile_b).yellow()
);
let data_a = &mut read_inputfile(inputfile_a)?;
let data_b = &mut read_inputfile(inputfile_b)?;
data_a.print_diff(data_b, strict);
}
SubCommand::Sha256Sum { inputfile } => {
match fs::read_to_string(inputfile.clone()) {
Ok(content) => {
println!(
"{} {}",
digest(content).as_str(),
PathBuf::from(inputfile.clone())
.canonicalize()
.unwrap_or_default()
.to_str()
.unwrap_or_default()
)
}
Err(err) => {
eprintln!("{}", FileError::Reading(inputfile, err.to_string()));
}
};
}
};
Ok(()) Ok(())
} }

View File

@@ -29,7 +29,7 @@ use serde::{Deserialize, Serialize};
use crate::model::onkostar_editor::OnkostarEditor; use crate::model::onkostar_editor::OnkostarEditor;
use crate::model::requirements::{Requirement, Requires}; use crate::model::requirements::{Requirement, Requires};
use crate::model::{Comparable, FolderContent, Listable, Ordner, Sortable}; use crate::model::{Ansichten, Comparable, FolderContent, Listable, Ordner, Sortable};
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
@@ -58,6 +58,9 @@ pub struct DataCatalogue {
entries: Entries, entries: Entries,
#[serde(rename = "Ordner")] #[serde(rename = "Ordner")]
ordner: Ordner, ordner: Ordner,
#[serde(rename = "Ansichten", default)]
#[serde(skip_serializing_if = "Option::is_none")]
ansichten: Option<Ansichten>,
} }
impl Listable for DataCatalogue { impl Listable for DataCatalogue {
@@ -102,7 +105,8 @@ impl Comparable for DataCatalogue {
impl Requires for DataCatalogue { impl Requires for DataCatalogue {
fn get_required_entries<'a>(&'a self, all: &'a OnkostarEditor) -> Vec<Requirement> { fn get_required_entries<'a>(&'a self, all: &'a OnkostarEditor) -> Vec<Requirement> {
self.entries let mut result = self
.entries
.entry .entry
.iter() .iter()
.filter(|&entry| entry.property_catalogue.is_some()) .filter(|&entry| entry.property_catalogue.is_some())
@@ -116,7 +120,10 @@ impl Requires for DataCatalogue {
Some(contained) => Requirement::PropertyCatalogue(contained), Some(contained) => Requirement::PropertyCatalogue(contained),
None => Requirement::ExternalPropertyCatalogue(entry), None => Requirement::ExternalPropertyCatalogue(entry),
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>();
result.sort_unstable_by_key(|item| item.sorting_key());
result
} }
fn to_requirement_string<'a>(&'a self, all: &'a OnkostarEditor) -> String { fn to_requirement_string<'a>(&'a self, all: &'a OnkostarEditor) -> String {
@@ -127,10 +134,10 @@ impl Requires for DataCatalogue {
.iter() .iter()
.map(|entry| match entry { .map(|entry| match entry {
Requirement::PropertyCatalogue(_) => { Requirement::PropertyCatalogue(_) => {
Some(format!(" - {}\n", entry.to_string())) Some(format!(" - {}\n", entry))
} }
Requirement::ExternalPropertyCatalogue(_) => { Requirement::ExternalPropertyCatalogue(_) => {
Some(format!(" - {}\n", entry.to_string())) Some(format!(" - {}\n", entry))
} }
_ => None, _ => None,
}) })

View File

@@ -22,17 +22,20 @@
* SOFTWARE. * SOFTWARE.
*/ */
use std::cmp::Ordering;
use std::collections::HashSet; use std::collections::HashSet;
use console::style; use console::style;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::checks::CheckNotice::ErrorWithCode;
use crate::checks::{CheckNotice, Checkable};
use crate::model::onkostar_editor::OnkostarEditor; use crate::model::onkostar_editor::OnkostarEditor;
use crate::model::requirements::{Requirement, Requires}; use crate::model::requirements::{Requirement, Requires};
use crate::model::{ use crate::model::{
apply_profile_to_form_entry, apply_profile_to_form_field, Ansichten, Comparable, Entries, apply_profile_to_form_entry, apply_profile_to_form_field, Ansichten, Comparable, Entries,
Filter, FolderContent, FormEntry, FormEntryContainer, Listable, MenuCategory, Filter, FolderContent, FormEntry, FormEntryContainer, Kennzahlen, Listable, MenuCategory,
PlausibilityRules, RefEntries, Script, Sortable, PlausibilityRules, PunkteKategorien, RefEntries, Script, Sortable,
}; };
use crate::model::{Haeufigkeiten, Ordner}; use crate::model::{Haeufigkeiten, Ordner};
use crate::profile::Profile; use crate::profile::Profile;
@@ -79,18 +82,26 @@ pub struct DataForm {
#[serde(rename = "EmailTemplate")] #[serde(rename = "EmailTemplate")]
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
mail_template: Option<String>, mail_template: Option<String>,
#[serde(rename = "ErkrankungText")] #[serde(rename = "ErkrankungText", default)]
erkrankung_text: String, #[serde(skip_serializing_if = "Option::is_none")]
erkrankung_text: Option<String>,
#[serde(rename = "ErkrankungTextLong")] #[serde(rename = "ErkrankungTextLong")]
erkrankung_text_long: String, #[serde(skip_serializing_if = "Option::is_none")]
erkrankung_text_long: Option<String>,
#[serde(rename = "ErkrankungProzedurText")] #[serde(rename = "ErkrankungProzedurText")]
erkrankung_prozedur_text: String, #[serde(skip_serializing_if = "Option::is_none")]
erkrankung_prozedur_text: Option<String>,
#[serde(rename = "ErkrankungSummary")] #[serde(rename = "ErkrankungSummary")]
erkrankung_summary: String, #[serde(skip_serializing_if = "Option::is_none")]
erkrankung_summary: Option<String>,
#[serde(rename = "ErkrankungBigSummary")] #[serde(rename = "ErkrankungBigSummary")]
erkrankung_big_summary: String, #[serde(skip_serializing_if = "Option::is_none")]
erkrankung_big_summary: Option<String>,
#[serde(rename = "Kontext")] #[serde(rename = "Kontext")]
kontext: String, kontext: String,
#[serde(rename = "Datenart")]
#[serde(skip_serializing_if = "Option::is_none")]
datenart: Option<String>,
#[serde(rename = "TudokReadonly")] #[serde(rename = "TudokReadonly")]
tudok_readonly: bool, tudok_readonly: bool,
#[serde(rename = "VitalstatusRelevant")] #[serde(rename = "VitalstatusRelevant")]
@@ -137,6 +148,9 @@ pub struct DataForm {
guid: String, guid: String,
#[serde(rename = "Revision")] #[serde(rename = "Revision")]
revision: u16, revision: u16,
#[serde(rename = "VerknuepftGUID")]
#[serde(skip_serializing_if = "Option::is_none")]
verknuepft_guid: Option<String>,
#[serde(rename = "SeitenzahlSichtbar")] #[serde(rename = "SeitenzahlSichtbar")]
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
seitenanzahl_sichtbar: Option<bool>, seitenanzahl_sichtbar: Option<bool>,
@@ -147,12 +161,15 @@ pub struct DataForm {
#[serde(rename = "Haeufigkeiten")] #[serde(rename = "Haeufigkeiten")]
haeufigkeiten: Haeufigkeiten, haeufigkeiten: Haeufigkeiten,
#[serde(rename = "Kennzahlen")] #[serde(rename = "Kennzahlen")]
kennzahlen: String, kennzahlen: Kennzahlen,
#[serde(rename = "Ordner")] #[serde(rename = "Ordner")]
ordner: Ordner, ordner: Ordner,
#[serde(rename = "MenuCategory")] #[serde(rename = "MenuCategory")]
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
menu_category: Option<MenuCategory>, menu_category: Option<MenuCategory>,
#[serde(rename = "PunkteKategorien")]
#[serde(skip_serializing_if = "Option::is_none")]
punkte_kategorien: Option<PunkteKategorien>,
#[serde(rename = "Ansichten")] #[serde(rename = "Ansichten")]
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
ansichten: Option<Ansichten>, ansichten: Option<Ansichten>,
@@ -192,25 +209,13 @@ impl FormEntryContainer for DataForm {
impl Listable for DataForm { impl Listable for DataForm {
fn to_listed_string(&self) -> String { fn to_listed_string(&self) -> String {
format!( format!(
"Formular ({}) '{}' in Revision '{}' {}", "Formular ({}) '{}' in Revision '{}'",
match self.is_system_library_content() { match self.is_system_library_content() {
true => style("S").yellow(), true => style("S").yellow(),
_ => style("u"), _ => style("u"),
}, },
style(&self.name).yellow(), style(&self.name).yellow(),
style(&self.revision).yellow(), style(&self.revision).yellow()
if self
.entries
.entry
.iter()
.filter(|entry| entry.procedure_date_status != "none")
.count()
== 0
{
style("Formular hat keine Angabe zum Prozedurdatum!").red()
} else {
style("")
}
) )
} }
} }
@@ -252,9 +257,54 @@ impl Comparable for DataForm {
fn get_revision(&self) -> u16 { fn get_revision(&self) -> u16 {
self.revision self.revision
} }
fn compare_by_requirement(a: &Self, b: &Self) -> Ordering {
if a.get_name() == b.get_name()
|| a.is_system_library_content()
|| b.is_system_library_content()
{
return Ordering::Equal;
}
if a.requires_form_reference(&b.get_name()) || a.requires_subform(&b.get_name()) {
return Ordering::Greater;
}
Ordering::Less
}
} }
impl Requires for DataForm { impl Requires for DataForm {
fn requires_form_reference(&self, name: &str) -> bool {
self.entries
.entry
.iter()
.map(|item| {
item.type_ == "formReference"
&& match item.referenced_data_form.as_ref() {
Some(refname) => refname == name,
_ => false,
}
})
.filter(|&it| it)
.last()
.unwrap_or_default()
}
fn requires_subform(&self, name: &str) -> bool {
self.entries
.entry
.iter()
.map(|item| {
item.type_ == "subform"
&& match item.referenced_data_form.as_ref() {
Some(refname) => refname == name,
_ => false,
}
})
.filter(|&it| it)
.last()
.unwrap_or_default()
}
fn get_required_entries<'a>(&'a self, all: &'a OnkostarEditor) -> Vec<Requirement> { fn get_required_entries<'a>(&'a self, all: &'a OnkostarEditor) -> Vec<Requirement> {
let mut result = self let mut result = self
.data_catalogues .data_catalogues
@@ -267,6 +317,7 @@ impl Requires for DataForm {
None => Requirement::ExternalDataCatalogue(entry.to_string()), None => Requirement::ExternalDataCatalogue(entry.to_string()),
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
result.sort_unstable_by_key(|item| item.sorting_key());
let referenced_forms = &mut self let referenced_forms = &mut self
.entries .entries
@@ -287,58 +338,33 @@ impl Requires for DataForm {
}, },
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
referenced_forms.sort_unstable_by_key(|item| item.sorting_key());
result.append(referenced_forms); result.append(referenced_forms);
let sub_forms = &mut self
.entries
.entry
.iter()
.filter(|&entry| entry.get_type() == "subform")
.filter_map(|entry| match &entry.referenced_data_form {
Some(name) => Some(name),
None => None,
})
.collect::<HashSet<_>>()
.into_iter()
.map(|entry| match all.find_data_form(entry.as_str()) {
Some(contained) => Requirement::DataFormSubform(contained),
None => match all.find_unterformular(entry.as_str()) {
Some(contained) => Requirement::UnterformularSubform(contained),
None => Requirement::ExternalUnterformularSubform(entry.to_string()),
},
})
.collect::<Vec<_>>();
sub_forms.sort_unstable_by_key(|item| item.sorting_key());
result.append(sub_forms);
result result
} }
fn to_requirement_string<'a>(&'a self, all: &'a OnkostarEditor) -> String {
format!(
"{}\n{}",
self.to_listed_string(),
self.get_required_entries(all)
.iter()
.map(|entry| match entry {
Requirement::DataCatalogue(x) => {
let inner = x
.get_required_entries(all)
.iter()
.map(|inner_entry| match inner_entry {
Requirement::PropertyCatalogue(y) => Some(y.to_listed_string()),
Requirement::ExternalPropertyCatalogue(name) => Some(format!(
"Merkmalskatalog (-) '{}' - hier nicht enthalten",
style(name).yellow()
)),
_ => None,
})
.filter(Option::is_some)
.map(|item| format!(" - {}\n", item.unwrap()))
.collect::<Vec<_>>()
.join("");
if inner.is_empty() {
Some(format!(" + {}\n", x.to_listed_string()))
} else {
Some(format!(" + {}\n{}", x.to_listed_string(), inner))
}
}
Requirement::ExternalDataCatalogue(_) => {
Some(format!(" + {}\n", entry.to_string()))
}
Requirement::DataFormReference(_)
| Requirement::ExternalDataFormReference(_)
| Requirement::UnterformularReference(_)
| Requirement::ExternalUnterformularReference(_) => {
Some(format!(" > {}\n", entry.to_string()))
}
_ => None,
})
.filter(Option::is_some)
.flatten()
.collect::<Vec<_>>()
.join("")
)
}
} }
impl FolderContent for DataForm { impl FolderContent for DataForm {
@@ -347,6 +373,30 @@ impl FolderContent for DataForm {
} }
} }
impl Checkable for DataForm {
fn check(&self) -> Vec<CheckNotice> {
if self
.entries
.entry
.iter()
.filter(|entry| entry.procedure_date_status != "none")
.count()
== 0
{
return vec![ErrorWithCode {
code: "2023-0002".to_string(),
description: format!(
"Formular '{}' hat keine Angabe zum Prozedurdatum",
self.name
),
line: None,
example: None,
}];
}
vec![]
}
}
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct DataCatalogues { pub struct DataCatalogues {
@@ -415,6 +465,9 @@ pub struct Entry {
grafik_ausrichtung: Option<String>, grafik_ausrichtung: Option<String>,
#[serde(rename = "Mandatory")] #[serde(rename = "Mandatory")]
mandatory: String, mandatory: String,
#[serde(rename = "Datenart", default)]
#[serde(skip_serializing_if = "Option::is_none")]
datenart: Option<String>,
#[serde(rename = "Filter")] #[serde(rename = "Filter")]
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
filter: Option<Filter>, filter: Option<Filter>,
@@ -434,6 +487,9 @@ pub struct Entry {
#[serde(rename = "AnzeigeAuswahl")] #[serde(rename = "AnzeigeAuswahl")]
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
anzeige_auswahl: Option<String>, anzeige_auswahl: Option<String>,
#[serde(rename = "Druckvorlage")]
#[serde(skip_serializing_if = "Option::is_none")]
druckvorlage: Option<String>,
#[serde(rename = "VersionFrom")] #[serde(rename = "VersionFrom")]
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
version_from: Option<String>, version_from: Option<String>,
@@ -441,6 +497,9 @@ pub struct Entry {
speichern: String, speichern: String,
#[serde(rename = "LeerAusblenden")] #[serde(rename = "LeerAusblenden")]
leer_ausblenden: bool, leer_ausblenden: bool,
#[serde(rename = "Inhalt")]
#[serde(skip_serializing_if = "Option::is_none")]
inhalt: Option<String>,
#[serde(rename = "GeschlossenAnzeigen")] #[serde(rename = "GeschlossenAnzeigen")]
geschlossen_anzeigen: bool, geschlossen_anzeigen: bool,
#[serde(rename = "Min")] #[serde(rename = "Min")]
@@ -450,7 +509,8 @@ pub struct Entry {
#[serde(rename = "InUebersichtAnzeigen")] #[serde(rename = "InUebersichtAnzeigen")]
in_uebersicht_anzeigen: bool, in_uebersicht_anzeigen: bool,
#[serde(rename = "Hinweis")] #[serde(rename = "Hinweis")]
hinweis: String, #[serde(skip_serializing_if = "Option::is_none")]
hinweis: Option<String>,
#[serde(rename = "Vorschlagskategorie")] #[serde(rename = "Vorschlagskategorie")]
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
vorschlagskategorie: Option<String>, vorschlagskategorie: Option<String>,
@@ -522,12 +582,21 @@ pub struct Entry {
in_auswertung: bool, in_auswertung: bool,
#[serde(rename = "InAuswertungGraph")] #[serde(rename = "InAuswertungGraph")]
in_auswertung_graph: bool, in_auswertung_graph: bool,
#[serde(rename = "FragebogenItemNummer")]
#[serde(skip_serializing_if = "Option::is_none")]
fragebogen_item_nummer: Option<u8>,
#[serde(rename = "Score")]
#[serde(skip_serializing_if = "Option::is_none")]
score: Option<String>,
#[serde(rename = "AlignmentPatModul")] #[serde(rename = "AlignmentPatModul")]
alignment_pat_modul: String, alignment_pat_modul: String,
#[serde(rename = "DirectionPatModul")] #[serde(rename = "DirectionPatModul")]
direction_pat_modul: String, direction_pat_modul: String,
#[serde(rename = "SeitenumbruchPatModul")] #[serde(rename = "SeitenumbruchPatModul")]
seitenumbruch_pat_modul: bool, seitenumbruch_pat_modul: bool,
#[serde(rename = "Kontaktliste")]
#[serde(skip_serializing_if = "Option::is_none")]
kontaktliste: Option<String>,
#[serde(rename = "MarkierungIgnorieren")] #[serde(rename = "MarkierungIgnorieren")]
markierung_ignorieren: bool, markierung_ignorieren: bool,
#[serde(rename = "SucheArt")] #[serde(rename = "SucheArt")]

View File

@@ -22,12 +22,14 @@
* SOFTWARE. * SOFTWARE.
*/ */
use std::cmp::Ordering;
use std::collections::hash_map::DefaultHasher; use std::collections::hash_map::DefaultHasher;
use std::fmt::Debug; use std::fmt::Debug;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::model::requirements::Requires;
use crate::profile::{FormField, FormReference, Profile}; use crate::profile::{FormField, FormReference, Profile};
pub mod data_catalogue; pub mod data_catalogue;
@@ -94,11 +96,15 @@ pub struct Ansicht {
#[serde(rename = "Konfiguration")] #[serde(rename = "Konfiguration")]
konfiguration: String, konfiguration: String,
#[serde(rename = "DataForm")] #[serde(rename = "DataForm")]
data_form: String, #[serde(skip_serializing_if = "Option::is_none")]
data_form: Option<String>,
#[serde(rename = "DataCatalogue")] #[serde(rename = "DataCatalogue")]
data_catalogue: String, data_catalogue: String,
#[serde(rename = "TypAuswahl")] #[serde(rename = "TypAuswahl")]
typ_auswahl: String, typ_auswahl: String,
#[serde(rename = "PersonenstammKontext", default)]
#[serde(skip_serializing_if = "Option::is_none")]
personenstamm_kontext: Option<String>,
#[serde(rename = "Suche")] #[serde(rename = "Suche")]
suche: bool, suche: bool,
#[serde(rename = "SID")] #[serde(rename = "SID")]
@@ -129,6 +135,66 @@ pub struct MenuCategory {
column: String, column: String,
} }
#[derive(Serialize, Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct PunkteKategorien {
#[serde(rename = "PunkteKategorie", default)]
punkte_kategorie: Vec<PunkteKategorie>,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct PunkteKategorie {
#[serde(rename = "Name")]
name: String,
#[serde(rename = "Beschreibung")]
beschreibung: String,
#[serde(rename = "MaxLeerwerte")]
max_leerwerte: u16,
#[serde(rename = "Berechnung")]
berechnung: String,
#[serde(rename = "Felder")]
#[serde(skip_serializing_if = "Option::is_none")]
felder: Option<Felder>,
#[serde(rename = "Vergleichswerttabellen")]
vergleichswerttabellen: String,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct Felder {
#[serde(rename = "Feld", default)]
feld: Vec<Feld>,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct Feld {
#[serde(rename = "DataFormEntryName")]
data_form_entry_name: String,
#[serde(rename = "ManuellePunkte")]
manuelle_punkte: bool,
#[serde(rename = "Werte")]
#[serde(skip_serializing_if = "Option::is_none")]
werte: Option<FeldWerte>,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct FeldWerte {
#[serde(rename = "Wert", default)]
wert: Vec<FeldWert>,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct FeldWert {
#[serde(rename = "Wert")]
wert: String,
#[serde(rename = "Punkte")]
punkte: String,
}
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct Filter { pub struct Filter {
@@ -187,8 +253,12 @@ pub struct Haeufigkeit {
taeglich_aktualisieren: bool, taeglich_aktualisieren: bool,
#[serde(rename = "Typ")] #[serde(rename = "Typ")]
typ: String, typ: String,
#[serde(rename = "NichtBerechnen")]
#[serde(skip_serializing_if = "Option::is_none")]
nicht_berechnen: Option<String>,
#[serde(rename = "TabellenName")] #[serde(rename = "TabellenName")]
tabellen_name: String, #[serde(skip_serializing_if = "Option::is_none")]
tabellen_name: Option<String>,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@@ -212,6 +282,32 @@ pub struct Ordner {
parent_order: Option<Box<Ordner>>, parent_order: Option<Box<Ordner>>,
} }
#[derive(Serialize, Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct Kennzahlen {
#[serde(rename = "Kennzahl", default)]
kennzahl: Vec<Kennzahl>,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct Kennzahl {
#[serde(rename = "Name")]
name: String,
#[serde(rename = "Nummer")]
nummer: String,
#[serde(rename = "Beschreibung")]
beschreibung: String,
#[serde(rename = "Notiz")]
notiz: String,
#[serde(rename = "Vorgabe")]
vorgabe: String,
#[serde(rename = "Haeufigkeitenzaehler")]
haeufigkeitenzaehler: String,
#[serde(rename = "Haeufigkeitennenner")]
haeufigkeitennenner: String,
}
fn apply_profile_to_form_entry<E>(entry: &mut E, form_reference: &FormReference) fn apply_profile_to_form_entry<E>(entry: &mut E, form_reference: &FormReference)
where where
E: FormEntry, E: FormEntry,
@@ -251,6 +347,7 @@ pub trait Listable {
pub trait Sortable { pub trait Sortable {
fn sorting_key(&self) -> String; fn sorting_key(&self) -> String;
fn sorted(&mut self) -> &Self fn sorted(&mut self) -> &Self
where where
Self: Sized, Self: Sized,
@@ -267,6 +364,12 @@ pub trait Comparable: Debug {
format!("{:?}", self).hash(&mut h); format!("{:?}", self).hash(&mut h);
h.finish().to_string() h.finish().to_string()
} }
fn compare_by_requirement(_: &Self, _: &Self) -> Ordering
where
Self: Requires,
{
Ordering::Equal
}
} }
pub trait FormEntry { pub trait FormEntry {

View File

@@ -30,10 +30,11 @@ use console::style;
use quick_xml::de::from_str; use quick_xml::de::from_str;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::checks::{CheckNotice, Checkable};
use crate::model::data_catalogue::DataCatalogue; use crate::model::data_catalogue::DataCatalogue;
use crate::model::data_form::DataForm; use crate::model::data_form::DataForm;
use crate::model::property_catalogue::PropertyCatalogue; use crate::model::property_catalogue::PropertyCatalogue;
use crate::model::requirements::Requires; use crate::model::requirements::{Requirement, Requires};
use crate::model::unterformular::Unterformular; use crate::model::unterformular::Unterformular;
use crate::model::{Comparable, FolderContent, FormEntryContainer, Listable, Sortable}; use crate::model::{Comparable, FolderContent, FormEntryContainer, Listable, Sortable};
use crate::profile::Profile; use crate::profile::Profile;
@@ -229,17 +230,25 @@ impl OnkostarEditor {
item.sorted(); item.sorted();
}); });
/*self.editor
.data_form
.sort_unstable_by_key(|e| e.sorting_key());*/
self.editor self.editor
.data_form .data_form
.sort_unstable_by_key(|e| e.sorting_key()); .sort_unstable_by(DataForm::compare_by_requirement);
self.editor.data_form.iter_mut().for_each(|item| { self.editor.data_form.iter_mut().for_each(|item| {
item.sorted(); item.sorted();
}); });
/*self.editor
.unterformular
.sort_unstable_by_key(|e| e.sorting_key());*/
self.editor self.editor
.unterformular .unterformular
.sort_unstable_by_key(|e| e.sorting_key()); .sort_unstable_by(Unterformular::compare_by_requirement);
self.editor.unterformular.iter_mut().for_each(|item| { self.editor.unterformular.iter_mut().for_each(|item| {
item.sorted(); item.sorted();
@@ -401,6 +410,89 @@ impl FromStr for OnkostarEditor {
} }
} }
impl Checkable for OnkostarEditor {
fn check(&self) -> Vec<CheckNotice> {
// Inner form checks
let mut result = self
.editor
.data_form
.iter()
.flat_map(|entity| entity.check())
.collect::<Vec<_>>();
let other = &mut self
.editor
.unterformular
.iter()
.flat_map(|entity| entity.check())
.collect::<Vec<_>>();
result.append(other);
// Check requirements
let mut requirement_checked_forms = vec![];
fn requirement_error(
form: &impl Comparable,
item: &impl Comparable,
t: &str,
) -> CheckNotice {
CheckNotice::ErrorWithCode {
code: "2023-0004".to_string(),
description: format!(
"'{}' hat einen Verweis auf zuvor nicht definiertes {t} '{}' (OSTARSUPP-13212)",
form.get_name(),
item.get_name()
),
line: None,
example: None,
}
}
self.editor.unterformular.iter().for_each(|form| {
requirement_checked_forms.push(form.get_name());
form.get_required_entries(self)
.iter()
.for_each(|entry| match entry {
Requirement::DataFormReference(item) => {
if !requirement_checked_forms.contains(&item.get_name()) {
result.push(requirement_error(form, *item, "Formular"))
}
}
Requirement::UnterformularReference(item) => {
if !requirement_checked_forms.contains(&item.get_name()) {
result.push(requirement_error(form, *item, "Unterformular"))
}
}
_ => {}
});
});
self.editor.data_form.iter().for_each(|form| {
requirement_checked_forms.push(form.get_name());
form.get_required_entries(self)
.iter()
.for_each(|entry| match entry {
Requirement::DataFormReference(item) => {
if !requirement_checked_forms.contains(&item.get_name()) {
result.push(requirement_error(form, *item, "Formular"))
}
}
Requirement::UnterformularReference(item) => {
if !requirement_checked_forms.contains(&item.get_name()) {
result.push(requirement_error(form, *item, "Unterformular"))
}
}
_ => {}
});
});
result
}
}
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct InfoXML { pub struct InfoXML {

View File

@@ -127,12 +127,12 @@ pub struct Version {
#[serde(rename = "Revision")] #[serde(rename = "Revision")]
revision: u16, revision: u16,
#[serde(rename = "Entries")] #[serde(rename = "Entries")]
entries: VersionEntries, entries: Option<VersionEntries>,
#[serde(rename = "Abbildung")] #[serde(rename = "Abbildung")]
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
abbildung: Option<Vec<Abbildung>>, abbildung: Option<Vec<Abbildung>>,
#[serde(rename = "Categories")] #[serde(rename = "Categories")]
categories: Categories, categories: Option<Categories>,
} }
impl Sortable for Version { impl Sortable for Version {
@@ -151,19 +151,23 @@ impl Sortable for Version {
}); });
} }
self.entries if let Some(ref mut entries) = self.entries {
entries
.content .content
.sort_unstable_by_key(|item| item.sorting_key()); .sort_unstable_by_key(|item| item.sorting_key());
self.entries.content.iter_mut().for_each(|item| { entries.content.iter_mut().for_each(|item| {
item.sorted(); item.sorted();
}); });
}
self.categories if let Some(ref mut categories) = self.categories {
categories
.content .content
.sort_unstable_by_key(|item| item.sorting_key()); .sort_unstable_by_key(|item| item.sorting_key());
self.categories.content.iter_mut().for_each(|item| { categories.content.iter_mut().for_each(|item| {
item.sorted(); item.sorted();
}); });
}
self self
} }
@@ -184,13 +188,16 @@ pub struct VersionEntry {
#[serde(rename = "ShortDescription")] #[serde(rename = "ShortDescription")]
short_description: String, short_description: String,
#[serde(rename = "Description")] #[serde(rename = "Description")]
description: String, description: Option<String>,
#[serde(rename = "Synonyms", default)] #[serde(rename = "Synonyms", default)]
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
synonyms: Option<String>, synonyms: Option<String>,
#[serde(rename = "Note", default)] #[serde(rename = "Note", default)]
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
note: Option<String>, note: Option<String>,
#[serde(rename = "Type", default)]
#[serde(skip_serializing_if = "Option::is_none")]
type_: Option<String>,
#[serde(rename = "Position")] #[serde(rename = "Position")]
position: String, position: String,
} }
@@ -331,8 +338,9 @@ pub struct AbbildungEntry {
#[serde(rename = "Synonyms")] #[serde(rename = "Synonyms")]
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
synonyms: Option<String>, synonyms: Option<String>,
#[serde(rename = "Note")] #[serde(rename = "Note", default)]
note: String, #[serde(skip_serializing_if = "Option::is_none")]
note: Option<String>,
#[serde(rename = "Position")] #[serde(rename = "Position")]
position: String, position: String,
} }

View File

@@ -27,7 +27,8 @@ use crate::model::data_form::DataForm;
use crate::model::onkostar_editor::OnkostarEditor; use crate::model::onkostar_editor::OnkostarEditor;
use crate::model::property_catalogue::PropertyCatalogue; use crate::model::property_catalogue::PropertyCatalogue;
use crate::model::unterformular::Unterformular; use crate::model::unterformular::Unterformular;
use crate::model::Listable; use crate::model::{Comparable, Listable, Sortable};
use std::fmt::Display;
#[allow(clippy::enum_variant_names)] #[allow(clippy::enum_variant_names)]
pub enum Requirement<'a> { pub enum Requirement<'a> {
@@ -40,33 +41,125 @@ pub enum Requirement<'a> {
#[allow(dead_code)] #[allow(dead_code)]
ExternalDataFormReference(String), ExternalDataFormReference(String),
ExternalUnterformularReference(String), ExternalUnterformularReference(String),
DataFormSubform(&'a DataForm),
UnterformularSubform(&'a Unterformular),
#[allow(dead_code)]
ExternalDataFormSubform(String),
ExternalUnterformularSubform(String),
} }
impl ToString for Requirement<'_> { impl Sortable for Requirement<'_> {
fn to_string(&self) -> String { fn sorting_key(&self) -> String {
match self { match self {
Requirement::PropertyCatalogue(item) => item.get_name(),
Requirement::DataCatalogue(item) => item.get_name(),
Requirement::DataFormReference(item) => item.get_name(),
Requirement::UnterformularReference(item) => item.get_name(),
Requirement::DataFormSubform(item) => item.get_name(),
Requirement::UnterformularSubform(item) => item.get_name(),
Requirement::ExternalPropertyCatalogue(name)
| Requirement::ExternalDataCatalogue(name)
| Requirement::ExternalDataFormReference(name)
| Requirement::ExternalDataFormSubform(name)
| Requirement::ExternalUnterformularReference(name)
| Requirement::ExternalUnterformularSubform(name) => name.to_string(),
}
}
}
impl Display for Requirement<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let str = match self {
Requirement::PropertyCatalogue(item) => item.to_listed_string(), Requirement::PropertyCatalogue(item) => item.to_listed_string(),
Requirement::DataCatalogue(item) => item.to_listed_string(), Requirement::DataCatalogue(item) => item.to_listed_string(),
Requirement::DataFormReference(item) => item.to_listed_string(),
Requirement::UnterformularReference(item) => item.to_listed_string(),
Requirement::DataFormSubform(item) => item.to_listed_string(),
Requirement::UnterformularSubform(item) => item.to_listed_string(),
Requirement::ExternalPropertyCatalogue(name) => { Requirement::ExternalPropertyCatalogue(name) => {
format!("Merkmalskatalog (-) '{}' - hier nicht enthalten", name) format!("Merkmalskatalog (-) '{}' - hier nicht enthalten", name)
} }
Requirement::ExternalDataCatalogue(name) => { Requirement::ExternalDataCatalogue(name) => {
format!("Datenkatalog (-) '{}' - hier nicht enthalten", name) format!("Datenkatalog (-) '{}' - hier nicht enthalten", name)
} }
Requirement::DataFormReference(item) => item.to_listed_string(), Requirement::ExternalDataFormReference(name)
Requirement::UnterformularReference(item) => item.to_listed_string(), | Requirement::ExternalDataFormSubform(name) => {
Requirement::ExternalDataFormReference(name) => {
format!("Formular (-) '{}' - hier nicht enthalten", name) format!("Formular (-) '{}' - hier nicht enthalten", name)
} }
Requirement::ExternalUnterformularReference(name) => { Requirement::ExternalUnterformularReference(name)
| Requirement::ExternalUnterformularSubform(name) => {
format!("Unterformular (-) '{}' - hier nicht enthalten", name) format!("Unterformular (-) '{}' - hier nicht enthalten", name)
} }
} };
write!(f, "{}", str)
} }
} }
pub trait Requires { pub trait Requires
where
Self: Listable,
{
fn requires_form_reference(&self, _: &str) -> bool {
false
}
fn requires_subform(&self, _: &str) -> bool {
false
}
fn get_required_entries<'a>(&'a self, all: &'a OnkostarEditor) -> Vec<Requirement>; fn get_required_entries<'a>(&'a self, all: &'a OnkostarEditor) -> Vec<Requirement>;
fn to_requirement_string<'a>(&'a self, all: &'a OnkostarEditor) -> String; fn to_requirement_string<'a>(&'a self, all: &'a OnkostarEditor) -> String {
format!(
"{}\n{}",
self.to_listed_string(),
self.get_required_entries(all)
.iter()
.map(|entry| match entry {
Requirement::DataCatalogue(x) => {
let inner = x
.get_required_entries(all)
.iter()
.map(|inner_entry| match inner_entry {
Requirement::PropertyCatalogue(_) => Some(inner_entry.to_string()),
Requirement::ExternalPropertyCatalogue(_) => {
Some(inner_entry.to_string())
}
_ => None,
})
.filter(Option::is_some)
.map(|item| format!(" - {}\n", item.unwrap()))
.collect::<Vec<_>>()
.join("");
if inner.is_empty() {
Some(format!(" + {}\n", x.to_listed_string()))
} else {
Some(format!(" + {}\n{}", x.to_listed_string(), inner))
}
}
Requirement::ExternalDataCatalogue(_) => {
Some(format!(" + {}\n", entry))
}
Requirement::DataFormReference(_)
| Requirement::ExternalDataFormReference(_)
| Requirement::UnterformularReference(_)
| Requirement::ExternalUnterformularReference(_) => {
Some(format!(" > {}\n", entry))
}
Requirement::DataFormSubform(_)
| Requirement::ExternalDataFormSubform(_)
| Requirement::UnterformularSubform(_)
| Requirement::ExternalUnterformularSubform(_) => {
Some(format!(" * {}\n", entry))
}
_ => None,
})
.filter(Option::is_some)
.flatten()
.collect::<Vec<_>>()
.join("")
)
}
} }

View File

@@ -22,8 +22,11 @@
* SOFTWARE. * SOFTWARE.
*/ */
use std::cmp::Ordering;
use std::collections::HashSet; use std::collections::HashSet;
use crate::checks::CheckNotice::ErrorWithCode;
use crate::checks::{CheckNotice, Checkable};
use console::style; use console::style;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -31,8 +34,8 @@ use crate::model::onkostar_editor::OnkostarEditor;
use crate::model::requirements::{Requirement, Requires}; use crate::model::requirements::{Requirement, Requires};
use crate::model::{ use crate::model::{
apply_profile_to_form_entry, apply_profile_to_form_field, Ansichten, Comparable, Entries, apply_profile_to_form_entry, apply_profile_to_form_field, Ansichten, Comparable, Entries,
Filter, FolderContent, FormEntry, FormEntryContainer, Listable, MenuCategory, Filter, FolderContent, FormEntry, FormEntryContainer, Kennzahlen, Listable, MenuCategory,
PlausibilityRules, RefEntries, Script, Sortable, PlausibilityRules, PunkteKategorien, RefEntries, Script, Sortable,
}; };
use crate::model::{Haeufigkeiten, Ordner}; use crate::model::{Haeufigkeiten, Ordner};
use crate::profile::Profile; use crate::profile::Profile;
@@ -79,7 +82,7 @@ pub struct Unterformular {
#[serde(rename = "EmailTemplate")] #[serde(rename = "EmailTemplate")]
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
mail_template: Option<String>, mail_template: Option<String>,
#[serde(rename = "ErkrankungText")] #[serde(rename = "ErkrankungText", default)]
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
erkrankung_text: Option<String>, erkrankung_text: Option<String>,
#[serde(rename = "ErkrankungTextLong")] #[serde(rename = "ErkrankungTextLong")]
@@ -96,6 +99,9 @@ pub struct Unterformular {
erkrankung_big_summary: Option<String>, erkrankung_big_summary: Option<String>,
#[serde(rename = "Kontext")] #[serde(rename = "Kontext")]
kontext: String, kontext: String,
#[serde(rename = "Datenart")]
#[serde(skip_serializing_if = "Option::is_none")]
datenart: Option<String>,
#[serde(rename = "TudokReadonly")] #[serde(rename = "TudokReadonly")]
tudok_readonly: bool, tudok_readonly: bool,
#[serde(rename = "VitalstatusRelevant")] #[serde(rename = "VitalstatusRelevant")]
@@ -158,12 +164,15 @@ pub struct Unterformular {
#[serde(rename = "Haeufigkeiten")] #[serde(rename = "Haeufigkeiten")]
haeufigkeiten: Haeufigkeiten, haeufigkeiten: Haeufigkeiten,
#[serde(rename = "Kennzahlen")] #[serde(rename = "Kennzahlen")]
kennzahlen: String, kennzahlen: Kennzahlen,
#[serde(rename = "Ordner")] #[serde(rename = "Ordner")]
ordner: Ordner, ordner: Ordner,
#[serde(rename = "MenuCategory")] #[serde(rename = "MenuCategory")]
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
menu_category: Option<MenuCategory>, menu_category: Option<MenuCategory>,
#[serde(rename = "PunkteKategorien")]
#[serde(skip_serializing_if = "Option::is_none")]
punkte_kategorien: Option<PunkteKategorien>,
#[serde(rename = "Ansichten")] #[serde(rename = "Ansichten")]
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
ansichten: Option<Ansichten>, ansichten: Option<Ansichten>,
@@ -203,18 +212,13 @@ impl FormEntryContainer for Unterformular {
impl Listable for Unterformular { impl Listable for Unterformular {
fn to_listed_string(&self) -> String { fn to_listed_string(&self) -> String {
format!( format!(
"Unterformular ({}) '{}' in Revision '{}' {}", "Unterformular ({}) '{}' in Revision '{}'",
match self.is_system_library_content() { match self.is_system_library_content() {
true => style("S").yellow(), true => style("S").yellow(),
_ => style("u"), _ => style("u"),
}, },
style(&self.name).yellow(), style(&self.name).yellow(),
style(&self.revision).yellow(), style(&self.revision).yellow(),
if self.hat_unterformulare {
style("Unterformular mit Markierung 'hat Unterformulare'!").red()
} else {
style("")
}
) )
} }
} }
@@ -256,9 +260,55 @@ impl Comparable for Unterformular {
fn get_revision(&self) -> u16 { fn get_revision(&self) -> u16 {
self.revision self.revision
} }
fn compare_by_requirement(a: &Self, b: &Self) -> Ordering {
if a.get_name() == b.get_name()
|| a.is_system_library_content()
|| b.is_system_library_content()
{
return Ordering::Equal;
}
if a.requires_form_reference(&b.get_name()) || a.requires_subform(&b.get_name()) {
return Ordering::Greater;
}
Ordering::Less
}
} }
impl Requires for Unterformular { impl Requires for Unterformular {
fn requires_form_reference(&self, name: &str) -> bool {
self.entries
.entry
.iter()
.map(|item| {
item.type_ == "formReference"
&& match item.referenced_data_form.as_ref() {
Some(refname) => refname == name,
_ => false,
}
})
.filter(|&it| it)
.last()
.unwrap_or_default()
}
fn requires_subform(&self, name: &str) -> bool {
self.entries
.entry
.iter()
.map(|item| {
item.type_ == "subform"
&& match item.referenced_data_form.as_ref() {
Some(refname) => refname == name,
_ => false,
}
})
.filter(|&it| it)
.last()
.unwrap_or_default()
}
fn get_required_entries<'a>(&'a self, all: &'a OnkostarEditor) -> Vec<Requirement> { fn get_required_entries<'a>(&'a self, all: &'a OnkostarEditor) -> Vec<Requirement> {
let mut result = self let mut result = self
.data_catalogues .data_catalogues
@@ -271,6 +321,7 @@ impl Requires for Unterformular {
None => Requirement::ExternalDataCatalogue(entry.to_string()), None => Requirement::ExternalDataCatalogue(entry.to_string()),
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
result.sort_unstable_by_key(|item| item.sorting_key());
let referenced_forms = &mut self let referenced_forms = &mut self
.entries .entries
@@ -291,57 +342,33 @@ impl Requires for Unterformular {
}, },
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
referenced_forms.sort_unstable_by_key(|item| item.sorting_key());
result.append(referenced_forms); result.append(referenced_forms);
let sub_forms = &mut self
.entries
.entry
.iter()
.filter(|&entry| entry.get_type() == "subform")
.filter_map(|entry| match &entry.referenced_data_form {
Some(name) => Some(name),
None => None,
})
.collect::<HashSet<_>>()
.into_iter()
.map(|entry| match all.find_data_form(entry.as_str()) {
Some(contained) => Requirement::DataFormSubform(contained),
None => match all.find_unterformular(entry.as_str()) {
Some(contained) => Requirement::UnterformularSubform(contained),
None => Requirement::ExternalUnterformularSubform(entry.to_string()),
},
})
.collect::<Vec<_>>();
sub_forms.sort_unstable_by_key(|item| item.sorting_key());
result.append(sub_forms);
result result
} }
fn to_requirement_string<'a>(&'a self, all: &'a OnkostarEditor) -> String {
format!(
"{}\n{}",
self.to_listed_string(),
self.get_required_entries(all)
.iter()
.map(|entry| match entry {
Requirement::DataCatalogue(x) => {
let inner = x
.get_required_entries(all)
.iter()
.map(|inner_entry| match inner_entry {
Requirement::PropertyCatalogue(_) => Some(inner_entry.to_string()),
Requirement::ExternalPropertyCatalogue(_) => {
Some(inner_entry.to_string())
}
_ => None,
})
.filter(Option::is_some)
.map(|item| format!(" - {}\n", item.unwrap()))
.collect::<Vec<_>>()
.join("");
if inner.is_empty() {
Some(format!(" + {}\n", x.to_listed_string()))
} else {
Some(format!(" + {}\n{}", x.to_listed_string(), inner))
}
}
Requirement::ExternalDataCatalogue(_) => {
Some(format!(" + {}\n", entry.to_string()))
}
Requirement::DataFormReference(_)
| Requirement::ExternalDataFormReference(_)
| Requirement::UnterformularReference(_)
| Requirement::ExternalUnterformularReference(_) => {
Some(format!(" > {}\n", entry.to_string()))
}
_ => None,
})
.filter(Option::is_some)
.flatten()
.collect::<Vec<_>>()
.join("")
)
}
} }
impl FolderContent for Unterformular { impl FolderContent for Unterformular {
@@ -350,6 +377,24 @@ impl FolderContent for Unterformular {
} }
} }
impl Checkable for Unterformular {
fn check(&self) -> Vec<CheckNotice> {
if self.hat_unterformulare {
return vec![ErrorWithCode {
code: "2023-0001".to_string(),
description: format!(
"Unterformular '{}' mit Markierung 'hat Unterformulare'",
self.name
),
line: None,
example: None,
}];
}
vec![]
}
}
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct DataCatalogues { pub struct DataCatalogues {
@@ -418,6 +463,9 @@ pub struct Entry {
grafik_ausrichtung: Option<String>, grafik_ausrichtung: Option<String>,
#[serde(rename = "Mandatory")] #[serde(rename = "Mandatory")]
mandatory: String, mandatory: String,
#[serde(rename = "Datenart", default)]
#[serde(skip_serializing_if = "Option::is_none")]
datenart: Option<String>,
#[serde(rename = "Filter")] #[serde(rename = "Filter")]
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
filter: Option<Filter>, filter: Option<Filter>,
@@ -437,6 +485,9 @@ pub struct Entry {
#[serde(rename = "AnzeigeAuswahl")] #[serde(rename = "AnzeigeAuswahl")]
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
anzeige_auswahl: Option<String>, anzeige_auswahl: Option<String>,
#[serde(rename = "Druckvorlage")]
#[serde(skip_serializing_if = "Option::is_none")]
druckvorlage: Option<String>,
#[serde(rename = "VersionFrom")] #[serde(rename = "VersionFrom")]
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
version_from: Option<String>, version_from: Option<String>,
@@ -444,6 +495,9 @@ pub struct Entry {
speichern: String, speichern: String,
#[serde(rename = "LeerAusblenden")] #[serde(rename = "LeerAusblenden")]
leer_ausblenden: bool, leer_ausblenden: bool,
#[serde(rename = "Inhalt")]
#[serde(skip_serializing_if = "Option::is_none")]
inhalt: Option<String>,
#[serde(rename = "GeschlossenAnzeigen")] #[serde(rename = "GeschlossenAnzeigen")]
geschlossen_anzeigen: bool, geschlossen_anzeigen: bool,
#[serde(rename = "Min")] #[serde(rename = "Min")]
@@ -526,12 +580,21 @@ pub struct Entry {
in_auswertung: bool, in_auswertung: bool,
#[serde(rename = "InAuswertungGraph")] #[serde(rename = "InAuswertungGraph")]
in_auswertung_graph: bool, in_auswertung_graph: bool,
#[serde(rename = "FragebogenItemNummer")]
#[serde(skip_serializing_if = "Option::is_none")]
fragebogen_item_nummer: Option<u8>,
#[serde(rename = "Score")]
#[serde(skip_serializing_if = "Option::is_none")]
score: Option<String>,
#[serde(rename = "AlignmentPatModul")] #[serde(rename = "AlignmentPatModul")]
alignment_pat_modul: String, alignment_pat_modul: String,
#[serde(rename = "DirectionPatModul")] #[serde(rename = "DirectionPatModul")]
direction_pat_modul: String, direction_pat_modul: String,
#[serde(rename = "SeitenumbruchPatModul")] #[serde(rename = "SeitenumbruchPatModul")]
seitenumbruch_pat_modul: bool, seitenumbruch_pat_modul: bool,
#[serde(rename = "Kontaktliste")]
#[serde(skip_serializing_if = "Option::is_none")]
kontaktliste: Option<String>,
#[serde(rename = "MarkierungIgnorieren")] #[serde(rename = "MarkierungIgnorieren")]
markierung_ignorieren: bool, markierung_ignorieren: bool,
#[serde(rename = "SucheArt")] #[serde(rename = "SucheArt")]

View File

@@ -34,6 +34,19 @@ pub struct Profile {
pub forms: Vec<Form>, pub forms: Vec<Form>,
} }
impl Profile {
pub fn embedded_profile(name: &str) -> Result<Profile, String> {
let s = match name {
"UKM" => include_str!("../examples/dnpm-ukm.yml"),
"UKW" => include_str!("../examples/dnpm-ukw.yml"),
"UMG" => include_str!("../examples/dnpm-umg.yml"),
_ => return Err(format!("Not an embedded profile: '{name}'")),
};
Profile::from_str(s)
}
}
impl FromStr for Profile { impl FromStr for Profile {
type Err = String; type Err = String;

135
src/unzip_osb.rs Normal file
View File

@@ -0,0 +1,135 @@
/*
* MIT License
*
* Copyright (c) 2023 Comprehensive Cancer Center Mainfranken
*
* 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.
*/
use console::style;
use deob::deobfuscate;
use std::path::Path;
use std::{fs, io};
macro_rules! started {
( $o:expr ) => {
println!("{: <6}{}", style("[..]").cyan(), $o);
};
}
macro_rules! ok {
( $o:expr ) => {
use console::Term;
let _ = Term::stdout().move_cursor_up(1);
println!("{: <6}{}", style("[OK]").green(), $o);
};
}
macro_rules! error {
( $o:expr, $e:expr ) => {
use console::Term;
let _ = Term::stdout().move_cursor_up(1);
println!("{: <6}{} - Error: {}", style("[ERR]").red(), $o, $e);
};
}
pub fn unzip_osb_using_password(path: &str, dir: &str, password: &str) {
println!("Entpacke OSB-Datei {}\n", style(path).yellow());
let file = match fs::File::open(path) {
Ok(file) => file,
Err(err) => {
println!(
"{: <6}Abbruch! - Kann Datei nicht entpacken: {}",
style("[ERR]").red(),
err
);
return;
}
};
let mut archive = match zip::ZipArchive::new(file) {
Ok(file) => file,
Err(err) => {
println!(
"{: <6}Abbruch! - Kann Datei nicht entpacken: {}",
style("[ERR]").red(),
err
);
return;
}
};
for i in 0..archive.len() {
let mut file = if let Ok(Ok(file)) = archive.by_index_decrypt(i, password.as_bytes()) {
file
} else {
println!(
"{: <6}Abbruch! - Kann Datei nicht entpacken",
style("[ERR]").red()
);
return;
};
let outpath = match file.enclosed_name() {
Some(path) => Path::new(dir).join(path.to_owned()),
None => continue,
};
started!(outpath.display());
if !file.is_dir() {
if let Some(p) = outpath.parent() {
if !p.exists() {
fs::create_dir_all(p).unwrap();
}
}
let mut outfile = match fs::File::create(&outpath) {
Ok(file) => file,
Err(err) => {
error!(outpath.display(), err);
continue;
}
};
match io::copy(&mut file, &mut outfile) {
Ok(_) => {}
Err(err) => {
error!(outpath.display(), err);
continue;
}
}
ok!(outpath.display());
} else {
if !outpath.exists() {
match fs::create_dir_all(&outpath) {
Ok(_) => {}
Err(err) => {
error!(outpath.display(), err);
continue;
}
}
}
ok!(outpath.display());
}
}
}
pub fn unzip_osb(path: &str, dir: &str) {
unzip_osb_using_password(path, dir, deobfuscate(env!("OSB_KEY").trim()).as_str());
}