diff --git a/Cargo.lock b/Cargo.lock index eaf6bc2ff..628df7aff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,7 +15,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", - "cipher", + "cipher 0.4.4", "cpufeatures", ] @@ -44,9 +44,9 @@ dependencies = [ [[package]] name = "aligned" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "377e4c0ba83e4431b10df45c1d4666f178ea9c552cac93e60c3a88bf32785923" +checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" dependencies = [ "as-slice", ] @@ -346,9 +346,15 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.0" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" + +[[package]] +name = "beef" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" [[package]] name = "bit_field" @@ -392,6 +398,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96eb4cdd6cf1b31d671e9efe75c5d1ec614776856cefbe109ca373554a6d514f" +dependencies = [ + "hybrid-array", +] + [[package]] name = "built" version = "0.8.0" @@ -400,9 +415,9 @@ checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64" [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "bytecount" @@ -451,9 +466,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.48" +version = "1.2.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c481bdbf0ed3b892f6f806287d72acd515b352a4ec27a208489b8c1bc839633a" +checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" dependencies = [ "find-msvc-tools", "jobserver", @@ -480,6 +495,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99cbf41c6ec3c4b9eaf7f8f5c11a72cd7d3aa0428125c20d5ef4d09907a0f019" dependencies = [ "cfg-if", + "cipher 0.5.0-rc.2", "cpufeatures", "rand_core 0.10.0-rc-2", ] @@ -528,7 +544,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common 0.1.7", - "inout", + "inout 0.1.4", +] + +[[package]] +name = "cipher" +version = "0.5.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "155e4a260750fa4f7754649f049748aacc31db238a358d85fd721002f230f92f" +dependencies = [ + "block-buffer 0.11.0", + "crypto-common 0.2.0-rc.5", + "inout 0.2.1", ] [[package]] @@ -555,9 +582,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.61" +version = "4.5.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39615915e2ece2550c0149addac32fb5bd312c657f43845bb9088cb9c8a7c992" +checksum = "004eef6b14ce34759aa7de4aea3217e368f463f46a3ed3764ca4b5a4404003b4" dependencies = [ "clap", ] @@ -627,9 +654,9 @@ dependencies = [ [[package]] name = "console" -version = "0.16.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b430743a6eb14e9764d4260d4c0d8123087d504eeb9c48f2b2a5e810dd369df4" +checksum = "03e45a4a8926227e4197636ba97a9fc9b00477e9f4bd711395687c5f0734bec4" dependencies = [ "encode_unicode", "libc", @@ -740,9 +767,9 @@ dependencies = [ [[package]] name = "criterion" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0dfe5e9e71bdcf4e4954f7d14da74d1cdb92a3a07686452d1509652684b1aab" +checksum = "4d883447757bb0ee46f233e9dc22eb84d93a9508c9b868687b274fc431d886bf" dependencies = [ "alloca", "anes", @@ -765,9 +792,9 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de36c2bee19fba779808f92bf5d9b0fa5a40095c277aba10c458a12b35d21d6" +checksum = "ed943f81ea2faa8dcecbbfa50164acf95d555afec96a27871663b300e387b2e4" dependencies = [ "cast", "itertools 0.13.0", @@ -926,7 +953,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25f104b501bf2364e78d0d3974cbc774f738f5865306ed128e1e0d7499c0ad96" dependencies = [ - "console 0.16.1", + "console 0.16.2", "shell-words", "tempfile", "zeroize", @@ -938,7 +965,7 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", + "block-buffer 0.10.4", "crypto-common 0.1.7", "subtle", ] @@ -1351,9 +1378,9 @@ dependencies = [ [[package]] name = "gif" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f954a9e9159ec994f73a30a12b96a702dde78f5547bcb561174597924f7d4162" +checksum = "f5df2ba84018d80c213569363bdcd0c64e6933c67fe4c1d60ecf822971a3c35e" dependencies = [ "color_quant", "weezl", @@ -1361,9 +1388,9 @@ dependencies = [ [[package]] name = "git2" -version = "0.20.2" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2deb07a133b1520dc1a5690e9bd08950108873d7ed5de38dcc74d3b5ebffa110" +checksum = "3e2b37e2f62729cdada11f0e6b3b6fe383c69c29fc619e391223e12856af308c" dependencies = [ "bitflags 2.10.0", "libc", @@ -1449,6 +1476,7 @@ dependencies = [ "glob", "hemtt-common", "hemtt-config", + "hemtt-format", "hemtt-p3d", "hemtt-paa", "hemtt-pbo", @@ -1557,6 +1585,16 @@ dependencies = [ "toml", ] +[[package]] +name = "hemtt-format" +version = "1.0.0" +dependencies = [ + "fs-err", + "insta", + "logos", + "paste", +] + [[package]] name = "hemtt-language-server" version = "1.18.1" @@ -1663,11 +1701,13 @@ name = "hemtt-signing" version = "1.0.0" dependencies = [ "byteorder", + "chacha20", "crypto-bigint", "fs-err", "hemtt-common", "hemtt-pbo", "rand 0.10.0-rc.5", + "rand_core 0.10.0-rc-2", "rsa", "sha-1", "thiserror 2.0.17", @@ -1890,9 +1930,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" dependencies = [ "base64", "bytes", @@ -1962,9 +2002,9 @@ checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ "icu_collections", "icu_locale_core", @@ -1976,9 +2016,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" @@ -2037,7 +2077,7 @@ dependencies = [ "rgb", "tiff", "zune-core 0.5.0", - "zune-jpeg 0.5.5", + "zune-jpeg 0.5.7", ] [[package]] @@ -2072,7 +2112,7 @@ version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9375e112e4b463ec1b1c6c011953545c65a30164fbab5b581df32b3abf0dcb88" dependencies = [ - "console 0.16.1", + "console 0.16.2", "portable-atomic", "unicode-width", "unit-prefix", @@ -2088,15 +2128,25 @@ dependencies = [ "generic-array", ] +[[package]] +name = "inout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7357b6e7aa75618c7864ebd0634b115a7218b0615f4cb1df33ac3eca23943d4" +dependencies = [ + "hybrid-array", +] + [[package]] name = "insta" -version = "1.44.3" +version = "1.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5c943d4415edd8153251b6f197de5eb1640e56d84e8d9159bea190421c73698" +checksum = "b76866be74d68b1595eb8060cb9191dca9c021db2316558e52ddc5d55d41b66c" dependencies = [ "console 0.15.11", "once_cell", "similar", + "tempfile", ] [[package]] @@ -2183,9 +2233,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010" [[package]] name = "jni" @@ -2231,21 +2281,22 @@ dependencies = [ [[package]] name = "keyvalues-parser" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a08dbe66b5c751fa0ccb33b447931646bf7dc055e8d48562dfed7fa6ee216bf" +checksum = "1c8e8b99861a87f1d04ade7898482bb9a9dcf2b784eaa48fc9907a429e45c2a3" dependencies = [ "pest", + "serde_core", ] [[package]] name = "keyvalues-serde" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2db0b750e9ae64e6c3d913832dc19ed580b9210bcd7c4dd6d0dd21af469fc14" +checksum = "fe5bbd40795b21e78109367a739c00365345b9b8c487ef2fd89e6d0e6f9e31f1" dependencies = [ "keyvalues-parser", - "serde", + "serde_core", ] [[package]] @@ -2268,9 +2319,9 @@ checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" [[package]] name = "libc" -version = "0.2.177" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libfuzzer-sys" @@ -2284,9 +2335,9 @@ dependencies = [ [[package]] name = "libgit2-sys" -version = "0.18.2+1.9.1" +version = "0.18.3+1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c42fe03df2bd3c53a3a9c7317ad91d80c81cd1fb0caec8d7cc4cd2bfa10c222" +checksum = "c9b3acc4b91781bb0b3386669d325163746af5f6e4f73e6d2d630e09a35f3487" dependencies = [ "cc", "libc", @@ -2304,13 +2355,13 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" dependencies = [ "bitflags 2.10.0", "libc", - "redox_syscall", + "redox_syscall 0.6.0", ] [[package]] @@ -2329,9 +2380,9 @@ dependencies = [ [[package]] name = "libz-rs-sys" -version = "0.5.2" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "840db8cf39d9ec4dd794376f38acc40d0fc65eec2a8f484f7fd375b84602becd" +checksum = "c10501e7805cee23da17c7790e59df2870c0d4043ec6d03f67d31e2b53e77415" dependencies = [ "zlib-rs", ] @@ -2397,9 +2448,43 @@ dependencies = [ [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "logos" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a790d11254054e5dc83902dba85d253ff06ceb0cfafb12be8773435cb9dfb4f4" +dependencies = [ + "logos-derive", +] + +[[package]] +name = "logos-codegen" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60337c43a38313b58871f8d5d76872b8e17aa9d51fad494b5e76092c0ce05f5" +dependencies = [ + "beef", + "fnv", + "proc-macro2", + "quote", + "regex-automata", + "regex-syntax", + "rustc_version", + "syn", +] + +[[package]] +name = "logos-derive" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d151b2ae667f69e10b8738f5cac0c746faa22b2e15ea7e83b55476afec3767dc" +dependencies = [ + "logos-codegen", +] [[package]] name = "loom" @@ -2482,9 +2567,9 @@ dependencies = [ [[package]] name = "mdbook-core" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ef8430ec21b88489dfffd90c0fb9bd3eab96bf7642ef0cab74754b9d2e5b7f6" +checksum = "39a3873d4afac65583f1acb56ff058df989d5b4a2464bb02c785549727d307ee" dependencies = [ "anyhow", "regex", @@ -2496,9 +2581,9 @@ dependencies = [ [[package]] name = "mdbook-preprocessor" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01f84f2b2ef3ccf2c6dd71255f9d90912cce7d5a5aa32899d033003d2c71f84d" +checksum = "4d87bf40be0597f26f0822f939a64f02bf92c4655ba04490aadbf83601a013bb" dependencies = [ "anyhow", "mdbook-core", @@ -2530,9 +2615,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", "wasi", @@ -2541,9 +2626,9 @@ dependencies = [ [[package]] name = "moxcms" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80986bbbcf925ebd3be54c26613d861255284584501595cf418320c078945608" +checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97" dependencies = [ "num-traits", "pxfm", @@ -2811,7 +2896,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.18", "smallvec", "windows-link 0.2.1", ] @@ -3003,9 +3088,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "f59e70c4aef1e55797c2e8fd94a4f2a973fc972cfde0e0b05f683667b0cd39dd" [[package]] name = "potential_utf" @@ -3109,9 +3194,9 @@ dependencies = [ [[package]] name = "pxfm" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3502d6155304a4173a5f2c34b52b7ed0dd085890326cb50fd625fdf39e86b3b" +checksum = "7186d3822593aa4393561d186d1393b3923e9d6163d3fbfd6e825e3e6cf3e6a8" dependencies = [ "num-traits", ] @@ -3323,6 +3408,15 @@ dependencies = [ "bitflags 2.10.0", ] +[[package]] +name = "redox_syscall" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec96166dafa0886eb81fe1c0a388bece180fbef2135f97c1e2cf8302e74b43b5" +dependencies = [ + "bitflags 2.10.0", +] + [[package]] name = "redox_users" version = "0.5.2" @@ -3365,9 +3459,9 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "reqwest" -version = "0.12.24" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64", "bytes", @@ -3517,6 +3611,15 @@ dependencies = [ "walkdir", ] +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "1.1.2" @@ -3545,9 +3648,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c" +checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" dependencies = [ "zeroize", ] @@ -3583,9 +3686,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "62049b2877bf12821e8f9ad256ee38fdc31db7387ec2d3b3f403024de2034aea" [[package]] name = "same-file" @@ -3706,9 +3809,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.146" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "217ca874ae0207aac254aa02c957ded05585a90892cc8d87f9e5fa49669dadd8" dependencies = [ "itoa", "memchr", @@ -3730,9 +3833,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" dependencies = [ "serde_core", ] @@ -3816,9 +3919,9 @@ dependencies = [ [[package]] name = "shell-words" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" +checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" [[package]] name = "shellexpand" @@ -3856,9 +3959,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] name = "simd_helpers" @@ -4007,9 +4110,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "supports-hyperlinks" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804f44ed3c63152de6a9f90acbea1a110441de43006ea51bcce8f436196a288b" +checksum = "e396b6523b11ccb83120b115a0b7366de372751aa6edf19844dfb13a6af97e91" [[package]] name = "syn" @@ -4340,9 +4443,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.8" +version = "0.9.10+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" dependencies = [ "indexmap", "serde_core", @@ -4355,27 +4458,27 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.3" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "tower" @@ -4408,9 +4511,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf146f99d442e8e68e585f5d798ccd3cad9a7835b917e09728880a862706456" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ "bitflags 2.10.0", "bytes", @@ -4472,9 +4575,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -4494,9 +4597,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -5516,9 +5619,9 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.5.2" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f06ae92f42f5e5c42443fd094f245eb656abf56dd7cce9b8b263236565e00f2" +checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3" [[package]] name = "zopfli" @@ -5592,9 +5695,9 @@ dependencies = [ [[package]] name = "zune-jpeg" -version = "0.5.5" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc6fb7703e32e9a07fb3f757360338b3a567a5054f21b5f52a666752e333d58e" +checksum = "51d915729b0e7d5fe35c2f294c5dc10b30207cc637920e5b59077bfa3da63f28" dependencies = [ "zune-core 0.5.0", ] diff --git a/Cargo.toml b/Cargo.toml index 8ba9cc50c..37c08bebf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ "libs/common", "libs/config", + "libs/format", "libs/lzo", "libs/p3d", "libs/paa", diff --git a/bin/Cargo.toml b/bin/Cargo.toml index 011301635..d593d86b4 100644 --- a/bin/Cargo.toml +++ b/bin/Cargo.toml @@ -22,6 +22,7 @@ bench = false hemtt-common = { path = "../libs/common" } hemtt-config = { path = "../libs/config", features = ["serde"] } +hemtt-format = { path = "../libs/format" } hemtt-p3d = { path = "../libs/p3d" } hemtt-paa = { path = "../libs/paa", features = ["generate"] } hemtt-pbo = { path = "../libs/pbo" } diff --git a/bin/dist/profile/autotest/ps_items.VR/functions/fnc_backpack.sqf b/bin/dist/profile/autotest/ps_items.VR/functions/fnc_backpack.sqf index b1ec8fc16..0dd82102a 100644 --- a/bin/dist/profile/autotest/ps_items.VR/functions/fnc_backpack.sqf +++ b/bin/dist/profile/autotest/ps_items.VR/functions/fnc_backpack.sqf @@ -1,12 +1,8 @@ -params [ - ["_class", "", [""]] -]; +params [["_class", "", [""]]]; if (_class == "") exitWith {}; -if (isNil "ps_cam") then { - ps_cam = "camera" camCreate [0,0,0]; -}; +if (isNil "ps_cam") then { ps_cam = "camera" camCreate [0, 0, 0]; }; ps_cam cameraEffect ["INTERNAL", "BACK"]; ps_cam camSetTarget objNull; diff --git a/bin/dist/profile/autotest/ps_items.VR/functions/fnc_firearm.sqf b/bin/dist/profile/autotest/ps_items.VR/functions/fnc_firearm.sqf index 0d2024fba..b22fb1b0a 100644 --- a/bin/dist/profile/autotest/ps_items.VR/functions/fnc_firearm.sqf +++ b/bin/dist/profile/autotest/ps_items.VR/functions/fnc_firearm.sqf @@ -1,27 +1,24 @@ -params [ - ["_class", "", [""]], - ["_pistol", false, [false]] -]; +params [["_class", "", [""]], ["_pistol", false, [false]]]; if (_class == "") exitWith {}; -if (isNil "ps_cam") then { - ps_cam = "camera" camCreate [0,0,0]; -}; +if (isNil "ps_cam") then { ps_cam = "camera" camCreate [0, 0, 0]; }; private _pos = getPos item_position; -private _holder = createVehicle ["groundweaponholder",_pos,[],0,"none"]; +private _holder = createVehicle ["groundweaponholder", _pos, [], 0, "none"]; _holder setPos _pos; -_holder addWeaponCargo [_class,1]; -_holder setVectorDirAndUp [[0,0,1],[1,0,0]]; - -private _fov = if (_pistol) then {0.3} else {0.7}; -private _campos = [_pos,0.5,90] call bis_fnc_relpos; -_campos set [2,_pos#2 + 0.5]; +_holder addWeaponCargo [_class, 1]; +_holder setVectorDirAndUp [[0, 0, 1], [1, 0, 0]]; + +private _fov = if (_pistol) then { + 0.3 +} else { 0.7 }; +private _campos = [_pos, 0.5, 90] call bis_fnc_relpos; +_campos set [2, _pos#2 + 0.5]; ps_cam camSetPos _campos; ps_cam camSetFov _fov; -ps_cam camSetTarget [_pos#0,_pos#1,_pos#2 + 0.57]; +ps_cam camSetTarget [_pos#0, _pos#1, _pos#2 + 0.57]; ps_cam camCommit 0; sleep 0.5; diff --git a/bin/dist/profile/autotest/ps_items.VR/functions/fnc_headgear.sqf b/bin/dist/profile/autotest/ps_items.VR/functions/fnc_headgear.sqf index c133fd707..462ec4d1d 100644 --- a/bin/dist/profile/autotest/ps_items.VR/functions/fnc_headgear.sqf +++ b/bin/dist/profile/autotest/ps_items.VR/functions/fnc_headgear.sqf @@ -1,26 +1,22 @@ -params [ - ["_class", "", [""]] -]; +params [["_class", "", [""]]]; if (_class == "") exitWith {}; -if (isNil "ps_cam") then { - ps_cam = "camera" camCreate [0,0,0]; -}; +if (isNil "ps_cam") then { ps_cam = "camera" camCreate [0, 0, 0]; }; private _pos = getPos item_position; -private _holder = createVehicle ["groundweaponholder",_pos,[],0,"none"]; +private _holder = createVehicle ["groundweaponholder", _pos, [], 0, "none"]; _holder setPos _pos; -_holder addWeaponCargo [_class,1]; -_holder setVectorDirAndUp [[0,0,1],[0,-1,0]]; +_holder addWeaponCargo [_class, 1]; +_holder setVectorDirAndUp [[0, 0, 1], [0, -1, 0]]; private _campos = [_pos, 2.5, 20] call bis_fnc_relpos; -_campos set [2,_pos#2 + 2.5]; +_campos set [2, _pos#2 + 2.5]; ps_cam cameraEffect ["INTERNAL", "BACK"]; ps_cam camSetPos _campos; ps_cam camSetFov 0.1; -ps_cam camSetTarget [_pos#0,_pos#1 + 0.425,_pos#2 + 1.3]; +ps_cam camSetTarget [_pos#0, _pos#1 + 0.425, _pos#2 + 1.3]; ps_cam camCommit 0; sleep 0.5; diff --git a/bin/dist/profile/autotest/ps_items.VR/functions/fnc_uniform.sqf b/bin/dist/profile/autotest/ps_items.VR/functions/fnc_uniform.sqf index 2cd6fbae4..444cf0ba4 100644 --- a/bin/dist/profile/autotest/ps_items.VR/functions/fnc_uniform.sqf +++ b/bin/dist/profile/autotest/ps_items.VR/functions/fnc_uniform.sqf @@ -1,12 +1,8 @@ -params [ - ["_uniform", "", [""]] -]; +params [["_uniform", "", [""]]]; if (_uniform == "") exitWith {}; -if (isNil "ps_cam") then { - ps_cam = "camera" camCreate [0,0,0]; -}; +if (isNil "ps_cam") then { ps_cam = "camera" camCreate [0, 0, 0]; }; ps_cam cameraEffect ["INTERNAL", "BACK"]; ps_cam camSetTarget objNull; diff --git a/bin/dist/profile/autotest/ps_items.VR/functions/fnc_vest.sqf b/bin/dist/profile/autotest/ps_items.VR/functions/fnc_vest.sqf index 5c863bcc8..378783a98 100644 --- a/bin/dist/profile/autotest/ps_items.VR/functions/fnc_vest.sqf +++ b/bin/dist/profile/autotest/ps_items.VR/functions/fnc_vest.sqf @@ -1,26 +1,22 @@ -params [ - ["_class", "", [""]] -]; +params [["_class", "", [""]]]; if (_class == "") exitWith {}; -if (isNil "ps_cam") then { - ps_cam = "camera" camCreate [0,0,0]; -}; +if (isNil "ps_cam") then { ps_cam = "camera" camCreate [0, 0, 0]; }; private _pos = getPos item_position; -private _holder = createVehicle ["groundweaponholder",_pos,[],0,"none"]; -_holder setPos _pos; -_holder addWeaponCargo [_class, 1]; -_holder setvectordirandup [[0.00173726,0.000167279,0.999998],[-0.995395,-0.0958456,0.00177588]]; - -private _campos = [_pos, 2, 90] call bis_fnc_relpos; -_campos set [2,_pos#2 + 1.2]; -ps_cam cameraEffect ["INTERNAL", "BACK"]; -ps_cam camSetPos _campos; -ps_cam camSetFov 0.4; -ps_cam camSetTarget [_pos#0,_pos#1 -0.2,_pos#2 + 0.9]; +private _holder = createVehicle ["groundweaponholder", _pos, [], 0, "none"]; +_holder setPos _pos; +_holder addWeaponCargo [_class, 1]; +_holder setvectordirandup [[0.00173726, 0.000167279, 0.999998], [-0.995395, -0.0958456, 0.00177588]]; + +private _campos = [_pos, 2, 90] call bis_fnc_relpos; +_campos set [2, _pos#2 + 1.2]; +ps_cam cameraEffect ["INTERNAL", "BACK"]; +ps_cam camSetPos _campos; +ps_cam camSetFov 0.4; +ps_cam camSetTarget [_pos#0, _pos#1 - 0.2, _pos#2 + 0.9]; ps_cam camCommit 0; sleep 0.5; diff --git a/bin/dist/profile/autotest/ps_items.VR/init.sqf b/bin/dist/profile/autotest/ps_items.VR/init.sqf index 7b890dd7d..73a0f8e94 100644 --- a/bin/dist/profile/autotest/ps_items.VR/init.sqf +++ b/bin/dist/profile/autotest/ps_items.VR/init.sqf @@ -9,7 +9,7 @@ addMissionEventHandler ["ExtensionCallback", { diag_log format ["%1: %2", _name, _function]; if (_name isEqualTo "hemtt_ps_items" || _name isEqualTo "hemtt_ps") then { switch (_function) do { - case "weapon_add": { + case "weapon_add" : { diag_log format ["Weapon: %1", _data]; if !(isClass (configFile >> "CfgWeapons" >> _data)) exitWith { "hemtt_comm" callExtension ["photoshoot:items:weapon_unsupported", [_data]]; @@ -53,17 +53,17 @@ addMissionEventHandler ["ExtensionCallback", { // "hemtt_comm" callExtension ["log", ["debug", format ["NVG: %1", _data]]]; // [_data] spawn ps_fnc_nvg; // }; - case 605: { + case 605 : { // Headgear "hemtt_comm" callExtension ["log", ["debug", format ["Headgear: %1", _data]]]; [_data] spawn ps_fnc_headgear; }; - case 701: { + case 701 : { // Vest "hemtt_comm" callExtension ["log", ["debug", format ["Vest: %1", _data]]]; [_data] spawn ps_fnc_vest; }; - case 801: { + case 801 : { // Uniform "hemtt_comm" callExtension ["log", ["debug", format ["Uniform: %1", _data]]]; [_data] spawn ps_fnc_uniform; @@ -74,11 +74,11 @@ addMissionEventHandler ["ExtensionCallback", { }; }; }; - case "vehicle_add": { + case "vehicle_add" : { private _type = getText (configFile >> "CfgVehicles" >> _data >> "vehicleClass"); "hemtt_comm" callExtension ["log", ["debug", format ["Type: %1", _type]]]; switch (_type) do { - case "Backpacks": { + case "Backpacks" : { "hemtt_comm" callExtension ["log", ["debug", format ["Backpack: %1", _data]]]; [_data] spawn ps_fnc_backpack; }; @@ -87,9 +87,7 @@ addMissionEventHandler ["ExtensionCallback", { }; }; }; - case "done": { - endMission "END1"; - }; + case "done" : { endMission "END1"; }; default { diag_log format ["Unknown: %1", _function]; "hemtt_comm" callExtension ["log", ["error", format ["Unknown: %1", _function]]]; @@ -107,7 +105,7 @@ showCinemaBorder false; diag_log format ["response: %1", "hemtt_comm" callExtension ["photoshoot:items:ready", []]]; if (isNil "ps_cam") then { - ps_cam = "camera" camCreate [0,0,0]; + ps_cam = "camera" camCreate [0, 0, 0]; showCinemaBorder false; }; }; diff --git a/bin/dist/profile/autotest/ps_previews.VR/fnc_export.sqf b/bin/dist/profile/autotest/ps_previews.VR/fnc_export.sqf index 6b441f21d..eb7a3d36a 100644 --- a/bin/dist/profile/autotest/ps_previews.VR/fnc_export.sqf +++ b/bin/dist/profile/autotest/ps_previews.VR/fnc_export.sqf @@ -3,55 +3,54 @@ disableSerialization; params [ - ["_delay",1,[0]], - ["_allVehicles",0,[0,""]], - ["_sides",[],[[]]], - ["_mods",[],[[]]], - ["_patches",[],[[]]], - ["_classes",[],[[]]] + ["_delay", 1, [0]], + ["_allVehicles", 0, [0, ""]], + ["_sides", [], [[]]], + ["_mods", [], [[]]], + ["_patches", [], [[]]], + ["_classes", [], [[]]] ]; _product = productVersion select 1; if (_allVehicles isEqualType "") then { _allVehicles = switch (toLower _allVehicles) do { - case "props": {-1}; - case "vehicles": {1}; - default {0}; + case "props": { -1 }; + case "vehicles": { 1 }; + default { 0 }; }; }; -_sides = +_sides; +_sides = + _sides; { - if (_x isEqualType sideUnknown) then {_sides set [_foreachindex,_x call bis_fnc_sideid];}; + if (_x isEqualType sideUnknown) then { _sides set [_foreachindex, _x call bis_fnc_sideid]; }; } forEach _sides; -if (count _sides == 0) then {_sides = [0,1,2,3,4,8];}; +if (count _sides == 0) then { _sides = [0, 1, 2, 3, 4, 8]; }; -_mods = +_mods; +_mods = + _mods; { - _mods set [_foreachindex,toLower _x]; + _mods set [_foreachindex, toLower _x]; } forEach _mods; _allMods = count _mods == 0; -_patches = +_patches; +_patches = + _patches; { - _patches set [_foreachindex,toLower _x]; + _patches set [_foreachindex, toLower _x]; } forEach _patches; _allPatches = count _patches == 0; -_classes = +_classes; +_classes = + _classes; { - _classes set [_foreachindex,toLower _x]; + _classes set [_foreachindex, toLower _x]; } forEach _classes; _allClasses = count _classes == 0; _cfgAll = configFile >> "cfgvehicles" >> "all"; -_restrictedModels = ["","\A3\Weapons_f\dummyweapon.p3d","\A3\Weapons_f\laserTgt.p3d","\A3\Structures_F\Mil\Helipads\HelipadEmpty_F.p3d"]; -_blacklist = [ - "WeaponHolder", - "LaserTarget", - "Bag_Base" +_restrictedModels = [ + "", "\A3\Weapons_f\dummyweapon.p3d", "\A3\Weapons_f\laserTgt.p3d", + "\A3\Structures_F\Mil\Helipads\HelipadEmpty_F.p3d" ]; +_blacklist = ["WeaponHolder", "LaserTarget", "Bag_Base"]; _dlcTable = []; _fnc_getDlc = { @@ -65,7 +64,7 @@ _fnc_getDlc = { if (count _dlcList > 0) then { _dlc = _dlcList select 0; { - if (_dlc == (_x select 0)) exitWith {_dlc = _x select 1;}; + if (_dlc == (_x select 0)) exitWith { _dlc = _x select 1; }; } forEach _dlcTable; }; }; @@ -112,48 +111,43 @@ _cfgVehicles = " " configClasses (configFile >> "cfgVehicles"); _cfgVehiclesCount = count _cfgVehicles; -if (_cfgVehiclesCount == 0) exitWith {["No classes found!"] call bis_fnc_error;}; +if (_cfgVehiclesCount == 0) exitWith { ["No classes found!"] call bis_fnc_error; }; _posZ = 250; _pos = [1024, 1024, _posZ]; _cam = "camera" camCreate _pos; -_cam cameraEffect ["internal","back"]; -_cam camPrepareFocus [-1,-1]; +_cam cameraEffect ["internal", "back"]; +_cam camPrepareFocus [-1, -1]; _cam camPrepareFOV 0.4; _cam camCommitPrepared 0; showCinemaBorder false; _sphereColor = "#(argb,8,8,3)color(0.93,1.0,0.98,0.1)"; -_sphereGround = createVehicle ["Sphere_3DEN",_pos,[],0,"none"]; -_sphereNoGround = createVehicle ["SphereNoGround_3DEN",_pos,[],0,"none"]; +_sphereGround = createVehicle ["Sphere_3DEN", _pos, [], 0, "none"]; +_sphereNoGround = createVehicle ["SphereNoGround_3DEN", _pos, [], 0, "none"]; { _x setPosATL _pos; _x setDir 0; - _x setObjectTexture [0,_sphereColor]; - _x setObjectTexture [1,_sphereColor]; + _x setObjectTexture [0, _sphereColor]; + _x setObjectTexture [1, _sphereColor]; _x hideObject true; -} forEach [_sphereGround,_sphereNoGround]; +} forEach [_sphereGround, _sphereNoGround]; setAperture 45; -setDate [2035,5,28,10,0]; +setDate [2035, 5, 28, 10, 0]; _display = [] call bis_fnc_displayMission; if (is3DEN) then { _display = findDisplay 313; - ["showinterface",false] call bis_fnc_3DENInterface; + ["showinterface", false] call bis_fnc_3DENInterface; }; // Original had text showing the path the image would be saved to _ctrlProgressH = 0.01; -_ctrlProgress = _display ctrlCreate ["RscProgress",-1]; -_ctrlProgress ctrlSetPosition [ - safezoneX, - safezoneY + safezoneH - _ctrlProgressH, - safezoneW, - _ctrlProgressH -]; +_ctrlProgress = _display ctrlCreate ["RscProgress", -1]; +_ctrlProgress ctrlSetPosition [safezoneX, safezoneY + safezoneH - _ctrlProgressH, safezoneW, _ctrlProgressH]; _ctrlProgress ctrlCommit 0; _screenTop = safezoneY; @@ -165,72 +159,72 @@ _screenRight = safezoneX + safezoneW; _class = configName _x; _dlc = _x call _fnc_getDlc; - if (_dlc != "") then {_dlc = _dlc + "\";}; - _fileName = format ["EditorPreviews\%2%1.png",_class,_dlc]; + if (_dlc != "") then { _dlc = _dlc + "\"; }; + _fileName = format ["EditorPreviews\%2%1.png", _class, _dlc]; _ctrlProgress progressSetPosition (_foreachindex / _cfgVehiclesCount); _camDirH = 135; _camDirV = 15; - _posLocal = +_pos; + _posLocal = + _pos; if (_class isKindOf "HeliH") then { - _posLocal set [2,0]; + _posLocal set [2, 0]; _camDirH = 90; _camDirV = 75; }; - _object = createVehicle [_class,_posLocal,[],0,"none"]; - if (_class isKindOf "allvehicles") then {_object setDir 90;} else {_object setDir 270;}; + _object = createVehicle [_class, _posLocal, [], 0, "none"]; + if (_class isKindOf "allvehicles") then { + _object setDir 90; + } else { _object setDir 270; }; if (primaryWeapon _object != "") then { _object switchMove "amovpercmstpslowwrfldnon" } else { if (handgunWeapon _object != "") then { _object switchMove "amovpercmstpslowwpstdnon"; - } else { - _object switchMove "amovpercmstpsnonwnondnon"; - }; + } else { _object switchMove "amovpercmstpsnonwnondnon"; }; }; _object setPosATL _posLocal; _object switchAction "default"; _timeCapture = time + _delay; if (_object isKindOf "FlagCarrierCore") then { - _object spawn {_this enableSimulation false;}; - } else { - _object enableSimulation false; - }; + _object spawn { _this enableSimulation false; }; + } else { _object enableSimulation false; }; _bbox = boundingBoxReal _object; _bbox1 = _bbox select 0; _bbox2 = _bbox select 1; _worldPositions = [ - _object modelToWorld [_bbox1 select 0,_bbox1 select 1,_bbox1 select 2], - _object modelToWorld [_bbox1 select 0,_bbox1 select 1,_bbox2 select 2], - _object modelToWorld [_bbox1 select 0,_bbox2 select 1,_bbox1 select 2], - _object modelToWorld [_bbox1 select 0,_bbox2 select 1,_bbox2 select 2], - _object modelToWorld [_bbox2 select 0,_bbox1 select 1,_bbox1 select 2], - _object modelToWorld [_bbox2 select 0,_bbox1 select 1,_bbox2 select 2], - _object modelToWorld [_bbox2 select 0,_bbox2 select 1,_bbox1 select 2], - _object modelToWorld [_bbox2 select 0,_bbox2 select 1,_bbox2 select 2] + _object modelToWorld [_bbox1 select 0, _bbox1 select 1, _bbox1 select 2], + _object modelToWorld [_bbox1 select 0, _bbox1 select 1, _bbox2 select 2], + _object modelToWorld [_bbox1 select 0, _bbox2 select 1, _bbox1 select 2], + _object modelToWorld [_bbox1 select 0, _bbox2 select 1, _bbox2 select 2], + _object modelToWorld [_bbox2 select 0, _bbox1 select 1, _bbox1 select 2], + _object modelToWorld [_bbox2 select 0, _bbox1 select 1, _bbox2 select 2], + _object modelToWorld [_bbox2 select 0, _bbox2 select 1, _bbox1 select 2], + _object modelToWorld [_bbox2 select 0, _bbox2 select 1, _bbox2 select 2] ]; _pointLowest = 0; _pointHighest = 0; { _xZ = (_x select 2) - _posZ; - if (_xZ > _pointHighest) then {_pointHighest = _xZ;}; - if (_xZ < _pointLowest) then {_pointLowest = _xZ;}; + if (_xZ > _pointHighest) then { _pointHighest = _xZ; }; + if (_xZ < _pointLowest) then { _pointLowest = _xZ; }; } forEach _worldPositions; - _sphere = if (abs(_pointLowest) > abs(_pointHighest * 2/3)) then {_sphereNoGround} else {_sphereGround}; + _sphere = if (abs (_pointLowest) > abs (_pointHighest * 2 / 3)) then { + _sphereNoGround + } else { _sphereGround }; _sphere hideObject false; _sphere setPos _pos; _camAngle = _camDirV; _camDis = (1.5 * ((sizeof _class) max 0.1)) min 124 max 0.2; - _camPos = [_posLocal,_camDis,_camDirH] call bis_fnc_relpos; - _camPos set [2,((_object modelToWorld [0,0,0]) select 2) + (tan _camAngle * _camDis)]; + _camPos = [_posLocal, _camDis, _camDirH] call bis_fnc_relpos; + _camPos set [2, ((_object modelToWorld [0,0,0]) select 2) + (tan _camAngle * _camDis)]; _cam camPreparePos _camPos; - _cam camPrepareTarget (_object modelToWorld [0,0,0]); - _cam camPrepareFocus [-1,-1]; + _cam camPrepareTarget (_object modelToWorld [0, 0, 0]); + _cam camPrepareFocus [-1, -1]; _cam camPrepareFOV 0.7; _cam camCommitPrepared 0; _cam camPreload 0; @@ -238,13 +232,15 @@ _screenRight = safezoneX + safezoneW; sleep 0.01; if (_class isKindOf "man" && !(_class isKindOf "animal")) then { - _cam camPrepareTarget (_object modelToWorld [0,0,1.25]); + _cam camPrepareTarget (_object modelToWorld [0, 0, 1.25]); _cam camPrepareFOV 0.075; _cam camCommitPrepared 0; } else { _cam camPrepareTarget _object; - _fovStart = if (_camDis < 0.35) then {0.4} else {0.1}; + _fovStart = if (_camDis < 0.35) then { + 0.4 + } else { 0.1 }; for "_f" from _fovStart to 0.7 step 0.01 do { _cam camPrepareFOV _f; _cam camCommitPrepared 0; @@ -252,31 +248,31 @@ _screenRight = safezoneX + safezoneW; _onScreen = true; { _screenPos = worldToScreen _x; - if (count _screenPos == 0) then {_screenPos = [10,10];}; - if (abs (linearConversion [_screenLeft,_screenRight,_screenPos select 0,-1,1]) > 1) exitwith {_onScreen = false;}; - if (abs (linearConversion [_screenTop,_screenBottom,_screenPos select 1,-1,1]) > 1) exitwith {_onScreen = false;}; + if (count _screenPos == 0) then { _screenPos = [10, 10]; }; + if (abs (linearConversion [_screenLeft, _screenRight, _screenPos select 0, -1, 1]) > 1) exitwith { _onScreen = false; }; + if (abs (linearConversion [_screenTop, _screenBottom, _screenPos select 1, -1, 1]) > 1) exitwith { _onScreen = false; }; } forEach _worldPositions; if (_onScreen) exitwith {}; }; }; - waituntil {time > _timeCapture}; + waituntil { time > _timeCapture }; screenshot _fileName; sleep 0.01; - _object setPos [10,10,10]; + _object setPos [10, 10, 10]; deleteVehicle _object; _sphere hideObject true; } forEach _cfgVehicles; -_cam cameraEffect ["terminate","back"]; +_cam cameraEffect ["terminate", "back"]; camDestroy _cam; deleteVehicle _sphereGround; deleteVehicle _sphereNoGround; -setAperture -1; +setAperture - 1; ctrlDelete _ctrlProgress; if (is3DEN) then { - get3DENCamera cameraEffect ["internal","back"]; - ["showInterface",true] call bis_fnc_3DENInterface; + get3DENCamera cameraEffect ["internal", "back"]; + ["showInterface", true] call bis_fnc_3DENInterface; }; diff --git a/bin/dist/profile/autotest/ps_previews.VR/init.sqf b/bin/dist/profile/autotest/ps_previews.VR/init.sqf index 4890d739b..d7d71058a 100644 --- a/bin/dist/profile/autotest/ps_previews.VR/init.sqf +++ b/bin/dist/profile/autotest/ps_previews.VR/init.sqf @@ -23,9 +23,7 @@ addMissionEventHandler ["ExtensionCallback", { "hemtt_comm" callExtension ["photoshoot:previews:done", []]; }; }; - case "done": { - endMission "END1"; - }; + case "done": { endMission "END1"; }; default { diag_log format ["Unknown: %1", _function]; "hemtt_comm" callExtension ["log", ["error", format ["Unknown: %1", _function]]]; diff --git a/bin/src/commands/format.rs b/bin/src/commands/format.rs new file mode 100644 index 000000000..3740cfcc7 --- /dev/null +++ b/bin/src/commands/format.rs @@ -0,0 +1,105 @@ +use hemtt_format::{CONFIG_EXTENSIONS, FormatterConfig}; + +#[derive(clap::Parser)] +/// Format Config and SQF files +pub struct Command {} + +const IGNORED_FOLDERES: &[&str] = &[".hemttout", ".git", "include/"]; + +/// Execute the format command +/// +/// # Errors +/// [`Error`] depending on the modules +/// +/// # Panics +/// If a name is not provided, but this is usually handled by clap +pub fn execute(_cmd: &Command) -> ! { + if !dialoguer::Confirm::new() + .with_prompt("This feature is experimental, are you sure you want to continue?") + .interact() + .unwrap_or_default() + { + println!("Aborting format command"); + std::process::exit(0); + } + + // Using git2, check for dirty files + match git2::Repository::discover(".") { + Ok(repo) => { + let statuses = repo + .statuses(Some(git2::StatusOptions::new().include_untracked(true))) + .expect("Failed to get git statuses"); + if !statuses.is_empty() { + println!("You have uncommitted changes in your working directory. Please commit or stash them before running the format command."); + std::process::exit(1); + } + } + Err(_) => { + println!("Not a git repository, proceeding with format command."); + } + } + + let mut count = 0; + let mut errors = 0; + for entry in walkdir::WalkDir::new(".") { + let entry = entry.expect("Failed to read directory entry"); + let path = entry.path(); + let path_string = path.display().to_string(); + if IGNORED_FOLDERES + .iter() + .any(|&folder| path_string.contains(folder)) + { + continue; + } + if path.is_file() { + let ext = path + .extension() + .unwrap_or_default() + .to_str() + .unwrap_or_default(); + if CONFIG_EXTENSIONS.contains(&ext) { + let content = fs_err::read_to_string(path) + .unwrap_or_else(|_| panic!("Failed to read file {}", path.display())); + match hemtt_format::format_config(&content, &FormatterConfig::default()) { + Ok(formatted) => { + if formatted != content { + fs_err::write(path, formatted).unwrap_or_else(|_| { + panic!("Failed to write file {}", path.display()) + }); + debug!("Formatted {}", path.display()); + count += 1; + } + } + Err(err) => { + error!("Failed to format {}: {}", path.display(), err); + errors += 1; + } + } + } + // if SQF_EXTENSIONS.contains(&ext) { + // let content = std::fs::read_to_string(path) + // .unwrap_or_else(|_| panic!("Failed to read file {}", path.display())); + // match hemtt_format::format_sqf(&content, &FormatterConfig::default()) { + // Ok(formatted) => { + // if formatted != content { + // std::fs::write(path, formatted).unwrap_or_else(|_| { + // panic!("Failed to write file {}", path.display()) + // }); + // debug!("Formatted {}", path.display()); + // count += 1; + // } + // } + // Err(err) => { + // error!("Failed to format {}: {}", path.display(), err); + // errors += 1; + // } + // } + // } + } + } + info!("Formatted {} files", count); + if errors > 0 { + error!("Encountered {} errors during formatting", errors); + } + std::process::exit(i32::from(errors > 0)) +} diff --git a/bin/src/commands/mod.rs b/bin/src/commands/mod.rs index 6d00c099f..2c08f5ca1 100644 --- a/bin/src/commands/mod.rs +++ b/bin/src/commands/mod.rs @@ -2,6 +2,7 @@ pub mod book; pub mod build; pub mod check; pub mod dev; +pub mod format; pub mod launch; pub mod license; pub mod localization; diff --git a/bin/src/lib.rs b/bin/src/lib.rs index 83bf65584..6b90e7147 100644 --- a/bin/src/lib.rs +++ b/bin/src/lib.rs @@ -56,6 +56,8 @@ enum Commands { Release(commands::release::Command), #[clap(alias = "ln")] Localization(commands::localization::Command), + #[clap(alias = "fmt")] + Format(commands::format::Command), Script(commands::script::Command), Utils(commands::utils::Command), Value(commands::value::Command), @@ -101,6 +103,7 @@ pub fn execute(cli: &Cli) -> Result<(), Error> { | Commands::License(_) | Commands::Manage(_) | Commands::Value(_) + | Commands::Format(_) ) ); @@ -146,6 +149,7 @@ pub fn execute(cli: &Cli) -> Result<(), Error> { Commands::Build(cmd) => commands::build::execute(cmd), Commands::Release(cmd) => commands::release::execute(cmd), Commands::Localization(cmd) => commands::localization::execute(cmd), + Commands::Format(cmd) => commands::format::execute(cmd), Commands::Script(cmd) => commands::script::execute(cmd), Commands::Utils(cmd) => commands::utils::execute(cmd), Commands::Value(cmd) => commands::value::execute(cmd), diff --git a/bin/tests/workspace_bad_script/addons/main/test.sqf b/bin/tests/workspace_bad_script/addons/main/test.sqf index c18b1eae0..22ff34620 100644 --- a/bin/tests/workspace_bad_script/addons/main/test.sqf +++ b/bin/tests/workspace_bad_script/addons/main/test.sqf @@ -6,6 +6,6 @@ y = "A" + toUpper "b"; _a = -5; _b = sqrt 2; -_c = sqrt -1; +_c = sqrt - 1; toUpper "🌭"; diff --git a/bin/tests/workspace_simple/addons/main/init.sqf b/bin/tests/workspace_simple/addons/main/init.sqf index 717ef058b..c91dae3e7 100644 --- a/bin/tests/workspace_simple/addons/main/init.sqf +++ b/bin/tests/workspace_simple/addons/main/init.sqf @@ -1,7 +1,7 @@ private _test = 0; { - _text = _test + _x; -} forEach [1,2,3,4]; + _text = _test + _x; +} forEach [1, 2, 3, 4]; systemChat str _text; diff --git a/hls/languages/config.json b/hls/languages/config.json index ccf200d05..568b14697 100644 --- a/hls/languages/config.json +++ b/hls/languages/config.json @@ -22,7 +22,7 @@ }, { "name": "meta.class.declaration.arma_config", - "match": "\\b(class)\\s+([a-zA-Z_][a-zA-Z0-9_]*)(?:\\s*\\:\\s*([a-zA-Z_][a-zA-Z0-9_]*))?", + "match": "\\b(class)\\s+([a-zA-Z0-9_]*)(?:\\s*\\:\\s*([a-zA-Z_][a-zA-Z0-9_]*))?", "captures": { "1": { "name": "storage.type.arma_config" diff --git a/libs/format/Cargo.toml b/libs/format/Cargo.toml new file mode 100644 index 000000000..2f5bde701 --- /dev/null +++ b/libs/format/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "hemtt-format" +version = "1.0.0" +edition = "2024" +description = "An formatting library for hemtt" +license = "GPL-2.0" + +[lints] +workspace = true + +[dependencies] +logos = "0.16.0" +fs-err = { workspace = true } + +[dev-dependencies] +insta = { workspace = true } +paste = { workspace = true } diff --git a/libs/format/src/config.rs b/libs/format/src/config.rs new file mode 100644 index 000000000..1acead474 --- /dev/null +++ b/libs/format/src/config.rs @@ -0,0 +1,883 @@ +use logos::Logos; + +use crate::FormatterConfig; + +/// Check if we're in a context where minus should be treated as a negative number in config files +fn is_negative_number_context(output: &str) -> bool { + output.ends_with('{') + || output.ends_with('[') + || output.ends_with('(') || output.ends_with("(-") + || output.ends_with("= ") || output.ends_with('=') || output.ends_with("= -") + || output.ends_with(", ") || output.ends_with(", -") + // Arithmetic operators - negative numbers can appear after them + || output.ends_with("+ ") || output.ends_with("+ -") + || output.ends_with("- ") || output.ends_with("- -") + || output.ends_with("* ") || output.ends_with("* -") + || output.ends_with("/ ") || output.ends_with("/ -") + || output.ends_with("% ") || output.ends_with("% -") +} + +/// Format nested braces with proper spacing +fn format_nested_braces(content: &str) -> String { + if content.starts_with('{') && content.ends_with('}') { + let inner = &content[1..content.len() - 1]; + let parts: Vec<&str> = inner.split(',').map(str::trim).collect(); + format!("{{{}}}", parts.join(", ")) + } else { + content.to_string() + } +} + +/// Parse config array items from content +fn parse_config_array_items(content: &str) -> Vec { + let mut items = Vec::new(); + let mut current_item = String::new(); + let mut brace_depth = 0; + let mut string_delimiter = None; + let mut chars = content.chars().peekable(); + + while let Some(ch) = chars.next() { + match ch { + '"' if string_delimiter.is_none() => { + current_item.push(ch); + string_delimiter = Some(ch); + } + '"' if Some(ch) == string_delimiter => { + current_item.push(ch); + // Check if this is an escaped quote (doubled) + if chars.peek() == Some(&ch) { + if let Some(escaped_ch) = chars.next() { + current_item.push(escaped_ch); + } + } else { + string_delimiter = None; + } + } + '{' if string_delimiter.is_none() => { + current_item.push(ch); + brace_depth += 1; + } + '}' if string_delimiter.is_none() => { + current_item.push(ch); + brace_depth -= 1; + } + ',' if string_delimiter.is_none() && brace_depth == 0 => { + let trimmed = current_item.trim(); + if !trimmed.is_empty() { + // Format nested braces with proper spacing + let formatted = if trimmed.starts_with('{') && trimmed.ends_with('}') { + format_nested_braces(trimmed) + } else { + trimmed.to_string() + }; + items.push(formatted); + } + current_item.clear(); + } + _ => { + current_item.push(ch); + } + } + } + + // Add the last item + let trimmed = current_item.trim(); + if !trimmed.is_empty() { + // Format nested braces with proper spacing + let formatted = if trimmed.starts_with('{') && trimmed.ends_with('}') { + format_nested_braces(trimmed) + } else { + trimmed.to_string() + }; + items.push(formatted); + } + + items +} + +/// Format config array content with intelligent line breaking +fn format_config_array_content( + content: &str, + cfg: &FormatterConfig, + indent_level: usize, +) -> String { + let items = parse_config_array_items(content); + + if items.is_empty() { + return String::new(); + } + + // Calculate one-line format + let one_line = items.join(", "); + let line_length = one_line.len(); + + // Use single-line format for short arrays + if line_length <= 100 { + return one_line; + } + + // Format with line breaks - try to fit multiple items per line like original + let mut result = String::from("\n"); + result.push_str(&cfg.indent(indent_level + 1)); + + let mut current_line_length = cfg.indent(indent_level + 1).len(); + let mut first_item = true; + + for item in items { + let item_length = if first_item { + item.len() + } else { + 2 + item.len() // ", " + item + }; + + // Start a new line if this item would make the line too long + if !first_item && current_line_length + item_length > 100 { + result.push_str(",\n"); + result.push_str(&cfg.indent(indent_level + 1)); + result.push_str(&item); + current_line_length = cfg.indent(indent_level + 1).len() + item.len(); + } else { + if !first_item { + result.push_str(", "); + } + result.push_str(&item); + current_line_length += item_length; + } + + first_item = false; + } + + result.push('\n'); + result +} + +#[derive(Logos, Debug, PartialEq)] +enum Token { + // Keywords and identifiers + #[regex(r"[A-Za-z_][A-Za-z0-9_]*")] + Ident, + + // Numbers (integers, decimals, hexadecimal, and scientific notation) + #[regex(r"0[xX][0-9a-fA-F]+|[0-9]+(\.[0-9]+)?([eE][+-]?[0-9]+)?")] + Number, + + // Strings + #[regex(r#""([^"]|"")*""#)] + DoubleString, + #[regex(r"'([^']|\\.)*'")] + SingleString, + + // Operators and punctuation + #[token("=")] + Eq, + #[token("+=")] + PlusEq, + #[token(";")] + Semi, + #[token(",")] + Comma, + #[token(":")] + Colon, + #[token("{")] + LBrace, + #[token("}")] + RBrace, + #[token("(")] + LParen, + #[token(")")] + RParen, + #[token("[")] + LBracket, + #[token("]")] + RBracket, + + // Arithmetic operators + #[token("+")] + Plus, + #[token("-")] + Minus, + #[token("*")] + Multiply, + #[token("/")] + Divide, + #[token("%")] + Modulo, + #[token(">>")] + Config, + #[token("&&")] + And, + #[token("||")] + Or, + #[token("&")] + Ampersand, + #[token("|")] + Pipe, + #[token("\\")] + Backslash, + #[token(".")] + Dot, + #[token(">")] + GreaterThan, + #[token("<")] + LessThan, + #[token("!")] + Not, + #[token("==")] + EqEq, + #[token("!=")] + NotEq, + #[token("^")] + Caret, + + // Preprocessor lines + #[regex(r"#.*?", priority = 2)] + Preprocessor, + + // Comments + #[regex(r"//[^\n]*?")] + LineComment, + #[regex(r"/\*([^*]|\*[^/])*\*/")] + BlockComment, + + // Whitespace tokens - we need to handle these to preserve empty lines + #[regex(r"[ \t]+")] + Space, + #[regex(r"\n")] + Newline, +} + +/// Copy a multiline define verbatim from the source, preserving all formatting +fn copy_multiline_define_verbatim(lexer: &mut logos::Lexer, first_line: &str) -> String { + let mut result = String::new(); + result.push_str(first_line.trim()); + + // Get the source text and current position + let source = lexer.source(); + let mut pos = lexer.span().end; + + // Continue copying lines until we find one that doesn't end with backslash + while pos < source.len() { + // Skip any immediate newline after the current token + if pos < source.len() && source.as_bytes()[pos] == b'\n' { + pos += 1; + } + + // Find the start and end of the next line + let line_start = pos; + while pos < source.len() && source.as_bytes()[pos] != b'\n' { + pos += 1; + } + + // Extract the line content (without the newline) + if let Some(line_content) = source.get(line_start..pos) { + result.push('\n'); + result.push_str(line_content); + + // Check if this line ends the multiline define + let trimmed = line_content.trim(); + if !trimmed.ends_with('\\') { + break; + } + } else { + break; + } + } + + // Update lexer position to continue after the multiline define + // Skip the newline if we're at one + if pos < source.len() && source.as_bytes()[pos] == b'\n' { + pos += 1; + } + + // Create new lexer from the remaining source + let remaining_source = &source[pos..]; + *lexer = Token::lexer(remaining_source); + + result +} + +#[allow(clippy::too_many_lines)] +/// Format a configuration file according to the provided `FormatterConfig`. +/// Returns the formatted string or an error message if formatting fails. +/// +/// # Errors +/// Returns an error if the input contains unexpected tokens. +pub fn format_config(source: &str, cfg: &FormatterConfig) -> Result { + let mut lexer = Token::lexer(source); + let mut output = String::new(); + let mut indent_level: usize = 0; + let mut need_indent = true; + let mut in_array = false; + let mut array_content = String::new(); + let mut brace_count = 0; + let mut consecutive_newlines = 0; + let mut after_paren = false; + let mut newline_since_last_token = false; + let mut paren_depth: usize = 0; // Track parentheses depth to detect macro calls + + while let Some(token) = lexer.next() { + let Ok(token) = token else { + let span = lexer.span(); + let error_text = lexer.source().get(span.clone()).unwrap_or("unknown"); + return Err(format!( + "Unexpected token '{}' at position {}..{}", + error_text, span.start, span.end + )); + }; + + match token { + Token::Space => { + // Skip spaces - we handle indentation ourselves + consecutive_newlines = 0; + } + Token::Newline => { + consecutive_newlines += 1; + newline_since_last_token = true; + // Preserve empty lines - when we see the second newline, it represents an empty line + if consecutive_newlines == 1 { + // First newline - end the current line if not already ended + if !output.ends_with('\n') { + output.push('\n'); + } + } else if consecutive_newlines >= 2 { + // Additional newlines represent empty lines + output.push('\n'); + } + } + Token::Preprocessor => { + consecutive_newlines = 0; + let preprocessor_line = lexer.slice(); + let (command, _) = preprocessor_line + .trim() + .split_once(' ') + .unwrap_or_else(|| (preprocessor_line.trim(), "")); + if ["#endif", "#else"].contains(&command) { + indent_level = indent_level.saturating_sub(1); + } + + // Add newline and indentation for all preprocessor directives + if !output.is_empty() && !output.ends_with('\n') { + output.push('\n'); + } + output.push_str(&cfg.indent(indent_level)); + + // Check if this is the start of a multiline define + if preprocessor_line.trim_start().starts_with("#define") + && preprocessor_line.trim().ends_with('\\') + { + // This is a multiline define - copy verbatim until it ends + let multiline_content = + copy_multiline_define_verbatim(&mut lexer, preprocessor_line); + output.push_str(&multiline_content); + output.push('\n'); + } else { + // Regular preprocessor directive + output.push_str(preprocessor_line.trim()); + } + + if ["#ifdef", "#ifndef", "#if", "#else"].contains(&command) { + indent_level += 1; + } + + output.push('\n'); + need_indent = true; + } + Token::LineComment => { + consecutive_newlines = 0; + + if need_indent || newline_since_last_token { + // Comment at beginning of line or after a newline + if newline_since_last_token && !output.ends_with('\n') { + // Start a new line for the comment only if we're not already on a new line + output.push('\n'); + } + output.push_str(&cfg.indent(indent_level)); + } else { + // True inline comment - on same line as previous content + output.push(' '); + } + output.push_str(lexer.slice().trim_end()); + output.push('\n'); + need_indent = true; + newline_since_last_token = false; + } + Token::BlockComment => { + consecutive_newlines = 0; + let comment = lexer.slice(); + // Check if this is a multi-line comment + let is_multiline = comment.contains('\n'); + + if is_multiline { + // Multi-line block comment: treat like a line comment + if need_indent { + output.push_str(&cfg.indent(indent_level)); + } else { + output.push(' '); + } + output.push_str(comment.trim_end()); + output.push('\n'); + need_indent = true; + } else { + // Inline block comment: keep on same line + if !need_indent { + output.push(' '); + } + output.push_str(comment.trim()); + need_indent = false; + } + } + Token::LBracket => { + consecutive_newlines = 0; + // Add space before [ when inside macros, but only for function-like calls + if paren_depth > 0 + && !output.ends_with(' ') + && !output.ends_with('(') + && should_add_space_before_bracket(&output) + { + output.push(' '); + } + output.push('['); + need_indent = false; + } + Token::RBracket => { + consecutive_newlines = 0; + output.push(']'); + need_indent = false; + } + Token::LBrace => { + consecutive_newlines = 0; + if in_array { + brace_count += 1; + array_content.push('{'); + } else { + // Check if this is starting an array by looking ahead + let remaining = lexer.remainder(); + // Don't add space before brace when inside macro calls (paren_depth > 0) + if cfg.space_before_brace && !output.ends_with(' ') && paren_depth == 0 { + output.push(' '); + } + output.push('{'); + if paren_depth == 0 && is_array_content(remaining) { + in_array = true; + array_content.clear(); + brace_count = 0; + } else if is_empty_class(remaining) { + need_indent = false; + } else { + // Check if there's a comment on the same line (only whitespace before //) + let trimmed_remaining = remaining.trim_start(); + if trimmed_remaining.starts_with("//") { + // There's a comment right after this brace on the same line + // Don't add newline, let comment handler manage the line ending + indent_level += 1; + need_indent = false; + } else if paren_depth == 0 { + // No immediate comment, proceed normally (but only when not in macro) + output.push('\n'); + indent_level += 1; + need_indent = true; + } else { + // Inside macro parentheses, don't add newline or change indent + need_indent = false; + } + } + } + } + Token::RBrace => { + consecutive_newlines = 0; + if in_array { + if brace_count > 0 { + brace_count -= 1; + array_content.push('}'); + } else { + // End of array, format using local array function + let formatted = + format_config_array_content(&array_content, cfg, indent_level); + + output.push_str(&formatted); + output.push('}'); + in_array = false; + need_indent = false; + } + } else { + // Check if this is an empty class (no newline before the brace) + if output.ends_with('{') { + // This is an empty class like "class Name {}" + output.push('}'); + need_indent = false; + } else if paren_depth == 0 { + // Normal class ending (but only when not in macro) + indent_level = indent_level.saturating_sub(1); + output.push_str(&cfg.indent(indent_level)); + output.push('}'); + output.push('\n'); + need_indent = true; + } else { + // Inside macro parentheses, just add the brace without formatting + output.push('}'); + need_indent = false; + } + } + } + Token::Semi => { + consecutive_newlines = 0; + newline_since_last_token = false; + if in_array { + // Check if this semicolon ends the array assignment + if brace_count == 0 { + // We're at the top level of the array, this semicolon ends the assignment + let formatted = + format_config_array_content(&array_content, cfg, indent_level); + output.push_str(&formatted); + output.push(';'); + in_array = false; + + // Look ahead to see if there's a comment on the same line + let remaining = lexer.remainder(); + let next_line_comment = remaining.trim_start().starts_with("//"); + + if next_line_comment { + // Don't add newline, let the comment handler decide + need_indent = false; + } else { + output.push('\n'); + need_indent = true; + } + } else { + // Semicolon inside nested braces, add to array content + array_content.push(';'); + } + } else { + // If the last character is '}', put semicolon on same line + if output.trim_end().ends_with('}') { + // Remove the trailing newline from the previous RBrace + let trimmed = output.trim_end(); + output.truncate(trimmed.len()); + } + output.push(';'); + + // Look ahead to see if there's a comment on the same line + let remaining = lexer.remainder(); + let next_line_comment = remaining.trim_start().starts_with("//"); + + if next_line_comment { + // Don't add newline, let the comment handler decide + need_indent = false; + } else if paren_depth == 0 { + // Only add newlines when we're not inside macro parentheses + output.push('\n'); + need_indent = true; + } else { + // Inside macro parentheses, add space instead of newline + output.push(' '); + need_indent = false; + } + after_paren = false; + } + } + Token::Comma => { + consecutive_newlines = 0; + if in_array { + array_content.push(','); + } else { + output.push(','); + + // Check if there's already a space in the source after this comma + let span = lexer.span(); + let source_after = &source[span.end..]; + + // Don't add spaces after commas inside macro calls (paren_depth > 0) + // For regular context, preserve original spacing + if paren_depth == 0 + && (source_after.starts_with(' ') || source_after.starts_with('\t')) + { + output.push(' '); + } + + need_indent = false; + // Don't set after_paren = false here, we're still inside parentheses + } + } + Token::Colon => { + consecutive_newlines = 0; + if in_array { + array_content.push(':'); + } else { + output.push(':'); + need_indent = false; + } + } + Token::LParen => { + consecutive_newlines = 0; + if in_array { + array_content.push('('); + } else { + // Add space before ( when inside macros, but only for function-like calls + if paren_depth > 0 + && !output.ends_with(' ') + && !output.ends_with('(') + && should_add_space_before_paren(&output) + { + output.push(' '); + } + output.push('('); + need_indent = false; + after_paren = true; + paren_depth += 1; + } + } + Token::RParen => { + consecutive_newlines = 0; + if in_array { + array_content.push(')'); + } else { + output.push(')'); + need_indent = false; + after_paren = false; + paren_depth = paren_depth.saturating_sub(1); + } + } + // Tokens that should not have spaces around them + Token::Backslash | Token::Dot | Token::Not => { + consecutive_newlines = 0; + if in_array { + array_content.push_str(lexer.slice()); + } else { + if need_indent { + output.push_str(&cfg.indent(indent_level)); + need_indent = false; + } + output.push_str(lexer.slice()); + } + } + // Arithmetic and logical operators - add spaces around them, except for negative numbers + Token::Plus + | Token::Multiply + | Token::Divide + | Token::Modulo + | Token::Config + | Token::And + | Token::Or + | Token::Ampersand + | Token::Caret + | Token::Pipe + | Token::EqEq + | Token::NotEq => { + consecutive_newlines = 0; + if in_array { + array_content.push_str(lexer.slice()); + } else { + if need_indent { + output.push_str(&cfg.indent(indent_level)); + need_indent = false; + } else if !output.ends_with(' ') { + // Add space before operator + output.push(' '); + } + output.push_str(lexer.slice()); + output.push(' '); // Add space after operator + newline_since_last_token = false; + after_paren = false; + } + } + Token::Minus => { + consecutive_newlines = 0; + if in_array { + array_content.push_str(lexer.slice()); + } else { + if need_indent { + output.push_str(&cfg.indent(indent_level)); + need_indent = false; + } else if is_negative_number_context(&output) { + // Negative number - no space before minus + } else if !output.ends_with(' ') { + // Arithmetic operation - add space before + output.push(' '); + } + output.push_str(lexer.slice()); + + // Check if this should have space after (arithmetic) or not (negative number) + let remaining = lexer.remainder().trim_start(); + let next_char = remaining.chars().next(); + if next_char.is_some_and(|c| c.is_ascii_digit()) { + // Next token is a digit - likely negative number, no space after + } else { + // Arithmetic operation - add space after + output.push(' '); + } + newline_since_last_token = false; + after_paren = false; + } + } + Token::Eq => { + consecutive_newlines = 0; + if in_array { + array_content.push_str(lexer.slice()); + } else { + if need_indent { + output.push_str(&cfg.indent(indent_level)); + need_indent = false; + } else if !(output.ends_with(' ') || output.ends_with('+')) { + // Add space before equals + output.push(' '); + } + output.push_str(lexer.slice()); + output.push(' '); // Always add space after equals + newline_since_last_token = false; + after_paren = false; + } + } + _ => { + consecutive_newlines = 0; + if in_array { + array_content.push_str(lexer.slice()); + } else { + if need_indent { + output.push_str(&cfg.indent(indent_level)); + need_indent = false; + } else if !matches!(token, Token::LBracket | Token::RBracket) { + // Check if we should add space before this token + let ends_with_minus = output.ends_with('-'); + let is_number = matches!(token, Token::Number); + let is_negative_number_context = + ends_with_minus && is_number && is_negative_number_context(&output); + + let should_add_space = !(output.ends_with(' ') + || output.ends_with('(') + || output.ends_with('[') + || output.ends_with('=') + || output.ends_with('{') + || output.ends_with(',') + || output.ends_with('\\') // Don't add space after backslash + || output.ends_with('.') // Don't add space after dot + || output.ends_with('!') // Don't add space after exclamation mark + // Special case: don't add space after minus when followed by a number (negative number) + || is_negative_number_context + // Don't add space before braces - let LBrace handler manage spacing + || matches!(token, Token::LBrace) + // Don't add space immediately after opening parenthesis (but preserve spacing inside macros) + || (after_paren && paren_depth == 0)); + + if should_add_space { + output.push(' '); + } + } + // Reset after_paren flag after processing one token + if after_paren { + after_paren = false; + } + output.push_str(lexer.slice()); + newline_since_last_token = false; + // Don't automatically set after_paren = false here + // Let it be managed by specific token handlers + } + } + } + } + + Ok(output.trim_end().to_string() + "\n") +} + +fn is_array_content(content: &str) -> bool { + // Heuristic: if it contains mostly numbers, commas, and braces, it's likely an array + let trimmed = content.trim(); + if trimmed.is_empty() { + return false; + } + + // Find the content up to the first unmatched closing brace + let mut brace_depth = 0; + let mut array_end = None; + let chars = trimmed.char_indices(); + + for (i, ch) in chars { + match ch { + '{' => brace_depth += 1, + '}' => { + if brace_depth == 0 { + array_end = Some(i); + break; + } + brace_depth -= 1; + } + _ => {} + } + } + + // Get just the content within the braces + let array_content = if let Some(end) = array_end { + &trimmed[..end] + } else { + return false; // No closing brace found + }; + + if array_content.is_empty() { + return false; + } + + // First check if this looks like SQF code rather than a config array + // SQF patterns that indicate this is not a config array: + if array_content.contains("&&") + || array_content.contains("||") + || array_content.contains(';') + || array_content.contains("isNotEqualTo") + || array_content.contains("select") + || array_content.contains("getArray") + || array_content.contains("getNumber") + || array_content.contains("params") + || array_content.contains("private") + || array_content.contains("format") + { + return false; // This looks like SQF code, not a config array + } + + // Check if the content looks like an array (numbers, letters for macros, commas, braces, quotes, parentheses, decimal points) + let chars = array_content.chars(); + let mut has_content = false; + + for ch in chars { + match ch { + 'A'..='Z' | 'a'..='z' | '0'..='9' | '_' | '{' | '}' | '"' | '(' | ')' | '.' => { + has_content = true; + } + ',' | ' ' | '\t' | '\n' => { + // These are fine + } + _ => return false, // Something else, not an array + } + } + + has_content +} + +fn should_add_space_before_bracket(output: &str) -> bool { + let trimmed = output.trim_end(); + trimmed + .rfind(|c: char| !c.is_alphanumeric() && c != '_') + .map_or_else( + || trimmed.chars().any(char::is_lowercase), + |last_word_start| { + let last_word = &trimmed[last_word_start + 1..]; + last_word.chars().any(char::is_lowercase) + }, + ) +} + +fn should_add_space_before_paren(output: &str) -> bool { + let trimmed = output.trim_end(); + trimmed + .rfind(|c: char| !c.is_alphanumeric() && c != '_') + .map_or_else( + || trimmed.chars().any(char::is_lowercase), + |last_word_start| { + let last_word = &trimmed[last_word_start + 1..]; + last_word.chars().any(char::is_lowercase) + }, + ) +} + +fn is_empty_class(content: &str) -> bool { + // Check if the content is empty (only whitespace before closing brace) + let trimmed = content.trim(); + trimmed.starts_with('}') +} diff --git a/libs/format/src/lib.rs b/libs/format/src/lib.rs new file mode 100644 index 000000000..d0a2073a7 --- /dev/null +++ b/libs/format/src/lib.rs @@ -0,0 +1,40 @@ +mod config; +mod sqf; + +pub use config::format_config; +pub use sqf::format_sqf; + +pub const CONFIG_EXTENSIONS: [&str; 5] = ["hpp", "cpp", "rvmat", "ext", "inc"]; +pub const SQF_EXTENSIONS: [&str; 2] = ["sqf", "fsm"]; + +#[derive(Debug)] +pub enum IndentStyle { + Space, + Tab, +} + +#[derive(Debug)] +pub struct FormatterConfig { + indent_style: IndentStyle, + indent_size: usize, + space_before_brace: bool, +} + +impl FormatterConfig { + fn indent(&self, level: usize) -> String { + match self.indent_style { + IndentStyle::Space => " ".repeat(self.indent_size * level), + IndentStyle::Tab => "\t".repeat(level), + } + } +} + +impl Default for FormatterConfig { + fn default() -> Self { + Self { + indent_style: IndentStyle::Space, + indent_size: 4, + space_before_brace: true, + } + } +} diff --git a/libs/format/src/sqf.rs b/libs/format/src/sqf.rs new file mode 100644 index 000000000..91ae6bfec --- /dev/null +++ b/libs/format/src/sqf.rs @@ -0,0 +1,1101 @@ +use logos::Logos; + +use crate::FormatterConfig; + +#[derive(Logos, Debug, PartialEq, Eq)] +pub enum SqfToken { + // Keywords and identifiers (SQF is case-insensitive, but we’ll preserve case) + #[regex(r"[A-Za-z_][A-Za-z0-9_]*")] + Ident, + + // Numbers (decimal, hexadecimal, and scientific notation) + #[regex(r"0x[0-9A-Fa-f]+|[0-9]+(\.[0-9]+)?([eE][+-]?[0-9]+)?")] + Number, + + // Strings - SQF style with proper escaping (doubled quotes) + #[regex(r#""([^"]|"")*""#)] + DoubleString, + #[regex(r"'([^']|'')*'")] + SingleString, + + // Operators and punctuation + #[token("=")] + Eq, + #[token(";")] + Semi, + #[token(",")] + Comma, + #[token(":")] + Colon, + #[token("{")] + LBrace, + #[token("}")] + RBrace, + #[token("(")] + LParen, + #[token(")")] + RParen, + #[token("[")] + LBracket, + #[token("]")] + RBracket, + + // Arithmetic operators + #[token("+")] + Plus, + #[token("-")] + Minus, + #[token("*")] + Multiply, + #[token("/")] + Divide, + #[token("%")] + Modulo, + #[token("^")] + Power, + + // Comparison operators + #[token("==")] + Equal, + #[token("!=")] + NotEqual, + #[token("<")] + Less, + #[token("<=")] + LessEqual, + #[token(">")] + Greater, + #[token(">=")] + GreaterEqual, + + // Logical operators + #[token("&&")] + And, + #[token("||")] + Or, + #[token("!")] + Not, + + // Other common SQF operators and symbols + #[token(">>")] + Config, + #[token(".")] + Dot, + #[token("?")] + Question, + #[token("@")] + At, + #[token("$")] + Dollar, + #[token("&")] + Ampersand, + #[token("|")] + Pipe, + #[token("~")] + Tilde, + #[token("`")] + Backtick, + #[token("#")] + Hash, + #[token("\\")] + Backslash, + + // Preprocessor lines (only at start of line with specific keywords) + #[regex( + r"#(include|define|ifdef|ifndef|endif|undef|if|else|elif|pragma).*?", + priority = 2 + )] + Preprocessor, + + // Comments + #[regex(r"//[^\n]*?")] + LineComment, + #[regex(r"/\*([^*]|\*[^/])*\*/")] + BlockComment, + + // Whitespace tokens - we need to handle these to preserve empty lines + #[regex(r"[ \t]+")] + Space, + #[regex(r"\n")] + Newline, +} + +#[allow(clippy::too_many_lines)] +/// Format SQF code according to the provided `FormatterConfig`. +/// Returns the formatted string or an error message if formatting fails. +/// +/// # Errors +/// Returns an error if the input contains unexpected tokens. +pub fn format_sqf(source: &str, cfg: &FormatterConfig) -> Result { + let mut lexer = SqfToken::lexer(source); + let mut output = String::new(); + let mut indent_level = 0; + let mut need_indent = true; + let mut consecutive_newlines = 0; + let mut bracket_depth: i32 = 0; + let mut paren_depth: i32 = 0; + let mut seen_newline_since_semi = true; // Track if we've seen a newline token since the last semicolon + + while let Some(token) = lexer.next() { + let Ok(token) = token else { + let span = lexer.span(); + let error_text = lexer.source().get(span.clone()).unwrap_or("unknown"); + return Err(format!( + "Unexpected token '{}' at position {}..{}", + error_text, span.start, span.end + )); + }; + match token { + SqfToken::Space => { + // Skip spaces - we handle indentation ourselves + consecutive_newlines = 0; + } + SqfToken::Newline => { + consecutive_newlines += 1; + seen_newline_since_semi = true; // We've seen a newline token + // Only add newlines to the output if we have actual content already + // This prevents leading newlines in the output + if !output.is_empty() { + // Add a newline to the output for the first newline (unless we already have one) + if consecutive_newlines == 1 && !output.ends_with('\n') { + output.push('\n'); + } + // Preserve empty lines - when we see the second newline, it represents an empty line + // For each newline after the first, add it to the output + if consecutive_newlines >= 2 { + output.push('\n'); + } + } + need_indent = true; + } + SqfToken::Preprocessor => { + consecutive_newlines = 0; + if !output.is_empty() && !output.ends_with('\n') { + output.push('\n'); + } + output.push_str(lexer.slice().trim_end()); + output.push('\n'); + need_indent = true; + } + SqfToken::LineComment => { + consecutive_newlines = 0; + + // Special case: if the comment appears right after a semicolon (without any newline tokens in between) + // then it should be treated as an end-of-line comment + if need_indent && output.ends_with(";\n") && !seen_newline_since_semi { + // Remove the newline and put the comment on the same line as the semicolon + output.pop(); // Remove the '\n' + output.push(' '); + } else if need_indent { + output.push_str(&cfg.indent(indent_level)); + } else if !output.ends_with(' ') { + output.push(' '); + } + output.push_str(lexer.slice().trim_end()); + output.push('\n'); + need_indent = true; + } + SqfToken::BlockComment => { + consecutive_newlines = 0; + let comment = lexer.slice(); + // Check if this is a multi-line comment + let is_multiline = comment.contains('\n'); + + if is_multiline { + // Multi-line block comment: treat like a line comment + if need_indent { + output.push_str(&cfg.indent(indent_level)); + } else { + output.push(' '); + } + output.push_str(comment.trim_end()); + output.push('\n'); + need_indent = true; + } else { + // Inline block comment: keep on same line + if !need_indent { + output.push(' '); + } + output.push_str(comment.trim()); + need_indent = false; + } + } + SqfToken::LBrace => { + consecutive_newlines = 0; + if need_indent { + // At beginning of line, add proper indentation + output.push_str(&cfg.indent(indent_level)); + } else if cfg.space_before_brace && !output.ends_with(' ') && !output.ends_with('[') + { + // In middle of line, add space if needed, but not after [ + output.push(' '); + } + + output.push('{'); + output.push('\n'); + indent_level += 1; + need_indent = true; + } + SqfToken::RBrace => { + consecutive_newlines = 0; + if !need_indent { + output.push('\n'); + } + indent_level = indent_level.saturating_sub(1); + output.push_str(&cfg.indent(indent_level)); + output.push('}'); + need_indent = false; + } + SqfToken::Semi => { + consecutive_newlines = 0; + // If the last character is '}', put semicolon on same line + if output.trim_end().ends_with('}') { + // Remove the trailing newline from the previous RBrace + let trimmed = output.trim_end(); + output.truncate(trimmed.len()); + } + output.push(';'); + output.push('\n'); + need_indent = true; + seen_newline_since_semi = false; // We haven't seen a newline token since this semicolon + } + SqfToken::Comma => { + consecutive_newlines = 0; + output.push(','); + + // Check if we're inside a macro call (all caps identifier) + let in_macro_call = is_in_macro_call(&output); + + // Context-aware comma spacing: + // - In macro calls: preserve original spacing + // - In arrays (bracket_depth > 0): always add space (unless in macro) + // - In function calls (paren_depth > 0): preserve original spacing + // - In other contexts: add space + if in_macro_call || paren_depth > 0 { + // Inside macros or function calls - preserve original spacing + let span = lexer.span(); + let source_after = &source[span.end..]; + if source_after.starts_with(' ') || source_after.starts_with('\t') { + output.push(' '); + } + } else if bracket_depth > 0 { + // Inside arrays - always add space for consistency + output.push(' '); + } else { + // Other contexts - add space + output.push(' '); + } + + need_indent = false; + } + // Minus operator - special handling for negative numbers + SqfToken::Minus => { + consecutive_newlines = 0; + if need_indent { + output.push_str(&cfg.indent(indent_level)); + } else if is_negative_number_context(&output) { + // Negative number - no space before minus + } else if !output.ends_with(' ') { + // Arithmetic operation - add space before + // Check if this is immediately after a LParen or LBracket + if output.ends_with('(') || output.ends_with('[') { + // Don't add space after minus in this context + } else { + output.push(' '); + } + } + output.push_str(lexer.slice()); + + // Check if this is likely a negative number (no space after) or arithmetic (space after) + let remaining = lexer.remainder().trim_start(); + let should_add_space = remaining.chars().next().is_none_or(|next_char| { + if next_char.is_ascii_digit() { + // Next token starts with digit - likely negative number, no space + false + } else if next_char == '-' { + // Check if this looks like "- -number" which should become "- -number" (no space) + let after_minus = remaining.strip_prefix('-').unwrap_or("").trim_start(); + !after_minus + .chars() + .next() + .is_some_and(|c| c.is_ascii_digit()) + } else { + // Arithmetic operation - add space after + true + } + }); + + if should_add_space { + output.push(' '); + } + need_indent = false; + } + // Modulo operator - special handling for format specifiers + SqfToken::Modulo => { + consecutive_newlines = 0; + + // Check if this is part of a format specifier (% followed by a number) + let remaining = lexer.remainder(); + let is_format_specifier = + remaining.chars().next().is_some_and(|c| c.is_ascii_digit()); + + if is_format_specifier { + // Format specifier like %1, %2 - no spaces around % + if need_indent { + output.push_str(&cfg.indent(indent_level)); + } + output.push_str(lexer.slice()); + } else { + // Regular modulo operator - add spaces around it + if need_indent { + output.push_str(&cfg.indent(indent_level)); + } else if !output.ends_with(' ') { + output.push(' '); + } + output.push_str(lexer.slice()); + output.push(' '); + } + need_indent = false; + } + // Arithmetic and comparison operators - add spaces around them + SqfToken::Plus + | SqfToken::Multiply + | SqfToken::Divide + | SqfToken::Power + | SqfToken::Equal + | SqfToken::NotEqual + | SqfToken::Less + | SqfToken::LessEqual + | SqfToken::Greater + | SqfToken::GreaterEqual + | SqfToken::And + | SqfToken::Or + | SqfToken::Eq + | SqfToken::Hash => { + consecutive_newlines = 0; + if need_indent { + output.push_str(&cfg.indent(indent_level)); + } else if !output.ends_with(' ') { + output.push(' '); + } + output.push_str(lexer.slice()); + output.push(' '); + need_indent = false; + } + // Left parenthesis - add space before if preceded by certain keywords + SqfToken::LParen => { + consecutive_newlines = 0; + paren_depth += 1; + if need_indent { + output.push_str(&cfg.indent(indent_level)); + } else if should_add_space_before_paren(&output) && !output.ends_with(' ') { + output.push(' '); + } + output.push_str(lexer.slice()); + need_indent = false; + } + // Left bracket - track depth for array formatting + SqfToken::LBracket => { + consecutive_newlines = 0; + if need_indent { + output.push_str(&cfg.indent(indent_level)); + } else if should_add_space_before_bracket(&output) && !output.ends_with(' ') { + // Add space before [ when preceded by certain keywords or in certain contexts + output.push(' '); + } + output.push('['); + bracket_depth += 1; + need_indent = false; + } + // Right bracket - track depth + SqfToken::RBracket => { + consecutive_newlines = 0; + bracket_depth = bracket_depth.saturating_sub(1); + output.push(']'); + need_indent = false; + } + // String literals - preserve them exactly but add spacing + SqfToken::DoubleString | SqfToken::SingleString => { + consecutive_newlines = 0; + if need_indent { + output.push_str(&cfg.indent(indent_level)); + } else if !output.ends_with(' ') + && !output.ends_with('(') + && !output.ends_with('[') + && !output.ends_with('=') + && !output.ends_with('{') + { + output.push(' '); + } + output.push_str(lexer.slice()); + need_indent = false; + } + // Dot and backslash - no spaces around them + SqfToken::Dot | SqfToken::Backslash => { + consecutive_newlines = 0; + if need_indent { + output.push_str(&cfg.indent(indent_level)); + } + output.push_str(lexer.slice()); + } + // Not operator - add space before it when following identifiers + SqfToken::Not => { + consecutive_newlines = 0; + if need_indent { + output.push_str(&cfg.indent(indent_level)); + } else if should_add_space_before_not(&output) && !output.ends_with(' ') { + output.push(' '); + } + output.push_str(lexer.slice()); + } + // Right parenthesis - might need space after it + SqfToken::RParen => { + consecutive_newlines = 0; + paren_depth -= 1; + if need_indent { + output.push_str(&cfg.indent(indent_level)); + } + output.push_str(lexer.slice()); + need_indent = false; + } + // No preceding space for colon + SqfToken::Colon => { + consecutive_newlines = 0; + if need_indent { + output.push_str(&cfg.indent(indent_level)); + } + output.push_str(lexer.slice()); + need_indent = false; + } + // Everything else - add space before if needed + _ => { + consecutive_newlines = 0; + // Special case for 'else' - should be on same line as previous '}' + if lexer.slice() == "else" && output.trim_end().ends_with('}') { + // Remove the previous newline if it exists + if output.ends_with('\n') { + output.pop(); + } + output.push(' '); + output.push_str(lexer.slice()); + need_indent = false; + } else if need_indent { + output.push_str(&cfg.indent(indent_level)); + need_indent = false; + output.push_str(lexer.slice()); + } else { + // Check if we should add space before this token + let ends_with_minus = output.ends_with('-'); + let is_number = matches!(token, SqfToken::Number); + let is_negative_number_context = + ends_with_minus && is_number && is_negative_number_context(&output); + + let should_add_space = !(output.ends_with(' ') + || output.ends_with('(') + || output.ends_with('[') + || output.ends_with('=') + || output.ends_with('{') + || output.ends_with(',') && paren_depth > 0 + || output.ends_with('\\') // Don't add space after backslash + || output.ends_with('.') // Don't add space after dot + || output.ends_with('!') // Don't add space after bang + || output.ends_with('%') // Don't add space after % (for format specifiers like %1) + || (output.len() >= 2 && output.ends_with(|c: char| c.is_ascii_digit()) && output.chars().rev().nth(1) == Some('%')) // Don't add space after format specifiers like %1 + // Special case: don't add space after minus when followed by a number (negative number) + || is_negative_number_context); + + if should_add_space { + output.push(' '); + } + output.push_str(lexer.slice()); + } + } + } + } + + // Post-process to format arrays nicely + let formatted = post_process_arrays(&output, cfg); + + // Post-process to fix negative numbers (remove spaces) - do this first + let formatted = post_process_negative_numbers(&formatted); + + // Post-process to convert simple blocks to single lines + let formatted = post_process_single_line_blocks(&formatted); + + Ok(formatted.trim_end().to_string() + "\n") +} + +fn should_add_space_before_paren(output: &str) -> bool { + // Use naming conventions: macros (all caps) don't get spaces, functions do + if let Some(last_word) = output.split_whitespace().last() { + // Handle case where last_word might have non-identifier chars at the end + // Extract just the identifier part from the end + let mut identifier = String::new(); + for ch in last_word.chars().rev() { + if ch.is_alphanumeric() || ch == '_' { + identifier.insert(0, ch); + } else { + break; + } + } + + if identifier.is_empty() { + return false; + } + + // Check if the identifier is all uppercase (macro convention) + if identifier + .chars() + .all(|c| c.is_uppercase() || c.is_numeric() || c == '_') + { + return false; // Macros like CSTRING() don't get spaces + } + + // Check if it's a valid identifier that could be a function + if identifier.chars().all(|c| c.is_alphanumeric() || c == '_') && !identifier.is_empty() { + return true; // Functions like getText() get spaces + } + } + + false +} + +fn should_add_space_before_not(output: &str) -> bool { + // Don't add space if we're at the start or already have space + if output.is_empty() || output.ends_with(' ') { + return false; + } + + // Don't add space if the last character is not alphanumeric or underscore + // This covers operators, punctuation, etc. + if let Some(last_char) = output.chars().last() + && !(last_char.is_alphanumeric() || last_char == '_') + { + return false; + } + + // Default: add space after identifiers + true +} + +fn is_in_macro_call(output: &str) -> bool { + // Look for the last opening parenthesis and check if it's preceded by an all-caps identifier + if let Some(last_paren_pos) = output.rfind('(') { + // Get the text before the last opening parenthesis + let before_paren = &output[..last_paren_pos]; + + // Extract the last identifier (word) before the parenthesis + if let Some(last_word_start) = + before_paren.rfind(|c: char| !c.is_alphanumeric() && c != '_') + { + let identifier = &before_paren[last_word_start + 1..]; + + // Check if the identifier is all uppercase (macro convention) + if !identifier.is_empty() + && identifier + .chars() + .all(|c| c.is_uppercase() || c.is_numeric() || c == '_') + && identifier.chars().any(char::is_alphabetic) + // Must contain at least one letter + { + return true; + } + } else { + // No non-alphanumeric character found, so the identifier starts at the beginning + let identifier = before_paren; + if !identifier.is_empty() + && identifier + .chars() + .all(|c| c.is_uppercase() || c.is_numeric() || c == '_') + && identifier.chars().any(char::is_alphabetic) + // Must contain at least one letter + { + return true; + } + } + } + + false +} + +fn should_add_space_before_bracket(output: &str) -> bool { + // Don't add space if we're at the start, already have space, or after certain characters + if output.is_empty() || output.ends_with(' ') || output.ends_with('(') || output.ends_with('[') + { + return false; + } + + // Don't add space if the last character is not alphanumeric or underscore + // This covers operators, punctuation, etc. + if let Some(last_char) = output.chars().last() + && !(last_char.is_alphanumeric() || last_char == '_') + { + return false; + } + + // Default: add space after identifiers + true +} + +fn post_process_arrays(input: &str, cfg: &FormatterConfig) -> String { + let mut result = String::new(); + let mut chars = input.chars().peekable(); + + while let Some(ch) = chars.next() { + result.push(ch); + if ch == '[' { + // Found start of array, process it + let array_content = extract_array_content(&mut chars); + + // Calculate current indentation level from the result + let current_indent_level = calculate_current_indent_level(&result, cfg); + + let formatted_array = + format_array_content_with_context(&array_content, cfg, current_indent_level); + result.push_str(&formatted_array); + result.push(']'); + } + } + + result +} + +fn extract_array_content(chars: &mut std::iter::Peekable) -> String { + let mut content = String::new(); + let mut bracket_depth = 1; + let mut in_string = false; + let mut string_delimiter = None; + + while let Some(ch) = chars.next() { + match ch { + '"' | '\'' if string_delimiter.is_none() => { + content.push(ch); + in_string = true; + string_delimiter = Some(ch); + } + '"' | '\'' if Some(ch) == string_delimiter => { + content.push(ch); + // Check for escaped quotes (doubled) + if chars.peek() == Some(&ch) { + if let Some(escaped_ch) = chars.next() { + content.push(escaped_ch); + } + } else { + in_string = false; + string_delimiter = None; + } + } + '[' if !in_string => { + content.push(ch); + bracket_depth += 1; + } + ']' if !in_string => { + bracket_depth -= 1; + if bracket_depth == 0 { + break; + } + content.push(ch); + } + _ => { + content.push(ch); + } + } + } + + content +} + +fn format_array_content_with_context( + content: &str, + cfg: &FormatterConfig, + base_indent_level: usize, +) -> String { + if content.trim().is_empty() { + return String::new(); + } + + // if the array contains comments, don't format it + // if content.contains("//") || content.contains("/*") { + // let mut ret = String::new(); + // let mut first = true; + // for line in content.lines() { + // if !line.starts_with(' ') { + // ret.push_str(&cfg.indent(base_indent_level + 1)); + // } else if first { + // ret.push(' '); + // } + // ret.push_str(line.trim()); + // ret.push('\n'); + // first = false; + // } + // ret.push_str(&cfg.indent(base_indent_level)); + // return ret; + // } + + let items = parse_array_items_simple(content); + + if items.is_empty() { + return String::new(); + } + + // Calculate one-line format + let one_line = items.join(", "); + let line_length = one_line.len(); + + // Use single-line format for short arrays (like config formatter) + if line_length <= 80 && items.len() <= 10 { + return one_line; + } + + // Check if this array contains code blocks - if so, keep it horizontally compact + let has_code_blocks = items.iter().any(|item| { + let trimmed = item.trim(); + trimmed.contains('{') && trimmed.contains('}') // Contains code blocks + }); + + if has_code_blocks { + // For arrays with code blocks, keep the original compact format + return one_line; + } + + // Check if this array should use multi-line formatting for nested arrays only + let has_many_nested_arrays = items.len() > 5 + && items.iter().any(|item| { + let trimmed = item.trim(); + trimmed.starts_with('[') && trimmed.ends_with(']') // Nested arrays + }); + + let mut result = String::new(); + result.push('\n'); + if has_many_nested_arrays { + // Format complex arrays with each item on its own line + for (i, item) in items.iter().enumerate() { + result.push_str(&cfg.indent(base_indent_level + 1)); + result.push_str(item.trim()); + if i < items.len() - 1 { + result.push(','); + } + result.push('\n'); + } + } else { + // Format simple arrays with line-length-based packing + result.push_str(&cfg.indent(base_indent_level + 1)); + + let mut current_line_length = cfg.indent(base_indent_level + 1).len(); + let mut first_item = true; + + for item in items { + let item_length = if first_item { + item.len() + } else { + 2 + item.len() // ", " + item + }; + + // Start a new line if this item would make the line too long + if !first_item && current_line_length + item_length > 80 { + result.push_str(",\n"); + result.push_str(&cfg.indent(base_indent_level + 1)); + result.push_str(item.trim()); + current_line_length = cfg.indent(base_indent_level + 1).len() + item.len(); + } else { + if !first_item { + result.push_str(", "); + } + result.push_str(item.trim()); + current_line_length += item_length; + } + + first_item = false; + } + + result.push('\n'); + } + result.push_str(&cfg.indent(base_indent_level)); + result +} + +fn calculate_current_indent_level(result: &str, _cfg: &FormatterConfig) -> usize { + // Get the current line (everything after the last newline) + let current_line = result.lines().last().unwrap_or(""); + + // Count leading spaces/tabs and convert to indent level + let leading_spaces = current_line.len() - current_line.trim_start().len(); + + // Assume 4 spaces per indent level (standard) + leading_spaces / 4 +} + +fn parse_array_items_simple(content: &str) -> Vec { + let mut items = Vec::new(); + let mut current_item = String::new(); + let mut bracket_depth = 0; + let mut in_string = false; + let mut string_delimiter = None; + + for ch in content.chars() { + match ch { + '"' | '\'' if string_delimiter.is_none() => { + current_item.push(ch); + in_string = true; + string_delimiter = Some(ch); + } + '"' | '\'' if Some(ch) == string_delimiter => { + current_item.push(ch); + in_string = false; + string_delimiter = None; + } + '[' | '{' | '(' if !in_string => { + current_item.push(ch); + bracket_depth += 1; + } + ']' | '}' | ')' if !in_string => { + current_item.push(ch); + bracket_depth -= 1; + } + ',' if !in_string && bracket_depth == 0 => { + if !current_item.trim().is_empty() { + items.push(current_item.trim().to_string()); + } + current_item.clear(); + } + _ => { + current_item.push(ch); + } + } + } + + if !current_item.trim().is_empty() { + items.push(current_item.trim().to_string()); + } + + items +} + +/// Post-process to convert simple multi-line blocks to single lines +fn post_process_single_line_blocks(input: &str) -> String { + let mut result = String::new(); + let lines: Vec<&str> = input.lines().collect(); + let mut i = 0; + + while i < lines.len() { + let line = lines[i]; + + // Look for pattern: line ending with { followed by content line and } + if line.trim_end().ends_with('{') { + // Check for empty block first: { followed by } + if i + 1 < lines.len() { + let next_line = lines[i + 1]; + + // Handle empty blocks: "prefix {" + "};" -> "prefix {};" + if next_line.trim() == "};" { + let prefix = line.trim_end().strip_suffix('{').unwrap_or("").trim(); + // Preserve original indentation + let indent = get_line_indent(line); + result.push_str(&indent); + result.push_str(prefix); + result.push_str(" {};\n"); + i += 2; // Skip the closing brace line + continue; + } + } + + // Check if we have a simple single-line block + if i + 2 < lines.len() { + let content_line = lines[i + 1]; + let end_line = lines[i + 2]; + + let end_line_trimmed = end_line.trim(); + if is_simple_block_content(content_line) + && (end_line_trimmed == "};" || end_line_trimmed.starts_with("}; ")) + { + // Convert to single line: preserve indentation + "prefix { content };" + let prefix = line.trim_end().strip_suffix('{').unwrap_or("").trim(); + let content = content_line.trim(); + let indent = get_line_indent(line); + result.push_str(&indent); + result.push_str(prefix); + result.push_str(" { "); + result.push_str(content); + result.push(' '); + result.push_str(end_line_trimmed); // Include the full line with comments + result.push('\n'); + i += 3; // Skip the content and closing brace lines + continue; + } else if is_simple_block_content(content_line) && end_line.trim() == "}" { + // Convert to single line without semicolon: preserve indentation + "prefix { content }" + let prefix = line.trim_end().strip_suffix('{').unwrap_or("").trim(); + let content = content_line.trim(); + let indent = get_line_indent(line); + result.push_str(&indent); + result.push_str(prefix); + result.push_str(" { "); + result.push_str(content); + result.push_str(" }\n"); + i += 3; // Skip the content and closing brace lines + continue; + } + } + } + + // Not a simple block, keep the line as-is + result.push_str(line); + result.push('\n'); + i += 1; + } + + result +} + +/// Extract the indentation (leading whitespace) from a line +fn get_line_indent(line: &str) -> String { + line.chars().take_while(|c| c.is_whitespace()).collect() +} + +/// Check if a line contains simple content suitable for single-line blocks +fn is_simple_block_content(line: &str) -> bool { + let content = line.trim(); + + // Should not be empty + if content.is_empty() { + return false; + } + + // Should be reasonably short + if content.len() > 60 { + return false; + } + + // Should not contain multiple statements (no internal semicolons) + if content.matches(';').count() > 1 { + return false; + } + + // Should not contain nested braces + if content.contains('{') || content.contains('}') { + return false; + } + + true +} + +/// Check if we're in a context where minus should be treated as a negative number +fn is_negative_number_context(output: &str) -> bool { + // When processing a number after a minus, check if the minus was in a negative number context + if output.ends_with('-') { + // Get the context before the minus + if let Some(before_minus) = output.strip_suffix('-') { + // Check if the context before the minus indicates negative number + let before_context_indicates_negative = before_minus.ends_with('{') + || before_minus.ends_with('[') + || before_minus.ends_with('(') + || before_minus.ends_with("= ") || before_minus.ends_with('=') + || before_minus.ends_with("== ") || before_minus.ends_with("==") + || before_minus.ends_with("!= ") || before_minus.ends_with("!=") + || before_minus.ends_with("< ") || before_minus.ends_with('<') + || before_minus.ends_with("> ") || before_minus.ends_with('>') + || before_minus.ends_with("<= ") || before_minus.ends_with("<=") + || before_minus.ends_with(">= ") || before_minus.ends_with(">=") + || before_minus.ends_with("&& ") || before_minus.ends_with("&&") + || before_minus.ends_with("|| ") || before_minus.ends_with("||") + || before_minus.ends_with(", ") + || before_minus.ends_with("return ") + || before_minus.ends_with("exitWith ") + || before_minus.ends_with("+ ") + || before_minus.ends_with("- ") // "- -" case + || before_minus.ends_with("* ") + || before_minus.ends_with("/ ") + || before_minus.ends_with("% "); + + if before_context_indicates_negative { + return true; + } + } + } + + // Check for arithmetic operators, but exclude "digit -" pattern + if output.ends_with("+ ") + || output.ends_with("* ") + || output.ends_with("/ ") + || output.ends_with("% ") + { + return true; + } + + // Special handling for minus: allow "- -" and "--" but not "digit -" + if output.ends_with("- ") { + // Check if it's "digit -" pattern (should NOT be negative context) + if output.len() >= 3 { + let before_space_minus = output.chars().nth(output.len() - 3); + if let Some(ch) = before_space_minus + && ch.is_ascii_digit() + { + return false; // "digit -" is not negative context + } + } + return true; // Other "- " patterns are negative context + } + + // Special case: double minus (arithmetic minus followed by negative minus) + if output.ends_with("--") { + return true; + } + + false +} + +/// Post-process to fix negative numbers by removing spaces between - and digits +fn post_process_negative_numbers(input: &str) -> String { + let mut result = String::with_capacity(input.len()); + let chars: Vec = input.chars().collect(); + let mut i = 0; + + while i < chars.len() { + let ch = chars[i]; + result.push(ch); + + // Look for pattern: "- " followed by digits (potential negative number) + if ch == '-' && i + 1 < chars.len() && chars[i + 1] == ' ' { + // Check if next non-space character is a digit + let mut j = i + 1; + while j < chars.len() && chars[j] == ' ' { + j += 1; + } + + if j < chars.len() && chars[j].is_ascii_digit() { + // Check if we're in a negative number context by looking backward + if is_negative_context_backward(&result) { + // Skip the space(s) and continue - don't add the spaces to result + i = j - 1; // Will be incremented at end of loop + } + } + } + + i += 1; + } + + result +} + +/// Check if the context before the minus suggests this is a negative number +fn is_negative_context_backward(text_before_minus: &str) -> bool { + let trimmed = text_before_minus.trim_end_matches('-').trim_end(); + + // If the context ends with an arithmetic operator, this is likely a binary operator, not a negative number + if trimmed.ends_with('+') + || trimmed.ends_with('-') + || trimmed.ends_with('*') + || trimmed.ends_with('/') + || trimmed.ends_with('%') + { + return false; + } + + // Common contexts where negative numbers appear + trimmed.ends_with('=') + || trimmed.ends_with('{') + || trimmed.ends_with('[') + || trimmed.ends_with('(') + || trimmed.ends_with(',') + || trimmed.ends_with("return") + || trimmed.ends_with("exitWith") +} diff --git a/libs/format/tests/config.rs b/libs/format/tests/config.rs new file mode 100644 index 000000000..fdbf9b996 --- /dev/null +++ b/libs/format/tests/config.rs @@ -0,0 +1,43 @@ +use hemtt_format::{FormatterConfig, format_config}; + +const ROOT: &str = "tests/config/"; + +macro_rules! config { + ($dir:ident) => { + paste::paste! { + #[test] + fn []() { + insta::assert_snapshot!(config(stringify!($dir))); + } + } + }; +} + +config!(array); +config!(basic); +config!(classes); +config!(comments); +config!(eject); +config!(empty); +config!(if); +config!(include); +config!(invalid); +config!(macro_arr2); +config!(macro_dogtag); +config!(macro_hash); +config!(macro_path); +config!(macro_sqf); +config!(macro); +config!(math); +config!(nested); +config!(numbers); +config!(parent); +config!(quotes); + +fn config(file: &str) -> String { + format_config( + &fs_err::read_to_string(format!("{ROOT}{file}.hpp")).expect("Failed to read test file"), + &FormatterConfig::default(), + ) + .unwrap_or_else(|err| format!("ERROR: {err}")) +} diff --git a/libs/format/tests/config/array.hpp b/libs/format/tests/config/array.hpp new file mode 100644 index 000000000..29b4a9866 --- /dev/null +++ b/libs/format/tests/config/array.hpp @@ -0,0 +1,6 @@ +short[] = {1,2,3,4}; +long[] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100}; +nested[] = {{1,2},{3,4}}; +longNested[] = {{1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15},{16,17,18,19,20},{21,22,23,24,25},{26,27,28,29,30},{31,32,33,34,35},{36,37,38,39,40}}; +float[] = {0.8, 1}; +append[] += {5,6,7}; diff --git a/libs/format/tests/config/basic.hpp b/libs/format/tests/config/basic.hpp new file mode 100644 index 000000000..e74b735b9 --- /dev/null +++ b/libs/format/tests/config/basic.hpp @@ -0,0 +1,3 @@ +class MyClass{ +#define VALUE 5 +value=5;class Nested{nestedValue=1;};}; diff --git a/libs/format/tests/config/classes.hpp b/libs/format/tests/config/classes.hpp new file mode 100644 index 000000000..9dd96e673 --- /dev/null +++ b/libs/format/tests/config/classes.hpp @@ -0,0 +1,18 @@ +class Basic { + value = 1; +}; +class Child: Basic { + value = 1.02; +}; +class External; +class Empty {}; +class Nested { + class Inner { + innerValue = 10; + }; +}; +class 2Values { + value1 = 1; + value2 = 2; +}; +class EmptyChild: Empty {}; diff --git a/libs/format/tests/config/comments.hpp b/libs/format/tests/config/comments.hpp new file mode 100644 index 000000000..157b49b3c --- /dev/null +++ b/libs/format/tests/config/comments.hpp @@ -0,0 +1,34 @@ +value = 1; // Comment after value +string /* Comment after ident */ = "test"; // Another comment +/*class Weapon_launch_Titan_F: Launcher_Base_F { + displayName = CSTRING(launch_Titan_Name); +}; + +class Weapon_launch_Titan_short_F: Launcher_Base_F { + displayName = CSTRING(launch_Titan_short_Name); +}; + +class Weapon_launch_B_Titan_F: Launcher_Base_F { + displayName = CSTRING(launch_Titan_Name); +};*/ +value = 2; +class MyClass { // Comment after class + value = 1; // Comment in class + class External; // Comment after external + class MyClass { // Comment after nested class + value = 2; // Comment in nested class + }; + // Comment after class + + class AnotherClass { + value = 0; + }; + + // Comment, but after a newline +}; + +class MyClass2 { + class Child: Parent { // Comment after child + value = 3; // Comment in child + }; +}; diff --git a/libs/format/tests/config/eject.hpp b/libs/format/tests/config/eject.hpp new file mode 100644 index 000000000..a15b9e9a1 --- /dev/null +++ b/libs/format/tests/config/eject.hpp @@ -0,0 +1,7 @@ +class CfgActions { + class None; + class Eject: None { + show = QUOTE(call compile getText (configFile >> 'CfgActions' >> 'Eject' >> 'GVAR(setting)')); + GVAR(setting) = QUOTE(profileNamespace getVariable [ARR_2('GVAR(showEjectAction)',0)]); + }; +}; diff --git a/libs/format/tests/config/empty.hpp b/libs/format/tests/config/empty.hpp new file mode 100644 index 000000000..7dd8a7401 --- /dev/null +++ b/libs/format/tests/config/empty.hpp @@ -0,0 +1,4 @@ +class Parent { + class FirstEmpty {}; + class SecondEmpty {}; +}; diff --git a/libs/format/tests/config/if.hpp b/libs/format/tests/config/if.hpp new file mode 100644 index 000000000..a38944bea --- /dev/null +++ b/libs/format/tests/config/if.hpp @@ -0,0 +1,6 @@ +#ifdef THING +value = "thing was defined"; +#else +#define NOTHING +value = "thing was not defined"; +#endif diff --git a/libs/format/tests/config/include.hpp b/libs/format/tests/config/include.hpp new file mode 100644 index 000000000..30c493c8b --- /dev/null +++ b/libs/format/tests/config/include.hpp @@ -0,0 +1,5 @@ +#include "..\something.hpp" + +class Test { + value = 1; +}; diff --git a/libs/format/tests/config/invalid.hpp b/libs/format/tests/config/invalid.hpp new file mode 100644 index 000000000..a0f606420 --- /dev/null +++ b/libs/format/tests/config/invalid.hpp @@ -0,0 +1,3 @@ +class invalid { + value = @1; +}; diff --git a/libs/format/tests/config/macro.hpp b/libs/format/tests/config/macro.hpp new file mode 100644 index 000000000..fc5a5f4f4 --- /dev/null +++ b/libs/format/tests/config/macro.hpp @@ -0,0 +1,6 @@ +#define VALUE {1,2,3} +#define QUOTE(x) #x + +class MyClass{ + requiredAddons[] = {QUOTE(ADDON), "ace_explosives"}; + value=VALUE;string=QUOTE(VALUE);author=ECSTRING(common,ACETeam);}; diff --git a/libs/format/tests/config/macro_arr2.hpp b/libs/format/tests/config/macro_arr2.hpp new file mode 100644 index 000000000..749c8f229 --- /dev/null +++ b/libs/format/tests/config/macro_arr2.hpp @@ -0,0 +1 @@ +statement = QUOTE([ARR_2({call FUNC(deploy)},[ARR_2(_target,_player)])] call CBA_fnc_execNextFrame); diff --git a/libs/format/tests/config/macro_dogtag.hpp b/libs/format/tests/config/macro_dogtag.hpp new file mode 100644 index 000000000..4c4b93a85 --- /dev/null +++ b/libs/format/tests/config/macro_dogtag.hpp @@ -0,0 +1,39 @@ +#define ACE_DOGTAG_ITEM(DOGTAGBASE,DOGTAGID) \ + class DOGTAGBASE##_##DOGTAGID: DOGTAGBASE { \ + author = ECSTRING(common,ACETeam); \ + scope = 1; \ + scopeArsenal = 0; \ + scopeCurator = 0; \ + descriptionShort = CSTRING(itemName); \ + GVAR(tagID) = DOGTAGID; \ + } + +class CfgWeapons { + class ACE_ItemCore; + class CBA_MiscItem_ItemInfo; + + class ACE_dogtag: ACE_ItemCore { + author = ECSTRING(common,ACETeam); + scope = 0; + displayName = CSTRING(itemName); + model = QPATHTOF(data\ace_dogtag.p3d); + picture = QPATHTOF(data\dogtagSingle.paa); + class ItemInfo: CBA_MiscItem_ItemInfo { + mass = 0; //too small to for 1 ? + }; + }; + ACE_DOGTAG_ITEM(ACE_dogtag,1); + ACE_DOGTAG_ITEM(ACE_dogtag,2); + ACE_DOGTAG_ITEM(ACE_dogtag,3); + ACE_DOGTAG_ITEM(ACE_dogtag,4); + ACE_DOGTAG_ITEM(ACE_dogtag,5); + ACE_DOGTAG_ITEM(ACE_dogtag,6); + ACE_DOGTAG_ITEM(ACE_dogtag,7); + ACE_DOGTAG_ITEM(ACE_dogtag,8); + ACE_DOGTAG_ITEM(ACE_dogtag,9); + ACE_DOGTAG_ITEM(ACE_dogtag,10); + ACE_DOGTAG_ITEM(ACE_dogtag,11); + ACE_DOGTAG_ITEM(ACE_dogtag,12); + ACE_DOGTAG_ITEM(ACE_dogtag,13); + ACE_DOGTAG_ITEM(ACE_dogtag,14); +}; diff --git a/libs/format/tests/config/macro_hash.hpp b/libs/format/tests/config/macro_hash.hpp new file mode 100644 index 000000000..c738b67dd --- /dev/null +++ b/libs/format/tests/config/macro_hash.hpp @@ -0,0 +1,4 @@ +#define MACRO_ADDWEAPON(WEAPON,COUNT) class _xx_##WEAPON { \ + weapon = #WEAPON; \ + count = COUNT; \ +} diff --git a/libs/format/tests/config/macro_path.hpp b/libs/format/tests/config/macro_path.hpp new file mode 100644 index 000000000..3eb63406c --- /dev/null +++ b/libs/format/tests/config/macro_path.hpp @@ -0,0 +1 @@ +class Data{texture=QPATHTOF(textures\target_ca.paa);}; diff --git a/libs/format/tests/config/macro_sqf.hpp b/libs/format/tests/config/macro_sqf.hpp new file mode 100644 index 000000000..caaa068ff --- /dev/null +++ b/libs/format/tests/config/macro_sqf.hpp @@ -0,0 +1,24 @@ +init = QUOTE(call COMPILE_FILE(scripts\initTargetWall)); + +condition = QUOTE(!alive player && {alive player == true}); + +onbuttonclick = QUOTE(playMission [ARR_2('','PATHTOF(missions\Arsenal.VR)')]); + +class EGVAR(arsenal,stats) { + class statBase {}; + class ACE_scopeHorizontalLimits: statBase { + scope = 2; + priority = 3; + stats[] = {"ACE_ScopeAdjust_Horizontal", "ACE_ScopeAdjust_HorizontalIncrement"}; + displayName = CSTRING(statHorizontalLimits); + showText = 1; + textStatement = QUOTE(params[ARR_2('_stat','_config')]; private _limits = getArray (_config >> _stat select 0); format [ARR_4('%1 / %2 MIL (∆ %3 MIL)',_limits select 0,_limits select 1,getNumber (_config >> _stat select 1))]); + condition = QUOTE(params[ARR_2('_stat','_config')]; private _limits = getArray (_config >> _stat select 0); _limits isNotEqualTo [] && {(_limits isNotEqualTo [ARR_2(0,0)])}); + tabs[] = {{}, {0}}; + }; + class ACE_scopeVerticalLimits: ACE_scopeHorizontalLimits { + stats[] = {"ACE_ScopeAdjust_Vertical", "ACE_ScopeAdjust_VerticalIncrement"}; + priority = 2; + displayName = CSTRING(statVerticalLimits); + }; +}; diff --git a/libs/format/tests/config/math.hpp b/libs/format/tests/config/math.hpp new file mode 100644 index 000000000..8110a2a70 --- /dev/null +++ b/libs/format/tests/config/math.hpp @@ -0,0 +1,10 @@ +add = 1 + 2; +sub = 1 - 2; +mul = 2 * 3; +div = 6 / 3; +mod = 5 % 2; +negAdd = -1 + -2; +negSub = -1 - -2; +negMul = -2 * -3; +negDiv = -6 / -3; +negMod = -5 % -2; diff --git a/libs/format/tests/config/nested.hpp b/libs/format/tests/config/nested.hpp new file mode 100644 index 000000000..1181edbed --- /dev/null +++ b/libs/format/tests/config/nested.hpp @@ -0,0 +1,18 @@ +class First { + value = 1; + class Second { + value = 1; + class Third { + value = 1; + class Fourth { + value = 1; + }; + class AnotherFourth { + value = 1; + }; + value = 21; + }; + value = 11; + }; + value = 2; +}; diff --git a/libs/format/tests/config/numbers.hpp b/libs/format/tests/config/numbers.hpp new file mode 100644 index 000000000..7ab508f15 --- /dev/null +++ b/libs/format/tests/config/numbers.hpp @@ -0,0 +1,17 @@ +class numbers { + hex=0xDEADBEEF; + number = 0x123; + exponent = 123e10; + exponent = 123e-10; + int = 123; + float = 123.456; + negative = -123; + negativeFloat = -123.456; + complex = -123.456e-10; + + addition = 1 + 2; + subtraction = 1 - 2; + negativeSubtraction = -1 - -2; + + brackets = (1 + 2) * (-1 - -2); +}; diff --git a/libs/format/tests/config/parent.hpp b/libs/format/tests/config/parent.hpp new file mode 100644 index 000000000..54c780bae --- /dev/null +++ b/libs/format/tests/config/parent.hpp @@ -0,0 +1,3 @@ +class Child:Parent{ + value = 1.02; +}; diff --git a/libs/format/tests/config/quotes.hpp b/libs/format/tests/config/quotes.hpp new file mode 100644 index 000000000..d0d2598be --- /dev/null +++ b/libs/format/tests/config/quotes.hpp @@ -0,0 +1,7 @@ +single = 'this is a single quote string'; +double = "this is a double quote string"; +single_wrapper = 'this is a "string"'; +double_wrapper = "this is a 'string'"; +escaped_double = "this is a ""string"""; +escaped_double_sqf = "[""value""] call my_fnc;"; +attributeLoad = "params [""_ctrlGroup""];"; diff --git a/libs/format/tests/snapshots/config__config_array.snap b/libs/format/tests/snapshots/config__config_array.snap new file mode 100644 index 000000000..ba52d5fb3 --- /dev/null +++ b/libs/format/tests/snapshots/config__config_array.snap @@ -0,0 +1,19 @@ +--- +source: libs/format/tests/config.rs +expression: config(stringify! (array)) +--- +short[] = {1, 2, 3, 4}; +long[] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, + 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, + 99, 100 +}; +nested[] = {{1, 2}, {3, 4}}; +longNested[] = { + {1, 2, 3, 4, 5}, {6, 7, 8, 9, 10}, {11, 12, 13, 14, 15}, {16, 17, 18, 19, 20}, + {21, 22, 23, 24, 25}, {26, 27, 28, 29, 30}, {31, 32, 33, 34, 35}, {36, 37, 38, 39, 40} +}; +float[] = {0.8, 1}; +append[] += {5, 6, 7}; diff --git a/libs/format/tests/snapshots/config__config_basic.snap b/libs/format/tests/snapshots/config__config_basic.snap new file mode 100644 index 000000000..dc6d8909d --- /dev/null +++ b/libs/format/tests/snapshots/config__config_basic.snap @@ -0,0 +1,11 @@ +--- +source: libs/format/tests/config.rs +expression: config(stringify! (basic)) +--- +class MyClass { + #define VALUE 5 + value = 5; + class Nested { + nestedValue = 1; + }; +}; diff --git a/libs/format/tests/snapshots/config__config_classes.snap b/libs/format/tests/snapshots/config__config_classes.snap new file mode 100644 index 000000000..bf1d72ec5 --- /dev/null +++ b/libs/format/tests/snapshots/config__config_classes.snap @@ -0,0 +1,22 @@ +--- +source: libs/format/tests/config.rs +expression: config(stringify! (classes)) +--- +class Basic { + value = 1; +}; +class Child: Basic { + value = 1.02; +}; +class External; +class Empty {}; +class Nested { + class Inner { + innerValue = 10; + }; +}; +class 2Values { + value1 = 1; + value2 = 2; +}; +class EmptyChild: Empty {}; diff --git a/libs/format/tests/snapshots/config__config_comments.snap b/libs/format/tests/snapshots/config__config_comments.snap new file mode 100644 index 000000000..49ffc7b4c --- /dev/null +++ b/libs/format/tests/snapshots/config__config_comments.snap @@ -0,0 +1,38 @@ +--- +source: libs/format/tests/config.rs +expression: config(stringify! (comments)) +--- +value = 1; // Comment after value +string /* Comment after ident */ = "test"; // Another comment +/*class Weapon_launch_Titan_F: Launcher_Base_F { + displayName = CSTRING(launch_Titan_Name); +}; + +class Weapon_launch_Titan_short_F: Launcher_Base_F { + displayName = CSTRING(launch_Titan_short_Name); +}; + +class Weapon_launch_B_Titan_F: Launcher_Base_F { + displayName = CSTRING(launch_Titan_Name); +};*/ +value = 2; +class MyClass { // Comment after class + value = 1; // Comment in class + class External; // Comment after external + class MyClass { // Comment after nested class + value = 2; // Comment in nested class + }; + // Comment after class + + class AnotherClass { + value = 0; + }; + + // Comment, but after a newline +}; + +class MyClass2 { + class Child: Parent { // Comment after child + value = 3; // Comment in child + }; +}; diff --git a/libs/format/tests/snapshots/config__config_eject.snap b/libs/format/tests/snapshots/config__config_eject.snap new file mode 100644 index 000000000..65692849d --- /dev/null +++ b/libs/format/tests/snapshots/config__config_eject.snap @@ -0,0 +1,11 @@ +--- +source: libs/format/tests/config.rs +expression: config(stringify! (eject)) +--- +class CfgActions { + class None; + class Eject: None { + show = QUOTE(call compile getText (configFile >> 'CfgActions' >> 'Eject' >> 'GVAR(setting)')); + GVAR(setting) = QUOTE(profileNamespace getVariable [ARR_2('GVAR(showEjectAction)',0)]); + }; +}; diff --git a/libs/format/tests/snapshots/config__config_empty.snap b/libs/format/tests/snapshots/config__config_empty.snap new file mode 100644 index 000000000..cbd4bb583 --- /dev/null +++ b/libs/format/tests/snapshots/config__config_empty.snap @@ -0,0 +1,8 @@ +--- +source: libs/format/tests/config.rs +expression: config(stringify! (empty)) +--- +class Parent { + class FirstEmpty {}; + class SecondEmpty {}; +}; diff --git a/libs/format/tests/snapshots/config__config_if.snap b/libs/format/tests/snapshots/config__config_if.snap new file mode 100644 index 000000000..7d08d4f01 --- /dev/null +++ b/libs/format/tests/snapshots/config__config_if.snap @@ -0,0 +1,10 @@ +--- +source: libs/format/tests/config.rs +expression: config(stringify! (if)) +--- +#ifdef THING + value = "thing was defined"; +#else + #define NOTHING + value = "thing was not defined"; +#endif diff --git a/libs/format/tests/snapshots/config__config_include.snap b/libs/format/tests/snapshots/config__config_include.snap new file mode 100644 index 000000000..74eec6ca1 --- /dev/null +++ b/libs/format/tests/snapshots/config__config_include.snap @@ -0,0 +1,9 @@ +--- +source: libs/format/tests/config.rs +expression: config(stringify! (include)) +--- +#include "..\something.hpp" + +class Test { + value = 1; +}; diff --git a/libs/format/tests/snapshots/config__config_invalid.snap b/libs/format/tests/snapshots/config__config_invalid.snap new file mode 100644 index 000000000..04649ecce --- /dev/null +++ b/libs/format/tests/snapshots/config__config_invalid.snap @@ -0,0 +1,5 @@ +--- +source: libs/format/tests/config.rs +expression: config(stringify! (invalid)) +--- +ERROR: Unexpected token '@' at position 28..29 diff --git a/libs/format/tests/snapshots/config__config_macro.snap b/libs/format/tests/snapshots/config__config_macro.snap new file mode 100644 index 000000000..3aaa4f933 --- /dev/null +++ b/libs/format/tests/snapshots/config__config_macro.snap @@ -0,0 +1,13 @@ +--- +source: libs/format/tests/config.rs +expression: config(stringify! (macro)) +--- +#define VALUE {1,2,3} +#define QUOTE(x) #x + +class MyClass { + requiredAddons[] = {QUOTE(ADDON), "ace_explosives"}; + value = VALUE; + string = QUOTE(VALUE); + author = ECSTRING(common,ACETeam); +}; diff --git a/libs/format/tests/snapshots/config__config_macro_arr2.snap b/libs/format/tests/snapshots/config__config_macro_arr2.snap new file mode 100644 index 000000000..fce91e2b5 --- /dev/null +++ b/libs/format/tests/snapshots/config__config_macro_arr2.snap @@ -0,0 +1,5 @@ +--- +source: libs/format/tests/config.rs +expression: config(stringify! (macro_arr2)) +--- +statement = QUOTE([ARR_2({call FUNC(deploy)},[ARR_2(_target,_player)])] call CBA_fnc_execNextFrame); diff --git a/libs/format/tests/snapshots/config__config_macro_dogtag.snap b/libs/format/tests/snapshots/config__config_macro_dogtag.snap new file mode 100644 index 000000000..3f4d0e923 --- /dev/null +++ b/libs/format/tests/snapshots/config__config_macro_dogtag.snap @@ -0,0 +1,43 @@ +--- +source: libs/format/tests/config.rs +expression: config(stringify! (macro_dogtag)) +--- +#define ACE_DOGTAG_ITEM(DOGTAGBASE,DOGTAGID) \ + class DOGTAGBASE##_##DOGTAGID: DOGTAGBASE { \ + author = ECSTRING(common,ACETeam); \ + scope = 1; \ + scopeArsenal = 0; \ + scopeCurator = 0; \ + descriptionShort = CSTRING(itemName); \ + GVAR(tagID) = DOGTAGID; \ + } + +class CfgWeapons { + class ACE_ItemCore; + class CBA_MiscItem_ItemInfo; + + class ACE_dogtag: ACE_ItemCore { + author = ECSTRING(common,ACETeam); + scope = 0; + displayName = CSTRING(itemName); + model = QPATHTOF(data\ace_dogtag.p3d); + picture = QPATHTOF(data\dogtagSingle.paa); + class ItemInfo: CBA_MiscItem_ItemInfo { + mass = 0; //too small to for 1 ? + }; + }; + ACE_DOGTAG_ITEM(ACE_dogtag,1); + ACE_DOGTAG_ITEM(ACE_dogtag,2); + ACE_DOGTAG_ITEM(ACE_dogtag,3); + ACE_DOGTAG_ITEM(ACE_dogtag,4); + ACE_DOGTAG_ITEM(ACE_dogtag,5); + ACE_DOGTAG_ITEM(ACE_dogtag,6); + ACE_DOGTAG_ITEM(ACE_dogtag,7); + ACE_DOGTAG_ITEM(ACE_dogtag,8); + ACE_DOGTAG_ITEM(ACE_dogtag,9); + ACE_DOGTAG_ITEM(ACE_dogtag,10); + ACE_DOGTAG_ITEM(ACE_dogtag,11); + ACE_DOGTAG_ITEM(ACE_dogtag,12); + ACE_DOGTAG_ITEM(ACE_dogtag,13); + ACE_DOGTAG_ITEM(ACE_dogtag,14); +}; diff --git a/libs/format/tests/snapshots/config__config_macro_hash.snap b/libs/format/tests/snapshots/config__config_macro_hash.snap new file mode 100644 index 000000000..e736df59f --- /dev/null +++ b/libs/format/tests/snapshots/config__config_macro_hash.snap @@ -0,0 +1,8 @@ +--- +source: libs/format/tests/config.rs +expression: config(stringify! (macro_hash)) +--- +#define MACRO_ADDWEAPON(WEAPON,COUNT) class _xx_##WEAPON { \ + weapon = #WEAPON; \ + count = COUNT; \ +} diff --git a/libs/format/tests/snapshots/config__config_macro_path.snap b/libs/format/tests/snapshots/config__config_macro_path.snap new file mode 100644 index 000000000..5d3b6ec2a --- /dev/null +++ b/libs/format/tests/snapshots/config__config_macro_path.snap @@ -0,0 +1,7 @@ +--- +source: libs/format/tests/config.rs +expression: config(stringify! (macro_path)) +--- +class Data { + texture = QPATHTOF(textures\target_ca.paa); +}; diff --git a/libs/format/tests/snapshots/config__config_macro_sqf.snap b/libs/format/tests/snapshots/config__config_macro_sqf.snap new file mode 100644 index 000000000..0f251b78b --- /dev/null +++ b/libs/format/tests/snapshots/config__config_macro_sqf.snap @@ -0,0 +1,28 @@ +--- +source: libs/format/tests/config.rs +expression: config(stringify! (macro_sqf)) +--- +init = QUOTE(call COMPILE_FILE(scripts\initTargetWall)); + +condition = QUOTE(!alive player && {alive player == true}); + +onbuttonclick = QUOTE(playMission [ARR_2('','PATHTOF(missions\Arsenal.VR)')]); + +class EGVAR(arsenal,stats) { + class statBase {}; + class ACE_scopeHorizontalLimits: statBase { + scope = 2; + priority = 3; + stats[] = {"ACE_ScopeAdjust_Horizontal", "ACE_ScopeAdjust_HorizontalIncrement"}; + displayName = CSTRING(statHorizontalLimits); + showText = 1; + textStatement = QUOTE(params [ARR_2('_stat','_config')]; private _limits = getArray (_config >> _stat select 0); format [ARR_4('%1 / %2 MIL (∆ %3 MIL)',_limits select 0,_limits select 1,getNumber (_config >> _stat select 1))]); + condition = QUOTE(params [ARR_2('_stat','_config')]; private _limits = getArray (_config >> _stat select 0); _limits isNotEqualTo [] && {(_limits isNotEqualTo [ARR_2(0,0)])}); + tabs[] = {{}, {0}}; + }; + class ACE_scopeVerticalLimits: ACE_scopeHorizontalLimits { + stats[] = {"ACE_ScopeAdjust_Vertical", "ACE_ScopeAdjust_VerticalIncrement"}; + priority = 2; + displayName = CSTRING(statVerticalLimits); + }; +}; diff --git a/libs/format/tests/snapshots/config__config_math.snap b/libs/format/tests/snapshots/config__config_math.snap new file mode 100644 index 000000000..276165ed7 --- /dev/null +++ b/libs/format/tests/snapshots/config__config_math.snap @@ -0,0 +1,14 @@ +--- +source: libs/format/tests/config.rs +expression: config(stringify! (math)) +--- +add = 1 + 2; +sub = 1 - 2; +mul = 2 * 3; +div = 6 / 3; +mod = 5 % 2; +negAdd = -1 + -2; +negSub = -1 - -2; +negMul = -2 * -3; +negDiv = -6 / -3; +negMod = -5 % -2; diff --git a/libs/format/tests/snapshots/config__config_nested.snap b/libs/format/tests/snapshots/config__config_nested.snap new file mode 100644 index 000000000..02a14ec41 --- /dev/null +++ b/libs/format/tests/snapshots/config__config_nested.snap @@ -0,0 +1,22 @@ +--- +source: libs/format/tests/config.rs +expression: config(stringify! (nested)) +--- +class First { + value = 1; + class Second { + value = 1; + class Third { + value = 1; + class Fourth { + value = 1; + }; + class AnotherFourth { + value = 1; + }; + value = 21; + }; + value = 11; + }; + value = 2; +}; diff --git a/libs/format/tests/snapshots/config__config_numbers.snap b/libs/format/tests/snapshots/config__config_numbers.snap new file mode 100644 index 000000000..808f1e702 --- /dev/null +++ b/libs/format/tests/snapshots/config__config_numbers.snap @@ -0,0 +1,21 @@ +--- +source: libs/format/tests/config.rs +expression: config(stringify! (numbers)) +--- +class numbers { + hex = 0xDEADBEEF; + number = 0x123; + exponent = 123e10; + exponent = 123e-10; + int = 123; + float = 123.456; + negative = -123; + negativeFloat = -123.456; + complex = -123.456e-10; + + addition = 1 + 2; + subtraction = 1 - 2; + negativeSubtraction = -1 - -2; + + brackets = (1 + 2) * (-1 - -2); +}; diff --git a/libs/format/tests/snapshots/config__config_parent.snap b/libs/format/tests/snapshots/config__config_parent.snap new file mode 100644 index 000000000..70d10878b --- /dev/null +++ b/libs/format/tests/snapshots/config__config_parent.snap @@ -0,0 +1,7 @@ +--- +source: libs/format/tests/config.rs +expression: config(stringify! (parent)) +--- +class Child: Parent { + value = 1.02; +}; diff --git a/libs/format/tests/snapshots/config__config_quotes.snap b/libs/format/tests/snapshots/config__config_quotes.snap new file mode 100644 index 000000000..9429ae30e --- /dev/null +++ b/libs/format/tests/snapshots/config__config_quotes.snap @@ -0,0 +1,11 @@ +--- +source: libs/format/tests/config.rs +expression: config(stringify! (quotes)) +--- +single = 'this is a single quote string'; +double = "this is a double quote string"; +single_wrapper = 'this is a "string"'; +double_wrapper = "this is a 'string'"; +escaped_double = "this is a ""string"""; +escaped_double_sqf = "[""value""] call my_fnc;"; +attributeLoad = "params [""_ctrlGroup""];"; diff --git a/libs/format/tests/snapshots/sqf__sqf_arrays.snap b/libs/format/tests/snapshots/sqf__sqf_arrays.snap new file mode 100644 index 000000000..9aa4baf38 --- /dev/null +++ b/libs/format/tests/snapshots/sqf__sqf_arrays.snap @@ -0,0 +1,21 @@ +--- +source: libs/format/tests/sqf.rs +expression: sqf(stringify! (arrays)) +--- +private _simple = [1, 2, 3]; +private _nested = [[1, 2], [3, 4]]; +private _short = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; +private _long = [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 +]; +private _longNested = [ + [1, 2, 3, 4, 5], + [6, 7, 8, 9, 10], + [11, 12, 13, 14, 15], + [16, 17, 18, 19, 20], + [21, 22, 23, 24, 25], + [26, 27, 28, 29, 30], + [31, 32, 33, 34, 35], + [36, 37, 38, 39, 40] +]; diff --git a/libs/format/tests/snapshots/sqf__sqf_blocks.snap b/libs/format/tests/snapshots/sqf__sqf_blocks.snap new file mode 100644 index 000000000..b1e0cec35 --- /dev/null +++ b/libs/format/tests/snapshots/sqf__sqf_blocks.snap @@ -0,0 +1,11 @@ +--- +source: libs/format/tests/sqf.rs +expression: sqf(stringify! (blocks)) +--- +private _fnc = { + params ["_a", "_b"]; + _a + _b +}; +{ + systemChat format ["Result: %1", [_x, 1] call _fnc]; +} forEach [1, 2, 3]; diff --git a/libs/format/tests/snapshots/sqf__sqf_cba.snap b/libs/format/tests/snapshots/sqf__sqf_cba.snap new file mode 100644 index 000000000..6c9395c81 --- /dev/null +++ b/libs/format/tests/snapshots/sqf__sqf_cba.snap @@ -0,0 +1,20 @@ +--- +source: libs/format/tests/sqf.rs +expression: sqf(stringify! (cba)) +--- +if (isServer) then { + [QGVAR(zeusUnitAssigned), LINKFUNC(handleZeusUnitAssigned)] call CBA_fnc_addEventHandler; + + // Only add an InitPost EH if setting is enabled (and apply retroactively) + ["CBA_settingsInitialized", { + if (GVAR(autoAddObjects)) then { + ["AllVehicles", "InitPost", LINKFUNC(addObjectToCurator), true, [], true] call CBA_fnc_addClassEventHandler; + }; + }] call CBA_fnc_addEventHandler; +}; + +[{ + [{ + systemChat "done waiting"; + }, _unit, 25] call CBA_fnc_waitAndExecute; +}, _unit, 25] call CBA_fnc_waitAndExecute; diff --git a/libs/format/tests/snapshots/sqf__sqf_cba_settings.snap b/libs/format/tests/snapshots/sqf__sqf_cba_settings.snap new file mode 100644 index 000000000..e796d5425 --- /dev/null +++ b/libs/format/tests/snapshots/sqf__sqf_cba_settings.snap @@ -0,0 +1,12 @@ +--- +source: libs/format/tests/sqf.rs +expression: sqf(stringify! (cba_settings)) +--- +[ + QGVAR(keepEngineRunning), + "CHECKBOX", + [LSTRING(SettingKeepEngineRunningName), LSTRING(SettingKeepEngineRunningDesc)], + ELSTRING(common,ACEKeybindCategoryVehicles), + false, // default value + true // isGlobal +] call CBA_fnc_addSetting; diff --git a/libs/format/tests/snapshots/sqf__sqf_comments.snap b/libs/format/tests/snapshots/sqf__sqf_comments.snap new file mode 100644 index 000000000..b26b90eb5 --- /dev/null +++ b/libs/format/tests/snapshots/sqf__sqf_comments.snap @@ -0,0 +1,18 @@ +--- +source: libs/format/tests/sqf.rs +expression: sqf(stringify! (comments)) +--- +private _myVar = "This is a variable"; // This is an end-of-line comment +private _myVar /* This is a block comment */ = "This is another variable"; +/* + This is a multi-line + block comment +*/ +private _anotherVar = "This is yet another variable"; // Another end-of-line comment +// A comment on its own line + +private _items = [ // start of items array + "ItemMap", // to see things + "ItemCompass", // to navigate + "ItemWatch", // to keep track of time +]; // end of items array diff --git a/libs/format/tests/snapshots/sqf__sqf_format.snap b/libs/format/tests/snapshots/sqf__sqf_format.snap new file mode 100644 index 000000000..5f5a99b63 --- /dev/null +++ b/libs/format/tests/snapshots/sqf__sqf_format.snap @@ -0,0 +1,13 @@ +--- +source: libs/format/tests/sqf.rs +expression: sqf(stringify! (format)) +--- +diag_log text format ["This is here as an example!!!"]; +if (true) then { + systemChat format [ + "This is a string with %1 %2 %3 %4 %5 %6 %7 %8", "one", "two", "three", + "four", "five", "six", "seven", "eight" + ]; +}; + +profileNamespace setVariable [format [QGVAR(PersistentZero_%1_%2), _weaponClass, _opticsClass], nil]; diff --git a/libs/format/tests/snapshots/sqf__sqf_hash.snap b/libs/format/tests/snapshots/sqf__sqf_hash.snap new file mode 100644 index 000000000..65a354ef3 --- /dev/null +++ b/libs/format/tests/snapshots/sqf__sqf_hash.snap @@ -0,0 +1,6 @@ +--- +source: libs/format/tests/sqf.rs +expression: sqf(stringify! (hash)) +--- +private _lod = (allLODs _weaponObj) # 0 # 2; +private _xMemMuzzle = getText (_config >> "muzzlePos"); diff --git a/libs/format/tests/snapshots/sqf__sqf_if.snap b/libs/format/tests/snapshots/sqf__sqf_if.snap new file mode 100644 index 000000000..9e37d76c1 --- /dev/null +++ b/libs/format/tests/snapshots/sqf__sqf_if.snap @@ -0,0 +1,15 @@ +--- +source: libs/format/tests/sqf.rs +expression: sqf(stringify! (if)) +--- +if (true) then { + _x = 5; + for "_i" from 0 to 10 do { _sum = _sum + _i; }; +}; // test + +if !(call _test) then { systemChat "true branch"; }; + +if ((abs (_actualOffset - _configOffset)) > 0.1) then {}; +if ((abs (_actualOffset - _configOffset)) > 0.1) then {}; + +if (_xPosOptic isEqualTo [0, 0, 0]) exitWith { -999 }; // e.g. akm has no proxy diff --git a/libs/format/tests/snapshots/sqf__sqf_if_and.snap b/libs/format/tests/snapshots/sqf__sqf_if_and.snap new file mode 100644 index 000000000..0079ba400 --- /dev/null +++ b/libs/format/tests/snapshots/sqf__sqf_if_and.snap @@ -0,0 +1,7 @@ +--- +source: libs/format/tests/sqf.rs +expression: sqf(stringify! (if_and)) +--- +if (alive player && { + player distance target < 10 +}) then { _near = true; }; diff --git a/libs/format/tests/snapshots/sqf__sqf_if_not.snap b/libs/format/tests/snapshots/sqf__sqf_if_not.snap new file mode 100644 index 000000000..ace9e09e6 --- /dev/null +++ b/libs/format/tests/snapshots/sqf__sqf_if_not.snap @@ -0,0 +1,5 @@ +--- +source: libs/format/tests/sqf.rs +expression: sqf(stringify! (if_not)) +--- +if (!hasInterface) exitWith {}; diff --git a/libs/format/tests/snapshots/sqf__sqf_include.snap b/libs/format/tests/snapshots/sqf__sqf_include.snap new file mode 100644 index 000000000..4c9bac481 --- /dev/null +++ b/libs/format/tests/snapshots/sqf__sqf_include.snap @@ -0,0 +1,7 @@ +--- +source: libs/format/tests/sqf.rs +expression: sqf(stringify! (include)) +--- +#include "..\script_component.hpp" + +systemChat "hello from include.sqf"; diff --git a/libs/format/tests/snapshots/sqf__sqf_macro_path.snap b/libs/format/tests/snapshots/sqf__sqf_macro_path.snap new file mode 100644 index 000000000..65f5e844a --- /dev/null +++ b/libs/format/tests/snapshots/sqf__sqf_macro_path.snap @@ -0,0 +1,5 @@ +--- +source: libs/format/tests/sqf.rs +expression: sqf(stringify! (macro_path)) +--- +private _texture = QPATHTOF(textures\target_ca.paa); diff --git a/libs/format/tests/snapshots/sqf__sqf_macros.snap b/libs/format/tests/snapshots/sqf__sqf_macros.snap new file mode 100644 index 000000000..5e70db4fa --- /dev/null +++ b/libs/format/tests/snapshots/sqf__sqf_macros.snap @@ -0,0 +1,12 @@ +--- +source: libs/format/tests/sqf.rs +expression: sqf(stringify! (macros)) +--- +private _result = call FUNC(test); +private _result = call EFUNC(main,test); + +TRACE_2("ace_glassesChanged eh",_unit,_glasses); +["ace_glassesChanged", { + params ["_unit", "_glasses"]; + TRACE_2("ace_glassesChanged eh",_unit,_glasses); +}] call CBA_fnc_addEventHandler; diff --git a/libs/format/tests/snapshots/sqf__sqf_numbers.snap b/libs/format/tests/snapshots/sqf__sqf_numbers.snap new file mode 100644 index 000000000..e0c5189a9 --- /dev/null +++ b/libs/format/tests/snapshots/sqf__sqf_numbers.snap @@ -0,0 +1,19 @@ +--- +source: libs/format/tests/sqf.rs +expression: sqf(stringify! (numbers)) +--- +private _number = 0xDEADBEEF; +private _number = 0x123; +private _exponent = 123e10; +private _exponent = 123e-10; +private _int = 123; +private _float = 123.456; +private _negative = -123; +private _negativeFloat = -123.456; +private _complex = -123.456e-10; + +private _addition = 1 + 2; +private _subtraction = 1 - 2; +private _negativeSubtraction = -1 - -2; + +private _brackets = (1 + 2) * (-1 - -2); diff --git a/libs/format/tests/snapshots/sqf__sqf_preserve_lines.snap b/libs/format/tests/snapshots/sqf__sqf_preserve_lines.snap new file mode 100644 index 000000000..073a512f2 --- /dev/null +++ b/libs/format/tests/snapshots/sqf__sqf_preserve_lines.snap @@ -0,0 +1,10 @@ +--- +source: libs/format/tests/sqf.rs +expression: sqf(stringify! (preserve_lines)) +--- +private _line1 = "this is line 1"; + + +private _line2 = "this is line 2"; + +private _line3 = "this is line 3"; diff --git a/libs/format/tests/snapshots/sqf__sqf_private.snap b/libs/format/tests/snapshots/sqf__sqf_private.snap new file mode 100644 index 000000000..669702485 --- /dev/null +++ b/libs/format/tests/snapshots/sqf__sqf_private.snap @@ -0,0 +1,14 @@ +--- +source: libs/format/tests/sqf.rs +expression: sqf(stringify! (private)) +--- +private _myVar = 10; +private _myFunc = { + params ["_param1", "_param2"]; + private _result = _param1 + _param2; + _result +}; +private _result = if (([1, 2] call _myFunc) == 3) then { + "Equal to 3" +} else { "Not equal to 3" }; +systemChat _result; diff --git a/libs/format/tests/snapshots/sqf__sqf_sameline.snap b/libs/format/tests/snapshots/sqf__sqf_sameline.snap new file mode 100644 index 000000000..319e056ed --- /dev/null +++ b/libs/format/tests/snapshots/sqf__sqf_sameline.snap @@ -0,0 +1,21 @@ +--- +source: libs/format/tests/sqf.rs +expression: sqf(stringify! (sameline)) +--- +// Remain one line +_myArray select { alive _x }; + +// Become one line +_myArray select { alive _x }; + +// Remaine multi line +_myArray select { + private _class = typeOf _x; + (_class == "SoldierWB" || _class == "SoldierEB") && alive _x +}; + +if (alive player) then { continue }; + +if (isServer) exitWith { false }; + +if (hasInterface) exitWith { 19 }; diff --git a/libs/format/tests/snapshots/sqf__sqf_switch.snap b/libs/format/tests/snapshots/sqf__sqf_switch.snap new file mode 100644 index 000000000..d9740c636 --- /dev/null +++ b/libs/format/tests/snapshots/sqf__sqf_switch.snap @@ -0,0 +1,13 @@ +--- +source: libs/format/tests/sqf.rs +expression: sqf(stringify! (switch)) +--- +switch (floor random 6) do { + case 1; + case 3; + case 5: { systemChat "Odd case selected"; }; + case 0; + case 2; + case 4: { systemChat "Even case selected"; }; + default { systemChat "Default case selected"; }; +}; diff --git a/libs/format/tests/sqf.rs b/libs/format/tests/sqf.rs new file mode 100644 index 000000000..991374cd1 --- /dev/null +++ b/libs/format/tests/sqf.rs @@ -0,0 +1,41 @@ +use hemtt_format::{FormatterConfig, format_sqf}; + +const ROOT: &str = "tests/sqf/"; + +macro_rules! sqf { + ($dir:ident) => { + paste::paste! { + #[test] + fn []() { + insta::assert_snapshot!(sqf(stringify!($dir))); + } + } + }; +} + +sqf!(arrays); +sqf!(blocks); +sqf!(cba_settings); +sqf!(cba); +sqf!(comments); +sqf!(format); +sqf!(hash); +sqf!(if_and); +sqf!(if_not); +sqf!(if); +sqf!(include); +sqf!(macro_path); +sqf!(macros); +sqf!(numbers); +sqf!(preserve_lines); +sqf!(private); +sqf!(sameline); +sqf!(switch); + +fn sqf(file: &str) -> String { + format_sqf( + &fs_err::read_to_string(format!("{ROOT}{file}.sqf")).expect("Failed to read test file"), + &FormatterConfig::default(), + ) + .unwrap_or_else(|err| format!("ERROR: {err}")) +} diff --git a/libs/format/tests/sqf/arrays.sqf b/libs/format/tests/sqf/arrays.sqf new file mode 100644 index 000000000..5efbccbb2 --- /dev/null +++ b/libs/format/tests/sqf/arrays.sqf @@ -0,0 +1,5 @@ +private _simple = [1,2,3]; +private _nested = [[1,2],[3,4]]; +private _short = [1,2,3,4,5,6,7,8,9,10]; +private _long = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40]; +private _longNested = [[1,2,3,4,5],[6,7,8,9,10],[11,12,13,14,15],[16,17,18,19,20],[21,22,23,24,25],[26,27,28,29,30],[31,32,33,34,35],[36,37,38,39,40]]; diff --git a/libs/format/tests/sqf/blocks.sqf b/libs/format/tests/sqf/blocks.sqf new file mode 100644 index 000000000..27d3bbe72 --- /dev/null +++ b/libs/format/tests/sqf/blocks.sqf @@ -0,0 +1,6 @@ +private _fnc = { + params["_a","_b"];_a+_b +}; +{ + systemChat format["Result: %1",[_x,1]call _fnc]; +}forEach[1,2,3]; diff --git a/libs/format/tests/sqf/cba.sqf b/libs/format/tests/sqf/cba.sqf new file mode 100644 index 000000000..d33af4907 --- /dev/null +++ b/libs/format/tests/sqf/cba.sqf @@ -0,0 +1,16 @@ +if (isServer) then { + [QGVAR(zeusUnitAssigned), LINKFUNC(handleZeusUnitAssigned)] call CBA_fnc_addEventHandler; + + // Only add an InitPost EH if setting is enabled (and apply retroactively) + ["CBA_settingsInitialized", { + if (GVAR(autoAddObjects)) then { + ["AllVehicles", "InitPost", LINKFUNC(addObjectToCurator), true, [], true] call CBA_fnc_addClassEventHandler; + }; + }] call CBA_fnc_addEventHandler; +}; + +[{ + [{ + systemChat "done waiting"; + }, _unit, 25] call CBA_fnc_waitAndExecute; +}, _unit, 25] call CBA_fnc_waitAndExecute; diff --git a/libs/format/tests/sqf/cba_settings.sqf b/libs/format/tests/sqf/cba_settings.sqf new file mode 100644 index 000000000..a7560b623 --- /dev/null +++ b/libs/format/tests/sqf/cba_settings.sqf @@ -0,0 +1,8 @@ +[ + QGVAR(keepEngineRunning), + "CHECKBOX", + [LSTRING(SettingKeepEngineRunningName), LSTRING(SettingKeepEngineRunningDesc)], + ELSTRING(common,ACEKeybindCategoryVehicles), + false, // default value + true // isGlobal +] call CBA_fnc_addSetting; diff --git a/libs/format/tests/sqf/comments.sqf b/libs/format/tests/sqf/comments.sqf new file mode 100644 index 000000000..3d59d1f5a --- /dev/null +++ b/libs/format/tests/sqf/comments.sqf @@ -0,0 +1,14 @@ +private _myVar = "This is a variable"; // This is an end-of-line comment +private _myVar /* This is a block comment */ = "This is another variable"; +/* + This is a multi-line + block comment +*/ +private _anotherVar = "This is yet another variable"; // Another end-of-line comment +// A comment on its own line + +private _items = [ // start of items array + "ItemMap", // to see things + "ItemCompass", // to navigate + "ItemWatch", // to keep track of time +]; // end of items array diff --git a/libs/format/tests/sqf/format.sqf b/libs/format/tests/sqf/format.sqf new file mode 100644 index 000000000..211e68ed5 --- /dev/null +++ b/libs/format/tests/sqf/format.sqf @@ -0,0 +1,6 @@ +diag_log text format["This is here as an example!!!"]; +if (true) then { + systemChat format ["This is a string with %1 %2 %3 %4 %5 %6 %7 %8", "one", "two", "three", "four", "five", "six", "seven", "eight"]; +}; + +profileNamespace setVariable [format[QGVAR(PersistentZero_%1_%2), _weaponClass, _opticsClass], nil]; diff --git a/libs/format/tests/sqf/hash.sqf b/libs/format/tests/sqf/hash.sqf new file mode 100644 index 000000000..1423370b8 --- /dev/null +++ b/libs/format/tests/sqf/hash.sqf @@ -0,0 +1,2 @@ +private _lod = (allLODs _weaponObj) # 0 # 2; +private _xMemMuzzle = getText (_config >> "muzzlePos"); diff --git a/libs/format/tests/sqf/if.sqf b/libs/format/tests/sqf/if.sqf new file mode 100644 index 000000000..00e919619 --- /dev/null +++ b/libs/format/tests/sqf/if.sqf @@ -0,0 +1,10 @@ +if(true)then{_x=5;for "_i" from 0 to 10 do {_sum=_sum+_i;};}; // test + +if!(call _test) then{ + systemChat "true branch"; +}; + +if ((abs (_actualOffset - _configOffset)) > 0.1) then {}; +if ((abs(_actualOffset-_configOffset))>0.1) then {}; + +if (_xPosOptic isEqualTo [0,0,0]) exitWith { -999 }; // e.g. akm has no proxy diff --git a/libs/format/tests/sqf/if_and.sqf b/libs/format/tests/sqf/if_and.sqf new file mode 100644 index 000000000..21144de31 --- /dev/null +++ b/libs/format/tests/sqf/if_and.sqf @@ -0,0 +1,3 @@ +if (alive player && {player distance target < 10}) then { + _near = true; +}; diff --git a/libs/format/tests/sqf/if_not.sqf b/libs/format/tests/sqf/if_not.sqf new file mode 100644 index 000000000..8fb440435 --- /dev/null +++ b/libs/format/tests/sqf/if_not.sqf @@ -0,0 +1 @@ +if (!hasInterface) exitWith {}; diff --git a/libs/format/tests/sqf/include.sqf b/libs/format/tests/sqf/include.sqf new file mode 100644 index 000000000..ac33ea972 --- /dev/null +++ b/libs/format/tests/sqf/include.sqf @@ -0,0 +1,3 @@ +#include "..\script_component.hpp" + +systemChat "hello from include.sqf"; diff --git a/libs/format/tests/sqf/macro_path.sqf b/libs/format/tests/sqf/macro_path.sqf new file mode 100644 index 000000000..96fe6cbfa --- /dev/null +++ b/libs/format/tests/sqf/macro_path.sqf @@ -0,0 +1 @@ +private _texture = QPATHTOF(textures\target_ca.paa); diff --git a/libs/format/tests/sqf/macros.sqf b/libs/format/tests/sqf/macros.sqf new file mode 100644 index 000000000..d216bfc5c --- /dev/null +++ b/libs/format/tests/sqf/macros.sqf @@ -0,0 +1,8 @@ +private _result = call FUNC(test); +private _result = call EFUNC(main,test); + +TRACE_2("ace_glassesChanged eh",_unit,_glasses); +["ace_glassesChanged", { + params["_unit","_glasses"]; + TRACE_2("ace_glassesChanged eh",_unit,_glasses); +}] call CBA_fnc_addEventHandler; diff --git a/libs/format/tests/sqf/numbers.sqf b/libs/format/tests/sqf/numbers.sqf new file mode 100644 index 000000000..9f876b180 --- /dev/null +++ b/libs/format/tests/sqf/numbers.sqf @@ -0,0 +1,15 @@ +private _number=0xDEADBEEF; +private _number = 0x123; +private _exponent = 123e10; +private _exponent = 123e-10; +private _int = 123; +private _float = 123.456; +private _negative = -123; +private _negativeFloat = -123.456; +private _complex = -123.456e-10; + +private _addition = 1 + 2; +private _subtraction = 1 - 2; +private _negativeSubtraction = -1 - -2; + +private _brackets = (1 + 2) * (-1 - -2); diff --git a/libs/format/tests/sqf/preserve_lines.sqf b/libs/format/tests/sqf/preserve_lines.sqf new file mode 100644 index 000000000..526cc0632 --- /dev/null +++ b/libs/format/tests/sqf/preserve_lines.sqf @@ -0,0 +1,6 @@ +private _line1 = "this is line 1"; + + +private _line2 = "this is line 2"; + +private _line3 = "this is line 3"; diff --git a/libs/format/tests/sqf/private.sqf b/libs/format/tests/sqf/private.sqf new file mode 100644 index 000000000..88d5cbc7b --- /dev/null +++ b/libs/format/tests/sqf/private.sqf @@ -0,0 +1,9 @@ +private _myVar = 10; +private _myFunc={ + params["_param1","_param2"]; + private _result = _param1+_param2;_result +}; +private _result =if(([1,2] call _myFunc)==3)then{ +"Equal to 3" +} else { "Not equal to 3"}; +systemChat _result; diff --git a/libs/format/tests/sqf/sameline.sqf b/libs/format/tests/sqf/sameline.sqf new file mode 100644 index 000000000..b4eac352a --- /dev/null +++ b/libs/format/tests/sqf/sameline.sqf @@ -0,0 +1,19 @@ +// Remain one line +_myArray select { alive _x }; + +// Become one line +_myArray select { + alive _x +}; + +// Remaine multi line +_myArray select { + private _class = typeOf _x; + (_class == "SoldierWB" || _class == "SoldierEB") && alive _x +}; + +if (alive player) then { continue }; + +if (isServer) exitWith { false }; + +if (hasInterface) exitWith { 19 }; diff --git a/libs/format/tests/sqf/switch.sqf b/libs/format/tests/sqf/switch.sqf new file mode 100644 index 000000000..c2811a398 --- /dev/null +++ b/libs/format/tests/sqf/switch.sqf @@ -0,0 +1,15 @@ +switch (floor random 6) do { + case 1; + case 3; + case 5: { + systemChat "Odd case selected"; + }; + case 0; + case 2; + case 4: { + systemChat "Even case selected"; + }; + default { + systemChat "Default case selected"; + }; +}; diff --git a/libs/signing/Cargo.toml b/libs/signing/Cargo.toml index 51fbd87bc..faa7ddd7d 100644 --- a/libs/signing/Cargo.toml +++ b/libs/signing/Cargo.toml @@ -19,7 +19,9 @@ hemtt-pbo = { path = "../pbo" } byteorder = { workspace = true } crypto-bigint = { version = "0.7.0-rc.10", features = ["alloc"] } rand = "0.10.0-rc.5" -rsa = "0.10.0-rc.10" +rand_core = "=0.10.0-rc-2" +chacha20 = "0.10.0-rc.5" +rsa = "0.10.0-rc.8" sha-1 = { workspace = true } thiserror = { workspace = true }