diff --git a/Cargo.lock b/Cargo.lock index f2b643c..9595a3d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "aho-corasick" -version = "1.1.4" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -17,20 +17,11 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - [[package]] name = "anstream" -version = "0.6.21" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" dependencies = [ "anstyle", "anstyle-parse", @@ -43,9 +34,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" @@ -58,22 +49,22 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.5" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.11" +version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -90,15 +81,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" - -[[package]] -name = "bumpalo" -version = "3.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" [[package]] name = "cassowary" @@ -115,40 +100,17 @@ dependencies = [ "rustversion", ] -[[package]] -name = "cc" -version = "1.2.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" -dependencies = [ - "find-msvc-tools", - "shlex", -] - [[package]] name = "cfg-if" -version = "1.0.4" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "chrono" -version = "0.4.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" -dependencies = [ - "iana-time-zone", - "js-sys", - "num-traits", - "wasm-bindgen", - "windows-link", -] +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "clap" -version = "4.5.57" +version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a" +checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" dependencies = [ "clap_builder", "clap_derive", @@ -156,9 +118,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.57" +version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238" +checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" dependencies = [ "anstream", "anstyle", @@ -168,9 +130,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.55" +version = "4.5.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" dependencies = [ "heck", "proc-macro2", @@ -180,9 +142,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.7" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "cognos" @@ -215,19 +177,13 @@ dependencies = [ [[package]] name = "convert_case" -version = "0.10.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" dependencies = [ "unicode-segmentation", ] -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - [[package]] name = "crossterm" version = "0.28.1" @@ -256,7 +212,7 @@ dependencies = [ "document-features", "mio", "parking_lot", - "rustix 1.1.3", + "rustix 1.0.8", "signal-hook", "signal-hook-mio", "winapi", @@ -273,30 +229,30 @@ dependencies = [ [[package]] name = "csv" -version = "1.4.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" +checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" dependencies = [ "csv-core", "itoa", "ryu", - "serde_core", + "serde", ] [[package]] name = "csv-core" -version = "0.1.13" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" +checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d" dependencies = [ "memchr", ] [[package]] name = "darling" -version = "0.23.0" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ "darling_core", "darling_macro", @@ -304,10 +260,11 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.23.0" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" dependencies = [ + "fnv", "ident_case", "proc-macro2", "quote", @@ -317,9 +274,9 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.23.0" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", @@ -328,31 +285,30 @@ dependencies = [ [[package]] name = "derive_more" -version = "2.1.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "2.1.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ "convert_case", "proc-macro2", "quote", - "rustc_version", "syn", ] [[package]] name = "document-features" -version = "0.2.12" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" dependencies = [ "litrs", ] @@ -371,12 +327,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.14" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -396,10 +352,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] -name = "find-msvc-tools" -version = "0.1.9" +name = "fnv" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" @@ -409,14 +365,14 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "getrandom" -version = "0.3.4" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", "r-efi", - "wasip2", + "wasi 0.14.3+wasi-0.2.4", ] [[package]] @@ -430,42 +386,12 @@ dependencies = [ "foldhash", ] -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" - [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "iana-time-zone" -version = "0.1.65" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -480,30 +406,27 @@ checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] name = "indexmap" -version = "2.13.0" +version = "2.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown", "serde", "serde_core", ] [[package]] name = "indoc" -version = "2.0.7" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" -dependencies = [ - "rustversion", -] +checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" [[package]] name = "instability" -version = "0.3.11" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357b7205c6cd18dd2c86ed312d1e70add149aea98e7ef72b9fdf0270e555c11d" +checksum = "435d80800b936787d62688c927b6490e887c7ef5ff9ce922c6c6050fca75eb9a" dependencies = [ "darling", "indoc", @@ -514,9 +437,9 @@ dependencies = [ [[package]] name = "is_terminal_polyfill" -version = "1.70.2" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" @@ -529,19 +452,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" - -[[package]] -name = "js-sys" -version = "0.3.85" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" -dependencies = [ - "once_cell", - "wasm-bindgen", -] +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "lazy_static" @@ -551,9 +464,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.180" +version = "0.2.176" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" [[package]] name = "linux-raw-sys" @@ -563,30 +476,31 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litrs" -version = "1.0.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" +checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" [[package]] name = "lock_api" -version = "0.4.14" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ + "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.29" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "lru" @@ -594,7 +508,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.5", + "hashbrown", ] [[package]] @@ -608,38 +522,29 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.6" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "mio" -version = "1.1.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", "log", - "wasi", - "windows-sys 0.61.2", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", ] [[package]] name = "nu-ansi-term" -version = "0.50.3" +version = "0.50.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", + "windows-sys 0.52.0", ] [[package]] @@ -650,15 +555,15 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "once_cell_polyfill" -version = "1.70.2" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" [[package]] name = "parking_lot" -version = "0.12.5" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -666,15 +571,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.12" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-link", + "windows-targets 0.52.6", ] [[package]] @@ -691,18 +596,18 @@ checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.44" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] @@ -736,18 +641,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.18" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ "bitflags", ] [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" dependencies = [ "aho-corasick", "memchr", @@ -756,16 +661,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.9" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" [[package]] name = "rom" version = "0.1.0" dependencies = [ "anyhow", - "chrono", "clap", "cognos", "crossterm 0.29.0", @@ -781,15 +685,6 @@ dependencies = [ "tracing-subscriber", ] -[[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 = "0.38.44" @@ -805,15 +700,15 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.3" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ "bitflags", "errno", "libc", - "linux-raw-sys 0.11.0", - "windows-sys 0.61.2", + "linux-raw-sys 0.9.4", + "windows-sys 0.60.2", ] [[package]] @@ -824,9 +719,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.22" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "scopeguard" @@ -834,12 +729,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "semver" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" - [[package]] name = "serde" version = "1.0.228" @@ -872,15 +761,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", + "ryu", "serde", "serde_core", - "zmij", ] [[package]] @@ -903,12 +792,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - [[package]] name = "signal-hook" version = "0.3.18" @@ -921,9 +804,9 @@ dependencies = [ [[package]] name = "signal-hook-mio" -version = "0.2.5" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" dependencies = [ "libc", "mio", @@ -932,11 +815,10 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.8" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ - "errno", "libc", ] @@ -982,9 +864,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.114" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -993,31 +875,31 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.24.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", "getrandom", "once_cell", - "rustix 1.1.3", - "windows-sys 0.61.2", + "rustix 1.0.8", + "windows-sys 0.60.2", ] [[package]] name = "thiserror" -version = "2.0.18" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.18" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -1035,9 +917,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.44" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -1046,9 +928,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.31" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", @@ -1057,9 +939,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.36" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", @@ -1078,9 +960,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.22" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" dependencies = [ "matchers", "nu-ansi-term", @@ -1096,9 +978,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-segmentation" @@ -1148,59 +1030,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasip2" -version = "1.0.2+wasi-0.2.9" +name = "wasi" +version = "0.14.3+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "6a51ae83037bdd272a9e28ce236db8c07016dd0d50c27038b3f407533c030c95" dependencies = [ "wit-bindgen", ] -[[package]] -name = "wasm-bindgen" -version = "0.2.108" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.108" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.108" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.108" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" -dependencies = [ - "unicode-ident", -] - [[package]] name = "winapi" version = "0.3.9" @@ -1223,63 +1060,19 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-core" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "windows-link" -version = "0.2.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] -name = "windows-result" -version = "0.4.1" +name = "windows-sys" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link", + "windows-targets 0.52.6", ] [[package]] @@ -1288,16 +1081,16 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] name = "windows-sys" -version = "0.61.2" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-link", + "windows-targets 0.53.3", ] [[package]] @@ -1306,14 +1099,31 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", ] [[package]] @@ -1322,42 +1132,84 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -1365,13 +1217,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "wit-bindgen" -version = "0.51.0" +name = "windows_x86_64_msvc" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] -name = "zmij" -version = "1.0.19" +name = "wit-bindgen" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" +checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814" diff --git a/Cargo.toml b/Cargo.toml index 5456e9a..a749884 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,25 +1,29 @@ [workspace] -members = ["cognos", "rom"] +members = [ "cognos", "rom" ] resolver = "3" [workspace.package] +name = "rom" version = "0.1.0" edition = "2024" authors = ["NotAShelf "] description = "Pretty build graphs for Nix builds" -rust-version = "1.91.1" +license = "MPL-2.0" +repository = "https://github.com/notashelf/rom" +homepage = "https://github.com/notashelf/rom" +rust-version = "1.85" +readme = true [workspace.dependencies] anyhow = "1.0.100" -clap = { version = "4.5.51", features = ["derive"] } +clap = { version = "4.5.48", features = ["derive"] } serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.145" serde_repr = "0.1.20" crossterm = "0.29.0" ratatui = "0.29.0" -indexmap = { version = "2.12.0", features = ["serde"] } -csv = "1.4.0" -chrono = "0.4.42" +indexmap = { version = "2.11.4", features = ["serde"] } +csv = "1.3.1" thiserror = "2.0.17" tracing = "0.1.41" tracing-subscriber = { version = "0.3.20", features = ["env-filter"] } diff --git a/LICENSE b/LICENSE index de0a651..baa49b9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,288 +1,328 @@ - EUROPEAN UNION PUBLIC LICENCE v. 1.2 - EUPL © the European Union 2007, 2016 - -This European Union Public Licence (the ‘EUPL’) applies to the Work (as defined -below) which is provided under the terms of this Licence. Any use of the Work, -other than as authorised under this Licence is prohibited (to the extent such -use is covered by a right of the copyright holder of the Work). - -The Work is provided under the terms of this Licence when the Licensor (as -defined below) has placed the following notice immediately following the -copyright notice for the Work: - - Licensed under the EUPL - -or has expressed by any other means his willingness to license under the EUPL. +Mozilla Public License, version 2.0 1. Definitions -In this Licence, the following terms have the following meaning: - -- ‘The Licence’: this Licence. - -- ‘The Original Work’: the work or software distributed or communicated by the - Licensor under this Licence, available as Source Code and also as Executable - Code as the case may be. - -- ‘Derivative Works’: the works or software that could be created by the - Licensee, based upon the Original Work or modifications thereof. This Licence - does not define the extent of modification or dependence on the Original Work - required in order to classify a work as a Derivative Work; this extent is - determined by copyright law applicable in the country mentioned in Article 15. - -- ‘The Work’: the Original Work or its Derivative Works. - -- ‘The Source Code’: the human-readable form of the Work which is the most - convenient for people to study and modify. - -- ‘The Executable Code’: any code which has generally been compiled and which is - meant to be interpreted by a computer as a program. - -- ‘The Licensor’: the natural or legal person that distributes or communicates - the Work under the Licence. - -- ‘Contributor(s)’: any natural or legal person who modifies the Work under the - Licence, or otherwise contributes to the creation of a Derivative Work. - -- ‘The Licensee’ or ‘You’: any natural or legal person who makes any usage of - the Work under the terms of the Licence. - -- ‘Distribution’ or ‘Communication’: any act of selling, giving, lending, - renting, distributing, communicating, transmitting, or otherwise making - available, online or offline, copies of the Work or providing access to its - essential functionalities at the disposal of any other natural or legal - person. - -2. Scope of the rights granted by the Licence - -The Licensor hereby grants You a worldwide, royalty-free, non-exclusive, -sublicensable licence to do the following, for the duration of copyright vested -in the Original Work: - -- use the Work in any circumstance and for all usage, -- reproduce the Work, -- modify the Work, and make Derivative Works based upon the Work, -- communicate to the public, including the right to make available or display - the Work or copies thereof to the public and perform publicly, as the case may - be, the Work, -- distribute the Work or copies thereof, -- lend and rent the Work or copies thereof, -- sublicense rights in the Work or copies thereof. - -Those rights can be exercised on any media, supports and formats, whether now -known or later invented, as far as the applicable law permits so. - -In the countries where moral rights apply, the Licensor waives his right to -exercise his moral right to the extent allowed by law in order to make effective -the licence of the economic rights here above listed. - -The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to -any patents held by the Licensor, to the extent necessary to make use of the -rights granted on the Work under this Licence. - -3. Communication of the Source Code - -The Licensor may provide the Work either in its Source Code form, or as -Executable Code. If the Work is provided as Executable Code, the Licensor -provides in addition a machine-readable copy of the Source Code of the Work -along with each copy of the Work that the Licensor distributes or indicates, in -a notice following the copyright notice attached to the Work, a repository where -the Source Code is easily and freely accessible for as long as the Licensor -continues to distribute or communicate the Work. - -4. Limitations on copyright - -Nothing in this Licence is intended to deprive the Licensee of the benefits from -any exception or limitation to the exclusive rights of the rights owners in the -Work, of the exhaustion of those rights or of other applicable limitations -thereto. - -5. Obligations of the Licensee - -The grant of the rights mentioned above is subject to some restrictions and -obligations imposed on the Licensee. Those obligations are the following: - -Attribution right: The Licensee shall keep intact all copyright, patent or -trademarks notices and all notices that refer to the Licence and to the -disclaimer of warranties. The Licensee must include a copy of such notices and a -copy of the Licence with every copy of the Work he/she distributes or -communicates. The Licensee must cause any Derivative Work to carry prominent -notices stating that the Work has been modified and the date of modification. - -Copyleft clause: If the Licensee distributes or communicates copies of the -Original Works or Derivative Works, this Distribution or Communication will be -done under the terms of this Licence or of a later version of this Licence -unless the Original Work is expressly distributed only under this version of the -Licence — for example by communicating ‘EUPL v. 1.2 only’. The Licensee -(becoming Licensor) cannot offer or impose any additional terms or conditions on -the Work or Derivative Work that alter or restrict the terms of the Licence. - -Compatibility clause: If the Licensee Distributes or Communicates Derivative -Works or copies thereof based upon both the Work and another work licensed under -a Compatible Licence, this Distribution or Communication can be done under the -terms of this Compatible Licence. For the sake of this clause, ‘Compatible -Licence’ refers to the licences listed in the appendix attached to this Licence. -Should the Licensee's obligations under the Compatible Licence conflict with -his/her obligations under this Licence, the obligations of the Compatible -Licence shall prevail. - -Provision of Source Code: When distributing or communicating copies of the Work, -the Licensee will provide a machine-readable copy of the Source Code or indicate -a repository where this Source will be easily and freely available for as long -as the Licensee continues to distribute or communicate the Work. - -Legal Protection: This Licence does not grant permission to use the trade names, -trademarks, service marks, or names of the Licensor, except as required for -reasonable and customary use in describing the origin of the Work and -reproducing the content of the copyright notice. - -6. Chain of Authorship - -The original Licensor warrants that the copyright in the Original Work granted -hereunder is owned by him/her or licensed to him/her and that he/she has the -power and authority to grant the Licence. - -Each Contributor warrants that the copyright in the modifications he/she brings -to the Work are owned by him/her or licensed to him/her and that he/she has the -power and authority to grant the Licence. - -Each time You accept the Licence, the original Licensor and subsequent -Contributors grant You a licence to their contributions to the Work, under the -terms of this Licence. - -7. Disclaimer of Warranty - -The Work is a work in progress, which is continuously improved by numerous -Contributors. It is not a finished work and may therefore contain defects or -‘bugs’ inherent to this type of development. - -For the above reason, the Work is provided under the Licence on an ‘as is’ basis -and without warranties of any kind concerning the Work, including without -limitation merchantability, fitness for a particular purpose, absence of defects -or errors, accuracy, non-infringement of intellectual property rights other than -copyright as stated in Article 6 of this Licence. - -This disclaimer of warranty is an essential part of the Licence and a condition -for the grant of any rights to the Work. - -8. Disclaimer of Liability - -Except in the cases of wilful misconduct or damages directly caused to natural -persons, the Licensor will in no event be liable for any direct or indirect, -material or moral, damages of any kind, arising out of the Licence or of the use -of the Work, including without limitation, damages for loss of goodwill, work -stoppage, computer failure or malfunction, loss of data or any commercial -damage, even if the Licensor has been advised of the possibility of such damage. -However, the Licensor will be liable under statutory product liability laws as -far such laws apply to the Work. - -9. Additional agreements - -While distributing the Work, You may choose to conclude an additional agreement, -defining obligations or services consistent with this Licence. However, if -accepting obligations, You may act only on your own behalf and on your sole -responsibility, not on behalf of the original Licensor or any other Contributor, -and only if You agree to indemnify, defend, and hold each Contributor harmless -for any liability incurred by, or claims asserted against such Contributor by -the fact You have accepted any warranty or additional liability. - -10. Acceptance of the Licence - -The provisions of this Licence can be accepted by clicking on an icon ‘I agree’ -placed under the bottom of a window displaying the text of this Licence or by -affirming consent in any other similar way, in accordance with the rules of -applicable law. Clicking on that icon indicates your clear and irrevocable -acceptance of this Licence and all of its terms and conditions. - -Similarly, you irrevocably accept this Licence and all of its terms and -conditions by exercising any rights granted to You by Article 2 of this Licence, -such as the use of the Work, the creation by You of a Derivative Work or the -Distribution or Communication by You of the Work or copies thereof. - -11. Information to the public - -In case of any Distribution or Communication of the Work by means of electronic -communication by You (for example, by offering to download the Work from a -remote location) the distribution channel or media (for example, a website) must -at least provide to the public the information requested by the applicable law -regarding the Licensor, the Licence and the way it may be accessible, concluded, -stored and reproduced by the Licensee. - -12. Termination of the Licence - -The Licence and the rights granted hereunder will terminate automatically upon -any breach by the Licensee of the terms of the Licence. - -Such a termination will not terminate the licences of any person who has -received the Work from the Licensee under the Licence, provided such persons -remain in full compliance with the Licence. - -13. Miscellaneous - -Without prejudice of Article 9 above, the Licence represents the complete -agreement between the Parties as to the Work. - -If any provision of the Licence is invalid or unenforceable under applicable -law, this will not affect the validity or enforceability of the Licence as a -whole. Such provision will be construed or reformed so as necessary to make it -valid and enforceable. - -The European Commission may publish other linguistic versions or new versions of -this Licence or updated versions of the Appendix, so far this is required and -reasonable, without reducing the scope of the rights granted by the Licence. New -versions of the Licence will be published with a unique version number. - -All linguistic versions of this Licence, approved by the European Commission, -have identical value. Parties can take advantage of the linguistic version of -their choice. - -14. Jurisdiction - -Without prejudice to specific agreement between parties, - -- any litigation resulting from the interpretation of this License, arising - between the European Union institutions, bodies, offices or agencies, as a - Licensor, and any Licensee, will be subject to the jurisdiction of the Court - of Justice of the European Union, as laid down in article 272 of the Treaty on - the Functioning of the European Union, - -- any litigation arising between other parties and resulting from the - interpretation of this License, will be subject to the exclusive jurisdiction - of the competent court where the Licensor resides or conducts its primary - business. - -15. Applicable Law - -Without prejudice to specific agreement between parties, - -- this Licence shall be governed by the law of the European Union Member State - where the Licensor has his seat, resides or has his registered office, - -- this licence shall be governed by Belgian law if the Licensor has no seat, - residence or registered office inside a European Union Member State. - -Appendix - -‘Compatible Licences’ according to Article 5 EUPL are: - -- GNU General Public License (GPL) v. 2, v. 3 -- GNU Affero General Public License (AGPL) v. 3 -- Open Software License (OSL) v. 2.1, v. 3.0 -- Eclipse Public License (EPL) v. 1.0 -- CeCILL v. 2.0, v. 2.1 -- Mozilla Public Licence (MPL) v. 2 -- GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3 -- Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for - works other than software -- European Union Public Licence (EUPL) v. 1.1, v. 1.2 -- Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong - Reciprocity (LiLiQ-R+). - -The European Commission may update this Appendix to later versions of the above -licences without producing a new version of the EUPL, as long as they provide -the rights granted in Article 2 of this Licence and protect the covered Source -Code from exclusive appropriation. - -All other changes or additions to this Appendix require the production of a new -EUPL version. - + 1.1. “Contributor” + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + + 1.2. “Contributor Version” + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor’s Contribution. + + 1.3. “Contribution” + means Covered Software of a particular Contributor. + + 1.4. “Covered Software” + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, + and Modifications of such Source Code Form, in each case + including portions thereof. + + 1.5. “Incompatible With Secondary Licenses” + means + + a. that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the terms + of a Secondary License. + + 1.6. “Executable Form” + means any form of the work other than Source Code Form. + + 1.7. “Larger Work” + means a work that combines Covered Software with other material, + in a separate file or files, that is not Covered Software. + + 1.8. “License” + means this document. + + 1.9. “Licensable” + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, + any and all of the rights conveyed by this License. + + 1.10. “Modifications” + means any of the following: + + a. any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + + 1.11. “Patent Claims” of a Contributor + means any patent claim(s), including without limitation, method, process, + and apparatus claims, in any patent Licensable by such Contributor that + would be infringed, but for the grant of the License, by the making, + using, selling, offering for sale, having made, import, or transfer of + either its Contributions or its Contributor Version. + + 1.12. “Secondary License” + means either the GNU General Public License, Version 2.0, the + GNU Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those licenses. + + 1.13. “Source Code Form” + means the form of the work preferred for making modifications. + + 1.14. “You” (or “Your”) + means an individual or a legal entity exercising rights under this License. + For legal entities, “You” includes any entity that controls, + is controlled by, or is under common control with You. For purposes of + this definition, “control” means (a) the power, direct or indirect, + to cause the direction or management of such entity, whether by contract + or otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + +2. License Grants and Conditions + + 2.1. Grants + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, + or as part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, + offer for sale, have made, import, and otherwise transfer either + its Contributions or its Contributor Version. + + 2.2. Effective Date + The licenses granted in Section 2.1 with respect to any Contribution + become effective for each Contribution on the date the Contributor + first distributes such Contribution. + + 2.3. Limitations on Grant Scope + The licenses granted in this Section 2 are the only rights granted + under this License. No additional rights or licenses will be implied + from the distribution or licensing of Covered Software under this License. + Notwithstanding Section 2.1(b) above, no patent license is granted + by a Contributor: + + a. for any code that a Contributor has removed from + Covered Software; or + + b. for infringements caused by: (i) Your and any other third party’s + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its + Contributor Version); or + + c. under Patent Claims infringed by Covered Software in the + absence of its Contributions. + + This License does not grant any rights in the trademarks, service marks, + or logos of any Contributor (except as may be necessary to comply with + the notice requirements in Section 3.4). + + 2.4. Subsequent Licenses + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this + License (see Section 10.2) or under the terms of a Secondary License + (if permitted under the terms of Section 3.3). + + 2.5. Representation + Each Contributor represents that the Contributor believes its + Contributions are its original creation(s) or it has sufficient rights + to grant the rights to its Contributions conveyed by this License. + + 2.6. Fair Use + This License is not intended to limit any rights You have under + applicable copyright doctrines of fair use, fair dealing, + or other equivalents. + + 2.7. Conditions + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the + licenses granted in Section 2.1. + +3. Responsibilities + + 3.1. Distribution of Source Form + All distribution of Covered Software in Source Code Form, including + any Modifications that You create or to which You contribute, must be + under the terms of this License. You must inform recipients that the + Source Code Form of the Covered Software is governed by the terms + of this License, and how they can obtain a copy of this License. + You may not attempt to alter or restrict the recipients’ rights + in the Source Code Form. + + 3.2. Distribution of Executable Form + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more than + the cost of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients’ rights in the Source Code Form under this License. + + 3.3. Distribution of a Larger Work + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for + the Covered Software. If the Larger Work is a combination of + Covered Software with a work governed by one or more Secondary Licenses, + and the Covered Software is not Incompatible With Secondary Licenses, + this License permits You to additionally distribute such Covered Software + under the terms of such Secondary License(s), so that the recipient of + the Larger Work may, at their option, further distribute the + Covered Software under the terms of either this License or such + Secondary License(s). + + 3.4. Notices + You may not remove or alter the substance of any license notices + (including copyright notices, patent notices, disclaimers of warranty, + or limitations of liability) contained within the Source Code Form of + the Covered Software, except that You may alter any license notices to + the extent required to remedy known factual inaccuracies. + + 3.5. Application of Additional Terms + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of + Covered Software. However, You may do so only on Your own behalf, + and not on behalf of any Contributor. You must make it absolutely clear + that any such warranty, support, indemnity, or liability obligation is + offered by You alone, and You hereby agree to indemnify every Contributor + for any liability incurred by such Contributor as a result of warranty, + support, indemnity or liability terms You offer. You may include + additional disclaimers of warranty and limitations of liability + specific to any jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + +If it is impossible for You to comply with any of the terms of this License +with respect to some or all of the Covered Software due to statute, +judicial order, or regulation then You must: (a) comply with the terms of +this License to the maximum extent possible; and (b) describe the limitations +and the code they affect. Such description must be placed in a text file +included with all distributions of the Covered Software under this License. +Except to the extent prohibited by statute or regulation, such description +must be sufficiently detailed for a recipient of ordinary skill +to be able to understand it. + +5. Termination + + 5.1. The rights granted under this License will terminate automatically + if You fail to comply with any of its terms. However, if You become + compliant, then the rights granted under this License from a particular + Contributor are reinstated (a) provisionally, unless and until such + Contributor explicitly and finally terminates Your grants, and (b) on an + ongoing basis, if such Contributor fails to notify You of the + non-compliance by some reasonable means prior to 60 days after You have + come back into compliance. Moreover, Your grants from a particular + Contributor are reinstated on an ongoing basis if such Contributor + notifies You of the non-compliance by some reasonable means, + this is the first time You have received notice of non-compliance with + this License from such Contributor, and You become compliant prior to + 30 days after Your receipt of the notice. + + 5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, + counter-claims, and cross-claims) alleging that a Contributor Version + directly or indirectly infringes any patent, then the rights granted + to You by any and all Contributors for the Covered Software under + Section 2.1 of this License shall terminate. + + 5.3. In the event of termination under Sections 5.1 or 5.2 above, all + end user license agreements (excluding distributors and resellers) which + have been validly granted by You or Your distributors under this License + prior to termination shall survive termination. + +6. Disclaimer of Warranty + +Covered Software is provided under this License on an “as is” basis, without +warranty of any kind, either expressed, implied, or statutory, including, +without limitation, warranties that the Covered Software is free of defects, +merchantable, fit for a particular purpose or non-infringing. The entire risk +as to the quality and performance of the Covered Software is with You. +Should any Covered Software prove defective in any respect, You +(not any Contributor) assume the cost of any necessary servicing, repair, +or correction. This disclaimer of warranty constitutes an essential part of +this License. No use of any Covered Software is authorized under this +License except under this disclaimer. + +7. Limitation of Liability + +Under no circumstances and under no legal theory, whether tort +(including negligence), contract, or otherwise, shall any Contributor, or +anyone who distributes Covered Software as permitted above, be liable to +You for any direct, indirect, special, incidental, or consequential damages +of any character including, without limitation, damages for lost profits, +loss of goodwill, work stoppage, computer failure or malfunction, or any and +all other commercial damages or losses, even if such party shall have been +informed of the possibility of such damages. This limitation of liability +shall not apply to liability for death or personal injury resulting from +such party’s negligence to the extent applicable law prohibits such +limitation. Some jurisdictions do not allow the exclusion or limitation of +incidental or consequential damages, so this exclusion and limitation may +not apply to You. + +8. Litigation + +Any litigation relating to this License may be brought only in the courts of +a jurisdiction where the defendant maintains its principal place of business +and such litigation shall be governed by laws of that jurisdiction, without +reference to its conflict-of-law provisions. Nothing in this Section shall +prevent a party’s ability to bring cross-claims or counter-claims. + +9. Miscellaneous + +This License represents the complete agreement concerning the subject matter +hereof. If any provision of this License is held to be unenforceable, +such provision shall be reformed only to the extent necessary to make it +enforceable. Any law or regulation which provides that the language of a +contract shall be construed against the drafter shall not be used to construe +this License against a Contributor. + +10. Versions of the License + + 10.1. New Versions + Mozilla Foundation is the license steward. Except as provided in + Section 10.3, no one other than the license steward has the right to + modify or publish new versions of this License. Each version will be + given a distinguishing version number. + + 10.2. Effect of New Versions + You may distribute the Covered Software under the terms of the version + of the License under which You originally received the Covered Software, + or under the terms of any subsequent version published + by the license steward. + + 10.3. Modified Versions + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a modified + version of this License if you rename the license and remove any + references to the name of the license steward (except to note that such + modified license differs from this License). + + 10.4. Distributing Source Code Form that is + Incompatible With Secondary Licenses + If You choose to distribute Source Code Form that is + Incompatible With Secondary Licenses under the terms of this version of + the License, the notice described in Exhibit B of this + License must be attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the terms of the + Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed + with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, +then You may include the notice in a location (such as a LICENSE file in a +relevant directory) where a recipient would be likely to +look for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - “Incompatible With Secondary Licenses” Notice + + This Source Code Form is “Incompatible With Secondary Licenses”, + as defined by the Mozilla Public License, v. 2.0. diff --git a/cognos/Cargo.toml b/cognos/Cargo.toml index 4e0a70d..102a00d 100644 --- a/cognos/Cargo.toml +++ b/cognos/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "cognos" description = "Minimalistic parser for Nix's ATerm .drv and internal-json log formats" -version.workspace = true -edition.workspace = true -authors.workspace = true +version.workspace = true +edition.workspace = true +authors.workspace = true rust-version.workspace = true [lib] diff --git a/cognos/src/lib.rs b/cognos/src/lib.rs index a6443ca..d9eda4d 100644 --- a/cognos/src/lib.rs +++ b/cognos/src/lib.rs @@ -1,5 +1,3 @@ -use std::collections::HashMap; - pub mod aterm; mod internal_json; mod state; @@ -11,32 +9,4 @@ pub use aterm::{ parse_drv_file, }; pub use internal_json::{Actions, Activities, Id, Verbosity}; -pub use state::{ - BuildInfo, - BuildStatus, - Dependencies, - Derivation, - Host, - OutputName, - ProgressState, - State, -}; - -/// Process a list of actions and return the resulting state -#[must_use] -pub fn process_actions(actions: Vec) -> State { - let mut state = State { - progress: ProgressState::JustStarted, - derivations: HashMap::new(), - builds: HashMap::new(), - dependencies: Dependencies { - deps: HashMap::new(), - }, - store_paths: HashMap::new(), - dependency_states: HashMap::new(), - }; - for action in actions { - state.imbibe(action); - } - state -} +pub use state::{BuildInfo, BuildStatus, Derivation, Host, State}; diff --git a/cognos/src/state.rs b/cognos/src/state.rs index c72db70..56139e8 100644 --- a/cognos/src/state.rs +++ b/cognos/src/state.rs @@ -11,7 +11,6 @@ pub enum StorePath { Uploaded, } -#[derive(Clone)] pub enum BuildStatus { Planned, Running, @@ -19,14 +18,12 @@ pub enum BuildStatus { Failed, } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum ProgressState { +pub enum Progress { JustStarted, InputReceived, Finished, } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum OutputName { Out, Doc, @@ -39,44 +36,15 @@ pub enum OutputName { Other(String), } -impl OutputName { - #[must_use] - pub fn parse(name: &str) -> Self { - match name.to_lowercase().as_str() { - "out" => Self::Out, - "doc" => Self::Doc, - "dev" => Self::Dev, - "bin" => Self::Bin, - "info" => Self::Info, - "lib" => Self::Lib, - "man" => Self::Man, - "dist" => Self::Dist, - _ => Self::Other(name.to_string()), - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Host { - Localhost, - Remote(String), -} - -impl Host { - #[must_use] - pub fn name(&self) -> &str { - match self { - Self::Localhost => "localhost", - Self::Remote(name) => name, - } - } + Local, + Host(String), } pub struct Derivation { store_path: PathBuf, } -#[derive(Clone)] pub struct BuildInfo { start: f64, host: Host, @@ -92,68 +60,14 @@ pub enum DependencyState { } pub struct Dependencies { - pub deps: HashMap, + deps: HashMap, } // #[derive(Default)] pub struct State { - pub progress: ProgressState, - pub derivations: HashMap, - pub builds: HashMap, - pub dependencies: Dependencies, - pub store_paths: HashMap, - pub dependency_states: HashMap, + progress: Progress, } impl State { - pub fn imbibe(&mut self, action: Actions) { - match action { - Actions::Start { - id, - activity: _activity, - .. - } => { - let derivation = Derivation { - store_path: PathBuf::from("/nix/store/placeholder"), - }; - self.derivations.insert(id, derivation); - - // Use the store_path to mark as used - let _path = &self.derivations.get(&id).unwrap().store_path; - - let build_info = BuildInfo { - start: 0.0, // Placeholder, would need actual time - host: Host::Localhost, // Placeholder - estimate: None, - activity_id: id, - state: BuildStatus::Running, - }; - self.builds.insert(id, build_info.clone()); - self.dependencies.deps.insert(id, build_info); - - // Use the fields to mark as used - let _start = self.builds.get(&id).unwrap().start; - let _host = &self.builds.get(&id).unwrap().host; - let _estimate = &self.builds.get(&id).unwrap().estimate; - let _activity_id = self.builds.get(&id).unwrap().activity_id; - - self.store_paths.insert(id, StorePath::Downloading); - self.dependency_states.insert(id, DependencyState::Running); - }, - Actions::Result { id, .. } => { - if let Some(build) = self.builds.get_mut(&id) { - build.state = BuildStatus::Complete; - } - }, - Actions::Stop { id } => { - if let Some(build) = self.builds.get_mut(&id) { - build.state = BuildStatus::Complete; - } - }, - Actions::Message { .. } => { - // Could update progress or other state - self.progress = ProgressState::InputReceived; - }, - } - } + pub fn imbibe(&mut self, update: Actions) {} } diff --git a/flake.lock b/flake.lock index c2a8a2c..cc5c8c8 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1770115704, - "narHash": "sha256-KHFT9UWOF2yRPlAnSXQJh6uVcgNcWlFqqiAZ7OVlHNc=", + "lastModified": 1759381078, + "narHash": "sha256-gTrEEp5gEspIcCOx9PD8kMaF1iEmfBcTbO0Jag2QhQs=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "e6eae2ee2110f3d31110d5c222cd395303343b08", + "rev": "7df7ff7d8e00218376575f0acdcc5d66741351ee", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 5550e67..18bd6fa 100644 --- a/flake.nix +++ b/flake.nix @@ -1,5 +1,7 @@ { - inputs.nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-unstable"; + description = "Rust Project Template"; + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + outputs = { self, nixpkgs, diff --git a/rom/Cargo.toml b/rom/Cargo.toml index 6affa5b..f1223bd 100644 --- a/rom/Cargo.toml +++ b/rom/Cargo.toml @@ -1,16 +1,16 @@ [package] name = "rom" -description.workspace = true -version.workspace = true -edition.workspace = true -authors.workspace = true +description.workspace = true +version.workspace = true +edition.workspace = true +authors.workspace = true rust-version.workspace = true [lib] path = "src/lib.rs" [dependencies] -cognos = { path = "../cognos" } +cognos = {path = "../cognos"} anyhow.workspace = true clap.workspace = true serde.workspace = true @@ -19,7 +19,6 @@ crossterm = "0.29" ratatui = "0.29" indexmap.workspace = true csv.workspace = true -chrono.workspace = true thiserror.workspace = true tracing.workspace = true tracing-subscriber.workspace = true diff --git a/rom/src/cache.rs b/rom/src/cache.rs deleted file mode 100644 index b3c9dbf..0000000 --- a/rom/src/cache.rs +++ /dev/null @@ -1,397 +0,0 @@ - -use std::{ - collections::HashMap, - fs::{self, File, OpenOptions}, - io::{BufReader, BufWriter}, - path::PathBuf, - time::SystemTime, -}; - -use csv::{Reader, Writer}; -use serde::{Deserialize, Serialize}; - -use crate::state::BuildReport; - -/// Maximum number of historical builds to keep per derivation -const HISTORY_LIMIT: usize = 10; - -/// Build report cache for CSV persistence -pub struct BuildReportCache { - cache_path: PathBuf, -} - -/// CSV row format for build reports -#[derive(Debug, Clone, Serialize, Deserialize)] -struct BuildReportRow { - hostname: String, - derivation_name: String, - utc_time: String, - build_seconds: u64, -} - -impl BuildReportCache { - /// Create a new cache instance with the given path - #[must_use] - pub fn new(cache_path: PathBuf) -> Self { - Self { cache_path } - } - - // FIXME: just use the dirs crate for this - /// Get the default cache directory path - /// - /// Uses `$XDG_STATE_HOME` if set, otherwise ``~/.local/state` - #[must_use] - pub fn default_cache_dir() -> PathBuf { - if let Ok(xdg_state) = std::env::var("XDG_STATE_HOME") { - PathBuf::from(xdg_state).join("rom") - } else if let Ok(home) = std::env::var("HOME") { - PathBuf::from(home).join(".local/state/rom") - } else { - PathBuf::from(".rom") - } - } - - /// Get the default cache file path - #[must_use] - pub fn default_cache_path() -> PathBuf { - Self::default_cache_dir().join("build-reports.csv") - } - - /// Load build reports from CSV - /// - /// Returns empty [`HashMap`] if file doesn't exist or parsing fails - pub fn load(&self) -> HashMap<(String, String), Vec> { - if !self.cache_path.exists() { - return HashMap::new(); - } - - let file = match File::open(&self.cache_path) { - Ok(f) => f, - Err(_) => return HashMap::new(), - }; - - let reader = BufReader::new(file); - let mut csv_reader = Reader::from_reader(reader); - - let mut reports: HashMap<(String, String), Vec> = - HashMap::new(); - - for result in csv_reader.deserialize() { - let row: BuildReportRow = match result { - Ok(r) => r, - Err(_) => continue, - }; - - let completed_at = match parse_utc_time(&row.utc_time) { - Some(t) => t, - None => continue, - }; - - let report = BuildReport { - derivation_name: row.derivation_name.clone(), - platform: String::new(), // FIXME: not stored in CSV, for simplicity and because I'm lazy - duration_secs: row.build_seconds as f64, - completed_at, - host: row.hostname.clone(), - success: true, // only successful builds are cached - }; - - let key = (row.hostname, row.derivation_name); - reports.entry(key).or_default().push(report); - } - - // Sort each entry by timestamp (newest first) and limit to HISTORY_LIMIT - for entries in reports.values_mut() { - entries.sort_by(|a, b| b.completed_at.cmp(&a.completed_at)); - entries.truncate(HISTORY_LIMIT); - } - - reports - } - - /// Save build reports to CSV - /// - /// Merges with existing reports and enforces history limit - pub fn save( - &self, - reports: &HashMap<(String, String), Vec>, - ) -> Result<(), std::io::Error> { - // Ensure directory exists - if let Some(parent) = self.cache_path.parent() { - fs::create_dir_all(parent)?; - } - - // Load existing reports to merge - let mut merged = self.load(); - - // Merge new reports - for ((host, drv_name), new_reports) in reports { - let key = (host.clone(), drv_name.clone()); - let existing = merged.entry(key).or_default(); - - // Add new reports - existing.extend(new_reports.iter().cloned()); - - // Sort by timestamp (newest first) - existing.sort_by(|a, b| b.completed_at.cmp(&a.completed_at)); - - // Keep only most recent HISTORY_LIMIT entries - existing.truncate(HISTORY_LIMIT); - } - - // Write to CSV - let file = OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(&self.cache_path)?; - - let writer = BufWriter::new(file); - let mut csv_writer = Writer::from_writer(writer); - - // Flatten and write all reports - for ((hostname, derivation_name), entries) in merged { - for report in entries { - let row = BuildReportRow { - hostname: hostname.clone(), - derivation_name: derivation_name.clone(), - utc_time: format_utc_time(report.completed_at), - build_seconds: report.duration_secs as u64, - }; - csv_writer.serialize(row)?; - } - } - - csv_writer.flush()?; - Ok(()) - } - - /// Calculate median build time from historical reports - /// - /// Returns [`None`] if there are no reports - #[must_use] - pub fn calculate_median(reports: &[BuildReport]) -> Option { - if reports.is_empty() { - return None; - } - - let mut durations: Vec = - reports.iter().map(|r| r.duration_secs as u64).collect(); - durations.sort_unstable(); - - let len = durations.len(); - if len % 2 == 1 { - Some(durations[len / 2]) - } else { - let mid1 = durations[len / 2 - 1]; - let mid2 = durations[len / 2]; - Some((mid1 + mid2) / 2) - } - } - - /// Get median build time for a specific derivation on a host - #[must_use] - pub fn get_estimate( - &self, - reports: &HashMap<(String, String), Vec>, - host: &str, - derivation_name: &str, - ) -> Option { - let key = (host.to_string(), derivation_name.to_string()); - let entries = reports.get(&key)?; - Self::calculate_median(entries) - } -} - -/// Parse UTC time string in format "%Y-%m-%d %H:%M:%S" -fn parse_utc_time(s: &str) -> Option { - // Simple parsing for "YYYY-MM-DD HH:MM:SS" format - let parts: Vec<&str> = s.split([' ', '-', ':']).collect(); - if parts.len() != 6 { - return None; - } - - let year: i64 = parts[0].parse().ok()?; - let month: u64 = parts[1].parse().ok()?; - let day: u64 = parts[2].parse().ok()?; - let hour: u64 = parts[3].parse().ok()?; - let minute: u64 = parts[4].parse().ok()?; - let second: u64 = parts[5].parse().ok()?; - - // Approximate conversion to Unix timestamp - // This is a simplified calculation that doesn't handle leap years perfectly - let days_since_epoch = (year - 1970) * 365 - + (year - 1969) / 4 - + days_until_month(month) - + day as i64 - - 1; - let seconds_since_epoch = - days_since_epoch as u64 * 86400 + hour * 3600 + minute * 60 + second; - - Some( - SystemTime::UNIX_EPOCH - + std::time::Duration::from_secs(seconds_since_epoch), - ) -} - -// FIXME: I'm really sure there's a library for this but lets just get -// this thing compiling -/// Calculate days until the start of a month (approximation) -const fn days_until_month(month: u64) -> i64 { - match month { - 1 => 0, - 2 => 31, - 3 => 59, - 4 => 90, - 5 => 120, - 6 => 151, - 7 => 181, - 8 => 212, - 9 => 243, - 10 => 273, - 11 => 304, - 12 => 334, - _ => 0, - } -} - -// FIXME: does Chrono do this? -/// Format SystemTime as UTC string in format "%Y-%m-%d %H:%M:%S" -fn format_utc_time(time: SystemTime) -> String { - let duration = time - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap_or_default(); - let secs = duration.as_secs(); - - let days = secs / 86400; - let remaining = secs % 86400; - let hours = remaining / 3600; - let minutes = (remaining % 3600) / 60; - let seconds = remaining % 60; - - // Approximate conversion from days since epoch to date - let mut year = 1970; - let mut days_left = days as i64; - - // Subtract full years - while days_left >= 365 { - if is_leap_year(year) && days_left >= 366 { - days_left -= 366; - year += 1; - } else if !is_leap_year(year) { - days_left -= 365; - year += 1; - } else { - break; - } - } - - // Calculate month and day - let (month, day) = calculate_month_day(days_left as u64, is_leap_year(year)); - - format!("{year:04}-{month:02}-{day:02} {hours:02}:{minutes:02}:{seconds:02}") -} - -const fn is_leap_year(year: i64) -> bool { - (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0) -} - -fn calculate_month_day(days: u64, is_leap: bool) -> (u8, u8) { - let days_in_month: [u8; 12] = if is_leap { - [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] - } else { - [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] - }; - - let mut remaining = days as i32; - for (i, &month_days) in days_in_month.iter().enumerate() { - if remaining < i32::from(month_days) { - return ((i + 1) as u8, (remaining + 1) as u8); - } - remaining -= i32::from(month_days); - } - - (12, 31) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_calculate_median_odd() { - let reports = vec![ - BuildReport { - derivation_name: "test".to_string(), - platform: "x86_64-linux".to_string(), - duration_secs: 10.0, - completed_at: SystemTime::UNIX_EPOCH, - host: "localhost".to_string(), - success: true, - }, - BuildReport { - derivation_name: "test".to_string(), - platform: "x86_64-linux".to_string(), - duration_secs: 20.0, - completed_at: SystemTime::UNIX_EPOCH, - host: "localhost".to_string(), - success: true, - }, - BuildReport { - derivation_name: "test".to_string(), - platform: "x86_64-linux".to_string(), - duration_secs: 30.0, - completed_at: SystemTime::UNIX_EPOCH, - host: "localhost".to_string(), - success: true, - }, - ]; - - assert_eq!(BuildReportCache::calculate_median(&reports), Some(20)); - } - - #[test] - fn test_calculate_median_even() { - let reports = vec![ - BuildReport { - derivation_name: "test".to_string(), - platform: "x86_64-linux".to_string(), - duration_secs: 10.0, - completed_at: SystemTime::UNIX_EPOCH, - host: "localhost".to_string(), - success: true, - }, - BuildReport { - derivation_name: "test".to_string(), - platform: "x86_64-linux".to_string(), - duration_secs: 20.0, - completed_at: SystemTime::UNIX_EPOCH, - host: "localhost".to_string(), - success: true, - }, - ]; - - assert_eq!(BuildReportCache::calculate_median(&reports), Some(15)); - } - - #[test] - fn test_calculate_median_empty() { - let reports = vec![]; - assert_eq!(BuildReportCache::calculate_median(&reports), None); - } - - #[test] - fn test_format_parse_utc_time() { - let time = - SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(1_000_000); - let formatted = format_utc_time(time); - let parsed = parse_utc_time(&formatted).unwrap(); - - // Allow small difference due to approximation - let diff = parsed - .duration_since(time) - .unwrap_or_else(|e| e.duration()) - .as_secs(); - assert!(diff < 86400); // less than 1 day difference - } -} diff --git a/rom/src/cli.rs b/rom/src/cli.rs index e6508e7..937ebd9 100644 --- a/rom/src/cli.rs +++ b/rom/src/cli.rs @@ -6,7 +6,6 @@ use std::{ }; use clap::Parser; -use cognos::ProgressState; #[derive(Debug, Parser)] #[command(name = "rom", version, about = "ROM - A Nix build output monitor")] @@ -33,14 +32,6 @@ pub struct Cli { /// Summary display style: concise, table, full #[arg(long, global = true, default_value = "concise")] pub summary: String, - - /// Log prefix style: short, full, none - #[arg(long, global = true, default_value = "short")] - pub log_prefix: String, - - /// Maximum number of log lines to display - #[arg(long, global = true)] - pub log_lines: Option, } #[derive(Debug, clap::Subcommand)] @@ -94,8 +85,6 @@ pub fn run() -> eyre::Result<()> { cli.format.clone(), cli.legend.clone(), cli.summary.clone(), - cli.log_prefix.clone(), - cli.log_lines, )?; Ok(()) }, @@ -111,8 +100,6 @@ pub fn run() -> eyre::Result<()> { cli.format.clone(), cli.legend.clone(), cli.summary.clone(), - cli.log_prefix.clone(), - cli.log_lines, )?; Ok(()) }, @@ -122,18 +109,14 @@ pub fn run() -> eyre::Result<()> { // If no args provided and --json is set, use piping mode from stdin if args.is_empty() && cli.json { let config = crate::types::Config { - piping: false, - silent: cli.silent, - input_mode: crate::types::InputMode::Json, - show_timers: true, - width: None, - format: crate::types::DisplayFormat::from_str(&cli.format), - legend_style: cli.legend.clone(), - summary_style: cli.summary.clone(), - log_prefix_style: crate::types::LogPrefixStyle::from_str( - &cli.log_prefix, - ), - log_line_limit: cli.log_lines, + piping: false, + silent: cli.silent, + input_mode: crate::types::InputMode::Json, + show_timers: true, + width: None, + format: crate::types::DisplayFormat::from_str(&cli.format), + legend_style: cli.legend.clone(), + summary_style: cli.summary.clone(), }; let stdin = io::stdin(); @@ -156,8 +139,6 @@ pub fn run() -> eyre::Result<()> { cli.format.clone(), cli.legend.clone(), cli.summary.clone(), - cli.log_prefix.clone(), - cli.log_lines, )?; Ok(()) }, @@ -167,18 +148,14 @@ pub fn run() -> eyre::Result<()> { // If no args provided and --json is set, use piping mode from stdin if args.is_empty() && cli.json { let config = crate::types::Config { - piping: false, - silent: cli.silent, - input_mode: crate::types::InputMode::Json, - show_timers: true, - width: None, - format: crate::types::DisplayFormat::from_str(&cli.format), - legend_style: cli.legend.clone(), - summary_style: cli.summary.clone(), - log_prefix_style: crate::types::LogPrefixStyle::from_str( - &cli.log_prefix, - ), - log_line_limit: cli.log_lines, + piping: false, + silent: cli.silent, + input_mode: crate::types::InputMode::Json, + show_timers: true, + width: None, + format: crate::types::DisplayFormat::from_str(&cli.format), + legend_style: cli.legend.clone(), + summary_style: cli.summary.clone(), }; let stdin = io::stdin(); @@ -201,8 +178,6 @@ pub fn run() -> eyre::Result<()> { cli.format.clone(), cli.legend.clone(), cli.summary.clone(), - cli.log_prefix.clone(), - cli.log_lines, )?; Ok(()) }, @@ -212,18 +187,14 @@ pub fn run() -> eyre::Result<()> { // If no args provided and --json is set, use piping mode from stdin if args.is_empty() && cli.json { let config = crate::types::Config { - piping: false, - silent: cli.silent, - input_mode: crate::types::InputMode::Json, - show_timers: true, - width: None, - format: crate::types::DisplayFormat::from_str(&cli.format), - legend_style: cli.legend.clone(), - summary_style: cli.summary.clone(), - log_prefix_style: crate::types::LogPrefixStyle::from_str( - &cli.log_prefix, - ), - log_line_limit: cli.log_lines, + piping: false, + silent: cli.silent, + input_mode: crate::types::InputMode::Json, + show_timers: true, + width: None, + format: crate::types::DisplayFormat::from_str(&cli.format), + legend_style: cli.legend.clone(), + summary_style: cli.summary.clone(), }; let stdin = io::stdin(); @@ -246,8 +217,6 @@ pub fn run() -> eyre::Result<()> { cli.format.clone(), cli.legend.clone(), cli.summary.clone(), - cli.log_prefix.clone(), - cli.log_lines, )?; Ok(()) }, @@ -269,10 +238,6 @@ pub fn run() -> eyre::Result<()> { format: crate::types::DisplayFormat::from_str(&cli.format), legend_style: cli.legend.clone(), summary_style: cli.summary.clone(), - log_prefix_style: crate::types::LogPrefixStyle::from_str( - &cli.log_prefix, - ), - log_line_limit: cli.log_lines, }; let stdin = io::stdin(); @@ -288,7 +253,6 @@ pub fn run() -> eyre::Result<()> { /// /// Everything before `--` is for the package name and rom arguments. /// Everything after `--` goes directly to nix. -#[must_use] pub fn parse_args_with_separator( args: &[String], ) -> (Vec, Vec) { @@ -314,8 +278,6 @@ fn run_nix_build_wrapper( format: String, legend_style: String, summary_style: String, - log_prefix: String, - log_lines: Option, ) -> eyre::Result<()> { // Validate that at least one package/flake is specified if package_and_rom_args.is_empty() { @@ -346,8 +308,6 @@ fn run_nix_build_wrapper( format, legend_style, summary_style, - crate::types::LogPrefixStyle::from_str(&log_prefix), - log_lines, )?; if exit_code != 0 { std::process::exit(exit_code); @@ -363,8 +323,6 @@ fn run_nix_shell_wrapper( format: String, legend_style: String, summary_style: String, - log_prefix: String, - log_lines: Option, ) -> eyre::Result<()> { // Validate that at least one package/flake is specified if package_and_rom_args.is_empty() { @@ -400,8 +358,6 @@ fn run_nix_shell_wrapper( format, legend_style, summary_style, - crate::types::LogPrefixStyle::from_str(&log_prefix), - log_lines, )?; if exit_code != 0 { @@ -433,8 +389,6 @@ fn run_nix_develop_wrapper( format: String, legend_style: String, summary_style: String, - log_prefix: String, - log_lines: Option, ) -> eyre::Result<()> { // Validate that at least one package/flake is specified (can be empty for // current flake) develop without args is valid (uses current directory's @@ -463,8 +417,6 @@ fn run_nix_develop_wrapper( format, legend_style, summary_style, - crate::types::LogPrefixStyle::from_str(&log_prefix), - log_lines, )?; if exit_code != 0 { @@ -496,8 +448,6 @@ fn run_monitored_command( format_str: String, legend_style_str: String, summary_style_str: String, - log_prefix_style: crate::types::LogPrefixStyle, - log_line_limit: Option, ) -> eyre::Result { use std::{ io::{BufRead, BufReader}, @@ -529,13 +479,6 @@ fn run_monitored_command( let start_time = Arc::new(Mutex::new(crate::state::current_time())); let start_time_clone = start_time.clone(); - // Buffer for build logs - collected and passed to Display for coordinated - // rendering - let log_buffer = - Arc::new(Mutex::new(std::collections::VecDeque::::new())); - let log_buffer_clone = log_buffer.clone(); - let log_buffer_render = log_buffer.clone(); - // Spawn thread to read and parse stderr (where nix outputs logs) let stderr_thread = thread::spawn(move || { use tracing::debug; @@ -550,62 +493,19 @@ fn run_monitored_command( if let Ok(action) = serde_json::from_str::(json_line) { debug!("Parsed JSON message #{}: {:?}", json_count, action); - // Process the action first to update state + // Print messages immediately to stdout + if let cognos::Actions::Message { msg, .. } = &action { + println!("{}", msg); + } + let mut state = state_clone.lock().unwrap(); let derivation_count_before = state.derivation_infos.len(); - crate::update::process_message(&mut state, action.clone()); + crate::update::process_message(&mut state, action); crate::update::maintain_state( &mut state, crate::state::current_time(), ); let derivation_count_after = state.derivation_infos.len(); - - // Now handle build log messages after state is updated - // Buffer them for coordinated rendering with the display - match &action { - cognos::Actions::Message { msg, .. } => { - let mut logs = log_buffer_clone.lock().unwrap(); - logs.push_back(msg.clone()); - // Keep only recent logs based on limit - if let Some(limit) = log_line_limit { - while logs.len() > limit { - logs.pop_front(); - } - } - }, - cognos::Actions::Result { - fields, - activity, - id, - } => { - // Build log lines come as Result actions with FileTransfer - // activity (101) and fields containing just the log - // text: fields = ["log line text"] - if matches!(activity, cognos::Activities::FileTransfer) - && !fields.is_empty() - { - if let Some(log_text) = fields[0].as_str() { - // Get the activity prefix (e.g., "hello> ") - let use_color = !silent; - let prefix = state - .get_activity_prefix(*id, &log_prefix_style, use_color) - .unwrap_or_default(); - - let prefixed_log = format!("{prefix}{log_text}"); - let mut logs = log_buffer_clone.lock().unwrap(); - logs.push_back(prefixed_log); - // Keep only recent logs based on limit - if let Some(limit) = log_line_limit { - while logs.len() > limit { - logs.pop_front(); - } - } - } - } - }, - _ => {}, - } - if derivation_count_after != derivation_count_before { debug!( "Derivation count changed: {} -> {}", @@ -616,16 +516,9 @@ fn run_monitored_command( debug!("Failed to parse JSON: {}", json_line); } } else { - // Non-JSON lines, buffer them + // Non-JSON lines, pass through non_json_count += 1; - let mut logs = log_buffer_clone.lock().unwrap(); - logs.push_back(line.clone()); - // Keep only recent logs based on limit - if let Some(limit) = log_line_limit { - while logs.len() > limit { - logs.pop_front(); - } - } + println!("{}", line); } } debug!( @@ -688,16 +581,15 @@ fn run_monitored_command( || !state.full_summary.planned_builds.is_empty(); if !silent { - // Get buffered logs for coordinated rendering - let logs: Vec = - log_buffer_render.lock().unwrap().iter().cloned().collect(); - - if has_activity || state.progress_state != ProgressState::JustStarted { + if has_activity + || state.progress_state != crate::state::ProgressState::JustStarted + { // Clear any previous timer display if last_timer_display.is_some() { + display.clear_previous().ok(); last_timer_display = None; } - let _ = display.render(&state, &logs); + let _ = display.render(&state, &[]); } else { // Show initial timer while waiting for activity let start = *start_time_clone.lock().unwrap(); @@ -707,7 +599,8 @@ fn run_monitored_command( // Only update if changed (to avoid flicker) if last_timer_display.as_ref() != Some(&timer_text) { - let _ = display.render(&state, &logs); + display.clear_previous().ok(); + eprintln!("{}", timer_text); last_timer_display = Some(timer_text); } } diff --git a/rom/src/display.rs b/rom/src/display.rs index 1e0b26e..999d2d6 100644 --- a/rom/src/display.rs +++ b/rom/src/display.rs @@ -14,10 +14,9 @@ use crossterm::{ use crate::state::{BuildStatus, DerivationId, State, current_time}; /// Format a duration in seconds to a human-readable string -#[must_use] pub fn format_duration(secs: f64) -> String { if secs < 60.0 { - format!("{secs:.0}s") + format!("{:.0}s", secs) } else if secs < 3600.0 { format!("{:.0}m{:.0}s", secs / 60.0, secs % 60.0) } else { @@ -64,9 +63,10 @@ impl Default for DisplayConfig { } pub struct Display { - writer: W, - config: DisplayConfig, - last_lines: usize, + writer: W, + config: DisplayConfig, + last_lines: usize, + using_alt_screen: bool, } struct TreeNode { @@ -80,6 +80,7 @@ impl Display { writer, config, last_lines: 0, + using_alt_screen: false, }) } @@ -114,7 +115,7 @@ impl Display { let mut lines = Vec::new(); - // Print build logs ABOVE the graph + // Print accumulated logs first (these go above the tree) for log in logs { lines.push(log.clone()); } @@ -153,8 +154,6 @@ impl Display { } pub fn render_final(&mut self, state: &State) -> io::Result<()> { - tracing::debug!("render_final called"); - // Clear any previous render self.clear_previous()?; @@ -182,8 +181,6 @@ impl Display { }, } - tracing::debug!("render_final: {} lines to print", lines.len()); - // Print final output (don't track last_lines since this is final) for line in lines { writeln!(self.writer, "{line}")?; @@ -211,10 +208,8 @@ impl Display { let failed = state.full_summary.failed_builds.len(); let planned = state.full_summary.planned_builds.len(); - let duration = current_time() - state.start_time; - - // Always print summary (like NOM's "Finished at HH:MM:SS after Xs") if running > 0 || completed > 0 || failed > 0 || planned > 0 { + let duration = current_time() - state.start_time; lines.push(format!( "{} {} {} │ {} {} │ {} {} │ {} {} │ {} {}", self.colored("━", Color::Blue), @@ -229,18 +224,6 @@ impl Display { self.colored("⏱", Color::Grey), self.format_duration(duration) )); - } else { - // Nothing built - just show "Finished after Xs" - let now = chrono::Local::now(); - let time_str = now.format("%H:%M:%S"); - lines.push(format!( - "{} {}", - self.colored(&format!("Finished at {time_str}"), Color::Green), - self.colored( - &format!("after {}", self.format_duration(duration)), - Color::Green - ) - )); } lines @@ -269,19 +252,19 @@ impl Display { (usize, usize, usize), > = std::collections::HashMap::new(); - for build in state.full_summary.running_builds.values() { + for (_, build) in &state.full_summary.running_builds { let host = build.host.name().to_string(); let entry = host_builds.entry(host).or_insert((0, 0, 0)); entry.0 += 1; } - for build in state.full_summary.completed_builds.values() { + for (_, build) in &state.full_summary.completed_builds { let host = build.host.name().to_string(); let entry = host_builds.entry(host).or_insert((0, 0, 0)); entry.1 += 1; } - for build in state.full_summary.failed_builds.values() { + for (_, build) in &state.full_summary.failed_builds { let host = build.host.name().to_string(); let entry = host_builds.entry(host).or_insert((0, 0, 0)); entry.2 += 1; @@ -293,13 +276,13 @@ impl Display { (usize, usize), > = std::collections::HashMap::new(); - for transfer in state.full_summary.running_downloads.values() { + for (_, transfer) in &state.full_summary.running_downloads { let host = transfer.host.name().to_string(); let entry = host_transfers.entry(host).or_insert((0, 0)); entry.0 += 1; } - for transfer in state.full_summary.running_uploads.values() { + for (_, transfer) in &state.full_summary.running_uploads { let host = transfer.host.name().to_string(); let entry = host_transfers.entry(host).or_insert((0, 0)); entry.1 += 1; @@ -400,9 +383,9 @@ impl Display { || downloading > 0 || uploading > 0 { - lines.push(self.colored(&"═".repeat(60), Color::Blue).clone()); + lines.push(format!("{}", self.colored(&"═".repeat(60), Color::Blue))); lines.push(format!("{} Build Summary", self.colored("┃", Color::Blue))); - lines.push(self.colored(&"─".repeat(60), Color::Blue).clone()); + lines.push(format!("{}", self.colored(&"─".repeat(60), Color::Blue))); // Builds section if running + completed + failed > 0 { @@ -449,7 +432,7 @@ impl Display { self.format_duration(duration) )); - lines.push(self.colored(&"═".repeat(60), Color::Blue).clone()); + lines.push(format!("{}", self.colored(&"═".repeat(60), Color::Blue))); } lines @@ -508,19 +491,19 @@ impl Display { let mut host_counts: HashMap = HashMap::new(); - for build in state.full_summary.running_builds.values() { + for (_, build) in &state.full_summary.running_builds { let host = build.host.name().to_string(); let entry = host_counts.entry(host).or_insert((0, 0, 0, 0)); entry.0 += 1; } - for build in state.full_summary.completed_builds.values() { + for (_, build) in &state.full_summary.completed_builds { let host = build.host.name().to_string(); let entry = host_counts.entry(host).or_insert((0, 0, 0, 0)); entry.1 += 1; } - for build in state.full_summary.failed_builds.values() { + for (_, build) in &state.full_summary.failed_builds { let host = build.host.name().to_string(); let entry = host_counts.entry(host).or_insert((0, 0, 0, 0)); entry.2 += 1; @@ -534,10 +517,9 @@ impl Display { )); // Summary line - let summary_prefix = if has_tree { "┗━" } else { "━" }; lines.push(format!( "{} ∑ {} {} │ {} {} │ {} {} │ {} {} │ {} {}", - self.colored(summary_prefix, Color::Blue), + self.colored("━", Color::Blue), self.colored("⏵", Color::Yellow), running, self.colored("✔", Color::Green), @@ -567,10 +549,9 @@ impl Display { let planned = state.full_summary.planned_builds.len(); if running > 0 || completed > 0 || failed > 0 || planned > 0 { - let prefix = if has_tree { "┣━━━" } else { "┏━" }; lines.push(format!( "{} Build Summary:", - self.colored(prefix, Color::Blue) + self.colored("┣━━━", Color::Blue) )); lines.push(format!( "┃ {} Running: {running}", @@ -638,14 +619,7 @@ impl Display { // Always show progress line, even if empty if running > 0 || planned > 0 || downloading > 0 || uploading > 0 { - let progress_line = if progress_parts.is_empty() { - format!( - "{} {} {}", - self.colored("━", Color::Blue), - self.colored("⏱", Color::Grey), - self.format_duration(duration) - ) - } else { + let progress_line = if !progress_parts.is_empty() { format!( "{} {} {} {}", self.colored("━", Color::Blue), @@ -653,6 +627,13 @@ impl Display { progress_parts.join(" "), self.format_duration(duration) ) + } else { + format!( + "{} {} {}", + self.colored("━", Color::Blue), + self.colored("⏱", Color::Grey), + self.format_duration(duration) + ) }; lines.push(progress_line); } @@ -698,23 +679,11 @@ impl Display { if let Some(info) = state.get_derivation_info(*drv_id) { let name = &info.name.name; let elapsed = current_time() - build.start; - - // Format time info - let mut time_info = String::new(); - if let Some(estimate_secs) = build.estimate { - let remaining = estimate_secs.saturating_sub(elapsed as u64); - time_info.push_str(&format!( - "∅ {} ", - self.format_duration(remaining as f64) - )); - } - time_info.push_str(&self.format_duration(elapsed)); - lines.push(format!( " {} {} {}", self.colored("⏵", Color::Yellow), name, - time_info + self.format_duration(elapsed) )); } } @@ -733,7 +702,7 @@ impl Display { if let Some(build_info) = primary_build { let name = &build_info.name.name; - lines.push(format!("BUILD GRAPH: {name}")); + lines.push(format!("BUILD GRAPH: {}", name)); lines.push("─".repeat(44)); // Get host information from running/completed builds @@ -768,8 +737,8 @@ impl Display { let duration = current_time() - state.start_time; // Format dashboard - lines.push(format!("Host │ {host}")); - lines.push(format!("Status │ {status}")); + lines.push(format!("Host │ {}", host)); + lines.push(format!("Status │ {}", status)); lines.push(format!("Duration │ {}", self.format_duration(duration))); lines.push("─".repeat(44)); @@ -798,7 +767,7 @@ impl Display { if let Some(build_info) = primary_build { let name = &build_info.name.name; - lines.push(format!("BUILD GRAPH: {name}")); + lines.push(format!("BUILD GRAPH: {}", name)); lines.push("─".repeat(44)); // Get host from build reports or completed builds @@ -829,8 +798,8 @@ impl Display { let duration = current_time() - state.start_time; - lines.push(format!("Host │ {host}")); - lines.push(format!("Status │ {status}")); + lines.push(format!("Host │ {}", host)); + lines.push(format!("Status │ {}", status)); lines.push(format!("Duration │ {}", self.format_duration(duration))); lines.push("─".repeat(44)); @@ -891,6 +860,28 @@ impl Display { lines } + fn is_active_or_has_active_descendants( + &self, + state: &State, + drv_id: DerivationId, + ) -> bool { + if let Some(info) = state.get_derivation_info(drv_id) { + match info.build_status { + BuildStatus::Building(_) => return true, + BuildStatus::Failed { .. } => return true, + _ => {}, + } + + // Check children + for input in &info.input_derivations { + if self.is_active_or_has_active_descendants(state, input.derivation) { + return true; + } + } + } + false + } + fn build_active_forest( &self, state: &State, @@ -984,19 +975,8 @@ impl Display { } } - // Time information + // Time elapsed let elapsed = current_time() - build_info.start; - - // Show estimate if available - if let Some(estimate_secs) = build_info.estimate { - let remaining = estimate_secs.saturating_sub(elapsed as u64); - line.push_str(&self.colored( - &format!(" ∅ {}", self.format_duration(remaining as f64)), - Color::DarkGrey, - )); - } - - // Show elapsed time line.push_str(&self.colored( &format!(" ⏱ {}", self.format_duration(elapsed)), Color::DarkGrey, @@ -1066,7 +1046,7 @@ impl Display { pub fn format_duration(&self, secs: f64) -> String { if secs < 60.0 { - format!("{secs:.0}s") + format!("{:.0}s", secs) } else if secs < 3600.0 { format!("{:.0}m{:.0}s", secs / 60.0, secs % 60.0) } else { @@ -1085,7 +1065,7 @@ impl Display { fn format_bytes(&self, bytes: u64, total: u64) -> String { let format_size = |b: u64| -> String { if b < 1024 { - format!("{b} B") + format!("{} B", b) } else if b < 1024 * 1024 { format!("{:.1} KB", b as f64 / 1024.0) } else if b < 1024 * 1024 * 1024 { diff --git a/rom/src/lib.rs b/rom/src/lib.rs index 5c7acc8..ac668ed 100644 --- a/rom/src/lib.rs +++ b/rom/src/lib.rs @@ -1,5 +1,4 @@ //! ROM - Rust Output Monitor -pub mod cache; pub mod cli; pub mod display; pub mod error; diff --git a/rom/src/monitor.rs b/rom/src/monitor.rs index 5c1f30a..e188c00 100644 --- a/rom/src/monitor.rs +++ b/rom/src/monitor.rs @@ -5,21 +5,10 @@ use std::{ time::Duration, }; -use cognos::Host; -use tracing::debug; - use crate::{ - cache::BuildReportCache, - display::{Display, DisplayConfig, LegendStyle, SummaryStyle}, + display::{Display, DisplayConfig}, error::{Result, RomError}, - state::{ - BuildStatus, - Derivation, - FailType, - State, - StorePath, - StorePathState, - }, + state::State, types::{Config, InputMode}, update, }; @@ -35,15 +24,15 @@ impl Monitor { /// Create a new monitor pub fn new(config: Config, writer: W) -> Result { let legend_style = match config.legend_style.to_lowercase().as_str() { - "compact" => LegendStyle::Compact, - "verbose" => LegendStyle::Verbose, - _ => LegendStyle::Table, + "compact" => crate::display::LegendStyle::Compact, + "verbose" => crate::display::LegendStyle::Verbose, + _ => crate::display::LegendStyle::Table, }; let summary_style = match config.summary_style.to_lowercase().as_str() { - "table" => SummaryStyle::Table, - "full" => SummaryStyle::Full, - _ => SummaryStyle::Concise, + "table" => crate::display::SummaryStyle::Table, + "full" => crate::display::SummaryStyle::Full, + _ => crate::display::SummaryStyle::Concise, }; let display_config = DisplayConfig { @@ -57,12 +46,7 @@ impl Monitor { }; let display = Display::new(writer, display_config)?; - let mut state = State::new(); - - // Load build cache for predictions - let cache_path = BuildReportCache::default_cache_path(); - let cache = BuildReportCache::new(cache_path); - state.build_cache = cache.load(); + let state = State::new(); Ok(Self { state, @@ -98,14 +82,6 @@ impl Monitor { self.display.render_final(&self.state)?; } - // Save build cache for future predictions - let cache_path = BuildReportCache::default_cache_path(); - let cache = BuildReportCache::new(cache_path); - if let Err(e) = cache.save(&self.state.build_cache) { - debug!("Failed to save build cache: {}", e); - // Don't fail the build if cache save fails - } - // Return error code if there were failures if self.state.has_errors() { return Err(RomError::BuildFailed); @@ -135,7 +111,7 @@ impl Monitor { Ok(action) => { // Handle message passthrough - print directly to stdout if let cognos::Actions::Message { msg, .. } = &action { - println!("{msg}"); + println!("{}", msg); } let changed = update::process_message(&mut self.state, action); @@ -149,13 +125,17 @@ impl Monitor { } } else { // Non-JSON lines in JSON mode are passed through - println!("{line}"); + println!("{}", line); Ok(false) } } /// Process a human-readable line fn process_human_line(&mut self, line: &str) -> Result { + // Parse human-readable nix output + // This is a simplified version - the full implementation would need + // comprehensive parsing of nix's output format + let line = line.trim(); // Skip empty lines @@ -172,7 +152,7 @@ impl Monitor { let build_info = crate::state::BuildInfo { start: now, - host: Host::Localhost, + host: crate::state::Host::Localhost, estimate: None, activity_id: None, }; @@ -193,21 +173,20 @@ impl Monitor { let path_id = self.state.get_or_create_store_path_id(path); let now = crate::state::current_time(); - // Try to extract byte size from the message - let total_bytes = extract_byte_size(line); - let transfer = crate::state::TransferInfo { - start: now, - host: Host::Localhost, - activity_id: 0, // no activity ID in human mode + start: now, + host: crate::state::Host::Localhost, + activity_id: 0, // No activity ID in human mode bytes_transferred: 0, - total_bytes, + total_bytes: None, }; if let Some(path_info) = self.state.get_store_path_info_mut(path_id) { path_info .states - .insert(StorePathState::Downloading(transfer.clone())); + .insert(crate::state::StorePathState::Downloading( + transfer.clone(), + )); } self @@ -221,113 +200,14 @@ impl Monitor { } } - // Detect download completions with byte sizes - if line.starts_with("downloaded") || line.contains("downloaded '") { - if let Some(path_str) = extract_path_from_message(line) { - if let Some(path) = StorePath::parse(&path_str) { - if let Some(&path_id) = self.state.store_path_ids.get(&path) { - let now = crate::state::current_time(); - let total_bytes = extract_byte_size(line).unwrap_or(0); - - // Get start time from running download if it exists - let start = self - .state - .full_summary - .running_downloads - .get(&path_id) - .map_or(now, |t| t.start); - - let completed = crate::state::CompletedTransferInfo { - start, - end: now, - host: Host::Localhost, - total_bytes, - }; - - if let Some(path_info) = self.state.get_store_path_info_mut(path_id) - { - path_info - .states - .insert(StorePathState::Downloaded(completed.clone())); - } - - self.state.full_summary.running_downloads.remove(&path_id); - self - .state - .full_summary - .completed_downloads - .insert(path_id, completed); - - return Ok(true); - } - } - } - } - - // Detect "checking outputs of" messages - if line.contains("checking outputs of") { - if let Some(drv_path) = extract_path_from_message(line) { - if let Some(drv) = crate::state::Derivation::parse(&drv_path) { - let drv_id = self.state.get_or_create_derivation_id(drv); - // Just mark it as "touched" - checking happens after build - // Reminds me of Sako... - self.state.touched_ids.insert(drv_id); - return Ok(true); - } - } - } - - // Detect "copying N paths" messages - if line.starts_with("copying") && line.contains("paths") { - // Extract number of paths if present - let words: Vec<&str> = line.split_whitespace().collect(); - if words.len() >= 2 { - if let Ok(count) = words[1].parse::() { - debug!("Copying {} paths", count); - return Ok(true); - } - } - } - // Detect errors if line.starts_with("error:") || line.contains("error:") { self.state.nix_errors.push(line.to_string()); + return Ok(true); + } - // Try to determine the error type and associated derivation - let fail_type = if line.contains("hash mismatch") - || line.contains("output path") - && (line.contains("hash") || line.contains("differs")) - { - FailType::HashMismatch - } else if line.contains("timed out") || line.contains("timeout") { - FailType::Timeout - } else if line.contains("dependency failed") - || line.contains("dependencies failed") - { - FailType::DependencyFailed - } else if line.contains("builder for") - && line.contains("failed with exit code") - { - // Try to extract exit code - if let Some(code_pos) = line.find("exit code") { - let after_code = &line[code_pos + 10..]; - let code_str = after_code - .split_whitespace() - .next() - .map(|s| s.trim_end_matches(|c: char| !c.is_ascii_digit())); - if let Some(code) = code_str.and_then(|s| s.parse::().ok()) { - FailType::BuildFailed(code) - } else { - FailType::Unknown - } - } else { - FailType::Unknown - } - } else { - FailType::Unknown - }; - - // Try to find the associated derivation and mark it as failed + // Detect build completions + if line.starts_with("built") || line.contains("built '") { if let Some(drv_path) = extract_path_from_message(line) { if let Some(drv) = crate::state::Derivation::parse(&drv_path) { if let Some(&drv_id) = self.state.derivation_ids.get(&drv) { @@ -338,35 +218,11 @@ impl Monitor { let now = crate::state::current_time(); self.state.update_build_status( drv_id, - crate::state::BuildStatus::Failed { + crate::state::BuildStatus::Built { info: build_info.clone(), - fail: crate::state::BuildFail { - at: now, - fail_type: fail_type.clone(), - }, + end: now, }, ); - } - } - } - } - } - - return Ok(true); - } - - // Detect build completions - if line.starts_with("built") || line.contains("built '") { - if let Some(drv_path) = extract_path_from_message(line) { - if let Some(drv) = Derivation::parse(&drv_path) { - if let Some(&drv_id) = self.state.derivation_ids.get(&drv) { - if let Some(info) = self.state.get_derivation_info(drv_id) { - if let BuildStatus::Building(build_info) = &info.build_status { - let now = crate::state::current_time(); - self.state.update_build_status(drv_id, BuildStatus::Built { - info: build_info.clone(), - end: now, - }); return Ok(true); } } @@ -414,33 +270,6 @@ fn extract_path_from_message(line: &str) -> Option { None } -/// Extract byte size from a message line (e.g., "downloaded 123 KiB") -fn extract_byte_size(line: &str) -> Option { - // Look for patterns like "123 KiB", "6.7 MiB", etc. - // Haha 6.7 - let words: Vec<&str> = line.split_whitespace().collect(); - for (i, word) in words.iter().enumerate() { - if i + 1 < words.len() { - let unit = words[i + 1]; - if matches!(unit, "B" | "KiB" | "MiB" | "GiB" | "TiB" | "PiB") { - if let Ok(value) = word.parse::() { - let multiplier = match unit { - "B" => 1_u64, - "KiB" => 1024, - "MiB" => 1024 * 1024, - "GiB" => 1024 * 1024 * 1024, - "TiB" => 1024_u64 * 1024 * 1024 * 1024, - "PiB" => 1024_u64 * 1024 * 1024 * 1024 * 1024, - _ => 1, - }; - return Some((value * multiplier as f64) as u64); - } - } - } - } - None -} - #[cfg(test)] mod tests { use super::*; @@ -467,19 +296,4 @@ mod tests { let path = extract_path_from_message(line); assert!(path.is_some()); } - - #[test] - fn test_extract_byte_size() { - let line = "downloaded 123 KiB in 2 seconds"; - assert_eq!(extract_byte_size(line), Some(123 * 1024)); - - let line2 = "downloading 4.5 MiB"; - assert_eq!( - extract_byte_size(line2), - Some((4.5 * 1024.0 * 1024.0) as u64) - ); - - let line3 = "no size here"; - assert_eq!(extract_byte_size(line3), None); - } } diff --git a/rom/src/state.rs b/rom/src/state.rs index 7f7fcfd..3cfae00 100644 --- a/rom/src/state.rs +++ b/rom/src/state.rs @@ -6,7 +6,7 @@ use std::{ time::{Duration, SystemTime}, }; -use cognos::{Host, Id, OutputName, ProgressState}; +use cognos::Id; use indexmap::IndexMap; /// Unique identifier for store paths @@ -18,6 +18,36 @@ pub type DerivationId = usize; /// Unique identifier for activities pub type ActivityId = Id; +/// Overall progress state +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ProgressState { + JustStarted, + InputReceived, + Finished, +} + +/// Build host information +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Host { + Localhost, + Remote(String), +} + +impl Host { + #[must_use] + pub const fn is_local(&self) -> bool { + matches!(self, Self::Localhost) + } + + #[must_use] + pub fn name(&self) -> &str { + match self { + Self::Localhost => "localhost", + Self::Remote(name) => name, + } + } +} + /// Store path representation #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct StorePath { @@ -81,6 +111,37 @@ impl Derivation { } } +/// Output name for derivations +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum OutputName { + Out, + Doc, + Dev, + Bin, + Info, + Lib, + Man, + Dist, + Other(String), +} + +impl OutputName { + #[must_use] + pub fn parse(name: &str) -> Self { + match name.to_lowercase().as_str() { + "out" => Self::Out, + "doc" => Self::Doc, + "dev" => Self::Dev, + "bin" => Self::Bin, + "info" => Self::Info, + "lib" => Self::Lib, + "man" => Self::Man, + "dist" => Self::Dist, + _ => Self::Other(name.to_string()), + } + } +} + /// Transfer information (download/upload) #[derive(Debug, Clone)] pub struct TransferInfo { @@ -317,20 +378,6 @@ pub struct ActivityStatus { pub text: String, pub parent: Option, pub phase: Option, - pub progress: Option, -} - -/// Activity progress for downloads/uploads/builds -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct ActivityProgress { - /// Bytes completed - pub done: u64, - /// Total bytes expected - pub expected: u64, - /// Currently running transfers - pub running: u64, - /// Failed transfers - pub failed: u64, } /// Build report for caching @@ -360,7 +407,6 @@ pub struct State { pub full_summary: DependencySummary, pub forest_roots: Vec, pub build_reports: HashMap>, - pub build_cache: HashMap<(String, String), Vec>, pub start_time: f64, pub progress_state: ProgressState, pub store_path_ids: HashMap, @@ -369,11 +415,8 @@ pub struct State { pub activities: HashMap, pub nix_errors: Vec, pub build_logs: Vec, - pub traces: Vec, pub build_platform: Option, pub evaluation_state: EvalInfo, - pub builds_activity: Option, - pub success_tokens: u64, next_store_path_id: StorePathId, next_derivation_id: DerivationId, } @@ -393,7 +436,6 @@ impl State { full_summary: DependencySummary::default(), forest_roots: Vec::new(), build_reports: HashMap::new(), - build_cache: HashMap::new(), start_time: current_time(), progress_state: ProgressState::JustStarted, store_path_ids: HashMap::new(), @@ -402,11 +444,8 @@ impl State { activities: HashMap::new(), nix_errors: Vec::new(), build_logs: Vec::new(), - traces: Vec::new(), build_platform: None, evaluation_state: EvalInfo::default(), - builds_activity: None, - success_tokens: 0, next_store_path_id: 0, next_derivation_id: 0, } @@ -564,7 +603,7 @@ impl State { // Create output set let mut output_set = HashSet::new(); for output in outputs { - output_set.insert(OutputName::parse(&output)); + output_set.insert(parse_output_name(&output)); } // Add to parent's input derivations @@ -679,130 +718,6 @@ impl State { .map(|(id, info)| (*id, info)) .collect() } - - /// Check if a derivation has a platform mismatch - #[must_use] - pub fn has_platform_mismatch(&self, id: DerivationId) -> bool { - if let (Some(build_platform), Some(info)) = - (&self.build_platform, self.get_derivation_info(id)) - { - if let Some(drv_platform) = &info.platform { - return build_platform != drv_platform; - } - } - false - } - - /// Get all derivations with platform mismatches - #[must_use] - pub fn platform_mismatches(&self) -> Vec { - self - .derivation_infos - .keys() - .filter(|&&id| self.has_platform_mismatch(id)) - .copied() - .collect() - } - - /// Get the activity prefix for a given activity ID by walking up the parent - /// chain to find a Build activity and extracting its derivation name. - /// Returns a prefix like "hello> " suitable for prepending to log lines. - /// If `use_color` is true and stderr is a TTY, the prefix will be blue. - /// The `prefix_style` determines whether to use short (pname only), full, or - /// no prefix. - #[must_use] - pub fn get_activity_prefix( - &self, - activity_id: ActivityId, - prefix_style: &crate::types::LogPrefixStyle, - use_color: bool, - ) -> Option { - use cognos::Activities; - - use crate::types::LogPrefixStyle; - - // If prefix style is None, return empty string - if matches!(prefix_style, LogPrefixStyle::None) { - return Some(String::new()); - } - - let mut current_id = activity_id; - let max_depth = 10; // Prevent infinite loops - let mut depth = 0; - - while depth < max_depth { - if let Some(activity) = self.activities.get(¤t_id) { - // Check if this is a Build activity (type 105) - if activity.activity == Activities::Build as u8 { - // Extract derivation path from the text field - // The text field typically contains something like: - // "building '/nix/store/...-hello-2.10.drv'" - if let Some(drv) = extract_derivation_from_text(&activity.text) { - // Look up the DerivationInfo for this derivation - let drv_id = self.derivation_ids.get(&drv); - let name = if matches!(prefix_style, LogPrefixStyle::Short) { - // Try to use pname if available - if let Some(id) = drv_id { - if let Some(drv_info) = self.derivation_infos.get(id) { - if let Some(pname) = &drv_info.pname { - pname.clone() - } else { - drv.name.clone() - } - } else { - drv.name.clone() - } - } else { - drv.name.clone() - } - } else { - // Full style - use full derivation name - drv.name.clone() - }; - - // Apply color if requested and stderr is a TTY - let colored_name = if use_color - && std::io::IsTerminal::is_terminal(&std::io::stderr()) - { - format!("\x1b[34m{name}\x1b[0m") - } else { - name - }; - - return Some(format!("{colored_name}> ")); - } - } - - // Move to parent activity - if let Some(parent_id) = activity.parent { - if parent_id == 0 { - break; // Reached root - } - current_id = parent_id; - depth += 1; - } else { - break; - } - } else { - break; - } - } - - None - } -} - -/// Extract derivation from activity text like "building -/// '/nix/store/...-hello-2.10.drv'" Returns the Derivation object -fn extract_derivation_from_text(text: &str) -> Option { - // Look for .drv path in text - if let Some(start) = text.find("/nix/store/") { - if let Some(end) = text[start..].find(".drv") { - let drv_path = &text[start..start + end + 4]; // Include .drv - return Derivation::parse(drv_path); - } - } - None } #[must_use] @@ -813,6 +728,20 @@ pub fn current_time() -> f64 { .as_secs_f64() } +fn parse_output_name(name: &str) -> OutputName { + match name { + "out" => OutputName::Out, + "doc" => OutputName::Doc, + "dev" => OutputName::Dev, + "bin" => OutputName::Bin, + "info" => OutputName::Info, + "lib" => OutputName::Lib, + "man" => OutputName::Man, + "dist" => OutputName::Dist, + _ => OutputName::Other(name.to_string()), + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/rom/src/types.rs b/rom/src/types.rs index 26eb8cd..d2f74b3 100644 --- a/rom/src/types.rs +++ b/rom/src/types.rs @@ -11,17 +11,6 @@ pub enum DisplayFormat { Dashboard, } -/// Log prefix style for build logs -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum LogPrefixStyle { - /// Just package name (pname) - Short, - /// Full derivation name with version - Full, - /// No prefix - None, -} - /// Summary display style #[derive(Debug, Clone, PartialEq, Eq)] pub enum SummaryStyle { @@ -34,7 +23,6 @@ pub enum SummaryStyle { } impl SummaryStyle { - #[must_use] pub fn from_str(s: &str) -> Self { match s.to_lowercase().as_str() { "concise" => Self::Concise, @@ -45,20 +33,7 @@ impl SummaryStyle { } } -impl LogPrefixStyle { - #[must_use] - pub fn from_str(s: &str) -> Self { - match s.to_lowercase().as_str() { - "short" => Self::Short, - "full" => Self::Full, - "none" => Self::None, - _ => Self::Short, - } - } -} - impl DisplayFormat { - #[must_use] pub fn from_str(s: &str) -> Self { match s.to_lowercase().as_str() { "tree" => Self::Tree, @@ -73,40 +48,34 @@ impl DisplayFormat { #[derive(Debug, Clone)] pub struct Config { /// Whether we're piping output through - pub piping: bool, + pub piping: bool, /// Silent mode - minimal output - pub silent: bool, + pub silent: bool, /// Input parsing mode - pub input_mode: InputMode, + pub input_mode: InputMode, /// Show completion times - pub show_timers: bool, + pub show_timers: bool, /// Terminal width override - pub width: Option, + pub width: Option, /// Display format - pub format: DisplayFormat, + pub format: DisplayFormat, /// Legend display style - pub legend_style: String, + pub legend_style: String, /// Summary display style - pub summary_style: String, - /// Log prefix style for build logs - pub log_prefix_style: LogPrefixStyle, - /// Maximum number of log lines to display (None = unlimited) - pub log_line_limit: Option, + pub summary_style: String, } impl Default for Config { fn default() -> Self { Self { - piping: false, - silent: false, - input_mode: InputMode::Human, - show_timers: true, - width: None, - format: DisplayFormat::Tree, - legend_style: "table".to_string(), - summary_style: "concise".to_string(), - log_prefix_style: LogPrefixStyle::Short, - log_line_limit: None, + piping: false, + silent: false, + input_mode: InputMode::Human, + show_timers: true, + width: None, + format: DisplayFormat::Tree, + legend_style: "table".to_string(), + summary_style: "concise".to_string(), } } } @@ -132,8 +101,6 @@ mod tests { assert_eq!(config.input_mode, InputMode::Human); assert!(config.show_timers); assert_eq!(config.format, DisplayFormat::Tree); - assert_eq!(config.log_prefix_style, LogPrefixStyle::Short); - assert_eq!(config.log_line_limit, None); } #[test] diff --git a/rom/src/update.rs b/rom/src/update.rs index 3e3a0a8..728555c 100644 --- a/rom/src/update.rs +++ b/rom/src/update.rs @@ -1,31 +1,29 @@ //! State update logic for processing nix messages -use cognos::{Actions, Activities, Host, Id, ProgressState, Verbosity}; +use cognos::{Actions, Activities, Id, Verbosity}; use tracing::{debug, trace}; -use crate::{ - cache::BuildReportCache, - state::{ - ActivityProgress, - ActivityStatus, - BuildFail, - BuildInfo, - BuildReport, - BuildStatus, - CompletedBuildInfo, - CompletedTransferInfo, - Derivation, - DerivationId, - FailType, - FailedBuildInfo, - InputDerivation, - State, - StorePath, - StorePathId, - StorePathState, - TransferInfo, - current_time, - }, +use crate::state::{ + ActivityStatus, + BuildFail, + BuildInfo, + BuildStatus, + CompletedBuildInfo, + CompletedTransferInfo, + Derivation, + DerivationId, + FailType, + FailedBuildInfo, + Host, + InputDerivation, + OutputName, + ProgressState, + State, + StorePath, + StorePathId, + StorePathState, + TransferInfo, + current_time, }; /// Process a nix JSON message and update state @@ -91,38 +89,21 @@ fn handle_start( text: text.clone(), parent: parent_id, phase: None, - progress: None, }); let changed = match activity_u8 { - 105 => handle_build_start(state, id, parent_id, &text, &fields, now), /* Build */ + 104 | 105 => handle_build_start(state, id, parent_id, &text, &fields, now), /* Builds | Build */ 108 => handle_substitute_start(state, id, &text, &fields, now), /* Substitute */ - 109 => handle_query_path_info_start(state, id, &text, &fields, now), /* QueryPathInfo */ - 110 => handle_post_build_hook_start(state, id, &text, &fields, now), /* PostBuildHook */ - 101 => handle_file_transfer_start(state, id, &text, &fields, now), /* FileTransfer */ - 100 => handle_copy_path_start(state, id, &text, &fields, now), /* CopyPath */ - 104 => { - // Builds activity - track this as the top-level builds activity - if state.builds_activity.is_none() { - state.builds_activity = Some(id); - true - } else { - false - } - }, - 102 | 103 | 106 | 107 | 111 | 112 => { - // Realise, CopyPaths, OptimiseStore, VerifyPaths, BuildWaiting, FetchTree - // These activities have no fields and are just tracked - true - }, - _ => { - debug!("Unknown activity type: {}", activity_u8); - false - }, + 101 => handle_transfer_start(state, id, &text, &fields, now, false), /* FileTransfer */ + 100 | 103 => handle_transfer_start(state, id, &text, &fields, now, true), /* CopyPath | CopyPaths */ + _ => false, }; // Track parent-child relationships for dependency tree - if changed && activity_u8 == 105 && parent_id.is_some() { + if changed + && (activity_u8 == 104 || activity_u8 == 105) + && parent_id.is_some() + { let parent_act_id = parent_id.unwrap(); // Find parent and child derivation IDs @@ -132,8 +113,8 @@ fn handle_start( if let Some(parent_drv_id) = parent_drv_id { if let Some(child_drv_id) = child_drv_id { debug!( - "Establishing parent-child relationship: parent={parent_drv_id}, \ - child={child_drv_id}" + "Establishing parent-child relationship: parent={}, child={}", + parent_drv_id, child_drv_id ); // Add child as a dependency of parent @@ -172,19 +153,9 @@ fn handle_stop(state: &mut State, id: Id, now: f64) -> bool { state.activities.remove(&id); match activity_status.activity { - 105 => handle_build_stop(state, id, now), // Build - 108 => handle_substitute_stop(state, id, now), // Substitute - 101 | 100 => handle_transfer_stop(state, id, now), // FileTransfer, - // CopyPath - 109 | 110 => { - // QueryPathInfo, PostBuildHook - just acknowledge stop - false - }, - 102 | 103 | 104 | 106 | 107 | 111 | 112 => { - // Realise, CopyPaths, Builds, OptimiseStore, VerifyPaths, BuildWaiting, - // FetchTree - false - }, + 104 | 105 => handle_build_stop(state, id, now), // Builds | Build + 108 => handle_substitute_stop(state, id, now), // Substitute + 101 | 100 | 103 => handle_transfer_stop(state, id, now), /* FileTransfer, CopyPath, CopyPaths */ _ => false, } } else { @@ -198,7 +169,7 @@ fn handle_message(state: &mut State, level: Verbosity, msg: String) -> bool { // Extract phase from log messages like "Running phase: configurePhase" if let Some(phase_start) = msg.find("Running phase: ") { - let phase_name = &msg[phase_start + 15..]; // skip "Running phase: " + let phase_name = &msg[phase_start + 15..]; // Skip "Running phase: " let phase = phase_name.trim().to_string(); // Find the active build and update its phase @@ -260,14 +231,6 @@ fn handle_message(state: &mut State, level: Verbosity, msg: String) -> bool { } true // return true since we stored the log }, - Verbosity::Talkative - | Verbosity::Chatty - | Verbosity::Debug - | Verbosity::Vomit => { - // These are trace-level messages, store separately - state.traces.push(msg.clone()); - true - }, _ => { true // return true since we stored the log }, @@ -277,184 +240,41 @@ fn handle_message(state: &mut State, level: Verbosity, msg: String) -> bool { fn handle_result( state: &mut State, id: Id, - result_type: u8, + activity: u8, fields: Vec, _now: f64, ) -> bool { - // Result message types are DIFFERENT from Activity types - // Type 100: FileLinked (2 ints) - // Type 101: BuildLogLine (1 text) - // Type 102: UntrustedPath (1 text - store path) - // Type 103: CorruptedPath (1 text - store path) - // Type 104: SetPhase (1 text) - // Type 105: Progress (4 ints: done, expected, running, failed) - // Type 106: SetExpected (2 ints: activity type, count) - // Type 107: PostBuildLogLine (1 text) - // Type 108: FetchStatus (1 text) - - match result_type { - 100 => { - // FileLinked: 2 int fields (linked count, total count) + match activity { + 101 | 108 => { + // FileTransfer or Substitute + // Fields contain progress information + // XXX: Format: [bytes_transferred, total_bytes] if fields.len() >= 2 { - let linked = fields[0].as_u64().unwrap_or(0); - let total = fields[1].as_u64().unwrap_or(0); - debug!("FileLinked: {}/{}", linked, total); - // File linking is reported but doesn't need state tracking - } - false - }, - 101 => { - // BuildLogLine: 1 text field - if let Some(line) = fields.first().and_then(|f| f.as_str()) { - state.build_logs.push(line.to_string()); - return true; - } - false - }, - 102 => { - // UntrustedPath: 1 text field (store path) - if let Some(path_str) = fields.first().and_then(|f| f.as_str()) { - debug!("Untrusted path reported: {}", path_str); - state - .nix_errors - .push(format!("Untrusted path: {}", path_str)); - return true; - } - false - }, - 103 => { - // CorruptedPath: 1 text field (store path) - if let Some(path_str) = fields.first().and_then(|f| f.as_str()) { - state.nix_errors.push(format!("Corrupted path: {path_str}")); - return true; + update_transfer_progress(state, id, &fields); } false }, 104 => { - // SetPhase: 1 text field - if let Some(phase_str) = fields.first().and_then(|f| f.as_str()) { - if let Some(activity) = state.activities.get_mut(&id) { - activity.phase = Some(phase_str.to_string()); - return true; - } - } - false - }, - 105 => { - // Progress: 4 int fields (done, expected, running, failed) - if fields.len() >= 4 { - if let (Some(done), Some(expected), Some(running), Some(failed)) = ( - fields[0].as_u64(), - fields[1].as_u64(), - fields[2].as_u64(), - fields[3].as_u64(), - ) { - // If this progress is for the Builds activity, track success tokens - if state.builds_activity == Some(id) { - if let Some(activity) = state.activities.get(&id) { - if let Some(prev_progress) = &activity.progress { - let new_done = done.saturating_sub(prev_progress.done); - if new_done > 0 { - state.success_tokens = - state.success_tokens.saturating_add(new_done); - } - } - } - } - + // Builds activity type - contains phase information + if !fields.is_empty() { + if let Some(phase_str) = fields[0].as_str() { + // Update the activity's phase field if let Some(activity) = state.activities.get_mut(&id) { - activity.progress = Some(ActivityProgress { - done, - expected, - running, - failed, - }); + activity.phase = Some(phase_str.to_string()); return true; } } } false }, - 106 => { - // SetExpected: 2 int fields (activity type, count) - if fields.len() >= 2 { - let activity_type = fields[0].as_u64().unwrap_or(0); - let expected_count = fields[1].as_u64().unwrap_or(0); - debug!( - "SetExpected: activity_type={}, count={}", - activity_type, expected_count - ); - // Expected counts are informational and don't affect state tracking - } - false - }, - 107 => { - // PostBuildLogLine: 1 text field - if let Some(line) = fields.first().and_then(|f| f.as_str()) { - state.build_logs.push(format!("[post-build] {line}")); - return true; - } - false - }, - 108 => { - // FetchStatus: 1 text field - if let Some(status) = fields.first().and_then(|f| f.as_str()) { - debug!("Fetch status: {}", status); - // Fetch status is informational - } - false - }, - _ => { - debug!("Unknown result type: {}", result_type); - false + 105 => { + // Build completed, fields contain output path + complete_build(state, id) }, + _ => false, } } -/// Get build time estimate from cache -fn get_build_estimate( - state: &State, - derivation_name: &str, - host: &Host, -) -> Option { - // Use pname if available, otherwise derivation name - let lookup_name = derivation_name.to_string(); - let host_str = host.name(); - - BuildReportCache::calculate_median( - state - .build_cache - .get(&(host_str.to_string(), lookup_name))? - .as_slice(), - ) -} - -/// Record completed build for future predictions -fn record_build_completion( - state: &mut State, - derivation_name: String, - platform: Option, - start: f64, - end: f64, - host: &Host, -) { - let duration_secs = end - start; - let completed_at = std::time::SystemTime::now(); - - let report = BuildReport { - derivation_name: derivation_name.clone(), - platform: platform.unwrap_or_default(), - duration_secs, - completed_at, - host: host.name().to_string(), - success: true, - }; - - // Store in state for later CSV persistence - let key = (host.name().to_string(), derivation_name); - state.build_cache.entry(key).or_default().push(report); -} - fn handle_build_start( state: &mut State, id: Id, @@ -478,16 +298,13 @@ fn handle_build_start( if let Some(drv_path) = drv_path { debug!("Extracted derivation path: {}", drv_path); if let Some(drv) = Derivation::parse(&drv_path) { - let drv_id = state.get_or_create_derivation_id(drv.clone()); + let drv_id = state.get_or_create_derivation_id(drv); let host = extract_host(text); - // Get build time estimate from cache - let estimate = get_build_estimate(state, &drv.name, &host); - let build_info = BuildInfo { start: now, host, - estimate, + estimate: None, activity_id: Some(id), }; @@ -506,59 +323,74 @@ fn handle_build_start( ); // Mark as forest root if no parent + // Only add to forest roots if no parent if parent_id.is_none() && !state.forest_roots.contains(&drv_id) { state.forest_roots.push(drv_id); } + // Store activity -> derivation mapping + // Phase will be extracted from log messages return true; } debug!("Failed to parse derivation from path: {}", drv_path); } else { debug!( - "No derivation path in fields for Build activity {} - this should not \ - happen", + "No derivation path found - creating placeholder for activity {}", id ); + // For shell/develop commands, nix doesn't report specific derivation paths + // Create a placeholder derivation to track that builds are happening + use std::path::PathBuf; + + let placeholder_name = format!("building-{}", id); + let placeholder_path = format!("/nix/store/placeholder-{}.drv", id); + + let placeholder_drv = Derivation { + path: PathBuf::from(placeholder_path), + name: placeholder_name, + }; + + let drv_id = state.get_or_create_derivation_id(placeholder_drv); + let host = extract_host(text); + + let build_info = BuildInfo { + start: now, + host, + estimate: None, + activity_id: Some(id), + }; + + debug!( + "Setting placeholder derivation {} to Building status", + drv_id + ); + state.update_build_status(drv_id, BuildStatus::Building(build_info)); + + // Mark as forest root if no parent + if parent_id.is_none() && !state.forest_roots.contains(&drv_id) { + state.forest_roots.push(drv_id); + } + + return true; } false } -fn handle_build_stop(state: &mut State, id: Id, now: f64) -> bool { - // Check if we have success tokens to consume - if state.success_tokens > 0 { - // Find the derivation associated with this activity - for (drv_id, info) in state.derivation_infos.clone().iter() { - if let BuildStatus::Building(build_info) = &info.build_status { - if build_info.activity_id == Some(id) { - // Consume a success token and mark build as complete - state.success_tokens = state.success_tokens.saturating_sub(1); - state.update_build_status(*drv_id, BuildStatus::Built { - info: build_info.clone(), - end: now, - }); - - // Record build completion for future predictions - record_build_completion( - state, - info.name.name.clone(), - info.platform.clone(), - build_info.start, - now, - &build_info.host, - ); - - debug!( - "Build completed for derivation {} (success_tokens: {})", - drv_id, state.success_tokens - ); - return true; - } - } +fn handle_build_stop(state: &mut State, id: Id, _now: f64) -> bool { + // Find the derivation associated with this activity + for (drv_id, info) in &state.derivation_infos { + match &info.build_status { + BuildStatus::Building(build_info) + if build_info.activity_id == Some(id) => + { + // Build was stopped but not marked as completed + // It might be cancelled + debug!("Build stopped for derivation {}", drv_id); + return false; + }, + _ => {}, } } - - // No success tokens - build was stopped without completion signal - debug!("Build stopped for activity {} without success token", id); false } @@ -645,111 +477,45 @@ fn handle_substitute_stop(state: &mut State, id: Id, now: f64) -> bool { false } -fn handle_file_transfer_start( - _state: &mut State, - id: Id, - _text: &str, - fields: &[serde_json::Value], - _now: f64, -) -> bool { - // FileTransfer expects 1 text field: URL or description - if fields.is_empty() { - debug!("FileTransfer activity {} has no fields", id); - return false; - } - - // Just track the activity, actual progress comes via Result messages - true -} - -fn handle_copy_path_start( +fn handle_transfer_start( state: &mut State, id: Id, - _text: &str, + text: &str, fields: &[serde_json::Value], now: f64, + is_copy: bool, ) -> bool { - // CopyPath expects 3 text fields: path, from, to - if fields.len() < 3 { - debug!("CopyPath activity {} has insufficient fields", id); - return false; - } + let path_str = if fields.is_empty() { + extract_store_path(text) + } else { + fields[0].as_str().map(std::string::ToString::to_string) + }; - let path_str = fields[0].as_str(); - let _from_host = fields[1].as_str().map(|s| { - if s.is_empty() || s == "localhost" { - Host::Localhost - } else { - Host::Remote(s.to_string()) - } - }); - let to_host = fields[2].as_str().map(|s| { - if s.is_empty() || s == "localhost" { - Host::Localhost - } else { - Host::Remote(s.to_string()) - } - }); - - if let (Some(path_str), Some(to)) = (path_str, to_host) { - if let Some(path) = StorePath::parse(path_str) { + if let Some(path_str) = path_str { + if let Some(path) = StorePath::parse(&path_str) { let path_id = state.get_or_create_store_path_id(path); + let host = extract_host(text); let transfer = TransferInfo { - start: now, - host: to, // destination host - activity_id: id, + start: now, + host, + activity_id: id, bytes_transferred: 0, - total_bytes: None, + total_bytes: None, }; - // CopyPath is an upload from 'from' to 'to' - state.full_summary.running_uploads.insert(path_id, transfer); + if is_copy { + state.full_summary.running_uploads.insert(path_id, transfer); + } else { + state + .full_summary + .running_downloads + .insert(path_id, transfer); + } + return true; } } - - false -} - -fn handle_query_path_info_start( - _state: &mut State, - id: Id, - _text: &str, - fields: &[serde_json::Value], - _now: f64, -) -> bool { - // QueryPathInfo expects 2 text fields: path, host - if fields.len() < 2 { - debug!("QueryPathInfo activity {} has insufficient fields", id); - return false; - } - - // Just track the activity - true -} - -fn handle_post_build_hook_start( - _state: &mut State, - id: Id, - _text: &str, - fields: &[serde_json::Value], - _now: f64, -) -> bool { - // PostBuildHook expects 1 text field: derivation path - if fields.is_empty() { - debug!("PostBuildHook activity {} has no fields", id); - return false; - } - - let drv_path = fields[0].as_str(); - if let Some(drv_path) = drv_path { - if let Some(_drv) = Derivation::parse(drv_path) { - // Just track that the hook is running - return true; - } - } - false } @@ -798,6 +564,54 @@ fn handle_transfer_stop(state: &mut State, id: Id, now: f64) -> bool { false } +fn update_transfer_progress( + state: &mut State, + id: Id, + fields: &[serde_json::Value], +) { + if fields.len() < 2 { + return; + } + + let bytes_transferred = fields[0].as_u64().unwrap_or(0); + let total_bytes = fields[1].as_u64(); + + // Update running downloads + for transfer_info in state.full_summary.running_downloads.values_mut() { + if transfer_info.activity_id == id { + transfer_info.bytes_transferred = bytes_transferred; + transfer_info.total_bytes = total_bytes; + return; + } + } + + // Update running uploads + for transfer_info in state.full_summary.running_uploads.values_mut() { + if transfer_info.activity_id == id { + transfer_info.bytes_transferred = bytes_transferred; + transfer_info.total_bytes = total_bytes; + return; + } + } +} + +fn complete_build(state: &mut State, id: Id) -> bool { + // Find the derivation that just completed + for (drv_id, info) in &state.derivation_infos.clone() { + if let BuildStatus::Building(build_info) = &info.build_status { + if build_info.activity_id == Some(id) { + let end = current_time(); + state.update_build_status(*drv_id, BuildStatus::Built { + info: build_info.clone(), + end, + }); + return true; + } + } + } + false +} + fn extract_derivation_path(text: &str) -> Option { // Look for .drv paths in the text if let Some(start) = text.find("/nix/store/") { @@ -1072,3 +886,18 @@ pub fn finish_state(state: &mut State) { } } } + +/// Parse output name string to `OutputName` enum +fn parse_output_name(s: &str) -> Option { + match s { + "out" => Some(OutputName::Out), + "doc" => Some(OutputName::Doc), + "dev" => Some(OutputName::Dev), + "bin" => Some(OutputName::Bin), + "info" => Some(OutputName::Info), + "lib" => Some(OutputName::Lib), + "man" => Some(OutputName::Man), + "dist" => Some(OutputName::Dist), + other => Some(OutputName::Other(other.to_string())), + } +}