command-group-2.1.0/.cargo_vcs_info.json0000644000000001360000000000100136040ustar { "git": { "sha1": "b88296fed10b18aebb7ad5889dab5e43b621297f" }, "path_in_vcs": "" }command-group-2.1.0/.editorconfig000064400000000000000000000003541046102023000150530ustar 00000000000000root = true [*] indent_style = tab indent_size = 4 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.yml] indent_style = space indent_size = 2 [*.md] indent_style = space indent_size = 2 command-group-2.1.0/.gitignore000064400000000000000000000000231046102023000143570ustar 00000000000000/target Cargo.lock command-group-2.1.0/.rustfmt.toml000064400000000000000000000000211046102023000150440ustar 00000000000000hard_tabs = true command-group-2.1.0/CHANGELOG.md000064400000000000000000000037031046102023000142100ustar 00000000000000# Changelog ## Next (YYYY-MM-DD) ## v2.1.0 (2023-03-04) - Add new `.group()` builder API to allow setting Windows flags and use `kill_on_drop`. ([#15](https://github.com/watchexec/command-group/issues/15), [#17](https://github.com/watchexec/command-group/issues/17), [#18](https://github.com/watchexec/command-group/issues/18)) ## v2.0.1 (2022-12-28) - Fix bug on Windows where the wrong pointer was being null checked, leading to timeout errors. ([#13](https://github.com/watchexec/command-group/pull/13)) ## v2.0.0 (2022-12-04) - Increase MSRV to 1.60.0 and change policy for increasing it (no longer a breaking change). - Wait for all processes in the process group, avoiding zombies. ([#7](https://github.com/watchexec/command-group/pull/7)) - Update `nix` to 0.26 and limit features. ([#8](https://github.com/watchexec/command-group/pull/8)) ## v1.0.8 (2021-10-16) - Bugfix: compiling would fail when Tokio was missing the `io-util` feature (not `io-std`). ## v1.0.7 (2021-10-16) (yanked) - Bugfix: compiling would fail when Tokio was missing the `io-std` feature. ## v1.0.6 (2021-08-26) - Correctly handle timeouts on Windows. ([#2](https://github.com/watchexec/command-group/issues/2), [#3](https://github.com/watchexec/command-group/pull/3)) ## v1.0.5 (2021-08-13) - Internal: change usage of `feature = "tokio"` to `feature = "with-tokio"`. - Documentation: remove wrong mention of blocking reads on `AsyncGroupChild::wait_with_output()`. ## v1.0.4 (2021-07-26) New: Tokio implementation, gated on the `with-tokio` feature. ## v1.0.3 (2021-07-21) Bugfix: `GroupChild::try_wait()` would error if called after a child exited by itself. ## v1.0.2 (2021-07-21) Bugfix: `GroupChild::try_wait()` and `::wait()` could not be called twice. ## v1.0.1 (2021-07-21) Implement `Send`+`Sync` on `GroupChild` on Windows, and add a `Drop` implementation to close handles too (whoops). Do our best when `.into_inner()` is used... ## v1.0.0 (2021-07-20) Initial release command-group-2.1.0/CITATION.cff000064400000000000000000000006271046102023000142730ustar 00000000000000cff-version: 1.2.0 message: If you use this software, please cite it using these metadata. title: "Command Group: extension to Command to spawn in a process group" version: "2.1.0" date-released: 2023-03-04 repository-code: https://github.com/watchexec/command-group license: Apache-2.0 OR MIT authors: - family-names: Saparelli given-names: Félix orcid: https://orcid.org/0000-0002-2010-630X command-group-2.1.0/COPYRIGHT000064400000000000000000000024211046102023000136660ustar 00000000000000Short version for non-lawyers: This project is dual-licensed under Apache 2.0 and MIT terms. Longer version: Copyrights in this project are retained by their contributors. No copyright assignment is required to contribute. Some files include explicit copyright notices and/or license notices. For full authorship information, see the version control history. Except as otherwise noted (below and/or in individual files), the project is licensed under the Apache License, Version 2.0 or or the MIT license or , at your option. The read_both() method in src/child/windows.rs is adapted from the read2() function in the Rust stdlib at sys/unix/pipe.rs, copyright the Rust Contributors, licensed under Apache 2.0 and MIT. The job_object() and resume_threads() methods in src/stdlib/windows.rs are adapted from the Windows™ version of the new() method in watchexec at src/process.rs, copyright Matt Green, licensed under Apache 2.0 only. command-group-2.1.0/Cargo.lock0000644000000142000000000000100115540ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "async-trait" version = "0.1.51" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "autocfg" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "bitflags" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "bytes" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "command-group" version = "2.1.0" dependencies = [ "async-trait", "nix", "tokio", "winapi", ] [[package]] name = "hermit-abi" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] [[package]] name = "libc" version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "log" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ "cfg-if", ] [[package]] name = "memchr" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" [[package]] name = "mio" version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16" dependencies = [ "libc", "log", "miow", "ntapi", "winapi", ] [[package]] name = "miow" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" dependencies = [ "winapi", ] [[package]] name = "nix" version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ "bitflags", "cfg-if", "libc", "static_assertions", ] [[package]] name = "ntapi" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" dependencies = [ "winapi", ] [[package]] name = "num_cpus" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" dependencies = [ "hermit-abi", "libc", ] [[package]] name = "once_cell" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" [[package]] name = "pin-project-lite" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" [[package]] name = "proc-macro2" version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edc3358ebc67bc8b7fa0c007f945b0b18226f78437d61bec735a9eb96b61ee70" dependencies = [ "unicode-xid", ] [[package]] name = "quote" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" dependencies = [ "proc-macro2", ] [[package]] name = "signal-hook-registry" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" dependencies = [ "libc", ] [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "syn" version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d010a1623fbd906d51d650a9916aaefc05ffa0e4053ff7fe601167f3e715d194" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] [[package]] name = "tokio" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2c2416fdedca8443ae44b4527de1ea633af61d8f7169ffa6e72c5b53d24efcc" dependencies = [ "autocfg", "bytes", "libc", "memchr", "mio", "num_cpus", "once_cell", "pin-project-lite", "signal-hook-registry", "tokio-macros", "winapi", ] [[package]] name = "tokio-macros" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2dd85aeaba7b68df939bd357c6afb36c87951be9e80bf9c859f2fc3e9fca0fd" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "unicode-xid" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" command-group-2.1.0/Cargo.toml0000644000000040360000000000100116050ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.60.0" name = "command-group" version = "2.1.0" authors = ["Félix Saparelli "] exclude = [ "/bin", "/.github", ] autoexamples = false description = "Extension to Command to spawn in a process group" homepage = "https://github.com/watchexec/command-group" documentation = "https://docs.rs/command-group" readme = "README.md" keywords = [ "command", "extension", "process", "group", ] license = "Apache-2.0 OR MIT" repository = "https://github.com/watchexec/command-group" [package.metadata.docs.rs] all-features = true [[example]] name = "basic" [[example]] name = "builder" [[example]] name = "async" required-features = ["with-tokio"] [[example]] name = "kill_on_drop" required-features = ["with-tokio"] [[example]] name = "daemon" required-features = ["with-tokio"] [dependencies.async-trait] version = "0.1.50" optional = true [dependencies.tokio] version = "1.10.0" features = [ "io-util", "macros", "process", "rt", ] optional = true [dev-dependencies.tokio] version = "1.10.0" features = [ "io-util", "macros", "process", "rt", "rt-multi-thread", "time", ] [features] default = [] with-tokio = [ "async-trait", "tokio", ] [target."cfg(unix)".dependencies.nix] version = "0.26.1" features = [ "fs", "poll", "signal", ] default-features = false [target."cfg(windows)".dependencies.winapi] version = "0.3.9" features = [ "impl-default", "handleapi", "ioapiset", "jobapi2", "processthreadsapi", "tlhelp32", "winbase", ] command-group-2.1.0/Cargo.toml.orig000064400000000000000000000031441046102023000152650ustar 00000000000000[package] name = "command-group" version = "2.1.0" authors = ["Félix Saparelli "] license = "Apache-2.0 OR MIT" description = "Extension to Command to spawn in a process group" keywords = ["command", "extension", "process", "group"] documentation = "https://docs.rs/command-group" homepage = "https://github.com/watchexec/command-group" repository = "https://github.com/watchexec/command-group" readme = "README.md" edition = "2021" exclude = ["/bin", "/.github"] rust-version = "1.60.0" # there are a few windows-specific ones autoexamples = false [dependencies] async-trait = { version = "0.1.50", optional = true } [dependencies.tokio] version = "1.10.0" features = ["io-util", "macros", "process", "rt"] optional = true [target.'cfg(unix)'.dependencies.nix] version = "0.26.1" default-features = false features = ["fs", "poll", "signal"] [target.'cfg(windows)'.dependencies.winapi] version = "0.3.9" features = [ "impl-default", "handleapi", "ioapiset", "jobapi2", "processthreadsapi", "tlhelp32", "winbase", ] [features] default = [] with-tokio = ["async-trait", "tokio"] [dev-dependencies] tokio = { version = "1.10.0", features = ["io-util", "macros", "process", "rt", "rt-multi-thread", "time"] } [package.metadata.docs.rs] all-features = true [[example]] name = "basic" [[example]] name = "builder" [[example]] name = "async" required-features = ["with-tokio"] [[example]] name = "kill_on_drop" required-features = ["with-tokio"] [[example]] name = "daemon" required-features = ["with-tokio"] [[target.'cfg(windows)'.example]] name = "with_flags" required-features = ["with-tokio"] command-group-2.1.0/LICENSE-APACHE000064400000000000000000000227731046102023000143330ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product 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 NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of 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 reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS command-group-2.1.0/LICENSE-MIT000064400000000000000000000017771046102023000140440ustar 00000000000000Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. command-group-2.1.0/README.md000064400000000000000000000027421046102023000136600ustar 00000000000000[![Crate release version](https://flat.badgen.net/crates/v/command-group)](https://crates.io/crates/command-group) [![Crate license: Apache 2.0 or MIT](https://flat.badgen.net/badge/license/Apache%202.0%20or%20MIT)][copyright] [![CI status](https://github.com/watchexec/command-group/actions/workflows/test.yml/badge.svg)](https://github.com/watchexec/command-group/actions/workflows/test.yml) # Command Group _Extension to [`Command`](https://doc.rust-lang.org/std/process/struct.Command.html) to spawn in a process group._ - **[API documentation][docs]**. - [Dual-licensed][copyright] with Apache 2.0 and MIT. - Minimum Supported Rust Version: 1.60.0. - Only the last five stable versions are supported. - MSRV increases within that range at publish time will not incur major version bumps. [copyright]: ./COPYRIGHT [docs]: https://docs.rs/command-group ## Quick start ```toml [dependencies] command-group = "2.1.0" ``` ```rust use std::process::Command; use command_group::CommandGroup; let mut child = Command::new("watch").arg("ls").group_spawn()?; let status = child.wait()?; dbg!(status); ``` ### Async: Tokio ```toml [dependencies] command-group = { version = "2.1.0", features = ["with-tokio"] } tokio = { version = "1.10.0", features = ["full"] } ``` ```rust use tokio::process::Command; use command_group::AsyncCommandGroup; let mut child = Command::new("watch").arg("ls").group_spawn()?; let status = child.wait().await?; dbg!(status); ``` Also see the [Examples](./examples)! command-group-2.1.0/examples/async.rs000064400000000000000000000005601046102023000156760ustar 00000000000000//! This example shows the basic usage of the async version //! of the `group_spawn` method, and collects the exit code. use command_group::AsyncCommandGroup; #[tokio::main] async fn main() { let mut handle = tokio::process::Command::new("ls").group_spawn().unwrap(); println!("{:?}", handle); let exit_code = handle.wait().await; println!("{:?}", exit_code); } command-group-2.1.0/examples/basic.rs000064400000000000000000000005151046102023000156420ustar 00000000000000//! This example shows the basic usage of the sync version //! of the `group_spawn` method, and collects the exit code. use command_group::CommandGroup; fn main() { let mut handle = std::process::Command::new("ls").group_spawn().unwrap(); println!("{:?}", handle); let exit_code = handle.wait(); println!("{:?}", exit_code); } command-group-2.1.0/examples/builder.rs000064400000000000000000000006121046102023000162050ustar 00000000000000//! This example shows the basic usage of the sync version //! of the `group` method, and collects the exit code. The //! group builder gives more control over the process group. use command_group::CommandGroup; fn main() { let mut handle = std::process::Command::new("ls").group().spawn().unwrap(); println!("{:?}", handle); let exit_code = handle.wait(); println!("{:?}", exit_code); } command-group-2.1.0/examples/daemon.rs000064400000000000000000000011201046102023000160150ustar 00000000000000//! This example shows how to use the `group_spawn` method //! to spawn a python server daemon in the background. //! //! NOTE: This example will not work on Windows, as the //! `kill_on_drop` flag is not supported via this API. //! //! See the `kill_on_drop` example for a Windows-compatible //! example. use std::process::Stdio; use command_group::AsyncCommandGroup; #[tokio::main] async fn main() { tokio::process::Command::new("python3") .args(&["-m", "http.server", "8000"]) .stderr(Stdio::null()) .stdout(Stdio::null()) .group_spawn() .expect("failed to spawn server"); } command-group-2.1.0/examples/kill_on_drop.rs000064400000000000000000000007661046102023000172440ustar 00000000000000//! This example shows how to use the `group` builder //! to spawn a python server daemon in the background, //! while supporting windows by explicitly setting the //! `kill_on_drop` flag. use std::process::Stdio; use command_group::AsyncCommandGroup; #[tokio::main] async fn main() { tokio::process::Command::new("python3") .args(&["-m", "http.server", "8000"]) .stderr(Stdio::null()) .stdout(Stdio::null()) .group() .kill_on_drop(true) .spawn() .expect("failed to spawn server"); } command-group-2.1.0/examples/with_flags.rs000064400000000000000000000010231046102023000167030ustar 00000000000000//! This example shows how to use the `group` builder //! to spawn a python server daemon in the background, //! while setting creation flags on windows to hide the //! console window. use std::process::Stdio; use command_group::AsyncCommandGroup; use winapi::um::winbase::CREATE_NO_WINDOW; #[tokio::main] async fn main() { let group = tokio::process::Command::new("python3") .args(&["-m", "http.server", "8000"]) .stderr(Stdio::null()) .stdout(Stdio::null()) .group() .creation_flags(CREATE_NO_WINDOW) .spawn(); } command-group-2.1.0/logo.png000064400000000000000000004312611046102023000140510ustar 00000000000000PNG  IHDR+ pHYs(( ivtEXtSoftwarewww.inkscape.org< IDATxyXyϩ0vd[ a,=4(k10(c1}RkaP2Ȯh<<3c+s~9..{wrDDDr/_īW<++ )))˗/Tdee!33W/_999HOORSS_Ӄ[5sss011 ajj ===*U zzz033!J*RJ?8 ""*,$%%!11O>ERRŝ;w?QQQxn߾liD*ghh ƍQFhԨL@DDJ)w\v ӈ$666oh޼9 ""%>> . ##Ct+Q6loQHDD""*t\xt.]gϞ"ʕW_} Z7|cccYDD&8 "JIIK`\rI5jj ڵCٲeEgDq@DDo$$$ 88vHMKUVر#jԨ!:$""- 9sϟǏE'YXXuh߾=:t?\t IKKŋ N""-lmmѡC)SFtDD,33!!!oN?{,rssEgDI&oZ,""R4\.ǵkpq… EDj666СиqcvHp@D222p)ȑ#DD|򰳳C=ЩS'N""bH EEE!((ɓ'yk>"R:]]]|7ѣlmmamm-:""5`СCDDZFر#lmmѥK,YRt}DD >|ɓ'!:蝌ѩS'ݻw$"" IJJѣGǏs~"R;/pttV*:"""##~߿/_HSd2|Wݻ7z:uN""j p߿@xx""_a@ӦMEiTΝ;q}9DDBU^puuT""%z18\zUt$YYYeDDJb޽ xM?QRtFH^x@lٲOFAA$""-ZCŊE'=>Qvv6N8-[e]vpqqA߾}all,:H-q@DTDaaa39DDZTRprr lll D'  !>>ظq#n޼):ԭ[yyiH>p"" /pqq"bV||<֭[???>>Xf ={&:H`jj }}}K,%Jd2u)Mnn.:FZZrss<ira̘15jʖ-+: HpB_sF__J9J*_777 LMMa``ccc@__fffCRdgg###Fjj*Kz?&''999B D`ll#F`ҤI\""Hh,Y Rk\2*UҥKUTh,xHHH@||{3H-s3PfM9D8 "!>|˗٢sޫtҰDJPr~^Z5nBɈB||<ytt4$Y%J3M:u!"-TDDΝ;v ??_ti9TR5kGj矣bŊIEDDGG#::< 66%пL:uCDZ"R7obΜ9سg DӃ%j׮ KKKԪU 5kք%,--a`` :T(;;0'xO?AsH p@DJu5̜9nTWի+++|/Pvm#5{!""ݻwq]^DJ^zaƌhذ"`RDDD`ƌسgPhР6lfoee OJ DDD͛sb@ GGG̜9Rp@D c̝;7n䩴TlhРQ~}XYYYf044F 3888666|8z(Q3UTѷo_)DTL8?{BEPN|w4hjԨ!:HuVlذwP3ݻwW.:>Dj(//WƴixJ077GϞ=:pc%""m۶s1ydL:s8 R3.]°apm)ԩ {{{N""L8p8q D'G4l6l@fDQp@&2331sL,^NJ\Jꊑ#GBtZy}5k-:>@OOFܹsabb": "5Ç#22Rt ڷowwwՋSAAN> ???߿yyy=,--~zo^t }DӧLJCJwG gHL&-Z3339DIѣG1bĈNDDbT֬Y^zN!wHb1eN177ǠA0~xOD$Ç?9::b(W"$$ GFRRuùdeeaXx1nݺ%:|Xh\]]Ep@$III9r$':i޽;d2$""`\H֮]˳$"9777NjN!4 &~DDjHO˽HW^aܸqؼyP^=L0""Rtlٲ˖-ý{DcŊS  Cr  ]]]9DD߰`zժUïۋN!:Pff&fΜE@tA׮]?e˖sH°b l߾sL&.] vT"?0h DFFNZprr?/Bt \ׯGffeii͛7UVSDJ9s`|AyyyrʢsHB>}5kLJ 'b֬YC8 Rׯ_*WI&ݝ/FbbԴiSرuB8 R5 SNj?FFFsH`ӦM={67øqDi$իW9r$v):EXXX`1b EΝ;1{lܿ_tӧ֯_2eʈN!()ŋѿ<|PtV ===9DDArssc̝;XjհeiFt @ VZqB 4͛q=sODD WD "<<B&MD'ihoSLAnn"3ѣG0`.\ :EkXYYa̙pppL&CDDZ1cPVZa֭^"3a޽hڴ)*Rzu͛۷/DDr:::ׯñ{nԪUKtVFÆ }v)Djg} 3PjUL>C A%D 6`Μ9 UVN?D"wqM)lٲ4i<<F,DDD$uXv-~GQk͛7ǁPre)D iׯgϞֺtuqG\"""5QFSZʕ<- IDATqA4kLt m={ Ptiȑ#\J*aؽ{7ʖ-+:Gmǣm۶ؿ"4Š+䄌 )jwޅ"""RGGGܹs...SVzz:-:H!x \a͢SV*UfSt )Ɂ0zhNjNQ[Æ úu렧':q@j+-- NNN8z$0p@,_eʔCDDDJ+xzzbb޽033BI8 nݺSR5۷BDDD*777D֭/{{{ܹs"""RѣVZ?B$ wA֭YHX|9J-[͛all,:G-֭[+>$$$HHH"i^^^? `f\D˖-q. iEZ@^^t%Uʕ+oDHڂ 0ydx)\.?˗N4wwwZ SHK̙38<>} XXXSNhҤ輏:{,ڵk˻*qqqpmdggdɒhҤ uҥK#"*l=6l"iǏҥK!D .!YXr%Eٺu+MǏh+V@U\V8999hܸ1""">%JիWѠA)NRR&Om۶!//nbbѣGFFF cǎENN7nP#jԩ7o j1bDiӃ/3gB6FjժΝ;'wTn޼Ν;FرcX ʈ… ۷/ &`ҥ3Hp@ 3}t̞=[tdl{AJD8qb^8d2lܸV^T=x_~%233rc 9"DDDM6xY?I& D/Nӧc֬Y3HCz{{{ 7k,>1};vڅRJN!- 77"^`` ԯ__ UEp/$$C ŽbbbСC"L+H5LMM₄\vMt$;wzzzhӦ<mҥ8q IŜ9s%:N8Ik``@*hvgggwРAشi[hݺ5O%K"66 .#"G~>(̝;? RsP,_&L!I%KѣG)?~ KKK|1qqjJe+++)2 AAAAڵkWw|}}(iǏ_~xIZ`<==Eg@kDgHR5pE.I-[kǭ[TU4SNUvGtzz:t颐S]E@DhvvvI2e V^-:I6n܈#F|5Y-*Irl޼Y!z>4Oue[Nq]߲4;;{FhhBHH*4h˗/w;vҿGlӦMpssq)TPAt i`.о}{DGG+#G"??_5{l-ₓ'O* =-['Nɑ5j֯_/:T$v°aÊ}jycޖSԻ;ɓ' ?Z W^U#Ǒ9r$~͛7dXBDjشi~d29"cϞ=SHp@* t٢S$[lA޽E!##*UBJJR߬Y3:u fffJ9~ll,ώ;r&L˗+ǏGNv|"" WWWdddN%JҥR< ŋݻ7RB\dݻWir ttܸq*_-_|cϜ9S@9g}I~w/_^to߾Bj[n[nHKK")5kѬY3)Dob!x8;;#77W=vۧcVbb"M^ ?۷Oi ""h޼9BCCQn])ݻSH p@ŋS$믿Fhh(j׮-:h9sF%!C(l? | 9֧Zv-.]cر ;އdeea*y,"",--q؈NW^BW||<:v숄)ҫW/>}ʕB*AAA{$7Wi׶m5k=zc}9yyy>ց0l0-Eٳ Bxx87,$b+S (:ER>}.];}5>Ǝ+C*-_r^zo}-/_^>yd'ODgr@^V-!.NZ[nK(!e˖$700PyL&GFF]Qrrrk׮ׯ_βeǏ/IDj.//O>zh7ѠAEDq@oIOO҇L&Ϙ1C_ Ѓ 6F2esϟodѢE]PP SSSyLL'y.^(/Yc!<<\ޤI.]Z~AѹD/_.CJ_<55U_ I/F^":E2 m6lE֭[͛/^#ܔ3~a>y6lMjj*ƍWϻ}6v*t-[<͚5õk>{4ٸqyfN?}ENN\ I 쌀)ann]vSHEr9[?ȟ[n]l߾M6UBeddRJJ_abprr*G{ G^ {}໋⾭J*P*U+bŊ\2V5j@OOm۶Çѿl۶Mew ..1b[NUDХK$%%N'bŢ3H"8 %K0i$QZ58qB8tPӱcGl޼*UR@+((H#mgCCd*csss>Nnn.~g,\ؗ"))  #"mv]t 111S$cٲe?~ B ɨWBrΝSȱN}Z!}>B5g^&?dffbJ}?666?BHMMիWPFDr2qDٳGtIZpuu=Eƹs矋N!bbbx=:={bرRqNojRM!7oތM =[z<"nժUÅ x)SH0XDDzꥴoqi+WNt criӦqe}ϣܽ{9bHMMU豁')R2e[[[){{{ JHH@.] ѣ= 333)$2 o㣰SCBBccuVkQFJ\Ν;J;6i%K"00}" /^@.](:@ [nxI8p ###)$߁رcѳgONiӦGS݆ӟߒ_C1sLm=*vӇ "R޽C" =Bnݐ&:@o߾vI;v,QD )$ʸ]ѨQ#/Vb}I߅@>Roş]иqcn%y)VZ &NGbȑ3H8"sQ)-A=1r9/^-Z|Zd޽ BJ0c %ש@|pر3g,-iii2dzGRY8Dd2.]ӧN 6`3Hdrb߾}pttxyya3HbN>:{|,_Çk1118x 8g*4,<+Z`ooGGw0`߿,I&q8KD*`L2Etp2 ;wRիWѦMNn̙Eg^cƌN:y8~8vǀwTIqd}޷AԪU GQvm,ZӧOGnn(u@ D]8Ξ=͛N!%@%$$૯BllfϞ~ItIѣf*w9λd5j@TT "2/ɓEgWreZR"233ѫW/_d2,[ ɡ&}Ȼ62T>,>@2d.]$:E(L+V`SHoq.Ճvݻ3H 9֭v/^Aq0 _짟~®]Dg%닱cNJN!{9* mzX"7LHHi`ii:u۷GZPF ̟?&2fԩSEguV\Rt)L.kM5ӧagg<)Œ?˖-A 00𣿷]v4itҝw|7<ʇ.i*}Ϙ1CCp,Y۶mI5kľ}аaCL2Eɓ'Ѷm[)T <@=~رc,jgΜAn_bӦMQr_wDVFgϞE=РAlܸP&=xm۶内DZf޼yZ}<111S8PSYYYpppgD3p@EyyyŋE;w`Ȑ!Q,X/_*T$zV%ؽ{7+m( pttDnn*Hjd2VZCNӧGffD^7ٳ'~WKX>|5k2e U'*4שм 4-7$wRnKOOԩ~:^xx8~W:dC>}Ds5n ƸzRCK.Mڵk]vAOOOt ^bҥUׯ+丯ݺuGxB0 $"us~?T}^^^3iԨCCC)ϟW1sssm64i;vĉ'|EGGc ѺukƍZxjժ+WK.$YW~ gg m "nܸm*R$ɰo>Kt }kkk|"100ѣGѮ];)TDm۶0DPݻwrss G*gXtISеkW^j>ʔ)W]@²acc0)*'ɰyfNOХK;vLti===K8LضmZl):E䄌 )/Hѣqu*Wn]9r(k &"m;"###8pjrn„ Dgпp !{*:Cʕ+@CY1i >ǔ+WǏG Dv%:x.:CLLLpj2 &"m;"* KKK:tH+v#: À+)*%ɰqF4kLt )_W_}-[hݭC1`N!p f… Dgܼy$:@1/up"wA3QQ3grϟܹsEg8N[1Kt)^+:)y"*~ rp ˗/uOt)_s@DyQd2_-ZRyyypvvFrr@#GǏEg)D{G/9 Pgѧ044ġCPfM)*wwwZA8tʗ//:L/9 PsDʖ-C\tJٳ6m88q _N!P b9%??9{p@Daee;wBWWWtJ33*'''NQ%K{3HE~AODP9Qqu .R8p rssEhTׯ_Rnnn7nw1]?3DDĚH,IDQ*RTjJ*=P%HD٬!!g_ǒܹ;wx8|$13{=Htן aVP‡I&/dCTQQQX`z "tUvZ1?!!/6lZh"""X+TI^^ WG211'O@ዡ!85j" 6 *dԩHLLdC4ؿ?XG! BO >022bE41cz "8s 6l:֭[___1# i!o^^^Xr%Zf BBBX *.++ #FF?w2G#GWn߾cǎ֭[BZиqcnݺfQDȃ1TUT9@RRVVV^K ''ΆFAff& ##x>}'O =="cUx9.^(ܹs G B$!| bbbyfQDV1dܸqȚBO3S ;w!VZ!44Ƀ0a=zE&[[[hҤ`g;x )HD7) uԁ#`ggUWXuT))Ν;}6>|Wx" {sݺu띯Xr%:t x.Bp ѦM\ruь1[laC֨ #GW^cQQQܿHΟ?O?YYYjgnn֭[mڴA-xyf?)) |,P*]6RZ8Sǣq:i044DI_XHQQݻ۷o) gBĠYfVqM\p̈́ѣGCP`…>}:!z17oTQDs!![Tȳgꊴ4QDahh3gs"C˖-u_h޼[FժUGHH/^hG*лwom۶}7oDZcܾ}ARM6+\\\аaCڰ]JJJDGG#&&׮]Cvv6hD hذV} 22.\@xx8]} !/]vzS`͛7QZ5Qd ï:h֯_ѣG4 ڴi0ƨSooonٲGFqq`ydbb3f`ĉZhpIL8qqq&WժUѪU+4o-X:Eo шYG#Xd%C 0 011˗ѴiSQJV:#227'NG1 ;vD.]ЩS'ڴSn߾fuA HfI&\2-ݸqZ  2;v`CVǏO>aC4۶mðaX;NNNHHH`C6 BCCѦMA;w.͛'Henݺk׮hժ`@ᔖ… 8z(9wﲎDt̝;wPN1!<ؼy^myQуu ٠O7K6+lڴu H1d)**+ ^zW^pppml"7oȑ#8ry<{zIlE:u^zW֭z#j%&&k֬/Bq@:tSNA_-[빛\BMъ+bl_Zh9*V(cǎ&y&nݺ7n ..wEJJ3<{ nORMAAh֬$Uy777l۶ K.EPP֯_TֱϼYG iӦXd [QuUd 77uu ǎuD>VVVoC311u#Ǐ 9_pp0,WիFtt4W( ԭ[nnnpww;`mm:СCXf .]:ӧAFAϞ=o"8sssܺu6 GeCpvvvvV: y?gϞeC=+WeM6aȑodd4XZZo~~>pE\x/_ӧOyCԪU nnnD۶mѢE QVp5k`(**bpRI?GѴiS<~u`CP9^z!8R'O] 3g턪ϋv f޽;;u?/_Dxx8BBBhZnmۢm۶>ď?͛7#//u=G-Zʕ+!茓'Ok׮Pլرc,t `9aĉcbڴi48~PˀׯG=BCCE+$$Dé]ii)"""0oM s 4Ћ˅uaQ m&¬t _~%Sޓw;ZƨQPPP H\7&suF+Wbɒ%x ̙kֱcG,, &MBdd$uh֭[1|p Cjj*7n/^"ի#..ӞHV|e? 6_ؠ~Z3^޽{wu0w\{h/#%%%8v[[[5 /ADD=̄yjԨիW!4̞=u ɣ{`˖-c߿?#?LtV`7n/J޽{sj{ADPP|||РA,YDԻ> FBBΝ *6v !r2h ۗu mذAtots0)twr"֜R@?ܾ}=jժ#G͛mff9s !!...{Y~=lllXTii)&N:Q~?u mڴ UTahח{FƍQNL8׮]{/,,ѣGѽ{wlRg.OLL긞æMиqc())el;;;4o\nnnXG/5U&MժU:Ξ=_u ɢM"??x(׋h4ӧOYG5jyptt $&&"22yyyg200@JJ W^?f͚%@*K1j(rʂ@AxSSSt Cv޺r&338vvޭӯQQQ`"Q#FmXTڵ333QGC̙3_o+AKMxg.qmryi֬tIXfرO?fԩgϞ+oQQfݚ5k2p~ $AVVޞ?K-I$''cŊcJP ((欣w~_rssn:ԯ_CE\\cԨQ>?/_ҥK! 8L4 ێJ&///T*1!VR%lݺU^۸ Drr2C8q"bꫯбcG1yOT*ǩyNC䢸puuE~p 㭯hҤ """U?+V˱{nNXT&E1l01ӧ!9ߜ={1Ell,XG!}`hذV}f͚<%{?;;;DFF֖~كA ~Μ9۳AYYYpqqAJJ (Q( E6mXG Z_rdi/38p M6iOJpqqap=}toP(c'0`7~R:!DGXXX`Æ cJAڰa߿?z:ٴipq1HJ%>3Nm:sJKKc8;;CVVV000tbEyGЕ|d+VdCzsA:gܹcJ*Xz5gwY "jժ8sO l2ڵS{1 *t m~0.֭['q ;;u IEcj͚5VGgo߾=|QQQIoe[u Au  `ӧݻ7YG!"R(8p@u(WC_(cBΆ Ν;!KѣGc0ӧؿJ*aƍc_~tjٲ%jժũyNCTW|Gw^^^UV +Y IDAT6~077gC0?>̩X`ڵkؽ{7Zd lÇahh+*T LMMaffcccTXT `ii R KKKRJox'ܹs"-TpÇ|2i gggA6VYiyyyGAA^|"䠤YYYPȀZFVVJJJ"|χ7n=zޞǿ!!Dh0aQuVL0...0N:ԩScE|2J_b,^˗/#vY 011A 26BwmW\I&% dffɓ'[^jllA~'N[nW>>>~] 33Fqq1rssɣT*cŊd Bj5pQ'~c-ҥ 100@dd$XGLaaa=ҢE o>>>9gPBUJٰaLLLX… 9ll2xu nݺcVFF_F3gc鈎۷xb4oޜAJ{nWDDD **J_۰acȕ}&DׯS! Zu QMٳg;ޠAL2u Y}CTĖ-[`dd?_n]L6 {..] OOO( r׶m[;S۴4\pD"|wtbӦM/w*UbA@@6l:`/_.QM 00999c槟~1&u ݋ɓ'sj舩S"""%bTr{D0R|BBBuVAh4=z4_4k֌uBXbŏ?:h!KzMw8|0W\\Zj!--ueee?hӦ }?|q%GO9h4pvvFBBy.GTXZZܹshҤ .]ӦMO}OOO1!u;055ERR+դ@/V̟?_ccc:N$Y[kFXX U[!99k֬/;\Ü'pyAٳgn'ƙҙԩxs۶m>}:o#OOOV^-3ܽ{4FFFXӧO5̿v5mT{Ynm۶A3wԉ._111,((( QըQCs%-j,X Zf9_{A)S0=222ܽ{Xp/ 2P&''X|̿tjX5F͆ 4۷гgOͣGx3:~8ѪUP-wXM۶mpթSGSRR"-[[[+B]_}/d]HHHШT*?HB]wf%KRW4/_d-Oj~gMǎzqvv>|L7 sڴiSѳV^]3o>XPo߾= ۹\__eCֺwĄuz9~W E^^{_ۛ/甉hΝ9s攻]np MFТE 888%%%x9pEN{wR aff: !'j^^^r (8p v:`d[~:ܘ-R˗/EK.u<ī[}gسgXG)B͛x9puuE&M{.** [Fqq1. n$"eSvf͚k!DZ.]oooq*T*-#Ueٲ#hϘS@&w8p oJ7_ѪU+jJ1sss1`Qrj'ICR}cǎպBxyyaؽ{7(S ľ}XG,~:=: ,,,pB1)Sm?+&L3B"%%S;}8cYrgCZjE%KbŊccC,\3f̠7Sٳ'yC?\o| mR/Kٹs'n:C@ _P*0aBΎbzn8r͚5@HHPPFCO }I&?`J]۶mԔPZZ ???T^>h˖-5jJKKYG!zHP{u]T}իmKG #Qxg``[nAJv.\(?_b zzׇ-O7o#G2W՘?;SNBb٬cK,awZp899u5mѴEEEE"ڿxyyŋZC h֬n޼: GGGQx#… e9e˖_ƌ(w^j֬3g2A׮]߻[Zƅ 0~xԮ]>>>Xj>|b)..ƸqhO$> SB􋁁/^: tR1x%err,]cǎcw\2So*O*UQfMDΝR6j8q"+VZʥKuֱB\O>~ٳBtKѡC1uV$''-Z$J&M=i * G&M+##;wƥK8hpeL<puuży󐘘u>>deeo,$h g'MD+ Sr]\TT˗Y|?~۷!aÆ͍u uԁ:aܹM6r _ll,Ν EXd ܹeu!4jW$ݻwS;WWWi_qE!D4k gCAAAR#ŋQPP:LMM1w\1H}w#Hu?)))裏#Ճ;$iiiXt)ЧOټyԮ]v<'!R0vXގs%覅 Ąu `ʕcBOHOO=YG݌3h裏py1$O>ؿ?;]vhӦ 6m>}O?Eƍ;?-[p1a 77܏@j888&TZuBcSNŏ?:*TdT\u|`X`ݻwaee: )cǎGcH.\ *hOVVڷohi;v/\]]ѨQ#XXX#>>ׯ_q5 ==]Ԅ'665*w3f`ѢE$",=ׯg"S233YG݂ 0sL1<ٳg.00ӧOgF XGի#22jҪ|tE'VXXZZo1T*da)_k׮رc.55u֕#|W(7o,eV߿ӏ;[nښӇ)žBɓYǐ ccc>|Xqq1ф{+..7nܠ?3gpjWF 74^z'O5{)v:VtPZZUV!9$eРA^:?uZh0l0?~T>9syyyΟ?DZM+bʔ)cbPun-:t\BQF:т1q `„ ={"9rS[ر*TD,b"AƍKLLѣGYLg +V`AsgJp]Ϧ5Xll޼6 #Dsnۼys_^4rGў)M: tNn*3###Q iFz*lllɓOHե:uٳg+% ,̙3Y ={`cAAAcNRSS(s7n6@q۷GHHLԫWW^EJ7o)![bVbƍ8wvvvȿ>wǏگ( $''f͚BtZ YGᕉ ߿SGT`֬YXݾ}Я_?1N:N:1U?+V  \~k_F$\zwAnn.իWsUTu EPZZ,&M˗A!;wĐ!CXݼy0{l1Lg GZZ(rrrBll,m4 ZlHQXz5ƏU>`&D_|8uޮl7oΝ:5kOOO4j...hܸN#HJJbWl3` :u ccr(**BRRnݺXݛu&ܹ777䰎" UTAF ggg jb"b4hgW^^^x"e/ۛu m޼_~%z/==&nݒ&NP(8~8vU?Ʈ]xJECCCݻ~~~0uVzJ* ...pvv~S$ppp# k}lEDDӓu҉_|{Zj:ddd 66׿DƏիWkڵk;"}ƀXGaO>8t:Ճ?#puu1x-nݺHIIaWFpp0$Ǐ ~'|7cZtt4>Ɩ(~5BTTLMM9q%|G(..1!DT*vޭ'<M4Ajj*(`dd  ݻw'|B+AVĉY~=Bjj*RSSݻ7=&SNCcBpttDaa!(zV <Ϭ#nС4HXXMYfiɓiOyx?3fA0j(4m Bc<|O<$ѣGx1> ?Ξ=l}w;v!8p n: 6mڄ 6V\5j ''u(Jǣ~ʕ+1i$1dGTǟ]_B*U&Mޢ@aa!^xTܽ{?Fjj?~MNNJBXXVϚ+ر#N<:!ШQ#Y=SbEܜu!vx&cAF?==C?!ܲ7rʨZ*aaa"33xJJJ0tPp>gرغuޝ /_@) 8v(ž}W_?$ 1xհaCtޝu `g~'3<"鉋âE8̙3yL$u29&Ž$Uؽ{@ښ4iJI}e>C޽EKK>ѣG͍sC<&"ZxV7<&bm/aɒ%ARĄ XDRzxxH9 CTPus4m)))c舱crnEl۶D;;wJ%.]s"{HFB3FvPo޼u7$ǁXէ~ 1ւ ЪU+{Yh9---Ř1c '!DTO<9s8ѵkW$N;wJOB{u֕&{A~~>$R8t萬^mCQTصk+COz[l~qn~z\vD{~'\~sEռqFBLm.~u )رu^гhPn]Y^E'l2ΛWKBPRRM6ѣG?OBttcJ*s^ku:СC1`{ռ[nCff&!Dwĉϝ;W&^ /_[i~j'OǏY`_رcJKKY%:DP P(0oY= L);x{n1Yѣݺuc|=6o:sz ˗/1|BdeeaժUw<]ϗ#ٳ'jԨ:o? r[CCC1H|g>|8LM2su!--4"_V‹/8mѢڵks"Ҿ}{z=Rd ˹0MPfMGHܹ{{{QHIIIϟ677uAzz:ϩ!DfΜ pjk׮<' ָv횬Bwի'*U>6xMuKŊ/ȈuitժU4'r浳shڴ)ωOP`4'ѡC1xSTTC1I`޽, m<<oן|ر4B^9mѢ\]] $Q'O)R~쯿*8ˈ}]6ׯgAPիWG.]8ݻw/^xs"B!~z+eL4 cB$cF6D =hᄾyѰaC,^uA}PTʽ8B!RPNm ###~Iԓ'O0c 1!$ב(8{fS~~>vލN:bmȐ!EGG#**4Bf֭Y[[{<cB$o߾033c7blPNrի*T:)p94hN:Z:(\\\Ʃmpp0i!Ç9o8`HZѣ}R6OYZHII՝D}9vG=| .DÆ M6q` ĩ]IIǐB۽{wHbbb8oH?u\|!hò9F :t`ߗ;88`̙z}é݉'sB!m6NLMMeu,:!DBv KKK1xV @v۷l+[>͚5Cz8!D|~::ɓ' d"!իz-XpP݋NNKKt% 46oLwϏS ;v4BbΝu VVVZڵ+bUFFΞ=+@"B!w^Nv CCCHߡCh/BT0 qqqw2www1ƞ={:ҥ vGEqq1i!pqNXXXEj6l`"-[DZXERR '^ݛ̙3#,tؑS[9A!.==.]V`沎AB!B!V^nrVZʪ튊h?!HoƩ>GVV:!D"4| ;;|vLjкuk1 %P?câڿ^T Eر+(X{h41'v}_b-b؍.`/(MAs? -3;9]Ι32%F:̙39B!PudyF7[N!Zm۶ppp; ^>}Nx= Lgٳ' NRF &omvDEE !uݻwwQin~:bbbN ѽ{wś7oxk 'hݺjy~ßi~URE*>s6BpX;_;BЧml cVc 5j@RRʼn iii055;.(tG*WWW2"VZQzL888x[xTԏr9DBHiV^ZF <}yph@ݩ/gggL6M8#'h?!hXƵEƍHzV"LMMѩS'ųgp-([>Z-BHH5T {W;3K!NRi[>nAڵ4h...066Vl!Ҷbtx 88'O(Q -- bA^^9to%^zTv1T^/^(3B!?~<֬Yv'ODppBurrBѬY3x{{nݺUVcHMME||<Ξ=XPPPGNڵ^Mf 0`n'nߩS'W!nݺHHHPzٕ(J B|5"""kr|f]ZRi3g !eee)ZlY1___Yiii8}4f̘Kx{#cǎߟrUEBuUxX^@h$_1akNUWefiB!۷p)ܺu ǏG*Ux̰z/]M6v;KD}z!v Kc(Ft; DիWc޼y֢RT噞B:O>Ů]ЦMWOuիW5^'!D;yxxvb jwddd- ^~-V^ y>>>077WzٳgdD!OSyO>}zR;kkkhB4H%{{{ 0@o&TU@@J۝;wL!\1?ሌăPPҥ BBBеkWUZك-[͛|_ ڴi+++dggڎ?v;X4zCJTD˗/ !A*U:M8QNb~Ν;1x`8;;cܸqxQY[[cc*z!b >ju<}W; m/|,ڵk(g3VFFF*]dB!Du]t .q޷o_ܹsB^`ffV$vP-w| W?6fffطo9B!|3gy+(***9OOO 4H:.^X7n, 56l؀ݻwS!}x@FFTTtA0~xסCĺx"NZk=Z賱cx; +W0j(r"K͛7Ҷ*ppR 6; "&%*mG,--sNZnZZZږ8c gϞŁt5kքcǎU`NN>|uA'*}o;wp \rW\'O ɐ 8:: nnnhݺ5:u ƍJ*xة8Ġ[nJor>dXYk ahh۷<"FFF>WoBZZxŋva ~ ۶mp㐔$bΝ. cݻwǡCNW&&&WxA.BQ%v؁=zVѣGѩSr733C=#G;aPjUpر2AݺuKՑDWxA~ѨQ##薄Gjcccz Jm-ΝӋƿ5܋/Nw...*MD !D{a׮]mۢz6.hj5ݻܽ{2e V qqqؿ?~Wwall'Oڵkkwٕ,,z}TiTT*ĉ5jzN&asJBtK˖-N(:cxҕh#o999; ީK"B6srr¬Yp]lڴ ...;v쀥aggҶ, OI}}}_}SPP/R/&TL_ڂΝSz:>|/_*]qtti=~XO<9B!eDhh(1˗H$۷/;^V7HNN.T4h={D~~>bcc?X!c W\ȑ#1c t ڵd2"##˽_TTw" @}!h/}i*;J_%(,, UJL[X[[###C`رqة\#GsdD!UVEժUGGGTZ| jԨWWW8;;W:\pT4nIIImgg={b„ 6/_iӦV*?NKF犊`gglSQ9^~ Tk q7Eof͚f^UgRNKK9B5agg[[[TR(9W?iA&G 76m ܿ?L&5 oooxyyuV{FDN"0Tf͚رcb<ܾ} 4P w-"""0a„a=Pd"$BMx{{RU~K/˗zܹsh"0 Hή|͛7D"vEvڢMѬVZ|6(uc8rةB!@ԩWWW ...Qe>FWΝ;zٳ&00111ߴiS؈R7!Dmx5J*+qbATPP%K`ɒ%xF 3! :unݺSj׮ڵkEz̙3xbh5kz)J#G^B5mREEEbe.+x~~~e.Cӧ1vX8ԥ4T ///4jhԨ6lHJk+B&Uqaɒ%Z=z ,, C hXXXׯ_;!''Vk׮Dk͛7;TL>۶m@mCB4mڴApp07o А}-W{g!110k֬Ѻ?#cٲe8|f趲Bxx3iBCft@.ƍ w&Mc [nŴiӨU;ssshڵCpp07nLD ;wƖ-[>|^ziodd͛7G#BGӦMqFP۵kb͚5;R7n`̘1;D? 77!O>A~ЧODXWԣhѢJ_׼ys۷Z/>nQQQ8p 2335Ro*Uk>Bvї6mva ԩ#v @ :)Ν;w>Ӻarttq)<{ VBvVƼy4V7qHMM^-5kDdd$LLLq$ .]5۴isQ㟐^gh]*\TT+++X:t耣GyO\\z۳g>SˉɓbeBb0jՊOռ<=zQQQ8t>} sss~Z#3ЩSJײFLL |||38d21۷oHXhz!ڭ]v8ui ٕW@vM6xKֽ|`c۶m1cRRRx)}0j( 2&SAUw޽{z*]#&&֭[ ~~ohh?,㐕ݻ#&&F2]«f͚ٳ'LBBJi֬wݻuv&6a"4%?^J$ 2{ƢErJy !111!C0eki*l޽{xAIܹsnRx>mۖ?ŋ`ҥ֭`1TtoVz{777_۷ǭ[3<~ɰT*E*U TRE 胦M/]Vi@|WXlprr; ]va?t̙3hٲ e߿sADD BgeeÇcpvv;Q?4,YYYx9RRRsٳgHKKCbb"/:::炌8yyyhР>|Xk;w򞃺ݻ;ɓ'Jm'H0b,[ Zu;!D7={ ...b3gbɒ%J-k[p9rSNE\\`uBemmSbԩ*i#55xRRR/^ 99)))HNNFrr25:RRR{nH500Q*m{yyaӦMxдK.k׮Jtqq/Ν;ܢA!QsSQ"mJ;a S `ٳI?bcc ;:uױn:,X^.Bz1qDL>b!-- O}yfXYYVuhBt_ƍu@{<}nnn&% bbAT5u\+WD``ueddoڵk\9=zիu7F~NN>|ѣGx>}d<{ b̻ $$F8qx5j _oaذajɧXtEƿ!{̘11jB`,\P4Ԗ GGr 22={$1M?b?EAAfуO?Tᇛ/7n)SpI"TaÆXr%Zn-v*ebjݻC\\K"fYjB\\jrBիW^ؿu AƿvABo>^ÇѥK_*t:bҥg} ;;;^O /Bu' -Z,,,Do %Zl4q%.,,dlʕlРAC f\.gDZ &(TODD_o;/\lll:Vvvvʕ+bNplmmEP7^~V /^dПh߾=yF޿gϞRP|}GC"+V|pl+qcTAUpܹêVq?!D,[B p8YZZƴix?1?F!y!aIR6j( 111I&7EZ؅ 49~_qÔ8;k+1ѧ:t({Uq.K'OdkV\Cq{)sqqQؘh'|&N(oakk[>cѓ#6oAڴi#9aggVXr9ߘMAѯ_?)g} ΧOyќ3g{Evv6۹s' V̹sj)\.giii w\ni#믢>IIIc=q>난yyylĈ4\fQQQy/W_} Eo ] xr SN>}gXV `-[d*M4~O.֨Q#~5wBt |ıcr;~gW7LLLL& :}4 a>{yU/>SPRXXX{j3ZxSh4De>k\.gSNJ߻wo;/!n^},w1022; ֭[ڵkXb,,,NGaÆ8wƎ+h= ={}hiiiѶm[>}Zu~cر`c #Gđ#G[l5Bԭ[W4VQ[^;N('Oƍ7ХK;Pvm266ƺua V!N:F 4RqM64W7mBB h)رcNG$%%?Gf+h]F_A!D5h\]]5Rq(,,ĠA0bj^",CCC9Rk֬%Kn޼yB-v jSz@jj*KHS 87n]ŋѢE :)))ӡCl۶ Ai޼9N<իk> йsghFǎ(jzϞ= [[[mff:$ v*J18 Jo?}tԩSḠQ?ϟ#::qTyh֬ZnZjʑ",ooo(D:ףGܾ}ӧO蝢"lܸuʕ+!y)W,Bt"""DlccC =$qtUբBBBv߸8|pvvFVd)**իWÆ ;|||0sLDGG!@'"}\nҒ鈈(k׈q fQQQg}}((4xܨAA_ZJc\.g,88X|R)cr\cy8ڵkkҤ梨agg>3{n&JŸ;vYW7lllܷ. ''u>կ_gΜ 4:$>>]tvy˗/%Ə1cƈF G!~YXXhPTT޽{ĉ*_[+Fr[߿?.^(vJ ѯ_?899aԩ:?Tm_~]R|T  f``QF!!!:t;sN"**Jr\\\0o<"D4n?i%v G?q޷ķ~+x10ư{n`xh˗Xbׯf͚aƍ%20EyyyńeeUǣZj+vz-==!!!ذaZL0)>|c@D"i|d2X$#hٲ%}fpvvO?$vbaҤIbAx4{lyבΝ?._vYꫯ4r43عsG?K.;VXBS"D'ߧeHÛE'OG˗={66nܨC  O _`kk+vH$۷/QO$$$/Ķmqozl IDATj|אdW|_H1H$`Xj]puu+ Jaaacccdgg_FJJ >>uܙל˗/q|¥UV1T*em۶eccϞ=S)߸8}v6}t֦Mfbbs[n,11Qwp ѿ DJm' X~~FO&M$NHRɂX@@gVVVUQGF)(T {{{7BBBD?n?Xaa!c7R(OOOg&MbFFF3f_q,""yzz z\MMMـXxx8|vQ6k,㣱sʊZr;j5L"ݮn$$$گR=AuE'^K,|T6ٵkטL&`r9{.ۼy3ر#300=wؘ|R+00P)(Tu֩Qr%&&j)zl̙ˬ?;;۷ 2 w^1vY7غuׯc57KHH`/f9H#h )ƱcJS 6Ѷm[D{mٲEQѨWRz>}ʆUk֬Q/EϝB+ Μ9ÌE?…am۶ef͚駟z7޽>SAsoѢ,iqe΀K.q1[[[A̬3BtU-DW7~RTj֏gϞAd bSܹۺ`ӦM8{,NyJiK(/ NC%-[Ć N(77qqq8u._ǏkdFӧ2>{ofݻWr단(DGG{%jjw;1?֮]$_^|7}% >-K?uL>]4*$Hf,[ jռys;w7o ̝;ذaJ֮]ׯSNi8aѸ}6Ю];Aۻw/#H}h[ƧwѲeK_j޼y?~ lmmrJ : { 1H$;w'Oƅ xcذaذaR'BTVZZի7oޝRI ԍϟkfFuN:ŬE?+ D®\"X~xUsP4 ۷VW9oV~}7߰2ˏg~~~c ݌jժ)u>OWPP֬Y\]]ϱszb񲂿 {4Z*|t?~,accSjJ:222DONݐJ:;3QOdd$333T$w.dM}Te)k׮PP( ׯ_f1чtv&f;vO?E~~ة(/###SU&5|"3fV5/s}d2l۶ X|9r9cժU?Qzu" gyK,^nnn6mͫ{uF DC٭n:>}[Ѻuk8q2 vFFFz;n[t) [~= BSQ1t频BBB4RLLLPF ddC:t +bڴi ,<=z޽{:u*MHǏxg,9XXXG50w\xB|\\\gFţywk׮!44gΜQ\\1c ==g.Y}ϟ?/wIWhJJ(IR1\ѣGd\]]5ZF`9B7yd#۷B̙3c4i~7oFxx86l(H(DBB~|'WmQjU8qSL<̙ q&zM.2d@ZZ(IR>L!C <<\TTfmmT'''&;wlᗅ(v%!HǏGaaa;vcժU3g~СCXx1u`̘1Yfr_L&Czz:RRR|R'nݺ%~la``D˗Yf9r$rrr.w022œ9sh$Kpݶ~IJR<QQQbMt&O TZ "_~Ju=z1XZZԔ@!'"v4iRXϞ=affk׮!,, ^^^5k ݻԩSqͱl2)ׯq-ܼy7n@ll,_C֭/eRÿbōݺuesB*B !Bч6f4hLٳbdǏWi[}xo>|R `ssspssC ƍ+xzΝ;KuXYYk׮ػw/ !!^ӧcሌDbb"6mҋ4%//>>>2e +++_Ů]PPPsssNNNOPn]ԯ_-Z()+-- ǏǑ#Gw^deegB޽1dtOWW^=?]t͛7.o̙J6m=Yeccl"FKjj*kذ$9O;t蠱c$i&s=z!'8Ns://*UG5ٳYbb^^^Էk׮r__Z56sLv=cEEEW^L*Rذ=~1XVV[|9srr*w;;;̾[ c~lRR%YDDg1>breee:~I$qFw^2ѿ ;;c狞QzuN "'O:u|0MvXZZhrJy0ѿC((*N… *edd.\Pi=!!!/J՗,,,*F"`sΒϟŋ ^RHRֿv9cdnkccN8Q`>{, }* cccֲeK6oɇr98y%+!GKuٳg;ԹR-ɡDXׯ_gժUy; ~ SSSFgРAjھ}{ѿ?((* ??? &ôiʬg۶m{YݻwTV&MO?=zTRwԮ]mݺT}lƌ, @:{cdz;*۲e ŋSN9NժUcwllӦM,66zԱz *J\d?;w(-;&M޽K5^h۶9ƕ+WcIcѣt٠A}v jJNNFf8I8,-** ;w)S`ʕ_ *O "_}(MV[֭رc?x<::Z⭞U^ɥ&XAժUQPPrM4Ahh(BCCK{.ߏѐd/ڵ 8ro<_!\]]QfM4hZ%޽{غu+n݊ǏT&Noؼy3NW^)}c888`…Z5@"W^Vaaa333X[[޾$PF ڞ1O"!!7oĥKpE<|5pqq)9hԨׯ_J vѣ䄰0 : A2~3fҥK.VZ8whK?clӦMHSLCk׮'VլYr /\ [rUXy]PPx!<<̺[n}Ponx+lܸqCs8t85k֬JR-ZĞ}ZgСCe),Y222|LW[kR+VKl3f`0tPR7"##E=)TG(#@aJ]Rݻw5:;i6mWD)>8v]zBHU C5iҤˬaÆJݻ8SV*UCڵkիWg&L`gϞ-yOي+T'EaÆnyWDDD 4`t{r9{5V=jܸ1M Htν{doߞ1oP_š fT'3|p#m ssșL/^̖,Y.\F6lI[[[{όD?ĪU>Ǘ,Y"H. ofL3z{{YKJJ w&M#~1vs粂&^:tcYf)5k+,,dYYYfΜɮ]V\zM4I KKKy27n(u8c^FM0A!D)k:cv(:Q'E?(*˗ #q=-Z˶mʭsƍ9?cNJ>޺Q|O?U.DҘ\.gݻwd|||عsc?~9::*UǏWj͛ܲe o˗t(y޽]; /C۠A{nZԩ: iilmÃ1o իW (,,eh0d2E EKs<55U9͞g 6|P}Dʊ?1wVzuڱc8 vK[t)8+}*Uי\.Wz?[[ےݻwYFx/ccc֧Ov>|ȦL\7.sB6rHiРAq~"""zs'Y[[P vvv|k*cA.;֭+@r";ﰨ]zł`I4DM%ؕ"j,11֘%bX" v jŊX.{?x]vugw|G/w枙;{wgs=cZ&Ny]V2{LO6HS߿7gիYݷ7op>xࢺ޽{'to߾EJ'OT|x}T*F٘PN2r%nv/Nmر@Č3ߛ&M`~~>jA1 N+,--at@I0 IDATm۶s΂ N:ywY2{ n2H899ijΐś7oo* 3fԺFD|l^^^xUD&&&b^^hтիqqqK.VVVد_6*J ڵk wٲeZΝ;֖u]2 {ꅏ?;aaQ{)cG^C)33ɓ' N:p֫Wآy}2%%Q.p%ʞX]رk'%%oΝ;qsgz_ǭ[Jw7Ϟ=ʕ+K.ܻw/""8q3^zvvvj*D Y;?sܻw/T*T*j*^61c'htqqSLcHaf!">;gРAܼy$k\.E!⇔(q+v튯^B{Rx}JJ vMP};wL{v5^ +VpN\`ܹ#8@dժU1##5b%8CR|~QLTISi{}~:TT|->t"MNNN:k)L!1*J[Y5xxx8gWٲez>}Z4j(TTx=Tr .DDa+˕+;v@Dd2!sE?6l58ݻwy{o|x]Nc\عs?nR"搲zMJiapp${Ե~zAXSQRժU9JNN68aDȦKr߼yaw}Gޕ$77#[BCC9]Z^nܸQ3p@,((b*UXӧO#0'zlDD\~=+χիW1331-- >|QQQ~zqcʕ cbba d޻wO=~WΫN+++7n'Ə?(x,Ԁcf„ > "l޼!B5w\BBݻK4I&\vA TT\oq rYR@;)W #~k׮m>}XߥK̜9S XjUVeʗ/ ,[{zzEOJJ:x_ߏK.\x}ʻߓSQPtt4n*)WdaC߾}C66I2e0--Mg$Nj鈈*;W\r_lYu"bG޽{u'(DJS4'M޽~vσJ*ѬcϞ=ĞObi޼yӦM#nP8qx9_PEf͚%왥2inϟ4Ei>}tM],ܹSSRx!YRaZZ ௿Zb}ٰvZDdk֬x}gggR࢔r$n8<ݩT*|`$bl=zسI,M>a *!{)--TRəFNE%"""8ɓ'Fԭ[נ6lY6 f6lxDDY9|0T*lѢ(6`rrrQ =#nܸu;Ĉ#a~:R]q߶o.wܠA>ScL&3gpBHř3g=?DPq9c$䭄J¦MoWկ_iVVAVXQ= DX5kj0 /_df322ٙUHv[Ǐ1++ ԩ5j`nn.Ƣ\.ņ `ZZZ n޼Ys4vXDD2ennnxm6w\ѮQF x ȨQQݺuQTEQpb$4j(~!BOzš S+@'`VVZ o1((Hm[@6'O5hE%Μ9 8ES@Rann.֬YSP͟?tS(DD#<K B $nP XF)ߟ5rݏ޽)T&ur᝝MMGԨQ#۲e ;ozT*|fQÇ 0u#XlYlڵ+*JŻs~7ENHj޼vB~011x4V(u?NR@y$TUZ"Rgǯ1(((HP?N0xK0BBBB{i6~3x` ;bbbSt{ɺKNdEq.k"")ט`XX?Q.0 Yҽ{w ?.wrÜƣ,o,Q(RK"1ԥKv7D233Im _zŻ>|(Jdd**CM6{VVQom۶iYf[Î<{4hЀEaɓvttD3Fv/_߾}O<W&իW1++ +T &Lhzz:m,YRڵk8k,W^O'O zǍ-a0 999-o (R{sH ue˖ *szHR g}F| ŋ9xQ>Np"f0>Z`V7oNĞT [_ںu+10 ڵӻ ''  -{nNqҥjW=<Jș3g0//V*k֬ǏcŊﻯ͛jۂ!ࠑao%FEE!CEP Pp&MF$-gggbܾ}8&%!t kժE TT\ecc9;bO>Zm#pرx?Ya0++b5޸qC HΚ5kbnn.^pA﹭ZBDvBk۷o<&wlNMM,0%ƎJw V쌯_)0o$!]6B5"D*T =(f!\/M;wg .$**>8q"~u Q֭V&1<<<0##onݺ[ERa%oE]@ÇRdF/S@R9CՙyĈ@)Řz+___ݺ ʕ+3?'> )kkkwJIIA*| 1ߦM|x{ڵklٲZjqΝjט/x\.k׮X"xzz7Bʕ"DUV%=(&ӧ͞!? Çož={](7776tڕx_񑳳3"L*dhhm- ׽{wڵ ,քkCDDQŒ޿_ެ,UTk׮ƈJ{ Ю];͠RTB#DUVE0U /E|RpuAFEU9\Vr鵄hϞ=.2 /_=*Cf ߿Ǜ7o]6C$ 4bժUDŽ.}'XPPfٳgEg#;;;ܱcqjL6MP?8pzP NjՈ?{RJ氏BK>}aCkݺuaR]|{nc>$$ŕa'$3g԰g޽lmmƇJ_}2 S޽{hkk+i[ <$RSSٙ# [Izhc 5ao@ԅ {Μ9bŊP(z-LxIImL0>۷ 6|pT*ǫ1׋c.LQ(3蹻#z zÇH|Z'Og999nyH&˗9QF]^a+ɴi<܈_988ׯaO166D/|ln߾U*Z$}u1țu>j޼V-[:t4BVRVSg"z.Æ #//4iB|쒐fgg귂 MEV<޽VVVmצӧOkK2e犐(Ơ3f/cǎu5j`fffYRqzƂիƤZTm%Khܻ;5buŘPT BRD ?oޞ!BԤIbbL0%+W5k011Xܹ3qu)22R^KKK6iJ?+!ruuŌ cERÇyO`~ի;V vBJe7ؠ 7Ěʔ)Ϟ=SѣGqXUTњM4hv-[߽{G(Al(1Xe͛G5vP(F_~8?uqKұcU*m5q۶m 7]t}͛7XE6^5kbŊ]T5ViӦGq}7N:ՠ6DJ^4m4͠5kFY#DU˖-IJ4P1O4B6mD~**6rwwgcT*>|X欬,6i(K<==177WaFQ2eɓ'%.NqijvxM_}>Uqۄۛu5]6n܈^*18KS^~]#H .ğf#)" #tgyf^C` :OPqmy'ݧ 4U֊+8'$$VZZGmC۶mc5T*l)GAaϟ?_k u LNNVTIvVs.˗/1errrd5Id}{$al޼9믤@1sa LJE\kX9\d2X`Q(PZ56lrϗ񱵵U~~>!KD֭k`KaŬΓ0m4Ed0ydx"R Q]Xvk׮abk]vٲe; E L}iaa4 Ŭaјdoh zME%RsΩ~ 6^_M![İKJWVxz8o:7e+ 򒤍2 ɓ'9֭[n:۷/}:ۻwor9ƪ%..WHLu~ԩSqŊxA3g`ll,bTT޽pܹ8tP̙j zP,5lؐ8) \СC޽{ZXHsrr8`ⶳիWl!n`BBF^~]bo߾իQO>bo߾3+4qmԩwED,1VA0%%EZI9SSS% ~(:tў~*xaLKKc=tKuIJ'O5~wrrt " 4 l"gggQzH˗yi@&M}I9x 5w\c8??݉VoV̙3msss‚mbX)B>>>XR%F@;vlQiF٢YWWW믿0??l7 ޵nZ :+Ղ!tI<|P¹L&À<~QX$ѣy;HgruuE(WqCvڤPTءCZqصk~ɾ2/F ۨƢGq6&Ͷm1ap߾};C2wt.Ν;Wp{r9OZ?7%QQR ѰaCJ%֭[O>D=ÇW;G&ac5VJ* _z;3HE͚5?Wݝ .(kpƌ?Knnn0{l-P3|p([,rG(0//%꼕V IDATyFqooo["k֬aTV&Anݠ_~ШQ#VudGj׮ GhHKK+:^ѣG#{ ΝP[[[۷z}+pĈwƴ4|2:u놈&MԷ޽;G5عsgLMMޤ$\`mC.tww׺-ǔPT)E ؤI5fU\kVcSժUIc@j. <fddKt:uk޽{\$4ܤ7mD.ϟO61%Jt5bԩS:~т~z-C~~~P(ڗ666رC-::u ;t+c*SFS~i)fF*U?Ojժt | jɴIMME.ߺukEBay;}ȖH+XZZ3-rgΜau\.I&':]ڵJ~ jժO< 6A]PP֭Сhʕ+o:fjCv} #Eff&|7p9AaҤI«!;;Qd(&FW"B (F@nnF0&MYvvvZU%%%*C-z9L=QFmXt)qN]F6%%԰0`TXvmܹϞ=cVRJT*qϞ=_o, &apҥ铃={V1G!A//^L|AY"Dj"n# 22X4 2DнXn6P.M>8v͛7b ɯ]p[%C&=L4Wϟî]DRZa Ƙ=i;P(h dee6l`F{L&0Te4nj֬ɫlBB-"k['`&Wn@K ڵkf_q>}Ǐ.]hO?رc~sC`` XÆ \έZ˃.]hpihذkٳgwww ,BIMMN:˗/ r=1AD3f TXW l4P(L> 2CMbccaӦMתU -PӣGeD04nX㘱xhXreZb|a ((G… B1OaI=(fCfʹR 3g]^.ƍI<( .,5S~}8NCdd$ԨQC7n{\/m  )+ݻw={wttCi`ՎM8Ѡ6DDDjkL2W'N@\\I.|PssR `0;-[.]]Zjh*oj [z*Ud@K 1l׮ԩSG߷nʻ6;voO>~z666BϞ=Eߓ޾}{3 lٲŠZTDDXp()(ssSп $mYQPH!CԛеkWASRRD0Rc `\u3p\ܹu@!* K,Q;nmm ;v0X@=}:T)יu @!7n$mBرCpG5k1PpprroVuoiĪ͛7×_~)ubj[,,,`ر#>>FaE&= Lܡ04 CLÆ ǏCLL ̛7M4MDi ''}Y@ER6 PfcY(m+ò{nMz= :Z={@TJ cf͚ """_k %??V\vll %<<\PC eʔ\633hF gai6[233I@1B,,,~ضm\~ !!<χA/FddZ U [H1RD0t]ߌe+g'11鍅Rٳg!''7o 22pvv]h䇷}ܥ++VPswppe̘1H\d #GU>44Ԥ(Ɓ99f \҅\.oooܹ3L<6l/_ HLLǏŋaкukpuu%mQ1ydx򎎎sN0G 9lmmIڪUݮœA?O7l@۷Oz̙3жm[eU&<<lmmK\ /^v|p1g4شiڱ#G٣{߿L'|9ݻnpvvv wtt$m`fP:uÚ5k[HNN'OŠ+`ԨQо}R.''Jw 4k׊hѲeKXh"X$-C )Z&}.ٳg!!!jiѣGޞ΁@T(I&i;۷uзP7|7DlȰ`r6 '޿O888\j,C`7b/_ڷoF+Vɓ'!99!66.]*8)q5 TG~ ((H$( re[XXƍ۲L2лw1uʕӻrGEE!rꫯXЩS'|O2  H;v3fH~}<|Ԏ=5;<~0 /`mm͹ٳg!..(Q)9tvqq i;C($pqqf͚رca̘11(3g΄XAuBER[hurX~=%<ǫi&T #q>x 899AVDEJ޽{j7 bg/f͚Dl1gbqr9TTw+VwŸ0s0A1m&O\EݻwIE\$)`ٲe0j(Q5=&l9`!O>gϒ6>xԪUupÇԥ=##wEr9lڴh:tH-L&N ;۷o'v}.zoݺ222h, +ai6p3(M Lf\,TGj`˖-tL&I& L˗/~I2`c sJb_nxz;FPb VVVd2ަ SaȑĂ"11#-\.[BfDbH5j… ڵk+Xf |Wd^re4iM,l۶ pEꎎVN< DRۥj,ηi[†իG4xann.L60qD^eWXAPbs"Dի#B1C3ABfw/_*Uo jٲe"|J+?ZjѣlX"{?%&''b2L}i\gee޸qC._L|RLL;w$jL&ׯl aS6>xU*&Pa‚A|}}Q` dgg6B={vfȽOOO8vCl4}㨏Gapaׯ_-WB6m$$$#Gk׮n2d념P^=V&ؽ{7_rERwpE[(*J-Ϛ(УGX"!kh'G&q8CDXj JLIFFQx p/b(5'}ӧOSn]رcXZZcŬ(_(̛7w{Ν;C-<==᫯yALL h-/믿ӧO!88Xf͚s.G mi6O={Ϟ=0%a6m2D= W\Q;ghv)))E ݻܹC 89۰aJ`HMM%m`ʖ- fB!IJJ ?~DHKK~BTϗ_~Bo !!Ad8~8駟B `Ȑ!p!+W|X-\.ÇE+WRY *@@@Av O̘1|/^… aСeʔa}Gi;D7RqP(ШQ#e.\܂͆ &wUT???]_&A_~}ARR!::"##!22N:~ZVZA&M f6֯_o^ ?kq͚5rR20,#"nذxZ7nAP(T*%M f^(222Ϗx{OO*ADZjJRu<5j(*3vXdFp{δAӧOgmg||Ɗ%k-ZW\<1-vڡN[ƌ#qq͛7޽{_nn.FDDFm:wZH~jժaHH>}T]lѾܺu6.]pn^3|=z4ϞPmٲ?tÇ$T}AAl߾բE ܿبQ#"mۊIE\d6z{{?Zg9sDDxzzĉȑ#V.-- pƌX^=^=z(k;ܹ#^zm4j޾}uELP( fʕ+ ܴiT*tttd]&::_~]?$TSu]v|&p `6mDiԨT*c=<ŗ/_}srr}Pr_x!`-[rncÆ IM12ڴiC'T7n@-<{ABU~}R9y[)/Ϟ=Cooo2[gƌK.#Fn7n\tr}-=|՛ pWŗv#GX5;;144ǍzVZz{{crlٲu?{ƍիWxp߅/_~Gev튈&+\.]v4bg۶myQ^^P̃ڵk CX- IDAT/ yyy *wwwRꫯD2yyy#GDzj^D\:u3-Yfz/??w܉XLf͚yBZ44h`q3Tɓ'x#Fgii֭ZYY鬣g*99*UÇWC1{lmwppL h1ZԞ___ w+ El(.|_())䄷n>}Jc"Kpp06`՟={6mI&a>}Gwq{ʕ+j8/_^+ӧOqڴiXBqƸl2NĆϛN!:pfee\{ͩ*U$==w]VVVo>#55wΪFi̚3giժڵW ca|5'by.RLO{V:uhUV *C١P?i ˵J UVM&8氀I_C =Nrss\rP(777 d+Wb&MXR^=?oI&\.@v#n.U\YӧOVRJgAhcݺu߭[7"8x`֩ I 6={Hiv}8tPe 3HL>=9zZ1SymqI hB]۷oWڵk.Zvv6>{ o߾ׯ_'OHD|<Nj>BBB>ǿ?<< a  N9s.^(5}6lbbbץkޥKƍGD &Ms_ߘx9ʼn^/J<ܹ5k,˗/v`ȑвeKck֬}fێ;/Q <<tTZ 'OysaH-v>~-JƖZ&xKKKKXv}||jǶm&v{/]ֿ'$$@-$ a`ܸqmݺh,x=?~99hP7o vgŊ$.\u> Yxb 1d2g N< ;vTB!CԩSM2?J%>>p%ܹ(Q CΝ;Z#>|B.5|!##fΜ M6U[ZZ|&QLP\9GJJ pK)YYYD_tgB͚55BllZХK..]N:iCk7$ "۰a[aLA,R|` (IOOݻw6Th{+0sLnp_` ->kCGP˗н{w6m(L)ry /c˗/ϩ\RRxyyIa+t}朝a׮]j1cƨ׍ JR#G۶m9s} ꫯDߊ%KZdУG/?9))) @Κ9$qr9o (ұk.\Qd0c ؾ}ݧ)8VI={Ο?/5@PH`naBg\ܹNRRNjZ'wwwN={FSI%;88hl 5d9-͚5KkW^An [? >|82eas=#4۷oEo߾ 4N;wĖHCٲe91$`gϦU`%&$޼y<\]]-ꋗ^cMXXqWT Cz˶j 5A׮]m^d,L hmmͩܺu$bܲn816:.]6mڈáVZp8)fo˗M6Pc0sIqqq4{S0 }s (Pl2<04/^P7m$TX ;w@֭ikk K,-[-F899=oΜ9F.t?-{JÆ =o"׷bڅH`YN#z/ >33SwL&@?;kg/(`bA ""b/ޢFE'c-jI$BlQ(ł((Eνᇯݽ3;.{3g9{p|uuu!88¬___vWQJg +o߾}I@3 .T*￴`www&0]ݻիW8q"DEE3V >/_Moɖ;w包4k ,--~P^R ---Z`Ǘ<000B;2d %bx$4.~?DGG*///Xx|???4iAÚήg:pJ,fҳgOaʕMaDL+VTؼye{ooo8}465 ...M6'==޽ wޭ6|͚5Ze˖'dIň .޽{3.hB=ֹn|NN6%\%"" " .d >n޼ ;w\;6aaahܸ1 S䰦Pl mÇM`Ș+VU4cx 8{hcƍԩS_.Vhݺ5Z >|zj9r?gn`U> &&&¾}DZ ނ'u[,XŘIIIX4۴iM_JC 5o߂Rv={"uǏKjDJ9ss???1ru{]ּysI! s0HtR8p \N<)꡶vڠP(}m===055066###044<ضmKxڶm+'O`Cjj*]֭[GBZ޽{ e˖aI<H9@Ei=000m Ct;>$PWWjժ`bb`dd```'ߏ? JPdCbb"taXu7mΞ= >>>h"޽_f L6OݻwR)@lB,Xm?=B(q)!YYYr~E#E9B`ooæM`ƌЬY3v `Μ9?K.A׮]Y$χ'O6Cm*|@eHIIAWvh lXv-kLEPDԳgO9SNE^"2B9s&!˖-4[nInO?$;:r֭/^Hn_B]vݾ}Jn*,,5nnnۻrJC^^~W%G=>(JԤIAO: и/rc+窲SSӲkRa0dѢE0~xfh,* VZ}}_f 'p9غumNݺu!$$oN}S...r !K*磏>l<eee%j[:Q@c @ DGt}m a۶msN=p\xP(`ԩ߿m͔!rXKWX!88:9!%/ju+ 6m$$$yjDR:aM~MRgpuut|dd$!K*ZpCDDkGnnZt .>Ŧ޼yCe @Es>0Qײ̛7z5L>]s(++ :D* 䰖,L9T×`T1?~\p˗//^L$olܸ]F- |1lؾ};m3иqcoϟ?OƘ*hKۈ¦k׮td\vv6B+vXrtCKll,ڵ ǁ 6LP;B1h!$s02A MhBO?˗Dpqq˗/C@@XYY7o֨l۶mQ: :ut|BBpBرc\rݻpE8v,]z uԁ9s{>xxx:w ryY<<:wM_UMH#@݈,333;p@zԩSPV-ARŋ0k,Amˢd~êZ@s`HEaa!\~ZODDk~7{zzz0i$s ZX@GG֬YǏ:?SL֭[ڵk!))wۂ@z8rViӿALJj۶m#h툔j6Ept`DRѣx{{DB+{{ƪΝ cƌtz=ܹS]4Ç :~޽,ǏNNNY777 A8tgll@T³g$ BBB%#ATTSaҤI""N:_`#..X()N}TMƍM`0PfNrrr`ܹ W\!ޟBõkĉA q`8w4hOB=W[^۷oCTTz5QF>VR}ZS9.{vAȟGa+\۷o311i%1b.]啲u׮]ѣ``` Yd(((ֿ L0EEE{ne(*qù9`0*!Ds|HLL t>3x%8OOO8}4ܺu nsss8|03:u \\\ 22Rm]0qRу/^C' 4077N8!E򦸸fȑXU9dff ngffo޼!`Qzs_~}pttĢK_~{9hŽ;/&`@@!kܼD>sWi@mB.^"ݻiiijjC͜???͛7K۶mOš5kiӦuo`ذa w!˖-Aa8ѣG_ aXtk #G@DКᛟĉZ[رZu`aa!8@@,t .HxA+yvZjQ ;111G40 rX;V_SƲ=8 قoE0'@Y233a޼yСCIK/֯_[HLL3g1cK.V^zUVa}) ^¦_Ox[TT"hMuдQnx>G]KySsV"PoooXhF̜9S񁁁*04,U 33>}J xW᪈[nAϞ=aҤI"Y 탇ˡYfoll ~~~=SDD`/ѤP(%;|0>hcmm ݻw}ٳg:@8BTu !N'1h-[56}|Ç%C֮] ƍ?yyy;vlصk2=zYYYPNNNXB9 PsTBvvZ37i~xDEEKmiѢ\r&O_oЫW/"{۷o_*9RGb&'OHКy4h;]Vw`ff& ƒ9pU8v؁?N5ϟÙ3gX$!5cuk*۵kZd0>$99^ 2`ɒ%вeKغu+1:t RRR`7@TT8;;c';;ƍ'V_~T"}р8}|VVu{ G9#V N:/ dffIy×cY9VVVT_n\* f̘!(@@E! @۶mFAzD`o.Kf%99f̘m۶cǎQ>SX~|utЁx)3gOjz{O=@pp%q򡡡XSs 0f"zK riaa *ypG 6L(wATHJJ۷K/-j͛7}nsa&h'rpX[[W렫6WQr`r,قwޅaÆAv`B`` Ãc``]v-BM6*),,4)fMԂsD4n;$hܸZT*Ʀ}4˗/ @TR!$(`^^8pE aؾ}j$x9m3 H!7o'|]vEl~~>L6 $)֮];022z۷oիu C hQTWJ]+V/qG$  4/%~Me [xXd xzzJǏm8^*WMugϞbg^=µkh``͛7pmf0x={ӧiÛDpww4Բcǎ!k}ԓGtu ,YtN v @IR'O`, [hiVuB*6m$y% 'ONdd$mg^#"!/"""1q0`k4;~8t bcc%lfi9-,`ܹGݻ Z>r4WWW7R)Y〴@GGGоmXZZ°aÈ/Kbbd}UGz௿ .P F3x;}B e% Dxu ܼy&O MzOqq1,X &YR)o-::ZrH1a„a| PC}8gR RRRWAiҤ(ǁP:vu%ǬYX2l0?S>]C 4An@=Q5rX+J4\@WWEׯl`'>>>3hҤ Zz ϟC>}`Æ TR8;;Re q`Am4a(q`oo_1p},9tr7qL|O۷/iӆx?r&(7oAPP2B>x"##ZDCQ*C qvvUW,dggC||`gg/OZNSN:rjHr~0!5899:תVZjk:o&X ͣGhJgϞ'G}3f̀+Wݺut{S6I#K.j̞:T{ KH)Э[7888@~~ߦVZْvض:]v0tPܾ}[c.X>#;x zJg}ɓ'!--Eh999#4055u,R"IQQWzYbȋ7o֭[7oK.Õ'NٳgO0!:::~?4iRڵk;++]сUV n{nnݺVFQQl5jR8w﮶6m@||D@Mv‚X|9EEE.]*Yo޼0BLv͢4W )nnnԩ(c4 Y HUgh6?#8;;Ú5kÇ压WWW*K''rƍW#&M\Ν;lRRm5mq^xAÉ鮊v#sU}eڴi`kk+YG>}:c Z\ֆQ5 pӨQ#?~< >\A۷テ-Z,[ bcca޽ЩS'j[3BQ*??R&8p5h߾}$''K`Iͤӌ]t}}}[j:::/NNN˞JC/_N7RzzzrJ;~d}UĤI-ZBTTܽ{Wc{t6$prrcccQiW\E2i$A6m`ƌ?gϞ={ FFFO\\Z \\\`TKyxxTy7e%HχM n%ȈwrEߨQ#SܺuKtѶmrEIPr81\xQǏ-[JWrr2դxE(..E@ Y@deeilT-Zooo".\ǎׯ_C\\ĉ=z4;w,--n GlbRط'6T;v d}+K.\Cd?)(,_tuuOMMXbEP%Ud|ڵK75/IpѱcGAA'!\B=5_~wϪ]6xzzի!<<233O?!CN:pssW0E޽+ر#4hϞ=+i*+n~ֈ7o^qϟ?lbܹ3$&&ׯ#ҥ \zU=_~%>K4 C^xs |7ڎ\քB5p%&0jajj гgOA6lƍnܸp G5kW\cNJjϐ'U=s&MzwlM6պ,C~<$BؒQUbjՂ6mڈڇp=Q}W-ܹ._ X5jDL;`Μ9E;!$$``` 8CHLLDbee%d ƢT*ۑC8OУG$iÆ HWWu΄RFEǏѺu됡!RTװaC+PCYv-/HVJhhsI O<ec^B}嗂޾}ݹs\uڵ I6gBٿ?sEOO~իWu 5hЀ&&&۷TɨT*T~}z\SAc@!KKK! B`h4رch߾}(,, Q%,,5΄۷:' /*bС(11gF>dƍݝrw>R`/^BSNڙb z~Ѽy͛7%'u4h?=j;v$Ν;whi[c儤-} vI);v>Һuk'z0`1.7RE1Q_tttP~~I 666<|0Cyzz">}:6!'''h"J󃃇"###"猫+UKoi2z"T*JII ٻwod|}}q]p pm ْGzW\{{r  6hР>ٮ]73i3$4kL2bWALL  j׭[7zﭞof͚ u[t颕`Æ Dt_v E~Ύx? px@ԩ6%7nm',, l`q6m@HH_nnv킴 utҥ;v$< ~Ze.]QAUy%5u򋥢*Ztׇ޽{ìY`˖-pʂزe |')5;k,2d۶m#ɓE@T*JN,t]pg Hx)<| ,Qsh>JGlx 믿*<8(..~BO>h۶-,]T-ݝ-lR>/_ w܁o_}5 t`eezzzӧO \]]!-- RSSܹse5kt8???"o·o߮NqCH-tRp{6m:f%޽{D 100 8Buܙ~#f ޽U &… <[xKKK̯]F}|>qJ^CINNe7AN|}}>HQAA[nܾ}ժU n:CgggO@@!gӦMpVV6oL^CSEś 5DBCCi`2!,@144osJ} '/:uR+Ğ+Wm۪"##1Y>РA^RQ M,,,@OO.pȑ*#lmmlǏ3g_غu+,Y ֥h^QF_vNhSBZx϶H\րbG!ua6.]p`(Ǐ\]zPvv6 4T*XcP̙LQ zVj(::y[_-x|!!!XmCff}ɓԴG~>wAcǎE 6]Ư0 IDATqqqDǏb)V055E999ͭ#sss{r Qs  ydׯ_3 mVtuu;7aÆh+X8C .DEEEjShтcnnQ/J%y&oh\#r {=Q(bMԩ[T8{,m A>yA^zU?*υ 6'OCPUĘ}}}ubwM,zpP(gϞ0Y#dffƍ+ؿ?xyy%~w ^[n jӭ[7]6bv 0 7@JJ$} ]v o߆~I$v:JHfFP(ٙwrU1svvsssQmE_%r%= &A@lWg|}}l~-ƌm? nnnݹs.uqvvz>X>boAfff+**s7|`kk _|-8k,x1DDD'tܹTL//]]]@~1b·$ɓ'AۑOݻwS}!(77W40 5]ڷo_9Kq(,,yu}d``@eF¾yԿ? 2JOOGfff066FD֭C?:㉄gG~UkNFFziBdeek˗/mɡ['zD;={F}$44T2lٲõ.z/..F[歧e˖(//׹?J>NgggAJRB?.t6My_|Xl6LLLPQBB*((\۸J@gFumG}vpפGӦM56i8u{=.["DohԨ4mTlsB.{A MA$ 8!!V^ŋyVkkk8~8RN|ͅ:Ţ/h?v36ܪ, '\ޥ6иqc8udgg j?dmx)ԩSYf[l4:RRR~P۷M!{NU5 &MMnT*̞=ttt=ζB.͛7]"ʔ!<m YA> Cxe `Ŋu QF9rlllV*ugϞ|dӧ6];w0>,Ax-h ŋpeaɒ%0f&Mܹ?~,b񁄄Im۶ 34lׯ#FHLL=B>\*&өS',zڴi۷oǢ 5j;F%3rYW'bΝpq())I`0wQrƍEspp%"" qh޽/QJdӦM/..nWbdkkݾ`TTTի')GBuAhYZZ"CCCzСX|65iM0h׮]h߾}h֭hƌKjJcȓ'O;tRSSi* 8q׸LLL۷oi,;={8NK՚ r5b mcԯi&eΝldiiٳg>ώ?mŊ/R~W"<5&(??˹w^,*#BM:<(gΜ2}l;v=,^X[[g}=7 iJ%5 !qu_\_ҨQ#hٲ:*4'N6 l ڵ+L2gBBBx;x`عs'PI///,wj 7o.T5kݻw,~,z>_~PV-8tඣGR Rg _}v+WԘ#U!f8qvY֥Bx:aGP̙3yG֘\zmڴ C'}B#8s 6 otuua˖-j ֍ɓ';qDؼy3Ç;vs)IIݻ7=ϟׯc!&LR)x 6 ŋRX?Bf͚u?yMOO*}%|p%uM BN FFF{Ex!u'\(((s6 8dggk܍V@̝;K}_CVViii0|pu9s O>$ 4Ht[ܕj2-h3 0l08{ÇسgvptRɡwPPDFFӿqF@ӏ֭[W!&&ƍWmSNA۶m!44T^q'T{,Bo߮1b۷)2228믿Vw: BhСԯg&deffÉ$֙8qɓDT_]tA8/j]Bٓ((?N$'ƌ3B?^pې̨sea,//N:Eel|q{[P=QPPJLL+)) ?o/!ţT*Qtt4Z+:jsΥ~!Ġ!:uD}Bp=`0j<ԯg&dlfdooO͛7 :"##'"l۶-JOOy cǎT1cƨer/|r"EDDLdll,]Qaa!ڿ?9JyWr(PPPn߾ւhҥԴ~HI׮]y}F*ZOͩkp;W"mYe0գ)[Hi :Ǝ[_~?xWWWnݺ:e˖?XE(y2fڳx8::ඏ? Bf`wkj"D̛7ױR䤑; Cf`ך9pI&0ZEܸ?ܸqJPn]믿J}ST*aر6l`ѢEU>8nΝ;VVV8L~?VK>гgO?~1XT)SqM0233"? EEE)8hQPP_+=^rq97n\'Odh=/֭PEM+S`h#$WB!C@xx8DGG8;;ѣG!""z!MrbӦM`mm]곙3gJ@+//FO aȑ+;&{:::0{jS*Ǣ@.ҥ eX6 K}_100@o޼5- FDʽVݽ{ZN8ڵkG>m2|rs}vhٲ%JII|>&''!C222MAAm|ȑ#jۿrJ禶Ó'OBn'B%Kn? ݻ79+vvv&\駟$ۉ'$~>}-ZuAs{ŴS)* ###jakk$##S9=9֮]K}bpIPPia0j$m۶%~֩S-YDPT={&! i[n9NJJBTqrrB_u^QFI*\]]%ڵk״05#G>6m6mڤ"/)\jT*߿?U:uꤵZe|}e,AAAOmcּysT*Ѯ]=z4BH,{X>lٲEqƗ/};`CRܹ9dJFAKn߾m^9T*jذ!!Xގ05[b.;t݋=DóE^SJ݊ݻ,iRSSъ+J}%2sQ?Y*7nD!Թsgmϝ;rssQ:uϱ]ԩSK(ׯ'6E/l100@iii, @Ą=4n`I.'.uTy-={ 2n8_z8?Ν;QQQ0vX`[wU?Y=lڴg?oEx"xxx@FFmSxsa8zh\\\R2,,,.Z䀾> 2Dm=$י”)S &&FP w5zAAA6)([u֕BCC%/'WGG^ `ܹWPPvbp)IKA8=1 qԪU 6l >L2n߾ !!!лwo֕u~оkسgO,0qDʢhYi"##{B^رbcc!))gQ$a̘1ʏI鸐 t(Jػw/&ǤI 6n(ٳ8"U "00PEC:u!P(uV"Uaq :lmm=vX$䴖Slo9pdgg#a8nݺfe0ȢE]whѢE(99Y2ߏmHӄHI|LZj={&9#G*"z*1)JKFFĶ[nTdhh()DW^>1\!U  JKK#>.̛7-[F{Xٰa1]v )Jj.YkLĂo߾!V^-3BM<+:uR,u9KZZ233:ޒrJBo޼Y i>}},W-B("3f̀֬Y]t33387n GÇ?ԪUKrZhA$n vQ*l2##@TRz?~ nnnp)ڦ###H/u̙3]' Czŋ@p?q0{lHKK`vIDU@'iBS̞=N28j׮ &Mؠ Bj䴆#G޽{=%QF,&Qhݺ5{xz̙rs3|p ]]]_Q8*gŊ,:77CCC"ῴKBj;vLy'm!$҇'B5kP_\"vI:&M{Kdllm6l=$(J nٲRUtڃ{{{í IIIM A|K… _~>#GPH0sLX|F!Ɂ[no߾f͚E̎#GI:"QN0`zHoAT]d Wxk+'އ >>$!!Abii cǎŦ.( hժlᄒXDFFjMrhٲ%4o^ymC( ZTΝ;ʕ+K}_5%!+V#GRZ _.yFFl`jjJ͚5#W€кuk:t(Ãussnݺe p]{.>ZjETY^x!ib6m6]tIJ{-J%U(]=c B'O.K7n̲m22g5v(11|"GGG#FP:z _~١RmHP7oޜvBE{psKqxڵ T*$mHH*,,DM6>eŊ%xiE;wvl&MmFT*Q ;pIE[3q@$W^`llLB<{ """h`0'@;)W믿dQ|ՅꛀuW*bb"oKKKv|a遟,Y~9ѤI֭zBCCnoc±c'svvOOOسg<~Mwrr",o޼IѣѦ-W_U{̾} --M#isf`wNF9ҾQU_|Aj eHɓšCۥn]~]={QF(77T3g` ###*ɘlR##c?]!~oJPPR*jܿV999_38JPƍXB{((..FM4v|?3mS5ӧSg1c'bKׯHMk-ZRsSTN궉Q^^L0֭+~Xd֬YѣXxE8C>28CmmmQAA(SQqq,U{ I#CP]p.]= 민AVuRg={+b O}p = C32dT_ yqq1ٳ'uĊ(= G %%%ҁ,B@ ʕ+ʊ9Iҭ[7,sծ@R[nE!ԩS'KRF廫/R$''.NXo߾M{PT(;;U;9 \222УGR%GN% Fy|V^ ;v,ٚ5k <Zh1Att4tY5)S`s,z?Oȑ#p5Am[hSLӧOױ٤Ͷ޵kWb+Pҷo_uaDz8SSS^|}}x6Vׯ뀘k!RsssOr EgjժP\\Zz5Сl*\|RPAW֨J-˗/=ݻwӷc._LaG~~>x.FFF]{{{c㨸X`RPϯr ,_E<}*'6<{LjCP*FSdA^^ m+Ru$''KzS#-ǎ#9] "׮]zԩzA9>}/4x`Vgffje:uSNIuz"66Vo+!qItBa*fj,'N6bU$;wF* ܹSp۶m"RK}n_wXR:RnݺEt,8=NB!! IDATEĉiIPOBRRRQBjL0t1 JdeeQqX* xbTXXX_F~~~O?aC8B'槟~J*TQbb"@4XgnnnT*3f =4sEYpƖ1>,, f͚ n{iTTTϭT2d,ae%&&Xp2x`[.mFT[nUlGdB#5YwN|;֭[G}"q)&=e 666S6lBCCE&y!m3f Q΢Eq+J4`^mmmQqq1YgϞ޽{S?lo!ϟ?&OOOB7nܶO>!|}}ϭBz1)eZdd$ѱDj֭i>}T;ރdo޼U_~wƍA %W^Ig 6m8pg:::ZeFVZ:/hq̝;"""e˖!ĉ銋S[ٳM6zjHMMvȑ 7oϟmqMb9I&!o߾4kLt[KKKЃ8v-uYHHH ZBSڬ}`kkK#.ҥK{Sp qѣGRL^mۢ;w`vo`Ed| {)_@ '*J=Չ>:yd}Y6x[Y5Mvu\]]ղ^z(==%$$C(==YXXP[vZgY=z$I{N߿/zs̡m>VsET׍TrJIM@tt4 )Vb \t=8W_)%Z՞j77r{ښh-uYb9055E/^zСV=qℨdt.88T*K\"%V?>BHeؾ޽;q.E^Ij&&&(33S+IJl2"UI!ԢE ꓊K7o^..~aeeN8Af?? 544D+7>mٻۢE 7u9rdTIII8>}ZYGnn.Z|lr lnMۣbtimPff&JHH@疖iYDZb IFDϏ??LLɐ2wboo/I6@/_j 4i}x񂨽Դ\֭+w)S; z%yS8(* oUhOaaa6nݺݩʔ)S΋ PV-YHyzzRWKZQQj4^^^DǀN: ;LAAAN|CՐ4]tI{.)ӧOj ̜9SڸqdBe۷GyEq}nH `ǎEc7X0VD,Q,1(**ݨF5b]A* eg]vvg.Ȩz>}tA0}ٳ\T6N@zН˗$5B1;wB0o[]\T۷Ki/S[ׯ_;.AΝԸqR;,|XlTXO%̙cMΐIIIi_\ׯ_<`\quݻsL&@)˲Sf"ζmb*8999f>5iDT,NP^~MqӓT*E&{{{2i$]p\~ݢܷo,el#mx zOuMLQ(P՜滺Qm wwwHOO/SNI~mBNN cÆ ~Ҧ]:lPX5kŋa^K,L'\}R?}tԉ}-[a辖,Y"x֝Ǯ]D 6s:tm`, Zs#F`*(1zd2<{LT0>%E?a E o-s:@!!!\ח[QQԯ_ԧ  ZHÇRD.YYY)S$>777 (4 'w(-+++ w)è]6'U*ܿJlj*߿?zE;'O2f+y>m6"IB#e͙3GlR(y L:4h666P(F gϞ6-[GS5_%T^j6_@+V+V@M|8.]e޽{&3 f͚  R ̜9S/P\˲p6-5 8PskT 2i$ 6[PX2M3f|jʕP/,*j{_ B1dprrB#uE O<\HҥKQ|vMN___;v,ݻ222L*;33/80RZ`ҤIpE/iVVVlχWrNhRÇ0 ̝;-KMbO\n Qb,ƍ\?lQ(7 <==sEMH!^^^% ))9r B)̙3ׯЊ+9v6E'G$55ΤaD/799ӇO||χ-ZZ裏ᅦSNۥw 7|aaaݬY -m&:9AtQC>}8"bСzѨQ#lyk׮z?wXRRR$Ph2@ "%ڷo/jL&۷cG/\Wϲl|ڴi!!!k.xÇ+sn6޽{ċӪU۲Tvm6 P( %%>\FM aݻeΞ=j,XZ?kkkx/QxUTthA`Ylܸ B!N"Ν6C+?#2d:aY ^:'j?(Lj5x" '}^:quu%]v%}ٷojuXYY("Ɉ#8O9s&/1֖N^^J 0d׮]SNhԨ@H\Nɀnnz6l`Q{^+N(aسgzOd E k?ԦCbF/,lJj:t萨~;pR|Rnݺзo_:u*^N8oR}322ƍp>8 ?{($ @-Z .\ ׮]#ڵښ\|4nܸTÃgTIIIA+{رcرck׮O|||HBB?zԬYS@ɐ!CΝ;u>l0oh۷ 8 pww'/^ VVV8`„ >ER(LNƇ 665zYp!L5ܿ_4Aƍ)r\vM vvvCͅ5krCXXrO8cGsXPuիjV9-eYbEɏhaz) 1QݺuCԔ)SP:!dȑѣGIll, rrl3JP(Ν;I*UMիw}m̞=ԩSGƏOn޼)ZyRѣ&pRV-B?~i::QTWRDDDzÇsn;biDK۶mіD.5j܆atRMN8m|壶͛"4,˒UVaAP!˗/6s!;w6C'۶m#'N6d5jDf̘!Zy+V QQQ'UĚnE_~d„ ѣd՜R<X 7CB3f ӧoI?!?iF K˲$44(J˗fH6;Q^=ҸqcT@AAA&ʆ R(DGGSNaQvڑٳgc?9*J%ٸqh/_LM&JYRɓ}דT2j(_uj*kBl#!o5?^'\mrYhr9#Ç׹M~~>Y|Y!6m6WFm~`Ĉu$[n6B#֬Y#YWWWm6Ǐ'}h4ئ̬YH&MD)+99 8Рe… SBW*dǎŅ >$''s:ȑ#~ Zr* #;ٴio݋… y9p xxx  haY̛7TBwIvv6!7o6DP*dСfINH(ySXXHlقmF VZE|||I~Dr+4-[$f,FCLE)\##,X@ڴiC,X@Nxvҥ\\N|||/5YfY /mtޝ;Ƃ ?ܿ>} RصkIKK6#G> ܺuۗab2d}>}:E)˜8rey֭ !77oXYYm۶ *4( %Xt%"PTTDϟ˱ɓ'o+%p1`Y̙3G(V%رcÇfTy%ׯqvv6W"##MP(5k`P'YlZ{.ܹ36֯_OV*JY'-8AːdÃxzz(N );:!,Zlْdgg=POOOlJRȮ]/ȃx9Ϡm'|"EQcƌѹMRRټyGXڻ ݻ7lmmI`` J2 B>}J._mF1W&...fgϞիWئ„ E)+%%3F_0cğ7ٱcqww'#G$/^Ipp09y$)((0h}+R@!~~~dÆ a2yd^e*o6xVN,C),Kf͚w%pIzOJbU>2ddH"@!ƍ6WKR(Aٳg <یR?<^hڴh,X%Ç')))g;wN2f̘A>#h"raN֬Ylܸx 6LwHa6ے'O$;/2ׯm~KӧO% >PQI&@9;;CNN[)ҨQ#~...RdffJG|Eߏ?^gsPժUE9ǎB6;;;sjhݺ5B //Ϡ򒓓}+5]pAl2+++gVWv ,s'66 v|eɳgȐ!C8_z5_> !.]"贏RԨQXYY;w >>,^ڵkm駟7771?r9 :y&9zuK[ bG %;;У4|f͚0 k)fׇ"lw@@}çV^-233jժu6'mݺUs0 5Brk׮7DtJIcx"X[[\_[[[ACVVrm 6cQ\v =R÷7oV bAh4pttD R̟?/|GذazQ5k|,[-999P^RgddT/*%=Zs,G E`YF:(Q-Z@ֿRH.~P(͛}Bb;vLԤIO>{{{zy=gΜRJ.\e!00P6&Zx1Wh M6&v}/oegg^6seYػw^_,OFԒERSp5K << b!MNjժmF1.]"F0FS%'P(,KFMrssE)k82h ###I6mȼyt.UTTdnݺ&pBRT\٤|' ɓ'ɓ'en7vX*EL&# m۶չ͜9sDH;$wZI F`AP,$M>SRPPm /#GP"##-ZycYdeenݺ4NN:;ݻ̝;WvyyyLF7omg*UD:D>s>\6oR IDATd"Xc9Yh߯_ ݻmdРAfhEKL$"" bpM7cǎ%UTA-W^_믿he>~|wg?ޤY%,˒GqڷgϞdѢE͛dȑz ]^իKnSC_>ٶmˍ{{CCƍ5@j d2Һukҿ  ͒%K,1bI7A{Pׯ_Glll 99۵ Ō)((2^w 6Ȟ={HD-wҥ&eYr-_t)Yf }\]]4hyy:l !cƌO^@رcP63g$"YdYrRjUg͚e|b VE-ShڴiC5jmn#ضmzoՙBX;v@î^|2XYY|HP]DrSY*xHPPmڴ )Iq/ch40d,uB=ǖ5jժ/Ha --MRQQQkprrB֎;D1Hv!o4y{{c+dڵfP(3%55mےf͚ 2dKLFV^-z aȑ#I~~Z:7n0jaÆq&͛IO?D ޗ3_>P(HTT>|xN8Qkn:rmKOO'Gѻի e'r9X"6mmBCCF_,nT0`ztJ'Bxx8}P(FP(~ /<{ ^Xx1}.""\rEr-ׯ-X'N儇;vYfq7##N:Rmo(JuV/;;; 8,YW\IƏ/H={>bҭ[7-:u|111dD& jGnn.Y|e`W_+++l3:RRRLbv-B1C:gUT j5vFҼyP|*T@%J.ׯ< @jj*q.#88N8aT &Nbcc)_ޤM$%%i\\\E믿0]vmOyiӦ:ρ7|2;7os///e+ɒ%7RTfRaÆPBAAk)-wʔ)UgϞz͇BBBاO[Ξ= @NN4i҄  ͛7է~N={K)(..άYn]?J%h4^o/\.(ee˖:}&X~}#G 3>Ha)$$$-[`AP 2d /Ivv6&B/^RQ./,aY 0ᅵ۾}{E I޽IVVQ́___l3P ƍ+S }uUV\?H^|)Ȫׯ'&vPZh[PXXZ bfdff7_jذ!va߾}}69s&Em7UC eeY:t(cCff&0 jڤ0ժU3<ݗo͊ 0`@)4k֌ruNI'OcYYYe/^S+WدaaaP^=ӧZhmJ>(((OORexyyABBoPfҥ:y+GVCժUѯo%DɆb6~ ۷cBHK.Kq?W_|rh۶-lݺU2IJsssRJ)Zp!VZ<ݻ%e8ܹ¦M?.$%%SӦM"+Wfǻ n߾[(**:uh=2 .]K@lӦM׶Z`gA<*dKK+B'Np?|p_rssرc0j((ﱶ&M_|6mkiƼ8IIRx(ذaʛ[uցL&㴿\zUy 4ھӧOn7n|IS?yAVCjJ_)`YΞ=jݺF5kD-t&GF?BhΝخP(`޽z~~~p%UXXiii#}6ܸqnܸ?/_J>0& dl2lBvv!Tž/۶m\i[[2ؿ6nڴ㧤K~~~^zҥKF޽{ذf͚uww_%#t]vt쨨(k[?'>|fjY&@s ߔJ%|Kl}1d5ٳgm.XYYUVQ6yTt_b~0岓<=2/2 rssM.Ċ$*U9݅" @ZZZ V[rܠ)UR{F?BhخP(q8U!C< ]u`PϞ=9s#==(t!Ю];f0 -Zlmm!>>G|X )52xժUخP("3W;N k֬?.^߇hX1YYY={V̿k太K&lڴ)g{ nKkF s!''(L0! &f(%aYn޼ Bk;4h-_Bڵ ݋~⅐'/(r |(Lٳg.ˬYcCeccvY >#tq/ lbҹ޸q#g[Y.1SsܹsT*,\"FY:SNΞ=k91hU"sE8 `aF'_@P,(/ |֮]k^zB ?~]%0%;OO׵UWR҇VZPŊMP̓;v,J.0*UeΝ;0 GK&~_sÅ 3++ b^x9:Lzݺuˤ)8SlhIJș3gH˖-M)0dfX4JlݺL<:tۗs~#ÇծU@N}:O]&kkkS2 zAիmhԑɉDFFj}Ϟ=ѣpB%y@Kl3L{B666^ b'gBhh/j<~=Zh4PvmtY틓=[i;;;믿o]v5ئƍy[N>-ќ}m6hm~~~_}DEERϟe˖LeBsIPBs-uwժU+Q΍{.g?ݹsl " ˗/RJZٳ"l)FT*(kꈓ_&I LF~Gl3HLL= PާGZ%cǎ%dNTm`'!'OB@O!!77J&==F!u߬Y3z*vygÆ %9a„R|ᇢ͚5W^!x;>j ǃFx[. ڵB9}ٳLk׮C A?/B(00P M:wlWm.E, PNfݺu0tP) .k '!O?aB):[>>>oL9>>>%R@єnĉ[,$pgر$KQhh(Y͘ӤIa+<۷/Z`Y4inR :1~xs÷%p p)`Ynݺ*TqrrϟëW7VAAAn U]L+>yYhYfi˗Klwu-Zׯ<TE'J%YX"e2Yq+j߾=f3ÇYOӦMC?G|kԩ"QTT666+** dYlֶөS'`YN82 *B>v Xd G(}P,BTT ~]d2 z|?.}˖-}@@ddd xx͛~/2w9;;ɓ'pykkkԩSTj `6h߾z޽[ ,@?O|l9,!!deY޽|r4iUFpXlGrr )e>8>nܸ]eQ֭^_$&&ԩSaÆfB0 ߋYp-Xb *_{l*puy;AO\RDKݻw/ls)ŋ iii|A6RΝ;C B{aX0,6 P˖--rn0Maa!̝;W6îhl޼L5~ ՃTOƑ#GAf͚ARR_~y?8xwhk [ s(ŗ5jT*Hy~0gSL6"Qf͚!ѣZM4Z ׮]+++YXtǠT*P(~9{Qٿ"##AVcWY4յLr.ozz:TZsPNͧ2`o߾ YYY:S@@x4 L2e2vs;w\!^z:;z(9Cr.]$ǥ R$ʺuJk̙-͚5 c' ȑ#P0UTILL uhcǎ?.5ӧeYfBJJ|AAϡaÆׯBvv6n60zhT*(**ҺϿ~ѣEtUA>|ls)ץ[[[Õ+W@@NJ'B/^F:t@/vX|ٳg`mmޘҌ3]L-jVX^VVV0n8HJJ®2 }tRIHH(^_XU\={~Xx1}ܤT*!22| ?S!2e 0 /^N[fOLA=Һϋ/ϟruu5˩>Bs$OͥHZk)-`/_d vX|`„ J(Y[[qS(ݜm}۹sJaؼy3ԨQ1Vj/kFfI<>R V[n<Ғq׺Z6Ն > IDATDt9~۸q# z Kn׫W/`Y/I&kv(@bb"١7*ԧOlS(fC\\ zMd2ӧܼy$''~˗/k=6˲p%7o4hUVY&m _5lٲݻ'|,,Xæ+9sd79BJS~rrr˫׹ˠ Ν;ӄfrHK=l? .Eimӧ޾ܴr:u*zRǎv1"irss!44Z\չsgz*vuat ZǣkEl3jE5j駟LRrJqR6[ @ĨBbbbDիA裏MHC~J%={X]R7@VCV*OӪM --M7KP5h E ,ݻ_7spJ^;|p Ø4H&ٳeYx9޽;Caa!5d6\@ JXvmK=a.XBDJa ߭Y\˃jժ|yxx@||v3Γ'O;Sݺu/bWUr:tHyn`AuҤI)ܹQQQ`cci{{{عs'\vͨdttү_v^vMv|Z49 ]wئR$ƣG@Ru=6 $^V*~7S 777Ijr0<[n.PHKKӧ}*KrzM`oo/zAAAOBzz/0ZQQ= ==J*S}vޓKuXittt@ 9梷6PJҶm[clS)eYرIdxx8DDDMT™3geY4hJl2d/O CʕoPBH aaa2TVVVǮd亾Ճ/^`$.\G)J&9saHNNs> iiiPTTsmkS%@Ԍ3ԵՑ#Gϵ޽;t__ߗ`KQQQ&_ 8Y**UxQU6_[lAI >\'ˢ aڵ!ȵdooSLxJdIOߡCK8x{{s>Fpp0˗/s΂)$B]6D...s=h[^@Fff&og]\\ѣGVSN~o֬ǏY~ԯF 0 #xb0lM<`0 [ll4;f~)$''KrZ(5kDTn|7k׮<ݥ%o߾ UVV[[[̦c6mB{L~ݻEЧO|x]lS)/󃴴4/ÇKy I >}f% ܸq 8q5j$uSV-XbbW,HII}!,O)>,(((1cu}ٱc9+\]][P0 ͛7BtX (+4n8f>dggm>lӦ  qU_[|###`ʕNB26Э[7jڴ)h4l7S(?\.Ν;áC,Q RSS~ܻ͛7cW6l؀W)HR? o"o_X(00PU>Cd/^LG{˖-ۀ.5jbF ={MHF74h0 W^-tUT‘#G@oe\uN L }7JeO\ڳQZ!&& g>} AAA!22h$&&Jm۶:.3 À;1ջwoHOOF3g .Cbqȭpu={6z[&:_?/^06@FB D~׬YoF߷Bbb"dee>s„ ^Ƨx_Wjѣ))SpNO* ĉt <}WާqAaa!kƍŒRE˲h?RRReY?>ʈݻc7#Nm66&N(w̏EG˲еkWѮaBŋiӦ ϟ?/^TUP=O|w72f E/)))0}t׮]h6bŊ}["(ʃܹs&aDQ$a٠hիWгgO|縍#inBoiӦrn., u)ӗs6"0rΨT*8y$L>aΝ;bryC4ʒ|[SخPJ èQUN8[u"8Gի0`_===3;_zTZӧ#"~(((R>! .<'N0ȗئR$@RR//ƨB p`YƎ[ .]d-^^^ii@իWԄ֠ALK6m/p) ҥ l޼eM6I/^ `E|||}.!""Xdv޽x%K^˖-nFQfMtd!ӧL~fR$v ݻK>g믿7mڄd Aa &~wlWS9YYYNNN&g XjY,? ƪE*korr2RpqC`,_۷oGnI@o fAtttg ޽&XPZ˗;@.s:v@@@x`4.\îPtlWS!f32e ܼyJKvv$ !`kk -*wKT:tbhEEE.^gϞooo:,:vj f_]?߾LQ7^4h___F.L&72" 6xA7~5zhl7S999fR9???oŋ4BÆ )B|'v?!eoo_<ٳgЪU+x|{gggNڵk=}u&L͈٘WܹGGGG ͬYiy_)o8udG> ‰' @ ŋȞ4P/T_v5),,kr^U.C۶m!,, =z]rڵkѓ-X6) 9s&jͅs>Ff͊Jji܊T*>p!쪛 eԺum&Edff/z?OժUx9 ,YD1=-Mh@ yyyK!:'rJN/UTc®]N$rssaȑ}\./! rrr$UXU\oqԮ] _~^ԡC?-ZՇ/ƮY1w\-Ӫ(% B# ǃFQFjǏKڷZjPPPiiB:JFLEӡJaa!Z >2ۛ3VZ?6s=G  6\e7[nT`` ˲b #M|}}ٳp5Uz Ѹq[Le!$$o* O(iBzСzZjVaݺuPre >X`\|er27oFwP(`)%s2xqqqGq>ưa 334 ,\eÑ[lݺU@^^vu͊I&i&M* HKKTCUR%q,\oXjrԩ% ݻT*O]M1#ݻwCK#Bk׮{}$??Lr /e?LUǎ!..m9߻gϞI6џ>8p £G$%%aWլ;FR;wͤHѱx4ؚ5kJ,kkk[/d4qh@L0m۶a"qm۶A5@RAÆ aȐ!O?ӧi63Coggcǎ'O`,x9"""āCr>FΝKeݻܒeYpqqg_(ׯ8-[Ʀxh޽{ή7R [lAy@e+TzZnDχÞ={ÇPTTm ҥKF~ZjdHOOvYa_ !мysx8q|||8ooo , 0d:+[[[nݵkWA|T*i#8{ޤ;v( hNL)Jظq#\r>d2,YB ͛ҥ X( #)) z!Z?"ɠsΰ{ni$5kDp̛7 77&NymӧOÜZn܊cΜ9ƮQPP7)De[n"ߚ9s&, qqqP~M>Xa/IhX;7t1zjlwS(ؾ};h9}[.\~mll ,, 4 ~ƍgK N< ǡCxw}]-d:}P(KS7+V@Ҁ 77W^%~ FCZw祋Qty6 k :>LDQQY+  (,,3gBtf͚ϓ?uEM[nEnI‘k1b}h111`kkӯ+V6"â6ߋKU;vb=,QK Fla]D  &#EFlAbCAxc,œ-Cٝ>gNyT?Qg=zTC[F˗/K$Iϟ]cccKJKK^z%M<9C4eСwQۻwrqtڵv_ :"j]Ϫm߯R/^H{@ڼys㺌 ofc+]Cѧ>grI#G#%''餃 {o>m4IT5Zn-]~]$Iʻ͛']? 8fff.ҤM~VVHt{iiFZbgZd7\ʕ+En"˗/K-ZϲB+U]~5g|SSSiժURFF(9;;pܹtmIjЦ;w2=WF___[cK7˗ ʵf J%%%%I#Fz~RCe=K޼y1 `Ҝ9sr=ZժU%''')00i;w w58p@ EJ7nT*siĈ:3ԧO fB۝;wEw^-,,{Gjŋ[F\.v풊+&o<={SC.]$ ȱծ]w  *Vj͚5%'''~)\+&H$IO> 4hP䰰0lٲ¿D}dbcj[HM$%%znJRpGڵkkvYϣ _hԩ?rmcƌ}tʭ[ޙT^=iRHH'[nncc5r׮]_wmۖRRݻwbJMMjժ% .|D/))I$##w> 5j$Iaaaғ'ODG<"tR``$IC>WW*KIRIY*uqZwOcNJ>4Ԙ1cׯ_Fj$88XOFRJJJ)kٳ<=a@8zΌq,T SxxxָkR)5jHrqqBBB ގ븞(=}Tr[7iDt$ItʕϞ2?6333!":B\|cZ-?EDDԢRդg_. ʱIǏsy知UJK|qm<2f ur>hڵ¯rnY;wNjժ]&7&&Fo(TlcC !k322.^(i'O5"GҼyT)==]Zjdff&c[PP{GܹsrUƛ7o$333VZ:V^{ԠA xFTTH._, ʵ1BعVTJBBBڵkKFFFٳTaؓ'Odn. ѧHkk7CCCi֬YRJJ!^:ׅt-I$ٳRF|jҥ{صjՒ-vf̘˄rŋR&M$,]I3%&&J5j~͕k377g(RppTV-ZjRXX$I `ǎ?0rnvvvO9֨^g_۷oR˥K͛ʕZ璋;3VXH˹DexԶm[|RDD8T*dcc#+k.ѧ=*J 4h 5kL:z$I5?}@W\)iϙlٲ%IzsUI3gΔ$J%HJ?===e9c"%={&mVrww/-ooo[97u!رCjڴԻwwMh`@>ydnn.#צFDDo9(P@8qbP~!=666ҝ;w$Iz;M6¿Sr5w@4HηiGInnn!: iǏv]SL2ғ'OD;u4h )k8:7($I@"<<{C6KQ|yQ4֬YgѴiS`駟r|5kbŊڵ+={yaժU^:.^ f-##Z™3gdptAcPTvԩ#: iX4n&""֢cJ=zM6ɓ'prrB͚5S'F #FC6=B>}": ƪ^'/U6l؀sΡ^zDݺus\/^k֬N:՘`يի}ʑTTQ?dɒ1c.\~ ׯ˗tϝ=Ybb"+ܻwOt :ci+W~]__&LYPHDDDo*J;sEѢEq!?B۸q#۹sЪU+ɒaݺuprrXDyf1dSJ\||1IP(eʔAٲeyL6h߾=T*(Yf Ǝ+:y LMM͛u>>>Q_777n8يpuueODĉ:u4i޹obb<{$557իWEG '$"5 @F.\@-d5k(BD1Q`ADGG/^ h")SO Ξ=ω󗑑9-Z;waÆxlY<<\~}L.""kƤIٳlWN㧟~%PzuYF+-R"##QR;y$<==edcckkkُKDK$888ӢJT"(( E@VZa̙c.$$ .(_e.s9izBLLLϫP֭[+W[nX~=,---كسg0hРKXb$"7w\!s羷+ɏsJN:ѣJP 88(Oe-L>=+iӦ$ ;wƑ#GdH-B֭[1t$ CXX|||*qH{b:M6_u:b5"OgϞ(O%&&u8}Ν;pwwq͚5,ҥK?X &LDN< {{{++`j=ݻѿ1dWD 8qKKKK?fϞlӃ .]J*A;{,5k_suuҸ/^D-Y3S%"u-lnΝ=Ԉ Dǐݓ'O`mmǏBDk$A$l۶ pqqɶW(ׯ\TP!մi ?ثW^EXQyk,nj_Ͱ@ͬZ 6Cv111ѣEG!"ʑ =ƍ>G9Y[[… ؽ{7jժmOOva֭O<ׯ_9йsg~\"N^7|۷o" ޢcpy&7n3=5 T*EG!" nܸOOOy[Ƃ жmv+W^jvڈFѢE?`mm-8Y333\|+VD222`cc}";SSS;w5j=Ԑ%6l :ǸqD "zOG)kiGѺuk)fff}h5TlY8pÇ1l0ًBDg\\\tW(زe 55eggggg1ذa-Z$:d3WV 6l@FF'׬Y3رch۶-ʔ)SrpnLMMْ$aԨQB愙1c#qH;}XzBjM65vԩS.pذaKII/^֭Yfe꒗E{=p<{=9)JDDDk׮gʕ8q%(OcȐ!:vǏg-gK jݻEG"##ѩS'QHc͘3gݿVZ>}:/)QQQh߾}v~[j'{hܸ1RRRdLTX.\@%d=.iCiiiȮDxb68@U\sy}EG!"_hh(j֬ѣGg[W\֭C[9yeܸq,_~A^dODyܹs_P`ӦM,54@Ϟ=1e1x%w?St"b~XYYns*Vu!&&NNNZw}'qJ.]}f͚+WȔ-Rԯ__v믿гgO|Rt!f̘޽{A9!"==;vEG\rFʕEG!"-_TTO9W*W 92|_۶m5F8}4>ѢE ˘ X~=e=&iXn(Bk Ql <EGF8vJ*%: i8J===\t ӧOO?s,--1}t :J2@ӧOe˖j=TqԩSqƲ>}:.\(1H;=zmڴ͛7EGL2x"EG R\9ݻWggռqt颓"QPTޭ=z47nm_vmڵk1bJ%?7o} ;~NY?,\PȑX`$"픘=zl񯯯;w0D-.ZhC`ȼ[}̝;v9_>fΜ~6?7nܸuu>'jժOaÆxLaÆaj4KJJ ucǎ"ڵk1zh1( &L#G!̩S0p@ar'\\\]]aii7~h_>BBBp%بmXF f͚;&L k?p@lڴImHKKN,5{hTmΝEo1GDS,]~~~.1ײeKL>jRRԩ6O*T/_FժU?_hh(dJ۷oAD$Iȑ# :0͚5CTT EGICa׮]:=!?'$XD$ABB<<}D@׮]~ .Dll 3ؼy3"<k׊!Lʕh___!ԤI|r1Hf%$$duOJJ z̙3ѤI.66Ë/ ;ʗ/}UVQfMߟ[.]B֭ի<}N:9ڷUV8ydWTb̙9s& ('IDpvv;5±c`bb": 6h84k %Gfooo}t"E`6mʖ-?݋zv\ryr5kb˖-JDynҥ<@ٲeqY+WNtGʔ) E8OOO̝;Wt "Y>xƍCŊVZXn>|+VnN%틥KYXX:GFFF~q_@L6 .]bODy+GUP!DFF4h;w$9szD"d߇+,--ׯ_o֭WIg+&Mرc c5h'NW(w[zzz BEG!pYx1O.:Z2eJ#_arp^H\^ɒ%_'"Y?ڵc/,) Q%K`cgϞk׮_DG!Ef+W`ذao FFFϚ6k P@[{p\H]:t:tǏEGQƢED 5_lEOOڵ(j#11=z@PP(DJ$OС6l@dddx;ae#lݺ5KǑĖ-[ޙ:ۺu+$:޽;zك֭[6޼y{{{̙3Gtgwz GAϞ=EG!"$''cETV Y IoF6m?V4i*UJtO,ccczj]/^.\%iOw8y(jlٲF*UDG!5vٳgU^(Dɼ,];w٠A;C Ƹuw7oV/cǎq^(GΞ=N:qf(^8ۇVZB'N $ ݻ7FN:F9s zeB˖-EG! ʱ#GGx(j6mEG!YȿyAAAիWիW#FbŊn?iP >^E2ޔcl\ ŠA ͛wTI6BBV^UVÇ044D߾}CP'""$IΝye˻ (Ot l\ۺu+F J%:ڱC@@EG!-Y߹sظq#aii 888DODD5jDGQ;zzzСCEG! ,[lQ͛7GXXJ.-: îŋEpp0ЧO899SNP(,H=yEGQ; =z(@mƍprrbw(_<+++QHe$aXx1N8DD~w E( Z ƍ4g5j|||DPKGvo>QHCe6a۶m]6lllPlY:t7n܀;/,H+8p[f^^^,鋰}1L4It P(0k,̙3Gt(S `ooŋsB?""J4i~ĢE!:i8/^t 9sOOlqPNc?bŊ""*1b\]]YYS`3?j {AJDG!5t xyy!..oQ`A'""̙3Yfaܹc`3f`ѢEc%KbΝС($P}ػw/PJ5 uHѰÇEGQ[nnn(Ϲcɒ%c-R]tYdlٲ.]Bnзo_(J'""~z?iii-XBt 2l<'I&M V6FիWHtgwccc~T*\tJjj*F@QԚ+(̚5faÆػw/RSSSm ?gϞEM:i)߰򕗗g,F%sNtQtC$ÇHLLDٲeajj.DD?[[[vW]H%&&!!!tH6pvvrfiiPԯ__t,޺t#:ZS((#d3vX]ݠqM4o߅~Yhݺ5l( 'YnӦMprrzg4}ؐ!CvZ,XPtnЏ])))0a6m$:Æ 0rHQHǰغu+!:ګSBCCQV-Qt·|v'""zիWakkk׮ (-[`С+bذaؽ{7DGQ{W^EFyfQtFfhf*,f͚l߾? $?~!99Yt+VDt;DDD vϡB !,, :utHsΡGx(f͚FÆ EG Ÿ({ ?CtPhQ߿-ZtpM4ATTʗ//:F~:Zh///N2ϝGg'""wI___4nܘ)SGeOj=Hmܽ{_5n޼):ҥ n݊2eʈqx(w1rH߿_tQJ:tժU{\2;닎1:  22Rty?Q>| 4` uAtt4R+l bnnGUVhx...x8jǽ~ڵ+DM6ETTʖ-+: ;8ҫW`ccQ}vT^]t"""pׯ_q%Q4JNB B #dbb}ah/ `}IrJ4jԈ. <`Oj }v̞=[tqơk׮4H\\z쉉'իWh'b۶m@jMP`Μ9\:tի׋BDDD 44u¹T@Z Jjs ":Ʊ?J,): Ǐc̘1سg((G@ݻ7?~,:)U֭[oFt"""Rg Ŋþ}кukQr}TH4oN('>>}?.: ѣGG,?C*UpIq4ÇѳgO\pAtTR%lذ]tdO? (iӦGRDG!5 dnn(X[[ݻvvvx8DDD$=ݻwgk>|?i,6*X : Ez8۵kjժ׃?ѣ SSSQ>H)Jlܸ+V+)..666ի)))hҥKcɒ%:t(V ń #Q4!6n܈!CB@:oGڵk5k֠vڢ霛7oٙH%giFt|!7ogϢQFhIgϞTqtBJJ fΜIP&MpE3tJJJ ƌ@Q|}}ѣGQVDD\\\pmQ###QdSuV[hX[[K.z8DDDZ寿BϞ=ѫW/yDTbزe 9@:+::x(ZC__cǎżyPHq4VRR-[ŋעh%KbΝС(DBtڃ`cc3gΈU/OOO? HcT*aڴi8YYYaϞ=T(Dpr!** hOM6Ett8DDDٳhժ ? 2,It^溯֭8Zŋh۶-CDD(TNɓ'` KbHNNG)Shժ(Dj D_~E+(Q3g΄3J8DDDၸ8qR۶m}v-[Vt"!DQT) bذa,BĉqaDD~ٳgh-Z`ٲehٲ(DDD6m~WQV=zB@X 0NEk) /FժUE!""3?>6nJ%:jܸ1BBBPJQ7eB 8z(&N(:֒$ ]6NODDDE>})SׯgO \]]qID9D#F !!AtVP!8;;fffXRRV^ŋB>+\06l;;;Q4r͛o&:+V&Lɓ'Tt""z6l؀ ">>^tװaCBt"ϐWWW_^tPL̘1NNN000(ׯvZ,Z=G'3>>>022H ݻF*2PfΜ#GBTCDD:,-- ۷oܹsqmqtB"En: 0@t"/!Cرc KKK̙32%""ddd 88sŭ[D۷ǶmP|yQ49} *_Ŋ+/:Ny&۬EG"""-RuޞſLJ%fϞÇ'@Ξ=G*W WWW3?|ܼyStRR%hժ(DZ Dy,11ƍ? :ΩX"&M'''CDD͛7+:α0QcQ> 3DG9J¤I0aCDD$997nҥKqt)-['''Q_oϟE'*U 'Oرcajj*:DYx8:iӦFjDG!Zl gX|9<==&:N255ň#0uTN DDDxϟJ%&Oy@t"";w~(: ;ԩ#: tMZ ׯGjj8:VZ DƍEG! l Qjj*̙KBR :uꄉ'W^?Q(ptt!8qQt\\\0x`(Jq(T*߿ .ӧEy*U–-[СCQtILL6o,: QP!q(|Xbn߾-:5j99/ l 5jR ZjCDD֭[ذa֯_ωDҥn:Gt""5 & ((Ht===t'NDϞ=P(DG""lDGG{AFF8??J(!: c رc@XZZ@Df ???\zUtҥKcɒ% DjٳgpssC``(fff9r$QjUqtڭ[j*lٲ/^cذaFbDG!a:x ƌ{B9< | EG"" _~zvWSe˖?z-: }ثW0o<,]*Jtsss 6 NNN@DO߿~WFll8 Xlg'Rcl 'OĨQp5Q#+(on氰 о}{Q(l Xx1-Z7oވC^DDw5Rɓ1gCD9" sE5 .]+`ذaׯLLLDG""RKؽ{7n݊Gr؛hԨ6n܈ BD" RqFL:PgϞprrBNP(DG""… X~=oߎ/_C9P`Axzzbʔ)(P8DKl `qqqpwwǶmDG\PQFBt""Y"886l[Dǡ\ٳ'֬Y *BD DZȑ#pvvEG\jԨ0p@.\Xt"|0l۶ STV VBnDG!/"- bҥx8K&&&իݻPt$"/`ǎGJJHKpww9`3~gQ3)R{-u%HcdddԩSضmvyj4XfԪUKt"Cl R7n߿/: }EgϞEݡT*EG""zJɓ';v >>^t$СC9a-bKHH'֮]tq +W0`5kfD$J™3gsN ..Nt$BJΘ;w.)":67n Hɒ%ѭ[7 "dv ݻё(t+V@zDG!|"WWWܾ}[tCEEΝѳgO Djj*:{Pϟ{{{QH&l 1oUrIDAT?<==Kq(SNE޽aff&:i:t w211ԩSt tԃ`Ǭ о}{ְ_H߿QQQHKK>8ٖ;b2&54  8*,q\6p1s3b2A"*RFԝ# WvHMVZB]~HG B~|^|n鮻Ҏ;TXXh: @kjjҏ~#S0JJJb ]VDب^mmm0V\]viզS@ڻw~+ ,Ζ~zN0úu!:tHoM'aG֭[eXL0@Аvܩ;wxn:y^UWWkΜ9|I###|z7uqIE999z(;;t8gΜѯ~+=ӊD"s0l6<^^nf>fHhT||3YfXtwWAAq?NA999cʕ+f: $zOhS)ÇDM i͚5VMM\.0 .^v9rD~_֩SLg!p ڹsN\/W_~3uvvAUUU^V^*^4UGGta9stHqqz!};. +2>>'|RotY9CS ԨFUUU< CCCzu9rD<u}1" Аz)رCPtjҥKUYY;VZŃҦ?zhhjjĄ4ıw}ڶm͛g:@b;wNvO< .Aɑ6 fLooﴓ؇˖{G=,X`:@cpMA=cڽ{M nQEE<JJJLgWŋܬIӐ233uj۶mr:s$Tww~_gU$1˗\n[ZrL ׿{WM<_u];C<,Yb:@a0#>#=#z饗499i:IfriŊ*//Vii) jmmUGGZ[[C V6oެ_\`F0Q~~_^cJOOWYYn-[&-%˥LyH?v]裏M!Y,mܸQ?-[f:@c0+裏_V45bZ|Eeee*))Qii鴃 R:;;cO8.J*ͦ͛7kZt):q{15ąKFŋkѢE*,,䶂u)kI~ P04(==][lѶm۸b`DWWvء{O @ܲX,r:*..… h"-ZHEEE***Rqq dLH$`0O>D===:uN<`0(kx{G?OU\\l:@ b`TOO~jϞ=5\t:t: (??΍+:}߯`0>oJVVt .AܹS<󌆇M3bnnph޼y͙3GC6MsUFF jj45>>h||\ϟW4FGG bയC488=Hj999x@ ,0 ˹sO'ӧM pH>`j4oiiitɥS'-khhH400pMd_wc> 0Kz嗵c*++~iΜ9s ?WCC.QYY_wyߖW@8z~_T45HaEׯ׶mկ~t\ 'h׮]|r`eddhƍھ}\." ӧ{n=35Hb .=)//t\ /СCڵk|>@ӦM|$I>{۷W%==]v~릛n2 ק{ꩧRww@֭[P .4u޽ )M/٣sΙ4w\mڴI{V\i:f366z TYY:m޼Y999s`V1Hi\/77WwqHy dĻ0|ƱcǴgK BsWphӦM1qz-۷OP86VUk֬w߭o}[2q.ç~W^yE?;f: vk˖-ںuM@c+ڪ}i޽3)pVw}M@BaFua޽[ӟ411a: %uuu۔n: \o(NfXtM7V6m҂ L'@ckٳz /w固˖-*((0IfPww?jjhh0qvVwuL@bYW^s=9`Ԓ%KqF}ߖ2) hii믿_]Ǐ7B~nv-_t0,ƀFMNNNkbA~S'%%% 1@9s:뭷$ժoQUaa$0@ B:x87xCæs_o[on7 "uA׫tWRR"׫o|Zv222L'H@|_Nl6VZ 6hÆ rݦWȈy:y$Itjڵڰa֭[\I/LKK|M1 AdggFr֭[G@a$D"'ϧo æ ժ y^y^p/?$1H!CCCjll |gH-S:$,a+ϧ \ceeeњ5kzUPP`: ` & I |:v&''MgLE.KZznf-^t N0-~_ 3?l6<V^jr-?,@b\QzԤ`0h: HҪUT]]*eff$۫G߯P(d: Hx٪Peee(//7H` k*c(Ԥ i@ܲZZt鴓*N$Ңf577?Ȉ4`eeeixQkk봫>Si5p8vrdZMR AP[[588h: Bv]˖-Ҋ+xt:MHjmmU[[[NuvvNC zG\%%%K,QZZ<_ vYC'NPggW vD*--MpIk`` vg.MNNN!C%%%_x)illL]]]QOONOΝ3ϟ/ө믿^NSNSyyyZhѴө% 9sF}}}   B B^B!EQFXVp8W^^TPP{}uיN 1`ȅ 4888mB&&&tyE"B!E"]pAcccА/yA(g=[zz}/--Mv},͛sQffΝ+&.ͦ\edd(++KYYYNޡ_<:IENDB`command-group-2.1.0/logo.svg000064400000000000000000000544561046102023000140730ustar 00000000000000 image/svg+xml command-group-2.1.0/src/builder.rs000064400000000000000000000017211046102023000151600ustar 00000000000000//! /// CommandGroupBuilder is a builder for a group of processes. /// /// It is created via the `group` method on [`Command`](std::process::Command) or /// [`AsyncCommand`](tokio::process::Command). pub struct CommandGroupBuilder<'a, T> { pub(crate) command: &'a mut T, #[allow(dead_code)] pub(crate) kill_on_drop: bool, #[allow(dead_code)] pub(crate) creation_flags: u32, } impl<'a, T> CommandGroupBuilder<'a, T> { pub(crate) fn new(command: &'a mut T) -> Self { Self { command, kill_on_drop: false, creation_flags: 0, } } /// See [`tokio::process::Command::kill_on_drop`]. #[cfg(any(target_os = "windows", feature = "with-tokio"))] pub fn kill_on_drop(&mut self, kill_on_drop: bool) -> &mut Self { self.kill_on_drop = kill_on_drop; self } /// Set the creation flags for the process. #[cfg(any(target_os = "windows"))] pub fn creation_flags(&mut self, creation_flags: u32) -> &mut Self { self.creation_flags = creation_flags; self } } command-group-2.1.0/src/lib.rs000064400000000000000000000026231046102023000143020ustar 00000000000000//! An extension to [`std::process::Command`] to support process groups on Unix and Windows. #![cfg_attr( feature = "with-tokio", doc = "With Tokio, the [`AsyncCommandGroup`] trait extends [`tokio::process::Command`](::tokio::process::Command)." )] #![doc = "\n"] #![cfg_attr( unix, doc = "On Unix, the [`UnixChildExt`] trait additionally provides" )] #![cfg_attr( unix, doc = "support for sending signals to processes and process groups (it’s implemented on this crate’s [`GroupChild`]," )] #![cfg_attr( all(unix, feature = "with-tokio"), doc = "[`AsyncGroupChild`], Tokio’s [`Child`](::tokio::process::Child)" )] #![cfg_attr(unix, doc = "and std’s [`Child`](std::process::Child)).")] #![doc(html_favicon_url = "https://watchexec.github.io/logo:command-group.svg")] #![doc(html_logo_url = "https://watchexec.github.io/logo:command-group.svg")] #![warn(missing_docs)] pub mod stdlib; #[cfg(unix)] mod unix_ext; #[cfg(feature = "with-tokio")] pub mod tokio; pub mod builder; #[cfg(windows)] pub(crate) mod winres; #[cfg(unix)] #[doc(inline)] pub use crate::unix_ext::UnixChildExt; #[cfg(unix)] #[doc(no_inline)] pub use nix::sys::signal::Signal; #[doc(inline)] pub use crate::stdlib::child::GroupChild; pub use crate::stdlib::CommandGroup; #[cfg(feature = "with-tokio")] #[doc(inline)] pub use crate::tokio::child::AsyncGroupChild; #[cfg(feature = "with-tokio")] pub use crate::tokio::AsyncCommandGroup; command-group-2.1.0/src/stdlib/child/unix.rs000064400000000000000000000115131046102023000170610ustar 00000000000000use std::{ convert::TryInto, io::{Error, ErrorKind, Read, Result}, os::unix::{ io::{AsRawFd, RawFd}, process::ExitStatusExt, }, process::{Child, ChildStderr, ChildStdin, ChildStdout, ExitStatus}, }; use nix::{ errno::Errno, libc, poll::{poll, PollFd, PollFlags}, sys::{ signal::{killpg, Signal}, wait::WaitPidFlag, }, unistd::Pid, }; pub(super) struct ChildImp { pgid: Pid, inner: Child, } impl ChildImp { pub(super) fn new(inner: Child) -> Self { Self { pgid: Pid::from_raw(inner.id().try_into().expect("Command PID > i32::MAX")), inner, } } pub(super) fn take_stdin(&mut self) -> Option { self.inner.stdin.take() } pub(super) fn take_stdout(&mut self) -> Option { self.inner.stdout.take() } pub(super) fn take_stderr(&mut self) -> Option { self.inner.stderr.take() } pub fn inner(&mut self) -> &mut Child { &mut self.inner } pub fn into_inner(self) -> Child { self.inner } pub(super) fn signal_imp(&mut self, sig: Signal) -> Result<()> { killpg(self.pgid, sig).map_err(Error::from) } pub fn kill(&mut self) -> Result<()> { self.signal_imp(Signal::SIGKILL) } pub fn id(&self) -> u32 { self.inner.id() } fn wait_imp(&mut self, flag: WaitPidFlag) -> Result> { let negpid = Pid::from_raw(-self.pgid.as_raw()); // Wait for processes in a loop until every process in this // process group has exited (this ensures that we reap any // zombies that may have been created if the parent exited after // spawning children, but didn't wait for those children to // exit). let mut parent_exit_status: Option = None; loop { // we can't use the safe wrapper directly because it doesn't // return the raw status, and we need it to convert to the // std's ExitStatus. let mut status: i32 = 0; match unsafe { libc::waitpid(negpid.into(), &mut status as *mut libc::c_int, flag.bits()) } { 0 => { // Zero should only happen if WNOHANG was passed in, // and means that no processes have yet to exit. return Ok(None); } -1 => { match Errno::last() { Errno::ECHILD => { // No more children to reap; this is a // graceful exit. return Ok(parent_exit_status); } errno => { return Err(Error::from(errno)); } } } pid => { // *A* process exited. Was it the parent process // that we started? If so, collect the exit signal, // otherwise we reaped a zombie process and should // continue in the loop. if self.pgid.as_raw() == pid { parent_exit_status = Some(ExitStatus::from_raw(status)); } else { // Reaped a zombie child; keep looping. } } }; } } pub fn wait(&mut self) -> Result { self.wait_imp(WaitPidFlag::empty()) .transpose() .unwrap_or_else(|| { Err(Error::new( ErrorKind::Other, "blocking waitpid returned pid=0", )) }) } pub fn try_wait(&mut self) -> Result> { self.wait_imp(WaitPidFlag::WNOHANG) } pub(super) fn read_both( mut out_r: ChildStdout, out_v: &mut Vec, mut err_r: ChildStderr, err_v: &mut Vec, ) -> Result<()> { let out_fd = out_r.as_raw_fd(); let err_fd = err_r.as_raw_fd(); set_nonblocking(out_fd, true)?; set_nonblocking(err_fd, true)?; let mut fds = [ PollFd::new(out_fd, PollFlags::POLLIN), PollFd::new(err_fd, PollFlags::POLLIN), ]; loop { poll(&mut fds, -1)?; if fds[0].revents().is_some() && read(&mut out_r, out_v)? { set_nonblocking(err_fd, false)?; return err_r.read_to_end(err_v).map(drop); } if fds[1].revents().is_some() && read(&mut err_r, err_v)? { set_nonblocking(out_fd, false)?; return out_r.read_to_end(out_v).map(drop); } } fn read(r: &mut impl Read, dst: &mut Vec) -> Result { match r.read_to_end(dst) { Ok(_) => Ok(true), Err(e) => { if e.raw_os_error() == Some(libc::EWOULDBLOCK) || e.raw_os_error() == Some(libc::EAGAIN) { Ok(false) } else { Err(e) } } } } #[cfg(target_os = "linux")] fn set_nonblocking(fd: RawFd, nonblocking: bool) -> Result<()> { let v = nonblocking as libc::c_int; let res = unsafe { libc::ioctl(fd, libc::FIONBIO, &v) }; Errno::result(res).map_err(Error::from).map(drop) } #[cfg(not(target_os = "linux"))] fn set_nonblocking(fd: RawFd, nonblocking: bool) -> Result<()> { use nix::fcntl::{fcntl, FcntlArg, OFlag}; let mut flags = OFlag::from_bits_truncate(fcntl(fd, FcntlArg::F_GETFL)?); flags.set(OFlag::O_NONBLOCK, nonblocking); fcntl(fd, FcntlArg::F_SETFL(flags)) .map_err(Error::from) .map(drop) } } } pub trait UnixChildExt { fn signal(&mut self, sig: Signal) -> Result<()>; } impl UnixChildExt for ChildImp { fn signal(&mut self, sig: Signal) -> Result<()> { self.signal_imp(sig) } } command-group-2.1.0/src/stdlib/child/windows.rs000064400000000000000000000050121046102023000175650ustar 00000000000000use std::{ io::{Read, Result}, mem, process::{Child, ChildStderr, ChildStdin, ChildStdout, ExitStatus}, }; use winapi::{ shared::{ basetsd::ULONG_PTR, minwindef::{DWORD, FALSE}, }, um::{ handleapi::CloseHandle, ioapiset::GetQueuedCompletionStatus, jobapi2::TerminateJobObject, minwinbase::OVERLAPPED, winbase::INFINITE, winnt::HANDLE, }, }; use crate::winres::*; pub(super) struct ChildImp { inner: Child, handles: JobPort, } impl ChildImp { pub fn new(inner: Child, job: HANDLE, completion_port: HANDLE) -> Self { Self { inner, handles: JobPort { job, completion_port, }, } } pub(super) fn take_stdin(&mut self) -> Option { self.inner.stdin.take() } pub(super) fn take_stdout(&mut self) -> Option { self.inner.stdout.take() } pub(super) fn take_stderr(&mut self) -> Option { self.inner.stderr.take() } pub fn inner(&mut self) -> &mut Child { &mut self.inner } pub fn into_inner(self) -> Child { // manually drop the completion port let its = mem::ManuallyDrop::new(self.handles); unsafe { CloseHandle(its.completion_port) }; // we leave the job handle unclosed, otherwise the Child is useless // (as closing it will terminate the job) // extract the Child self.inner } pub fn kill(&mut self) -> Result<()> { res_bool(unsafe { TerminateJobObject(self.handles.job, 1) }) } pub fn id(&self) -> u32 { self.inner.id() } fn wait_imp(&self, timeout: DWORD) -> Result<()> { let mut code: DWORD = 0; let mut key: ULONG_PTR = 0; let mut overlapped = mem::MaybeUninit::::uninit(); let mut lp_overlapped = overlapped.as_mut_ptr(); let result = unsafe { GetQueuedCompletionStatus( self.handles.completion_port, &mut code, &mut key, &mut lp_overlapped, timeout, ) }; // ignore timing out errors unless the timeout was specified to INFINITE // https://docs.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-getqueuedcompletionstatus if timeout != INFINITE && result == FALSE && lp_overlapped.is_null() { return Ok(()); } res_bool(result)?; Ok(()) } pub fn wait(&mut self) -> Result { self.wait_imp(INFINITE)?; self.inner.wait() } pub fn try_wait(&mut self) -> Result> { self.wait_imp(0)?; self.inner.try_wait() } pub(super) fn read_both( mut out_r: ChildStdout, out_v: &mut Vec, mut err_r: ChildStderr, err_v: &mut Vec, ) -> Result<()> { out_r.read_to_end(out_v)?; err_r.read_to_end(err_v)?; Ok(()) } } command-group-2.1.0/src/stdlib/child.rs000064400000000000000000000176601046102023000161070ustar 00000000000000use std::{ fmt, io::{Read, Result}, process::{Child, ExitStatus, Output}, }; #[cfg(unix)] pub(self) use unix::ChildImp; #[cfg(windows)] pub(self) use windows::ChildImp; #[cfg(unix)] use crate::UnixChildExt; #[cfg(unix)] use nix::sys::signal::Signal; #[cfg(windows)] use winapi::um::winnt::HANDLE; #[cfg(unix)] mod unix; #[cfg(windows)] mod windows; /// Representation of a running or exited child process group. /// /// This wraps the [`Child`] type in the standard library with methods that work /// with process groups. /// /// # Examples /// /// ```should_panic /// use std::process::Command; /// use command_group::CommandGroup; /// /// let mut child = Command::new("/bin/cat") /// .arg("file.txt") /// .group_spawn() /// .expect("failed to execute child"); /// /// let ecode = child.wait() /// .expect("failed to wait on child"); /// /// assert!(ecode.success()); /// ``` pub struct GroupChild { imp: ChildImp, exitstatus: Option, } impl fmt::Debug for GroupChild { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("GroupChild").finish() } } impl GroupChild { #[cfg(unix)] pub(crate) fn new(inner: Child) -> Self { Self { imp: ChildImp::new(inner), exitstatus: None, } } #[cfg(windows)] pub(crate) fn new(inner: Child, j: HANDLE, c: HANDLE) -> Self { Self { imp: ChildImp::new(inner, j, c), exitstatus: None, } } /// Returns the stdlib [`Child`] object. /// /// Note that the inner child may not be in the same state as this output child, due to how /// methods like `wait` and `kill` are implemented. It is not recommended to use this method /// _after_ using any of the other methods on this struct. /// /// # Examples /// /// Reading from stdout: /// /// ```no_run /// use std::io::Read; /// use std::process::{Command, Stdio}; /// use command_group::CommandGroup; /// /// let mut child = Command::new("ls").stdout(Stdio::piped()).group_spawn().expect("ls command didn't start"); /// let mut output = String::new(); /// if let Some(mut out) = child.inner().stdout.take() { /// out.read_to_string(&mut output).expect("failed to read from child"); /// } /// println!("output: {}", output); /// ``` pub fn inner(&mut self) -> &mut Child { self.imp.inner() } /// Consumes itself and returns the stdlib [`Child`] object. /// /// Note that the inner child may not be in the same state as this output child, due to how /// methods like `wait` and `kill` are implemented. It is not recommended to use this method /// _after_ using any of the other methods on this struct. /// #[cfg_attr( windows, doc = "On Windows, this unnavoidably leaves a handle unclosed. Prefer [`inner()`](Self::inner)." )] /// /// # Examples /// /// Writing to input: /// /// ```no_run /// use std::io::Write; /// use std::process::{Command, Stdio}; /// use command_group::CommandGroup; /// /// let mut child = Command::new("cat").stdin(Stdio::piped()).group_spawn().expect("cat command didn't start"); /// if let Some(mut din) = child.into_inner().stdin.take() { /// din.write_all(b"Woohoo!").expect("failed to write"); /// } /// ``` pub fn into_inner(self) -> Child { self.imp.into_inner() } /// Forces the child process group to exit. If the group has already exited, an [`InvalidInput`] /// error is returned. /// /// This is equivalent to sending a SIGKILL on Unix platforms. /// /// See [the stdlib documentation](Child::kill) for more. /// /// # Examples /// /// Basic usage: /// /// ```no_run /// use std::process::Command; /// use command_group::CommandGroup; /// /// let mut command = Command::new("yes"); /// if let Ok(mut child) = command.group_spawn() { /// child.kill().expect("command wasn't running"); /// } else { /// println!("yes command didn't start"); /// } /// ``` /// /// [`InvalidInput`]: std::io::ErrorKind::InvalidInput pub fn kill(&mut self) -> Result<()> { self.imp.kill() } /// Returns the OS-assigned process group identifier. /// /// See [the stdlib documentation](Child::id) for more. /// /// # Examples /// /// Basic usage: /// /// ```no_run /// use std::process::Command; /// use command_group::CommandGroup; /// /// let mut command = Command::new("ls"); /// if let Ok(child) = command.group_spawn() { /// println!("Child group's ID is {}", child.id()); /// } else { /// println!("ls command didn't start"); /// } /// ``` pub fn id(&self) -> u32 { self.imp.id() } /// Waits for the child group to exit completely, returning the status that /// the process leader exited with. /// /// See [the stdlib documentation](Child::wait) for more. /// /// # Examples /// /// Basic usage: /// /// ```no_run /// use std::process::Command; /// use command_group::CommandGroup; /// /// let mut command = Command::new("ls"); /// if let Ok(mut child) = command.group_spawn() { /// child.wait().expect("command wasn't running"); /// println!("Child has finished its execution!"); /// } else { /// println!("ls command didn't start"); /// } /// ``` pub fn wait(&mut self) -> Result { if let Some(es) = self.exitstatus { return Ok(es); } drop(self.imp.take_stdin()); let status = self.imp.wait()?; self.exitstatus = Some(status); Ok(status) } /// Attempts to collect the exit status of the child if it has already /// exited. /// /// See [the stdlib documentation](Child::try_wait) for more. /// /// # Examples /// /// Basic usage: /// /// ```no_run /// use std::process::Command; /// use command_group::CommandGroup; /// /// let mut child = Command::new("ls").group_spawn().unwrap(); /// /// match child.try_wait() { /// Ok(Some(status)) => println!("exited with: {}", status), /// Ok(None) => { /// println!("status not ready yet, let's really wait"); /// let res = child.wait(); /// println!("result: {:?}", res); /// } /// Err(e) => println!("error attempting to wait: {}", e), /// } /// ``` pub fn try_wait(&mut self) -> Result> { if self.exitstatus.is_some() { return Ok(self.exitstatus); } match self.imp.try_wait()? { Some(es) => { self.exitstatus = Some(es); Ok(Some(es)) } None => Ok(None), } } /// Simultaneously waits for the child to exit and collect all remaining /// output on the stdout/stderr handles, returning an `Output` /// instance. /// /// See [the stdlib documentation](Child::wait_with_output) for more. /// /// # Bugs /// /// On Windows, STDOUT is read before STDERR if both are piped, which may block. This is mostly /// because reading two outputs at the same time in synchronous code is horrendous. If you want /// this, please contribute a better version. Alternatively, prefer using the async API. /// /// # Examples /// /// Basic usage: /// /// ```should_panic /// use std::process::{Command, Stdio}; /// use command_group::CommandGroup; /// /// let child = Command::new("/bin/cat") /// .arg("file.txt") /// .stdout(Stdio::piped()) /// .group_spawn() /// .expect("failed to execute child"); /// /// let output = child /// .wait_with_output() /// .expect("failed to wait on child"); /// /// assert!(output.status.success()); /// ``` pub fn wait_with_output(mut self) -> Result { drop(self.imp.take_stdin()); let (mut stdout, mut stderr) = (Vec::new(), Vec::new()); match (self.imp.take_stdout(), self.imp.take_stderr()) { (None, None) => {} (Some(mut out), None) => { out.read_to_end(&mut stdout)?; } (None, Some(mut err)) => { err.read_to_end(&mut stderr)?; } (Some(out), Some(err)) => { let res = ChildImp::read_both(out, &mut stdout, err, &mut stderr); res.unwrap(); } } let status = self.imp.wait()?; Ok(Output { status, stdout, stderr, }) } } #[cfg(unix)] impl UnixChildExt for GroupChild { fn signal(&mut self, sig: Signal) -> Result<()> { self.imp.signal_imp(sig) } } command-group-2.1.0/src/stdlib/unix.rs000064400000000000000000000015751046102023000160050ustar 00000000000000use std::{io::Error, os::unix::process::CommandExt, process::Command}; use crate::{builder::CommandGroupBuilder, GroupChild}; use nix::unistd::setsid; impl CommandGroupBuilder<'_, Command> { /// Executes the command as a child process group, returning a handle to it. /// /// By default, stdin, stdout and stderr are inherited from the parent. /// /// On Windows, this creates a job object instead of a POSIX process group. /// /// # Examples /// /// Basic usage: /// /// ```no_run /// use std::process::Command; /// use command_group::CommandGroup; /// /// Command::new("ls") /// .group() /// .spawn() /// .expect("ls command failed to start"); /// ``` pub fn spawn(&mut self) -> std::io::Result { unsafe { self.command .pre_exec(|| setsid().map_err(Error::from).map(|_| ())); } self.command.spawn().map(GroupChild::new) } } command-group-2.1.0/src/stdlib/windows.rs000064400000000000000000000020621046102023000165040ustar 00000000000000use std::{ os::windows::{io::AsRawHandle, process::CommandExt}, process::Command, }; use winapi::um::winbase::CREATE_SUSPENDED; use crate::{builder::CommandGroupBuilder, winres::*, GroupChild}; impl CommandGroupBuilder<'_, Command> { /// Executes the command as a child process group, returning a handle to it. /// /// By default, stdin, stdout and stderr are inherited from the parent. /// /// On Windows, this creates a job object instead of a POSIX process group. /// /// # Examples /// /// Basic usage: /// /// ```no_run /// use std::process::Command; /// use command_group::CommandGroup; /// /// Command::new("ls") /// .group() /// .spawn() /// .expect("ls command failed to start"); /// ``` pub fn spawn(&mut self) -> std::io::Result { self.command .creation_flags(self.creation_flags | CREATE_SUSPENDED); let (job, completion_port) = job_object(self.kill_on_drop)?; let child = self.command.spawn()?; assign_child(child.as_raw_handle(), job)?; Ok(GroupChild::new(child, job, completion_port)) } } command-group-2.1.0/src/stdlib.rs000064400000000000000000000064351046102023000150220ustar 00000000000000//! Implementation of process group extensions for the //! standard library’s [`Command` type](std::process::Command). use std::{ io::Result, process::{Command, ExitStatus, Output}, }; use crate::{builder::CommandGroupBuilder, GroupChild}; #[cfg(target_family = "windows")] mod windows; #[cfg(target_family = "unix")] mod unix; pub(crate) mod child; /// Extensions for [`Command`](std::process::Command) adding support for process groups. pub trait CommandGroup { /// Executes the command as a child process group, returning a handle to it. /// /// By default, stdin, stdout and stderr are inherited from the parent. /// /// On Windows, this creates a job object instead of a POSIX process group. /// /// # Examples /// /// Basic usage: /// /// ```no_run /// use std::process::Command; /// use command_group::CommandGroup; /// /// Command::new("ls") /// .group_spawn() /// .expect("ls command failed to start"); /// ``` fn group_spawn(&mut self) -> Result { self.group().spawn() } /// Converts the implementor into a [`CommandGroupBuilder`](crate::CommandGroupBuilder), which can be used to /// set flags that are not available on the `Command` type. fn group(&mut self) -> CommandGroupBuilder; /// Executes the command as a child process group, waiting for it to finish and /// collecting all of its output. /// /// By default, stdout and stderr are captured (and used to provide the /// resulting output). Stdin is not inherited from the parent and any /// attempt by the child process to read from the stdin stream will result /// in the stream immediately closing. /// /// On Windows, this creates a job object instead of a POSIX process group. /// /// # Examples /// /// ```should_panic /// use std::process::Command; /// use std::io::{self, Write}; /// use command_group::CommandGroup; /// /// let output = Command::new("/bin/cat") /// .arg("file.txt") /// .group_output() /// .expect("failed to execute process"); /// /// println!("status: {}", output.status); /// io::stdout().write_all(&output.stdout).unwrap(); /// io::stderr().write_all(&output.stderr).unwrap(); /// /// assert!(output.status.success()); /// ``` fn group_output(&mut self) -> Result { self.group_spawn() .and_then(|child| child.wait_with_output()) } /// Executes a command as a child process group, waiting for it to finish and /// collecting its status. /// /// By default, stdin, stdout and stderr are inherited from the parent. /// /// On Windows, this creates a job object instead of a POSIX process group. /// /// # Examples /// /// ```should_panic /// use std::process::Command; /// use command_group::CommandGroup; /// /// let status = Command::new("/bin/cat") /// .arg("file.txt") /// .group_status() /// .expect("failed to execute process"); /// /// println!("process finished with: {}", status); /// /// assert!(status.success()); /// ``` fn group_status(&mut self) -> Result { self.group_spawn().and_then(|mut child| child.wait()) } } impl CommandGroup for Command { fn group<'a>(&'a mut self) -> CommandGroupBuilder<'a, Command> { CommandGroupBuilder::new(self) } } command-group-2.1.0/src/tokio/child/unix.rs000064400000000000000000000063551046102023000167350ustar 00000000000000use std::{ convert::TryInto, io::{Error, ErrorKind, Result}, os::unix::process::ExitStatusExt, process::ExitStatus, }; use nix::{ errno::Errno, libc, sys::{ signal::{killpg, Signal}, wait::WaitPidFlag, }, unistd::Pid, }; use tokio::{ process::{Child, ChildStderr, ChildStdin, ChildStdout}, task::spawn_blocking, }; pub(super) struct ChildImp { pgid: Pid, inner: Child, } impl ChildImp { pub(super) fn new(inner: Child) -> Self { let pid = inner .id() .expect("Command was reaped before we could read its PID") .try_into() .expect("Command PID > i32::MAX"); Self { pgid: Pid::from_raw(pid), inner, } } pub(super) fn take_stdin(&mut self) -> Option { self.inner.stdin.take() } pub(super) fn take_stdout(&mut self) -> Option { self.inner.stdout.take() } pub(super) fn take_stderr(&mut self) -> Option { self.inner.stderr.take() } pub fn inner(&mut self) -> &mut Child { &mut self.inner } pub fn into_inner(self) -> Child { self.inner } pub(super) fn signal_imp(&mut self, sig: Signal) -> Result<()> { killpg(self.pgid, sig).map_err(Error::from) } pub fn kill(&mut self) -> Result<()> { self.signal_imp(Signal::SIGKILL) } pub fn id(&self) -> Option { self.inner.id() } fn wait_imp(pgid: i32, flag: WaitPidFlag) -> Result> { // Wait for processes in a loop until every process in this // process group has exited (this ensures that we reap any // zombies that may have been created if the parent exited after // spawning children, but didn't wait for those children to // exit). let mut parent_exit_status: Option = None; loop { // we can't use the safe wrapper directly because it doesn't // return the raw status, and we need it to convert to the // std's ExitStatus. let mut status: i32 = 0; match unsafe { libc::waitpid(-pgid, &mut status as *mut libc::c_int, flag.bits()) } { 0 => { // Zero should only happen if WNOHANG was passed in, // and means that no processes have yet to exit. return Ok(None); } -1 => { match Errno::last() { Errno::ECHILD => { // No more children to reap; this is a // graceful exit. return Ok(parent_exit_status); } errno => { return Err(Error::from(errno)); } } } pid => { // *A* process exited. Was it the parent process // that we started? If so, collect the exit signal, // otherwise we reaped a zombie process and should // continue in the loop. if pgid == pid { parent_exit_status = Some(ExitStatus::from_raw(status)); } else { // Reaped a zombie child; keep looping. } } }; } } pub async fn wait(&mut self) -> Result { let pgid = self.pgid.as_raw(); spawn_blocking(move || Self::wait_imp(pgid, WaitPidFlag::empty())) .await? .transpose() .unwrap_or_else(|| { Err(Error::new( ErrorKind::Other, "blocking waitpid returned pid=0", )) }) } pub fn try_wait(&mut self) -> Result> { Self::wait_imp(self.pgid.as_raw(), WaitPidFlag::WNOHANG) } } impl crate::UnixChildExt for ChildImp { fn signal(&mut self, sig: Signal) -> Result<()> { self.signal_imp(sig) } } command-group-2.1.0/src/tokio/child/windows.rs000064400000000000000000000047521046102023000174430ustar 00000000000000use std::{io::Result, mem, process::ExitStatus}; use tokio::{ process::{Child, ChildStderr, ChildStdin, ChildStdout}, task::spawn_blocking, }; use winapi::{ shared::{ basetsd::ULONG_PTR, minwindef::{DWORD, FALSE}, }, um::{ handleapi::CloseHandle, ioapiset::GetQueuedCompletionStatus, jobapi2::TerminateJobObject, minwinbase::OVERLAPPED, winbase::INFINITE, winnt::HANDLE, }, }; use crate::winres::*; pub(super) struct ChildImp { inner: Child, handles: JobPort, } impl ChildImp { pub fn new(inner: Child, job: HANDLE, completion_port: HANDLE) -> Self { Self { inner, handles: JobPort { job, completion_port, }, } } pub(super) fn take_stdin(&mut self) -> Option { self.inner.stdin.take() } pub(super) fn take_stdout(&mut self) -> Option { self.inner.stdout.take() } pub(super) fn take_stderr(&mut self) -> Option { self.inner.stderr.take() } pub fn inner(&mut self) -> &mut Child { &mut self.inner } pub fn into_inner(self) -> Child { let its = mem::ManuallyDrop::new(self.handles); // manually drop the completion port unsafe { CloseHandle(its.completion_port) }; // we leave the job handle unclosed, otherwise the Child is useless // (as closing it will terminate the job) self.inner } pub fn kill(&mut self) -> Result<()> { res_bool(unsafe { TerminateJobObject(self.handles.job, 1) }) } pub fn id(&self) -> Option { self.inner.id() } fn wait_imp(handles: JobPort, timeout: DWORD) -> Result<()> { let mut code: DWORD = 0; let mut key: ULONG_PTR = 0; let mut overlapped = mem::MaybeUninit::::uninit(); let mut lp_overlapped = overlapped.as_mut_ptr(); let result = unsafe { GetQueuedCompletionStatus( handles.completion_port, &mut code, &mut key, &mut lp_overlapped, timeout, ) }; // ignore timing out errors unless the timeout was specified to INFINITE // https://docs.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-getqueuedcompletionstatus if timeout != INFINITE && result == FALSE && lp_overlapped.is_null() { return Ok(()); } res_bool(result)?; // don't drop them mem::forget(handles); Ok(()) } pub async fn wait(&mut self) -> Result { let handles = self.handles.clone(); spawn_blocking(|| Self::wait_imp(handles, INFINITE)).await??; self.inner.wait().await } pub fn try_wait(&mut self) -> Result> { Self::wait_imp(self.handles.clone(), 0)?; self.inner.try_wait() } } command-group-2.1.0/src/tokio/child.rs000064400000000000000000000217231046102023000157460ustar 00000000000000use std::{ fmt, io::Result, process::{ExitStatus, Output}, }; use tokio::{io::AsyncReadExt, process::Child}; #[cfg(unix)] pub(self) use unix::ChildImp; #[cfg(windows)] pub(self) use windows::ChildImp; #[cfg(unix)] use nix::sys::signal::Signal; #[cfg(windows)] use winapi::um::winnt::HANDLE; #[cfg(unix)] mod unix; #[cfg(windows)] mod windows; /// Representation of a running or exited child process group (Tokio variant). /// /// This wraps Tokio’s [`Child`] type with methods that work with process groups. /// /// # Examples /// /// ```should_panic /// # #[tokio::main] /// # async fn main() { /// use tokio::process::Command; /// use command_group::AsyncCommandGroup; /// /// let mut child = Command::new("/bin/cat") /// .arg("file.txt") /// .group_spawn() /// .expect("failed to execute child"); /// /// let ecode = child.wait() /// .await /// .expect("failed to wait on child"); /// /// assert!(ecode.success()); /// # } /// ``` pub struct AsyncGroupChild { imp: ChildImp, exitstatus: Option, } impl fmt::Debug for AsyncGroupChild { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("AsyncGroupChild").finish() } } impl AsyncGroupChild { #[cfg(unix)] pub(crate) fn new(inner: Child) -> Self { Self { imp: ChildImp::new(inner), exitstatus: None, } } #[cfg(windows)] pub(crate) fn new(inner: Child, j: HANDLE, c: HANDLE) -> Self { Self { imp: ChildImp::new(inner, j, c), exitstatus: None, } } /// Returns the stdlib [`Child`] object. /// /// Note that the inner child may not be in the same state as this output child, due to how /// methods like `wait` and `kill` are implemented. It is not recommended to use this method /// _after_ using any of the other methods on this struct. /// /// # Examples /// /// Reading from stdout: /// /// ```no_run /// # #[tokio::main] /// # async fn main() { /// use std::process::Stdio; /// use tokio::{io::AsyncReadExt, process::Command}; /// use command_group::AsyncCommandGroup; /// /// let mut child = Command::new("ls").stdout(Stdio::piped()).group_spawn().expect("ls command didn't start"); /// let mut output = String::new(); /// if let Some(mut out) = child.inner().stdout.take() { /// out.read_to_string(&mut output).await.expect("failed to read from child"); /// } /// println!("output: {}", output); /// # } /// ``` pub fn inner(&mut self) -> &mut Child { self.imp.inner() } /// Consumes itself and returns the stdlib [`Child`] object. /// /// Note that the inner child may not be in the same state as this output child, due to how /// methods like `wait` and `kill` are implemented. It is not recommended to use this method /// _after_ using any of the other methods on this struct. /// #[cfg_attr( windows, doc = "On Windows, this unnavoidably leaves a handle unclosed. Prefer [`inner()`](Self::inner)." )] /// /// # Examples /// /// Writing to input: /// /// ```no_run /// # #[tokio::main] /// # async fn main() { /// use std::process::Stdio; /// use tokio::{io::AsyncWriteExt, process::Command}; /// use command_group::AsyncCommandGroup; /// /// let mut child = Command::new("cat").stdin(Stdio::piped()).group_spawn().expect("cat command didn't start"); /// if let Some(mut din) = child.into_inner().stdin.take() { /// din.write_all(b"Woohoo!").await.expect("failed to write"); /// } /// # } /// ``` pub fn into_inner(self) -> Child { self.imp.into_inner() } /// Forces the child process group to exit. If the group has already exited, an [`InvalidInput`] /// error is returned. /// /// This is equivalent to sending a SIGKILL on Unix platforms. /// /// **Unlike the Tokio implementation**, this method does not wait for the child process group, /// and only sends the kill. You’ll need to call [`wait()`](Self::wait) yourself. /// /// See [the Tokio documentation](Child::kill) for more. /// /// # Examples /// /// Basic usage: /// /// ```no_run /// # #[tokio::main] /// # async fn main() { /// use tokio::process::Command; /// use command_group::AsyncCommandGroup; /// /// let mut command = Command::new("yes"); /// if let Ok(mut child) = command.group_spawn() { /// child.kill().expect("command wasn't running"); /// } else { /// println!("yes command didn't start"); /// } /// # } /// ``` /// /// [`InvalidInput`]: std::io::ErrorKind::InvalidInput pub fn kill(&mut self) -> Result<()> { self.imp.kill() } /// Returns the OS-assigned process group identifier. /// /// Like Tokio, this returns `None` if the child process group has alread exited, to avoid /// holding onto an expired (and possibly reused) PGID. /// /// See [the Tokio documentation](Child::id) for more. /// /// # Examples /// /// Basic usage: /// /// ```no_run /// # #[tokio::main] /// # async fn main() { /// use tokio::process::Command; /// use command_group::AsyncCommandGroup; /// /// let mut command = Command::new("ls"); /// if let Ok(child) = command.group_spawn() { /// if let Some(pgid) = child.id() { /// println!("Child group's ID is {}", pgid); /// } else { /// println!("Child group is gone"); /// } /// } else { /// println!("ls command didn't start"); /// } /// # } /// ``` pub fn id(&self) -> Option { self.imp.id() } /// Waits for the child group to exit completely, returning the status that the process leader /// exited with. /// /// See [the Tokio documentation](Child::wait) for more. /// /// The current implementation spawns a blocking task on the Tokio thread pool; contributions /// are welcome for a more async-y version. /// /// # Examples /// /// Basic usage: /// /// ```no_run /// # #[tokio::main] /// # async fn main() { /// use tokio::process::Command; /// use command_group::AsyncCommandGroup; /// /// let mut command = Command::new("ls"); /// if let Ok(mut child) = command.group_spawn() { /// child.wait().await.expect("command wasn't running"); /// println!("Child has finished its execution!"); /// } else { /// println!("ls command didn't start"); /// } /// # } /// ``` pub async fn wait(&mut self) -> Result { if let Some(es) = self.exitstatus { return Ok(es); } drop(self.imp.take_stdin()); let status = self.imp.wait().await?; self.exitstatus = Some(status); Ok(status) } /// Attempts to collect the exit status of the child if it has already exited. /// /// See [the Tokio documentation](Child::try_wait) for more. /// /// # Examples /// /// Basic usage: /// /// ```no_run /// # #[tokio::main] /// # async fn main() { /// use tokio::process::Command; /// use command_group::AsyncCommandGroup; /// /// let mut child = Command::new("ls").group_spawn().unwrap(); /// /// match child.try_wait() { /// Ok(Some(status)) => println!("exited with: {}", status), /// Ok(None) => { /// println!("status not ready yet, let's really wait"); /// let res = child.wait().await; /// println!("result: {:?}", res); /// } /// Err(e) => println!("error attempting to wait: {}", e), /// } /// # } /// ``` pub fn try_wait(&mut self) -> Result> { if self.exitstatus.is_some() { return Ok(self.exitstatus); } match self.imp.try_wait()? { Some(es) => { self.exitstatus = Some(es); Ok(Some(es)) } None => Ok(None), } } /// Simultaneously waits for the child to exit and collect all remaining output on the /// stdout/stderr handles, returning an `Output` instance. /// /// See [the Tokio documentation](Child::wait_with_output) for more. /// /// # Examples /// /// Basic usage: /// /// ```should_panic /// # #[tokio::main] /// # async fn main() { /// use std::process::Stdio; /// use tokio::process::Command; /// use command_group::AsyncCommandGroup; /// /// let child = Command::new("/bin/cat") /// .arg("file.txt") /// .stdout(Stdio::piped()) /// .group_spawn() /// .expect("failed to execute child"); /// /// let output = child /// .wait_with_output() /// .await /// .expect("failed to wait on child"); /// /// assert!(output.status.success()); /// # } /// ``` pub async fn wait_with_output(mut self) -> Result { drop(self.imp.take_stdin()); let (mut stdout, mut stderr) = (Vec::new(), Vec::new()); match (self.imp.take_stdout(), self.imp.take_stderr()) { (None, None) => {} (Some(mut out), None) => { out.read_to_end(&mut stdout).await?; } (None, Some(mut err)) => { err.read_to_end(&mut stderr).await?; } (Some(mut out), Some(mut err)) => { // TODO: replace with futures crate usage // and drop macros feature from tokio tokio::try_join!(out.read_to_end(&mut stdout), err.read_to_end(&mut stderr),)?; } } let status = self.imp.wait().await?; Ok(Output { status, stdout, stderr, }) } } #[cfg(unix)] impl crate::UnixChildExt for AsyncGroupChild { fn signal(&mut self, sig: Signal) -> Result<()> { self.imp.signal_imp(sig) } } command-group-2.1.0/src/tokio/unix.rs000064400000000000000000000015641046102023000156470ustar 00000000000000use std::io::Error; use crate::builder::CommandGroupBuilder; use crate::AsyncGroupChild; use nix::unistd::setsid; impl CommandGroupBuilder<'_, tokio::process::Command> { /// Executes the command as a child process group, returning a handle to it. /// /// By default, stdin, stdout and stderr are inherited from the parent. /// /// On Windows, this creates a job object instead of a POSIX process group. /// /// # Examples /// /// Basic usage: /// /// ```no_run /// use tokio::process::Command; /// use command_group::CommandGroup; /// /// Command::new("ls") /// .group() /// .spawn() /// .expect("ls command failed to start"); /// ``` pub fn spawn(&mut self) -> std::io::Result { unsafe { self.command .pre_exec(|| setsid().map_err(Error::from).map(|_| ())); } self.command.spawn().map(AsyncGroupChild::new) } } command-group-2.1.0/src/tokio/windows.rs000064400000000000000000000021221046102023000163450ustar 00000000000000use tokio::process::Command; use winapi::um::winbase::CREATE_SUSPENDED; use crate::{builder::CommandGroupBuilder, winres::*, AsyncGroupChild}; impl CommandGroupBuilder<'_, Command> { /// Executes the command as a child process group, returning a handle to it. /// /// By default, stdin, stdout and stderr are inherited from the parent. /// /// On Windows, this creates a job object instead of a POSIX process group. /// /// # Examples /// /// Basic usage: /// /// ```no_run /// use tokio::process::Command; /// use command_group::CommandGroup; /// /// Command::new("ls") /// .group() /// .spawn() /// .expect("ls command failed to start"); /// ``` pub fn spawn(&mut self) -> std::io::Result { let (job, completion_port) = job_object(self.kill_on_drop)?; self.command .creation_flags(self.creation_flags | CREATE_SUSPENDED); let child = self.command.spawn()?; assign_child( child .raw_handle() .expect("child has exited but it has not even started"), job, )?; Ok(AsyncGroupChild::new(child, job, completion_port)) } } command-group-2.1.0/src/tokio.rs000064400000000000000000000074101046102023000146600ustar 00000000000000//! Implementation of process group extensions for [Tokio](https://tokio.rs)’s //! asynchronous [`Command` type](::tokio::process::Command). use std::{ io::Result, process::{ExitStatus, Output}, }; use tokio::process::Command; use crate::{builder::CommandGroupBuilder, AsyncGroupChild}; #[cfg(target_family = "windows")] mod windows; #[cfg(target_family = "unix")] mod unix; pub(crate) mod child; /// Extensions for [`Command`](::tokio::process::Command) adding support for process groups. /// /// This uses [`async_trait`] for now to provide async methods as a trait. #[async_trait::async_trait] pub trait AsyncCommandGroup { /// Executes the command as a child process group, returning a handle to it. /// /// By default, stdin, stdout and stderr are inherited from the parent. /// /// On Windows, this creates a job object instead of a POSIX process group. /// /// # Examples /// /// Basic usage: /// /// ```no_run /// # #[tokio::main] /// # async fn main() { /// use tokio::process::Command; /// use command_group::AsyncCommandGroup; /// /// Command::new("ls") /// .group_spawn() /// .expect("ls command failed to start"); /// # } /// ``` fn group_spawn(&mut self) -> Result { self.group().spawn() } /// Converts the implementor into a [`CommandGroupBuilder`](crate::CommandGroupBuilder), which can be used to /// set flags that are not available on the `Command` type. fn group(&mut self) -> crate::builder::CommandGroupBuilder; /// Executes the command as a child process group, waiting for it to finish and /// collecting all of its output. /// /// By default, stdout and stderr are captured (and used to provide the /// resulting output). Stdin is not inherited from the parent and any /// attempt by the child process to read from the stdin stream will result /// in the stream immediately closing. /// /// On Windows, this creates a job object instead of a POSIX process group. /// /// # Examples /// /// ```should_panic /// # #[tokio::main] /// # async fn main() { /// use tokio::process::Command; /// use std::io::{self, Write}; /// use command_group::AsyncCommandGroup; /// /// let output = Command::new("/bin/cat") /// .arg("file.txt") /// .group_output() /// .await /// .expect("failed to execute process"); /// /// println!("status: {}", output.status); /// io::stdout().write_all(&output.stdout).unwrap(); /// io::stderr().write_all(&output.stderr).unwrap(); /// /// assert!(output.status.success()); /// # } /// ``` async fn group_output(&mut self) -> Result { let child = self.group_spawn()?; child.wait_with_output().await } /// Executes a command as a child process group, waiting for it to finish and /// collecting its status. /// /// By default, stdin, stdout and stderr are inherited from the parent. /// /// On Windows, this creates a job object instead of a POSIX process group. /// /// # Examples /// /// ```should_panic /// # #[tokio::main] /// # async fn main() { /// use tokio::process::Command; /// use command_group::AsyncCommandGroup; /// /// let status = Command::new("/bin/cat") /// .arg("file.txt") /// .group_status() /// .await /// .expect("failed to execute process"); /// /// println!("process finished with: {}", status); /// /// assert!(status.success()); /// # } /// ``` async fn group_status(&mut self) -> Result { let mut child = self.group_spawn()?; child.wait().await } } #[async_trait::async_trait] impl AsyncCommandGroup for Command { fn group<'a>(&'a mut self) -> CommandGroupBuilder<'a, Command> { CommandGroupBuilder::new(self) } } command-group-2.1.0/src/unix_ext.rs000064400000000000000000000033221046102023000153740ustar 00000000000000use std::{ convert::TryInto, io::{Error, Result}, process::Child, }; use nix::{ sys::signal::{kill, Signal}, unistd::Pid, }; /// Unix-specific extensions to process [`Child`]ren. pub trait UnixChildExt { /// Sends a signal to the child process. If the process has already exited, an [`InvalidInput`] /// error is returned. /// /// # Examples /// /// Basic usage: /// /// ```no_run /// use std::process::Command; /// use command_group::{UnixChildExt, Signal}; /// /// let mut command = Command::new("yes"); /// if let Ok(mut child) = command.spawn() { /// child.signal(Signal::SIGTERM).expect("command wasn't running"); /// } else { /// println!("yes command didn't start"); /// } /// ``` /// /// With a process group: /// /// ```no_run /// use std::process::Command; /// use command_group::{CommandGroup, UnixChildExt, Signal}; /// /// let mut command = Command::new("yes"); /// if let Ok(mut child) = command.group_spawn() { /// child.signal(Signal::SIGTERM).expect("command wasn't running"); /// } else { /// println!("yes command didn't start"); /// } /// ``` /// /// [`InvalidInput`]: std::io::ErrorKind::InvalidInput fn signal(&mut self, sig: Signal) -> Result<()>; } impl UnixChildExt for Child { fn signal(&mut self, sig: Signal) -> Result<()> { let pid = Pid::from_raw(self.id().try_into().expect("Command PID > i32::MAX")); kill(pid, sig).map_err(Error::from) } } #[cfg(feature = "with-tokio")] impl UnixChildExt for ::tokio::process::Child { fn signal(&mut self, sig: Signal) -> Result<()> { if let Some(id) = self.id() { let pid = Pid::from_raw(id.try_into().expect("Command PID > i32::MAX")); kill(pid, sig).map_err(Error::from) } else { Ok(()) } } } command-group-2.1.0/src/winres.rs000064400000000000000000000070771046102023000150530ustar 00000000000000use std::{ convert::TryInto, io::{Error, Result}, mem, os::windows::io::RawHandle, ptr, }; use winapi::{ shared::minwindef::{BOOL, DWORD, FALSE, LPVOID}, um::{ handleapi::{CloseHandle, INVALID_HANDLE_VALUE}, ioapiset::CreateIoCompletionPort, jobapi2::{AssignProcessToJobObject, CreateJobObjectW, SetInformationJobObject}, processthreadsapi::{GetProcessId, OpenThread, ResumeThread}, tlhelp32::{ CreateToolhelp32Snapshot, Thread32First, Thread32Next, TH32CS_SNAPTHREAD, THREADENTRY32, }, winnt::{ JobObjectAssociateCompletionPortInformation, JobObjectExtendedLimitInformation, HANDLE, JOBOBJECT_ASSOCIATE_COMPLETION_PORT, JOBOBJECT_EXTENDED_LIMIT_INFORMATION, JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, }, }, }; #[derive(Clone)] pub(crate) struct JobPort { pub job: HANDLE, pub completion_port: HANDLE, } impl Drop for JobPort { fn drop(&mut self) { unsafe { CloseHandle(self.job) }; unsafe { CloseHandle(self.completion_port) }; } } unsafe impl Send for JobPort {} unsafe impl Sync for JobPort {} pub(crate) fn res_null(handle: HANDLE) -> Result { if handle.is_null() { Err(Error::last_os_error()) } else { Ok(handle) } } pub(crate) fn res_bool(ret: BOOL) -> Result<()> { if ret == FALSE { Err(Error::last_os_error()) } else { Ok(()) } } pub(crate) fn res_neg(ret: DWORD) -> Result { if ret == DWORD::MAX { Err(Error::last_os_error()) } else { Ok(ret) } } pub(crate) fn job_object(kill_on_drop: bool) -> Result<(HANDLE, HANDLE)> { let job = res_null(unsafe { CreateJobObjectW(ptr::null_mut(), ptr::null()) })?; let completion_port = res_null(unsafe { CreateIoCompletionPort(INVALID_HANDLE_VALUE, ptr::null_mut(), 0, 1) })?; let mut associate_completion = JOBOBJECT_ASSOCIATE_COMPLETION_PORT { CompletionKey: job, CompletionPort: completion_port, }; res_bool(unsafe { SetInformationJobObject( job, JobObjectAssociateCompletionPortInformation, &mut associate_completion as *mut _ as LPVOID, mem::size_of_val(&associate_completion) .try_into() .expect("cannot safely cast to DWORD"), ) })?; let mut info = JOBOBJECT_EXTENDED_LIMIT_INFORMATION::default(); if kill_on_drop { info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; } res_bool(unsafe { SetInformationJobObject( job, JobObjectExtendedLimitInformation, &mut info as *mut _ as LPVOID, mem::size_of_val(&info) .try_into() .expect("cannot safely cast to DWORD"), ) })?; Ok((job, completion_port)) } // This is pretty terrible, but it's either this or we re-implement all of Rust's std::process just // to get at PROCESS_INFORMATION! fn resume_threads(child_process: HANDLE) -> Result<()> { let child_id = unsafe { GetProcessId(child_process) }; let h = res_null(unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0) })?; let mut entry = THREADENTRY32 { dwSize: 28, cntUsage: 0, th32ThreadID: 0, th32OwnerProcessID: 0, tpBasePri: 0, tpDeltaPri: 0, dwFlags: 0, }; let mut res = res_bool(unsafe { Thread32First(h, &mut entry) }); while res.is_ok() { if entry.th32OwnerProcessID == child_id { let thread_handle = res_null(unsafe { OpenThread(0x0002, 0, entry.th32ThreadID) })?; res_neg(unsafe { ResumeThread(thread_handle) })?; res_bool(unsafe { CloseHandle(thread_handle) })?; } res = res_bool(unsafe { Thread32Next(h, &mut entry) }); } res_bool(unsafe { CloseHandle(h) }) } pub(crate) fn assign_child(handle: RawHandle, job: HANDLE) -> Result<()> { let handle = handle as _; res_bool(unsafe { AssignProcessToJobObject(job, handle) })?; resume_threads(handle)?; Ok(()) } command-group-2.1.0/tests/stdlib_unix.rs000064400000000000000000000157551046102023000164450ustar 00000000000000#![cfg(unix)] use command_group::{CommandGroup, Signal, UnixChildExt}; use std::{ io::{Read, Result, Write}, os::unix::process::ExitStatusExt, process::{Command, Stdio}, thread::sleep, time::Duration, }; const DIE_TIME: Duration = Duration::from_millis(100); // each test has a _normal variant that uses the stdlib non-group API for comparison/debugging. #[test] fn inner_read_stdout_normal() -> Result<()> { let mut child = Command::new("echo") .arg("hello") .stdout(Stdio::piped()) .spawn()?; let mut output = String::new(); if let Some(mut out) = child.stdout.take() { out.read_to_string(&mut output)?; } assert_eq!(output.as_str(), "hello\n"); Ok(()) } #[test] fn inner_read_stdout_group() -> Result<()> { let mut child = Command::new("echo") .arg("hello") .stdout(Stdio::piped()) .group_spawn()?; let mut output = String::new(); if let Some(mut out) = child.inner().stdout.take() { out.read_to_string(&mut output)?; } assert_eq!(output.as_str(), "hello\n"); Ok(()) } #[test] fn into_inner_write_stdin_normal() -> Result<()> { let mut child = Command::new("cat") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn()?; if let Some(mut din) = child.stdin.take() { din.write_all(b"hello")?; } let mut output = String::new(); if let Some(mut out) = child.stdout.take() { out.read_to_string(&mut output)?; } assert_eq!(output.as_str(), "hello"); Ok(()) } #[test] fn into_inner_write_stdin_group() -> Result<()> { let mut child = Command::new("cat") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .group_spawn()? .into_inner(); if let Some(mut din) = child.stdin.take() { din.write_all(b"hello")?; } let mut output = String::new(); if let Some(mut out) = child.stdout.take() { out.read_to_string(&mut output)?; } assert_eq!(output.as_str(), "hello"); Ok(()) } #[test] fn kill_and_try_wait_normal() -> Result<()> { let mut child = Command::new("yes").stdout(Stdio::null()).spawn()?; assert!(child.try_wait()?.is_none()); child.kill()?; sleep(DIE_TIME); assert!(child.try_wait()?.is_some()); sleep(DIE_TIME); assert!(child.try_wait()?.is_some()); Ok(()) } #[test] fn kill_and_try_wait_group() -> Result<()> { let mut child = Command::new("yes").stdout(Stdio::null()).group_spawn()?; assert!(child.try_wait()?.is_none()); child.kill()?; sleep(DIE_TIME); assert!(child.try_wait()?.is_some()); sleep(DIE_TIME); assert!(child.try_wait()?.is_some()); Ok(()) } #[test] fn try_wait_twice_after_sigterm_normal() -> Result<()> { let mut child = Command::new("yes").stdout(Stdio::null()).spawn()?; assert!(child.try_wait()?.is_none(), "pre try_wait"); child.signal(Signal::SIGTERM)?; sleep(DIE_TIME); assert!(child.try_wait()?.is_some(), "first try_wait"); sleep(DIE_TIME); assert!(child.try_wait()?.is_some(), "second try_wait"); Ok(()) } #[test] fn try_wait_twice_after_sigterm_group() -> Result<()> { let mut child = Command::new("yes").stdout(Stdio::null()).group_spawn()?; assert!(child.try_wait()?.is_none(), "pre try_wait"); child.signal(Signal::SIGTERM)?; sleep(DIE_TIME); assert!(child.try_wait()?.is_some(), "first try_wait"); sleep(DIE_TIME); assert!(child.try_wait()?.is_some(), "second try_wait"); Ok(()) } #[test] fn wait_twice_after_sigterm_normal() -> Result<()> { let mut child = Command::new("yes").stdout(Stdio::null()).spawn()?; assert!(child.try_wait()?.is_none(), "pre try_wait"); child.signal(Signal::SIGTERM)?; let status = child.wait()?; assert_eq!( status.signal(), Some(Signal::SIGTERM as i32), "first wait status" ); let status = child.wait()?; assert_eq!( status.signal(), Some(Signal::SIGTERM as i32), "second wait status" ); Ok(()) } #[test] fn wait_twice_after_sigterm_group() -> Result<()> { let mut child = Command::new("yes").stdout(Stdio::null()).group_spawn()?; assert!(child.try_wait()?.is_none(), "pre try_wait"); child.signal(Signal::SIGTERM)?; let status = child.wait()?; assert_eq!( status.signal(), Some(Signal::SIGTERM as i32), "first wait status" ); let status = child.wait()?; assert_eq!( status.signal(), Some(Signal::SIGTERM as i32), "second wait status" ); Ok(()) } #[test] fn wait_after_die_normal() -> Result<()> { let mut child = Command::new("echo").stdout(Stdio::null()).spawn()?; sleep(DIE_TIME); let status = child.wait()?; assert!(status.success()); Ok(()) } #[test] fn wait_after_die_group() -> Result<()> { let mut child = Command::new("echo").stdout(Stdio::null()).group_spawn()?; sleep(DIE_TIME); let status = child.wait()?; assert!(status.success()); Ok(()) } #[test] fn try_wait_after_die_normal() -> Result<()> { let mut child = Command::new("echo").stdout(Stdio::null()).spawn()?; sleep(DIE_TIME); let status = child.try_wait()?; assert!(status.is_some()); assert!(status.unwrap().success()); Ok(()) } #[test] fn try_wait_after_die_group() -> Result<()> { let mut child = Command::new("echo").stdout(Stdio::null()).group_spawn()?; sleep(DIE_TIME); let status = child.try_wait()?; assert!(status.is_some()); assert!(status.unwrap().success()); Ok(()) } #[test] fn wait_normal() -> Result<()> { let mut command = Command::new("echo"); let mut child = command.spawn()?; let status = child.wait()?; assert!(status.success()); let status = child.wait()?; assert!(status.success()); Ok(()) } #[test] fn wait_group() -> Result<()> { let mut command = Command::new("echo"); let mut child = command.group_spawn()?; let status = child.wait()?; assert!(status.success()); let status = child.wait()?; assert!(status.success()); Ok(()) } #[test] fn wait_with_output_normal() -> Result<()> { let child = Command::new("echo") .arg("hello") .stdout(Stdio::piped()) .spawn()?; let output = child.wait_with_output()?; assert!(output.status.success()); assert_eq!(output.stdout, b"hello\n".to_vec()); assert_eq!(output.stderr, Vec::new()); Ok(()) } #[test] fn wait_with_output_group() -> Result<()> { let child = Command::new("echo") .arg("hello") .stdout(Stdio::piped()) .group_spawn()?; let output = child.wait_with_output()?; assert!(output.status.success()); assert_eq!(output.stdout, b"hello\n".to_vec()); assert_eq!(output.stderr, Vec::new()); Ok(()) } #[test] fn id_same_as_inner_group() -> Result<()> { let mut command = Command::new("echo"); let mut child = command.group_spawn()?; assert_eq!(child.id(), child.inner().id()); Ok(()) } #[test] fn signal_normal() -> Result<()> { let mut child = Command::new("yes").stdout(Stdio::null()).spawn()?; child.signal(Signal::SIGCONT)?; sleep(DIE_TIME); assert!(child.try_wait()?.is_none(), "not exited with sigcont"); child.signal(Signal::SIGTERM)?; sleep(DIE_TIME); assert!(child.try_wait()?.is_some(), "exited with sigterm"); Ok(()) } #[test] fn signal_group() -> Result<()> { let mut child = Command::new("yes").stdout(Stdio::null()).group_spawn()?; child.signal(Signal::SIGCONT)?; sleep(DIE_TIME); assert!(child.try_wait()?.is_none(), "not exited with sigcont"); child.signal(Signal::SIGTERM)?; sleep(DIE_TIME); assert!(child.try_wait()?.is_some(), "exited with sigterm"); Ok(()) } command-group-2.1.0/tests/stdlib_windows.rs000064400000000000000000000116501046102023000171420ustar 00000000000000#![cfg(windows)] use command_group::CommandGroup; use std::{ io::{Read, Result, Write}, process::{Command, Stdio}, thread::sleep, time::Duration, }; const DIE_TIME: Duration = Duration::from_millis(1000); // each test has a _normal variant that uses the stdlib non-group API for comparison/debugging. #[test] fn inner_read_stdout_normal() -> Result<()> { let mut child = Command::new("powershell.exe") .arg("/C") .arg("echo hello") .stdout(Stdio::piped()) .spawn()?; let mut output = String::new(); if let Some(mut out) = child.stdout.take() { out.read_to_string(&mut output)?; } assert_eq!(output.as_str(), "hello\r\n"); Ok(()) } #[test] fn inner_read_stdout_group() -> Result<()> { let mut child = Command::new("powershell.exe") .arg("/C") .arg("echo hello") .stdout(Stdio::piped()) .group_spawn()?; let mut output = String::new(); if let Some(mut out) = child.inner().stdout.take() { out.read_to_string(&mut output)?; } assert_eq!(output.as_str(), "hello\r\n"); Ok(()) } #[test] fn into_inner_write_stdin_normal() -> Result<()> { let mut child = Command::new("findstr") .arg("^") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn()?; if let Some(mut din) = child.stdin.take() { din.write_all(b"hello")?; } let mut output = String::new(); if let Some(mut out) = child.stdout.take() { out.read_to_string(&mut output)?; } assert_eq!(output.as_str(), "hello\r\n"); Ok(()) } #[test] fn into_inner_write_stdin_group() -> Result<()> { let mut child = Command::new("findstr") .arg("^") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .group_spawn()? .into_inner(); if let Some(mut din) = child.stdin.take() { din.write_all(b"hello")?; } let mut output = String::new(); if let Some(mut out) = child.stdout.take() { out.read_to_string(&mut output)?; } assert_eq!(output.as_str(), "hello\r\n"); Ok(()) } #[test] fn kill_and_try_wait_normal() -> Result<()> { let mut child = Command::new("powershell.exe") .arg("/C") .arg("pause") .spawn()?; assert!(child.try_wait()?.is_none()); child.kill()?; sleep(DIE_TIME); assert!(child.try_wait()?.is_some()); sleep(DIE_TIME); assert!(child.try_wait()?.is_some()); Ok(()) } #[test] fn kill_and_try_wait_group() -> Result<()> { let mut child = Command::new("powershell.exe") .arg("/C") .arg("pause") .group_spawn()?; assert!(child.try_wait()?.is_none()); child.kill()?; sleep(DIE_TIME); assert!(child.try_wait()?.is_some()); sleep(DIE_TIME); assert!(child.try_wait()?.is_some()); Ok(()) } #[test] fn wait_after_die_normal() -> Result<()> { let mut child = Command::new("powershell.exe") .arg("/C") .arg("echo hello") .spawn()?; sleep(DIE_TIME); let status = child.wait()?; assert!(status.success()); Ok(()) } #[test] fn wait_after_die_group() -> Result<()> { let mut child = Command::new("powershell.exe") .arg("/C") .arg("echo hello") .group_spawn()?; sleep(DIE_TIME); let status = child.wait()?; assert!(status.success()); Ok(()) } #[test] fn try_wait_after_die_normal() -> Result<()> { let mut child = Command::new("powershell.exe") .arg("/C") .arg("echo hello") .spawn()?; sleep(DIE_TIME * 10); let status = child.try_wait()?; assert!(status.is_some()); assert!(status.unwrap().success()); Ok(()) } #[test] fn try_wait_after_die_group() -> Result<()> { let mut child = Command::new("powershell.exe") .arg("/C") .arg("echo hello") .group_spawn()?; sleep(DIE_TIME * 10); let status = child.try_wait()?; assert!(status.is_some()); assert!(status.unwrap().success()); Ok(()) } #[test] fn wait_normal() -> Result<()> { let mut child = Command::new("powershell.exe") .arg("/C") .arg("echo hello") .spawn()?; let status = child.wait()?; assert!(status.success()); let status = child.wait()?; assert!(status.success()); Ok(()) } #[test] fn wait_group() -> Result<()> { let mut child = Command::new("powershell.exe") .arg("/C") .arg("echo hello") .group_spawn()?; let status = child.wait()?; assert!(status.success()); let status = child.wait()?; assert!(status.success()); Ok(()) } #[test] fn wait_with_output_normal() -> Result<()> { let child = Command::new("powershell.exe") .arg("/C") .arg("echo hello") .stdout(Stdio::piped()) .spawn()?; let output = child.wait_with_output()?; assert!(output.status.success()); assert_eq!(output.stdout, b"hello\r\n".to_vec()); assert_eq!(output.stderr, Vec::new()); Ok(()) } #[test] fn wait_with_output_group() -> Result<()> { let child = Command::new("powershell.exe") .arg("/C") .arg("echo hello") .stdout(Stdio::piped()) .group_spawn()?; let output = child.wait_with_output()?; assert!(output.status.success()); assert_eq!(output.stdout, b"hello\r\n".to_vec()); assert_eq!(output.stderr, Vec::new()); Ok(()) } #[test] fn id_same_as_inner_group() -> Result<()> { let mut child = Command::new("powershell.exe") .arg("/C") .arg("echo hello") .group_spawn()?; assert_eq!(child.id(), child.inner().id()); Ok(()) } command-group-2.1.0/tests/tokio_unix.rs000064400000000000000000000170251046102023000163010ustar 00000000000000#![cfg(all(unix, feature = "with-tokio"))] use command_group::{AsyncCommandGroup, Signal, UnixChildExt}; use std::{io::Result, os::unix::process::ExitStatusExt, process::Stdio, time::Duration}; use tokio::{ io::{AsyncReadExt, AsyncWriteExt}, process::Command, time::sleep, }; const DIE_TIME: Duration = Duration::from_millis(100); // each test has a _normal variant that uses the Tokio non-group API for comparison/debugging. #[tokio::test] async fn inner_read_stdout_normal() -> Result<()> { let mut child = Command::new("echo") .arg("hello") .stdout(Stdio::piped()) .spawn()?; let mut output = String::new(); if let Some(mut out) = child.stdout.take() { out.read_to_string(&mut output).await?; } assert_eq!(output.as_str(), "hello\n"); Ok(()) } #[tokio::test] async fn inner_read_stdout_group() -> Result<()> { let mut child = Command::new("echo") .arg("hello") .stdout(Stdio::piped()) .group_spawn()?; let mut output = String::new(); if let Some(mut out) = child.inner().stdout.take() { out.read_to_string(&mut output).await?; } assert_eq!(output.as_str(), "hello\n"); Ok(()) } #[tokio::test] async fn into_inner_write_stdin_normal() -> Result<()> { let mut child = Command::new("cat") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn()?; if let Some(mut din) = child.stdin.take() { din.write_all(b"hello").await?; } let mut output = String::new(); if let Some(mut out) = child.stdout.take() { out.read_to_string(&mut output).await?; } assert_eq!(output.as_str(), "hello"); Ok(()) } #[tokio::test] async fn into_inner_write_stdin_group() -> Result<()> { let mut child = Command::new("cat") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .group_spawn()? .into_inner(); if let Some(mut din) = child.stdin.take() { din.write_all(b"hello").await?; } let mut output = String::new(); if let Some(mut out) = child.stdout.take() { out.read_to_string(&mut output).await?; } assert_eq!(output.as_str(), "hello"); Ok(()) } #[tokio::test] async fn kill_and_try_wait_normal() -> Result<()> { let mut child = Command::new("yes").stdout(Stdio::null()).spawn()?; assert!(child.try_wait()?.is_none()); child.kill().await?; sleep(DIE_TIME).await; assert!(child.try_wait()?.is_some()); sleep(DIE_TIME).await; assert!(child.try_wait()?.is_some()); Ok(()) } #[tokio::test] async fn kill_and_try_wait_group() -> Result<()> { let mut child = Command::new("yes").stdout(Stdio::null()).group_spawn()?; assert!(child.try_wait()?.is_none()); child.kill()?; sleep(DIE_TIME).await; assert!(child.try_wait()?.is_some()); sleep(DIE_TIME).await; assert!(child.try_wait()?.is_some()); Ok(()) } #[tokio::test] async fn try_wait_twice_after_sigterm_normal() -> Result<()> { let mut child = Command::new("yes").stdout(Stdio::null()).spawn()?; assert!(child.try_wait()?.is_none(), "pre try_wait"); child.signal(Signal::SIGTERM)?; sleep(DIE_TIME).await; assert!(child.try_wait()?.is_some(), "first try_wait"); sleep(DIE_TIME).await; assert!(child.try_wait()?.is_some(), "second try_wait"); Ok(()) } #[tokio::test] async fn try_wait_twice_after_sigterm_group() -> Result<()> { let mut child = Command::new("yes").stdout(Stdio::null()).group_spawn()?; assert!(child.try_wait()?.is_none(), "pre try_wait"); child.signal(Signal::SIGTERM)?; sleep(DIE_TIME).await; assert!(child.try_wait()?.is_some(), "first try_wait"); sleep(DIE_TIME).await; assert!(child.try_wait()?.is_some(), "second try_wait"); Ok(()) } #[tokio::test] async fn wait_twice_after_sigterm_normal() -> Result<()> { let mut child = Command::new("yes").stdout(Stdio::null()).spawn()?; assert!(child.try_wait()?.is_none(), "pre try_wait"); child.signal(Signal::SIGTERM)?; let status = child.wait().await?; assert_eq!( status.signal(), Some(Signal::SIGTERM as i32), "first wait status" ); let status = child.wait().await?; assert_eq!( status.signal(), Some(Signal::SIGTERM as i32), "second wait status" ); Ok(()) } #[tokio::test] async fn wait_twice_after_sigterm_group() -> Result<()> { let mut child = Command::new("yes").stdout(Stdio::null()).group_spawn()?; assert!(child.try_wait()?.is_none(), "pre try_wait"); child.signal(Signal::SIGTERM)?; let status = child.wait().await?; assert_eq!( status.signal(), Some(Signal::SIGTERM as i32), "first wait status" ); let status = child.wait().await?; assert_eq!( status.signal(), Some(Signal::SIGTERM as i32), "second wait status" ); Ok(()) } #[tokio::test] async fn wait_after_die_normal() -> Result<()> { let mut child = Command::new("echo").stdout(Stdio::null()).spawn()?; sleep(DIE_TIME).await; let status = child.wait().await?; assert!(status.success()); Ok(()) } #[tokio::test] async fn wait_after_die_group() -> Result<()> { let mut child = Command::new("echo").stdout(Stdio::null()).group_spawn()?; sleep(DIE_TIME).await; let status = child.wait().await?; assert!(status.success()); Ok(()) } #[tokio::test] async fn try_wait_after_die_normal() -> Result<()> { let mut child = Command::new("echo").stdout(Stdio::null()).spawn()?; sleep(DIE_TIME).await; let status = child.try_wait()?; assert!(status.is_some()); assert!(status.unwrap().success()); Ok(()) } #[tokio::test] async fn try_wait_after_die_group() -> Result<()> { let mut child = Command::new("echo").stdout(Stdio::null()).group_spawn()?; sleep(DIE_TIME).await; let status = child.try_wait()?; assert!(status.is_some()); assert!(status.unwrap().success()); Ok(()) } #[tokio::test] async fn wait_normal() -> Result<()> { let mut command = Command::new("echo"); let mut child = command.spawn()?; let status = child.wait().await?; assert!(status.success()); let status = child.wait().await?; assert!(status.success()); Ok(()) } #[tokio::test] async fn wait_group() -> Result<()> { let mut command = Command::new("echo"); let mut child = command.group_spawn()?; let status = child.wait().await?; assert!(status.success()); let status = child.wait().await?; assert!(status.success()); Ok(()) } #[tokio::test] async fn wait_with_output_normal() -> Result<()> { let child = Command::new("echo") .arg("hello") .stdout(Stdio::piped()) .spawn()?; let output = child.wait_with_output().await?; assert!(output.status.success()); assert_eq!(output.stdout, b"hello\n".to_vec()); assert_eq!(output.stderr, Vec::new()); Ok(()) } #[tokio::test] async fn wait_with_output_group() -> Result<()> { let child = Command::new("echo") .arg("hello") .stdout(Stdio::piped()) .group_spawn()?; let output = child.wait_with_output().await?; assert!(output.status.success()); assert_eq!(output.stdout, b"hello\n".to_vec()); assert_eq!(output.stderr, Vec::new()); Ok(()) } #[tokio::test] async fn id_same_as_inner_group() -> Result<()> { let mut command = Command::new("echo"); let mut child = command.group_spawn()?; assert_eq!(child.id(), child.inner().id()); Ok(()) } #[tokio::test] async fn signal_normal() -> Result<()> { let mut child = Command::new("yes").stdout(Stdio::null()).spawn()?; child.signal(Signal::SIGCONT)?; sleep(DIE_TIME).await; assert!(child.try_wait()?.is_none(), "not exited with sigcont"); child.signal(Signal::SIGTERM)?; sleep(DIE_TIME).await; assert!(child.try_wait()?.is_some(), "exited with sigterm"); Ok(()) } #[tokio::test] async fn signal_group() -> Result<()> { let mut child = Command::new("yes").stdout(Stdio::null()).group_spawn()?; child.signal(Signal::SIGCONT)?; sleep(DIE_TIME).await; assert!(child.try_wait()?.is_none(), "not exited with sigcont"); child.signal(Signal::SIGTERM)?; sleep(DIE_TIME).await; assert!(child.try_wait()?.is_some(), "exited with sigterm"); Ok(()) } command-group-2.1.0/tests/tokio_windows.rs000064400000000000000000000124731046102023000170120ustar 00000000000000#![cfg(all(windows, feature = "with-tokio"))] use command_group::AsyncCommandGroup; use std::{io::Result, process::Stdio, time::Duration}; use tokio::{ io::{AsyncReadExt, AsyncWriteExt}, process::Command, time::sleep, }; const DIE_TIME: Duration = Duration::from_millis(1000); // each test has a _normal variant that uses the Tokio non-group API for comparison/debugging. #[tokio::test] async fn inner_read_stdout_normal() -> Result<()> { let mut child = Command::new("powershell.exe") .arg("/C") .arg("echo hello") .stdout(Stdio::piped()) .spawn()?; let mut output = String::new(); if let Some(mut out) = child.stdout.take() { out.read_to_string(&mut output).await?; } assert_eq!(output.as_str(), "hello\r\n"); Ok(()) } #[tokio::test] async fn inner_read_stdout_group() -> Result<()> { let mut child = Command::new("powershell.exe") .arg("/C") .arg("echo hello") .stdout(Stdio::piped()) .group_spawn()?; let mut output = String::new(); if let Some(mut out) = child.inner().stdout.take() { out.read_to_string(&mut output).await?; } assert_eq!(output.as_str(), "hello\r\n"); Ok(()) } #[tokio::test] async fn into_inner_write_stdin_normal() -> Result<()> { let mut child = Command::new("findstr") .arg("^") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn()?; if let Some(mut din) = child.stdin.take() { din.write_all(b"hello").await?; } let mut output = String::new(); if let Some(mut out) = child.stdout.take() { out.read_to_string(&mut output).await?; } assert_eq!(output.as_str(), "hello\r\n"); Ok(()) } #[tokio::test] async fn into_inner_write_stdin_group() -> Result<()> { let mut child = Command::new("findstr") .arg("^") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .group_spawn()? .into_inner(); if let Some(mut din) = child.stdin.take() { din.write_all(b"hello").await?; } let mut output = String::new(); if let Some(mut out) = child.stdout.take() { out.read_to_string(&mut output).await?; } assert_eq!(output.as_str(), "hello\r\n"); Ok(()) } #[tokio::test] async fn kill_and_try_wait_normal() -> Result<()> { let mut child = Command::new("powershell.exe") .arg("/C") .arg("pause") .spawn()?; assert!(child.try_wait()?.is_none()); child.kill().await?; sleep(DIE_TIME).await; assert!(child.try_wait()?.is_some()); sleep(DIE_TIME).await; assert!(child.try_wait()?.is_some()); Ok(()) } #[tokio::test] async fn kill_and_try_wait_group() -> Result<()> { let mut child = Command::new("powershell.exe") .arg("/C") .arg("pause") .group_spawn()?; assert!(child.try_wait()?.is_none()); child.kill()?; sleep(DIE_TIME).await; assert!(child.try_wait()?.is_some()); sleep(DIE_TIME).await; assert!(child.try_wait()?.is_some()); Ok(()) } #[tokio::test] async fn wait_after_die_normal() -> Result<()> { let mut child = Command::new("powershell.exe") .arg("/C") .arg("echo hello") .spawn()?; sleep(DIE_TIME).await; let status = child.wait().await?; assert!(status.success()); Ok(()) } #[tokio::test] async fn wait_after_die_group() -> Result<()> { let mut child = Command::new("powershell.exe") .arg("/C") .arg("echo hello") .group_spawn()?; sleep(DIE_TIME).await; let status = child.wait().await?; assert!(status.success()); Ok(()) } #[tokio::test] async fn try_wait_after_die_normal() -> Result<()> { let mut child = Command::new("powershell.exe") .arg("/C") .arg("echo hello") .spawn()?; sleep(DIE_TIME * 10).await; let status = child.try_wait()?; assert!(status.is_some()); assert!(status.unwrap().success()); Ok(()) } #[tokio::test] async fn try_wait_after_die_group() -> Result<()> { let mut child = Command::new("powershell.exe") .arg("/C") .arg("echo hello") .group_spawn()?; sleep(DIE_TIME * 10).await; let status = child.try_wait()?; assert!(status.is_some()); assert!(status.unwrap().success()); Ok(()) } #[tokio::test] async fn wait_normal() -> Result<()> { let mut child = Command::new("powershell.exe") .arg("/C") .arg("echo hello") .spawn()?; let status = child.wait().await?; assert!(status.success()); let status = child.wait().await?; assert!(status.success()); Ok(()) } #[tokio::test] async fn wait_group() -> Result<()> { let mut child = Command::new("powershell.exe") .arg("/C") .arg("echo hello") .group_spawn()?; let status = child.wait().await?; assert!(status.success()); let status = child.wait().await?; assert!(status.success()); Ok(()) } #[tokio::test] async fn wait_with_output_normal() -> Result<()> { let child = Command::new("powershell.exe") .arg("/C") .arg("echo hello") .stdout(Stdio::piped()) .spawn()?; let output = child.wait_with_output().await?; assert!(output.status.success()); assert_eq!(output.stdout, b"hello\r\n".to_vec()); assert_eq!(output.stderr, Vec::new()); Ok(()) } #[tokio::test] async fn wait_with_output_group() -> Result<()> { let child = Command::new("powershell.exe") .arg("/C") .arg("echo hello") .stdout(Stdio::piped()) .group_spawn()?; let output = child.wait_with_output().await?; assert!(output.status.success()); assert_eq!(output.stdout, b"hello\r\n".to_vec()); assert_eq!(output.stderr, Vec::new()); Ok(()) } #[tokio::test] async fn id_same_as_inner_group() -> Result<()> { let mut child = Command::new("powershell.exe") .arg("/C") .arg("echo hello") .group_spawn()?; assert_eq!(child.id(), child.inner().id()); Ok(()) }