├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── ci └── before_deploy.bash ├── lucid.svg └── src └── main.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: Continuous integration 4 | 5 | jobs: 6 | check: 7 | name: Check 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions-rs/toolchain@v1 12 | with: 13 | profile: minimal 14 | toolchain: stable 15 | override: true 16 | - uses: actions-rs/cargo@v1 17 | with: 18 | command: check 19 | 20 | test: 21 | name: Test Suite 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v2 25 | - uses: actions-rs/toolchain@v1 26 | with: 27 | profile: minimal 28 | toolchain: stable 29 | override: true 30 | - uses: actions-rs/cargo@v1 31 | with: 32 | command: test 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | cache: cargo 3 | matrix: 4 | include: 5 | # Stable channel. 6 | - os: linux 7 | rust: stable 8 | env: TARGET=x86_64-unknown-linux-gnu 9 | - os: linux 10 | rust: stable 11 | env: TARGET=x86_64-unknown-linux-musl 12 | 13 | addons: 14 | apt: 15 | packages: 16 | # needed to build deb packages 17 | - fakeroot 18 | 19 | env: 20 | global: 21 | # Default target on travis-ci. 22 | # Used as conditional check in the install stage 23 | - HOST=x86_64-unknown-linux-gnu 24 | # Used on the deployment script 25 | - PROJECT_NAME=lucid 26 | 27 | install: 28 | # prevent target re-add error from rustup 29 | - if [[ $TRAVIS_OS_NAME = linux && $HOST != $TARGET ]]; then rustup target add $TARGET; fi 30 | 31 | script: 32 | # Incorporate TARGET env var to the build and test process 33 | - cargo build --target $TARGET --verbose 34 | - cargo test --target $TARGET --verbose 35 | 36 | before_deploy: 37 | - bash ci/before_deploy.bash 38 | 39 | deploy: 40 | provider: releases 41 | # NOTE updating the `api_key.secure` 42 | # - go to: https://github.com/settings/tokens/new 43 | # - generate new token using `public_repo` scope 44 | # - encrypt it using: `travis encrypt API_KEY_HERE` 45 | # - paste the output below 46 | api_key: 47 | secure: "qhF3Lhab+3s1+/jBhiwsQxaHSnaaUMMgi0v270A1f2Z/hAcbmK87slZkN7nSWz4+zL+C37QwPoM+wuqNOjeDaxpmGyoYJjXhf9t0JIT7Bqi9HcVLxClDLquNIt90xNxvw8Biw8lnXxfqyM3tTktJ9TzcC8JLYQdxgScsAUEnQokTiyYoZAiHnbuDITtQVFh/5CS0kPbKSf1wbpzGzo4ZCKg6A7aVK+/8nYLXwCseSdtJ5iUZwgzt7+BuUv8kJSwojQZ5/WtfW6Pg6e6zHIfEbCSx+sTvjawWZnylL8sNd26kwY/HQR3hS6IQLFmxUhTpwnILuevVVmPcqTDBxrtFL1+KhtZkpA7aPW08qXEhlLA9/n3uk4V0hbl1NywgdEq3TQw3mpEadvIO0WuqMGFb6byXa+12ZhcMIye8jc37p/C5uAq3GdINxL4nsxOjhxP2GKxC6X99olyllw+/Hp2x0LZq1UynHQwiNntOza5zZifAPKHgAZCcnx8Cv+kBIcyo5f4EG2oyuiagcEkhweh+zseEeLh/X87cc43LH9gzh2aFAmVp/VirhT++78EnwlfsWf8yrzE7J1a580sU9Z3rtRjSuAjo7gd0XhPE1i9WMwZMUhmXyD+BAaFXRL+Xf98v3VojUfLAqnzJxQ4OPmBwXuUgxsWmsdq6Ao6wyjVrtoM=" 48 | # for uploading multiple files 49 | file_glob: true 50 | # NOTE explanation on each env variable 51 | # - PROJECT_NAME: name of the project, set on the `env.global` above 52 | # - TRAVIS_TAG: tag name that the build is being deployed for, usually the version number 53 | # - TARGET: target triple of the build 54 | file: 55 | - $PROJECT_NAME-$TRAVIS_TAG-$TARGET.* 56 | - $PROJECT_NAME*.deb 57 | # don't delete artifacts from previous stage 58 | skip_cleanup: true 59 | on: 60 | # deploy only if we push a tag 61 | tags: true 62 | # deploy only on stable channel that has TARGET env variable sets 63 | condition: $TRAVIS_RUST_VERSION = stable && $TARGET != "" 64 | 65 | notifications: 66 | email: 67 | on_success: never 68 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "atty" 7 | version = "0.2.14" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 10 | dependencies = [ 11 | "hermit-abi", 12 | "libc", 13 | "winapi", 14 | ] 15 | 16 | [[package]] 17 | name = "autocfg" 18 | version = "1.1.0" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 21 | 22 | [[package]] 23 | name = "bitflags" 24 | version = "1.3.2" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 27 | 28 | [[package]] 29 | name = "cfg-if" 30 | version = "1.0.0" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 33 | 34 | [[package]] 35 | name = "clap" 36 | version = "3.2.19" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "68d43934757334b5c0519ff882e1ab9647ac0258b47c24c4f490d78e42697fd5" 39 | dependencies = [ 40 | "atty", 41 | "bitflags", 42 | "clap_lex", 43 | "indexmap", 44 | "once_cell", 45 | "strsim", 46 | "termcolor", 47 | "terminal_size", 48 | "textwrap", 49 | ] 50 | 51 | [[package]] 52 | name = "clap_lex" 53 | version = "0.2.4" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" 56 | dependencies = [ 57 | "os_str_bytes", 58 | ] 59 | 60 | [[package]] 61 | name = "ctrlc" 62 | version = "3.2.3" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "1d91974fbbe88ec1df0c24a4f00f99583667a7e2e6272b2b92d294d81e462173" 65 | dependencies = [ 66 | "nix", 67 | "winapi", 68 | ] 69 | 70 | [[package]] 71 | name = "hashbrown" 72 | version = "0.11.2" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 75 | 76 | [[package]] 77 | name = "hermit-abi" 78 | version = "0.1.15" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9" 81 | dependencies = [ 82 | "libc", 83 | ] 84 | 85 | [[package]] 86 | name = "indexmap" 87 | version = "1.7.0" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" 90 | dependencies = [ 91 | "autocfg", 92 | "hashbrown", 93 | ] 94 | 95 | [[package]] 96 | name = "libc" 97 | version = "0.2.132" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" 100 | 101 | [[package]] 102 | name = "lucid" 103 | version = "0.3.0" 104 | dependencies = [ 105 | "clap", 106 | "ctrlc", 107 | "nix", 108 | ] 109 | 110 | [[package]] 111 | name = "memoffset" 112 | version = "0.6.4" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" 115 | dependencies = [ 116 | "autocfg", 117 | ] 118 | 119 | [[package]] 120 | name = "nix" 121 | version = "0.25.0" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb" 124 | dependencies = [ 125 | "autocfg", 126 | "bitflags", 127 | "cfg-if", 128 | "libc", 129 | "memoffset", 130 | "pin-utils", 131 | ] 132 | 133 | [[package]] 134 | name = "once_cell" 135 | version = "1.12.0" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" 138 | 139 | [[package]] 140 | name = "os_str_bytes" 141 | version = "6.0.0" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" 144 | 145 | [[package]] 146 | name = "pin-utils" 147 | version = "0.1.0" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 150 | 151 | [[package]] 152 | name = "strsim" 153 | version = "0.10.0" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 156 | 157 | [[package]] 158 | name = "termcolor" 159 | version = "1.1.2" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 162 | dependencies = [ 163 | "winapi-util", 164 | ] 165 | 166 | [[package]] 167 | name = "terminal_size" 168 | version = "0.1.17" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" 171 | dependencies = [ 172 | "libc", 173 | "winapi", 174 | ] 175 | 176 | [[package]] 177 | name = "textwrap" 178 | version = "0.15.0" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" 181 | dependencies = [ 182 | "terminal_size", 183 | ] 184 | 185 | [[package]] 186 | name = "winapi" 187 | version = "0.3.9" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 190 | dependencies = [ 191 | "winapi-i686-pc-windows-gnu", 192 | "winapi-x86_64-pc-windows-gnu", 193 | ] 194 | 195 | [[package]] 196 | name = "winapi-i686-pc-windows-gnu" 197 | version = "0.4.0" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 200 | 201 | [[package]] 202 | name = "winapi-util" 203 | version = "0.1.5" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 206 | dependencies = [ 207 | "winapi", 208 | ] 209 | 210 | [[package]] 211 | name = "winapi-x86_64-pc-windows-gnu" 212 | version = "0.4.0" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 215 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["David Peter "] 3 | categories = ["command-line-utilities"] 4 | description = "A mock-application for programs that spawn subprocesses." 5 | homepage = "https://github.com/sharkdp/lucid" 6 | license = "MIT/Apache-2.0" 7 | name = "lucid" 8 | readme = "README.md" 9 | repository = "https://github.com/sharkdp/lucid" 10 | version = "0.3.0" 11 | edition = "2021" 12 | 13 | [dependencies] 14 | nix = "0.25" 15 | 16 | [dependencies.ctrlc] 17 | version = "3.2" 18 | features = ["termination"] 19 | 20 | [dependencies.clap] 21 | version = "3" 22 | features = ["suggestions", "color", "wrap_help", "cargo"] 23 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy 2 | of this software and associated documentation files (the "Software"), to deal 3 | in the Software without restriction, including without limitation the rights 4 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 5 | copies of the Software, and to permit persons to whom the Software is 6 | furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all 9 | copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 | SOFTWARE. 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | lucid 2 | ===== 3 | 4 | [![Build Status](https://travis-ci.org/sharkdp/lucid.svg?branch=master)](https://travis-ci.org/sharkdp/lucid) 5 | 6 | A simple mock-application that can be used by other programs that work with child processes. 7 | 8 | `lucid` is similar to `sleep`, but has a few additional features that can be helpful 9 | when debugging applications that spawn subprocesses. 10 | 11 | ## Demo 12 | 13 | lucid demo 14 | 15 | 16 | ## Introduction 17 | 18 | Applications or scripts that handle child processes need to deal with a lot of different 19 | scenarios. 20 | 21 | There are really simple processes that successfully terminate after a short period of time: 22 | ``` bash 23 | lucid 2 24 | ``` 25 | 26 | Others also finish after some time, but fail with a **non-zero exit code**: 27 | ``` bash 28 | lucid 3 --exit-code=1 29 | ``` 30 | 31 | Some processes just **run forever** (but can be terminated via `SIGINT` or `SIGTERM`): 32 | ``` bash 33 | lucid 34 | ``` 35 | 36 | Others refuse to handle **termination signals** properly and just ignore them: 37 | ``` bash 38 | lucid 10 --no-interrupt 39 | ``` 40 | 41 | There are also processes that choose to **daemonize** themselves immediately: 42 | ``` bash 43 | lucid 10 --daemon 44 | ``` 45 | 46 | Many processes print a lot on **standard output**: 47 | ``` bash 48 | lucid 10 --verbose 49 | ``` 50 | 51 | While some others might generate **error messages**: 52 | ``` bash 53 | lucid 10 --stderr --verbose 54 | ``` 55 | 56 | ## Usage 57 | ``` 58 | USAGE: 59 | lucid [OPTIONS] [duration] 60 | 61 | OPTIONS: 62 | -c, --exit-code Terminate with the given exit code [default: 0] 63 | -d, --daemon Daemonize the process after launching 64 | -I, --no-interrupt Do not terminate when receiving SIGINT/SIGTERM signals 65 | -p, --prefix Prefix all messages with the given string [default: lucid] 66 | -v, --verbose Be noisy 67 | -q, --quiet Do not output anything 68 | -e, --stderr Print all messages to stderr 69 | -h, --help Prints help information 70 | -V, --version Prints version information 71 | 72 | ARGS: 73 | Sleep time in seconds. If no duration is given, the process will sleep forever. 74 | ``` 75 | 76 | ## Installation 77 | 78 | ### On Debian-based systems 79 | 80 | ``` bash 81 | wget "https://github.com/sharkdp/lucid/releases/download/v0.3.0/lucid_0.3.0_amd64.deb" 82 | sudo dpkg -i lucid_0.3.0_amd64.deb 83 | ``` 84 | 85 | ### On other distrubutions 86 | 87 | Check out the [release page](https://github.com/sharkdp/lucid/releases) for binary builds. 88 | 89 | ### Via cargo 90 | 91 | ``` 92 | cargo install lucid 93 | ``` 94 | -------------------------------------------------------------------------------- /ci/before_deploy.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Building and packaging for release 3 | 4 | set -ex 5 | 6 | build() { 7 | cargo build --target "$TARGET" --release --verbose 8 | } 9 | 10 | pack() { 11 | local tempdir 12 | local out_dir 13 | local package_name 14 | 15 | tempdir=$(mktemp -d 2>/dev/null || mktemp -d -t tmp) 16 | out_dir=$(pwd) 17 | package_name="$PROJECT_NAME-$TRAVIS_TAG-$TARGET" 18 | 19 | # create a "staging" directory 20 | mkdir "$tempdir/$package_name" 21 | 22 | # copying the main binary 23 | cp "target/$TARGET/release/$PROJECT_NAME" "$tempdir/$package_name/" 24 | strip "$tempdir/$package_name/$PROJECT_NAME" 25 | 26 | # readme and license 27 | cp README.md "$tempdir/$package_name" 28 | cp LICENSE-MIT "$tempdir/$package_name" 29 | cp LICENSE-APACHE "$tempdir/$package_name" 30 | 31 | # archiving 32 | pushd "$tempdir" 33 | tar czf "$out_dir/$package_name.tar.gz" "$package_name"/* 34 | popd 35 | rm -r "$tempdir" 36 | } 37 | 38 | make_deb() { 39 | local tempdir 40 | local architecture 41 | local version 42 | local dpkgname 43 | local conflictname 44 | 45 | case $TARGET in 46 | x86_64*) 47 | architecture=amd64 48 | ;; 49 | i686*) 50 | architecture=i386 51 | ;; 52 | *) 53 | echo "ERROR: unknown target" >&2 54 | return 1 55 | ;; 56 | esac 57 | version=${TRAVIS_TAG#v} 58 | if [[ $TARGET = *musl* ]]; then 59 | dpkgname=$PROJECT_NAME-musl 60 | conflictname=$PROJECT_NAME 61 | else 62 | dpkgname=$PROJECT_NAME 63 | conflictname=$PROJECT_NAME-musl 64 | fi 65 | 66 | tempdir=$(mktemp -d 2>/dev/null || mktemp -d -t tmp) 67 | 68 | # copy the main binary 69 | install -Dm755 "target/$TARGET/release/$PROJECT_NAME" "$tempdir/usr/bin/$PROJECT_NAME" 70 | strip "$tempdir/usr/bin/$PROJECT_NAME" 71 | 72 | # readme and license 73 | install -Dm644 README.md "$tempdir/usr/share/doc/$PROJECT_NAME/README.md" 74 | install -Dm644 LICENSE-MIT "$tempdir/usr/share/doc/$PROJECT_NAME/LICENSE-MIT" 75 | install -Dm644 LICENSE-APACHE "$tempdir/usr/share/doc/$PROJECT_NAME/LICENSE-APACHE" 76 | 77 | # Control file 78 | mkdir "$tempdir/DEBIAN" 79 | cat > "$tempdir/DEBIAN/control" < 85 | Architecture: $architecture 86 | Provides: $PROJECT_NAME 87 | Conflicts: $conflictname 88 | Description: A simple mock-application for programs that work with child processes. 89 | EOF 90 | 91 | fakeroot dpkg-deb --build "$tempdir" "${dpkgname}_${version}_${architecture}.deb" 92 | } 93 | 94 | 95 | main() { 96 | build 97 | pack 98 | if [[ $TARGET = *linux* ]]; then 99 | make_deb 100 | fi 101 | } 102 | 103 | main 104 | -------------------------------------------------------------------------------- /lucid.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 19 | 40 | 41 | 42 |                                                                                        ▶                                                                                      ▶ l                                                                                    ▶ lu                                                                                   ▶ luc                                                                                  ▶ luci                                                                                 ▶ lucid                                                                                ▶ lucid                                                                                ▶ lucid -                                                                              ▶ lucid -h                                                                                  <duration>    Sleep time in seconds. If no duration is given, the process will                                                                                       ~     ▶ lucid -h                                                                             lucid 0.1.0USAGE:    lucid [OPTIONS] [duration]OPTIONS:    -c--exit-code <CODE>    Terminate with the given exit code [default: 0]    -d--daemon              Daemonize the process after launching    -I--no-interrupt        Do not terminate when receiving SIGINT/SIGTERM signals    -p--prefix <PREFIX>     Prefix all messages with the given string [default:                              lucid]    -v--verbose             Be noisy    -q--quiet               Do not output anything    -e--stderr              Print all messages to stderr    -h--help                Prints help information    -V--version             Prints version informationARGS:    <duration>    Sleep time in seconds. If no duration is given, the process will                  sleep forever.▶ lucid 2                                                                              ▶ lucid 2                                                                              [lucid]: Going to sleep for 2.000s[lucid]: Woke up after 2.000s▶ lucid 2.                                                                             ▶ lucid 2.3                                                                            ▶ lucid 2.3                                                                            [lucid]: Going to sleep for 2.300s[lucid]: Woke up after 2.300s▶ lucid 2                                                                              ▶ lucid 2 -                                                                            ▶ lucid 2 --                                                                           ▶ lucid 2 --e                                                                          ▶ lucid 2 --ex                                                                         ▶ lucid 2 --exi                                                                        ▶ lucid 2 --exit                                                                       ▶ lucid 2 --exit-                                                                      ▶ lucid 2 --exit-c                                                                     ▶ lucid 2 --exit-cd                                                                    ▶ lucid 2 --exit-cde                                                                   ▶ lucid 2 --exit-c                                                                     ▶ lucid 2 --exit-co                                                                    ▶ lucid 2 --exit-cod                                                                   ▶ lucid 2 --exit-code                                                                  ▶ lucid 2 --exit-code                                                                  ▶ lucid 2 --exit-code 1                                                                ▶ lucid 2 --exit-code 1                                                                ~     ▶ lucid                                                                                [lucid]: Going to sleep forever^C ^C[lucid]: Caught termination signal - interrupting sleep.[lucid]: Woke up after 2.702s▶ lucid --                                                                             ▶ lucid --v                                                                            ▶ lucid --ve                                                                           ▶ lucid --ver                                                                          ▶ lucid --verb                                                                         ▶ lucid --verbo                                                                        ▶ lucid --verbos                                                                       ▶ lucid --verbose                                                                      ▶ lucid --verbose                                                                      [lucid]: getcwd() = "/home/shark"[lucid]: getpid() = 7657[lucid]: Still dreaming after 0.000s[lucid]: Still dreaming after 0.100s[lucid]: Still dreaming after 0.200s[lucid]: Still dreaming after 0.300s[lucid]: Still dreaming after 0.400s[lucid]: Still dreaming after 0.500s^C[lucid]: Still dreaming after 0.600s[lucid]: Caught termination signal - interrupting sleep.[lucid]: Woke up after 0.700s▶ lucid 4                                                                              ▶ lucid 4                                                                              ▶ lucid 4 -                                                                            ▶ lucid 4 --                                                                           ▶ lucid 4 --n                                                                          ▶ lucid 4 --no                                                                         ▶ lucid 4 --no-                                                                        ▶ lucid 4 --no-i                                                                       ▶ lucid 4 --no-in                                                                      ▶ lucid 4 --no-int                                                                     ▶ lucid 4 --no-inte                                                                    ▶ lucid 4 --no-inter                                                                   ▶ lucid 4 --no-interr                                                                  ▶ lucid 4 --no-interru                                                                 ▶ lucid 4 --no-interrup                                                                ▶ lucid 4 --no-interrupt                                                               ▶ lucid 4 --no-interrupt                                                               [lucid]: Going to sleep for 4.000s^C[lucid]: Ignoring termination signal.[lucid]: Woke up after 4.000s▶ lucid 3                                                                              ▶ lucid 3                                                                              ▶ lucid 3 -                                                                            ▶ lucid 3 --                                                                           ▶ lucid 3 --d                                                                          ▶ lucid 3 --da                                                                         ▶ lucid 3 --dae                                                                        ▶ lucid 3 --daem                                                                       ▶ lucid 3 --daemo                                                                      ▶ lucid 3 --daemon                                                                     ▶ lucid 3 --daemon                                                                     [lucid]: Going to sleep for 3.000s▶                                                                                      ▶ [lucid]: Woke up after 3.000s                                                         43 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Write}; 2 | use std::sync::atomic::{AtomicBool, Ordering}; 3 | use std::sync::Arc; 4 | use std::{thread, time}; 5 | 6 | use clap::{crate_name, crate_version, AppSettings, Arg, Command}; 7 | 8 | use nix::unistd; 9 | 10 | #[derive(Debug, PartialEq)] 11 | enum LucidError { 12 | DurationParseError, 13 | DurationNegative, 14 | FailedToDaemonize, 15 | } 16 | 17 | impl LucidError { 18 | fn message(&self) -> &str { 19 | match self { 20 | LucidError::DurationParseError => "Could not parse 'duration' argument", 21 | LucidError::DurationNegative => "Duration can not be negative", 22 | LucidError::FailedToDaemonize => "Failed to daemonize itself", 23 | } 24 | } 25 | } 26 | 27 | /// Determines how much information should be printed. 28 | #[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] 29 | enum VerbosityLevel { 30 | Quiet, 31 | Normal, 32 | Verbose, 33 | } 34 | 35 | type ExitCode = i32; 36 | 37 | struct OutputHandler<'a> { 38 | stdout: io::StdoutLock<'a>, 39 | stderr: io::StderrLock<'a>, 40 | prefix: &'a str, 41 | verbosity_level: VerbosityLevel, 42 | print_to_stderr: bool, 43 | } 44 | 45 | impl<'a> OutputHandler<'a> { 46 | fn new( 47 | stdout: io::StdoutLock<'a>, 48 | stderr: io::StderrLock<'a>, 49 | prefix: &'a str, 50 | verbosity_level: VerbosityLevel, 51 | print_to_stderr: bool, 52 | ) -> Self { 53 | OutputHandler { 54 | stdout, 55 | stderr, 56 | prefix, 57 | verbosity_level, 58 | print_to_stderr, 59 | } 60 | } 61 | 62 | fn print(&mut self, msg: &str) { 63 | match self.verbosity_level { 64 | VerbosityLevel::Verbose | VerbosityLevel::Normal => self.print_with_prefix(msg), 65 | _ => {} 66 | } 67 | } 68 | 69 | fn print_verbose(&mut self, msg: &str) { 70 | if self.verbosity_level == VerbosityLevel::Verbose { 71 | self.print_with_prefix(msg) 72 | } 73 | } 74 | 75 | fn print_with_prefix(&mut self, msg: &str) { 76 | let mut handle: Box = if self.print_to_stderr { 77 | Box::new(&mut self.stderr) 78 | } else { 79 | Box::new(&mut self.stdout) 80 | }; 81 | writeln!(handle, "[{}]: {}", self.prefix, msg).ok(); 82 | } 83 | } 84 | 85 | type Result = std::result::Result; 86 | 87 | fn duration_as_str(duration: &time::Duration) -> String { 88 | format!("{}.{:03}s", duration.as_secs(), duration.subsec_millis()) 89 | } 90 | 91 | fn duration_from_float(duration_sec: f64) -> Result { 92 | if duration_sec < 0.0 { 93 | return Err(LucidError::DurationNegative); 94 | } 95 | 96 | let secs = duration_sec.floor() as u64; 97 | let millisecs = ((duration_sec - secs as f64) * 1e3).round() as u64; 98 | 99 | Ok(time::Duration::from_millis(secs * 1000 + millisecs)) 100 | } 101 | 102 | fn run() -> Result { 103 | let app = Command::new(crate_name!()) 104 | .setting(AppSettings::DeriveDisplayOrder) 105 | .version(crate_version!()) 106 | .arg(Arg::new("duration").help( 107 | "Sleep time in seconds. If no duration is given, \ 108 | the process will sleep forever.", 109 | )) 110 | .arg( 111 | Arg::new("ignored") 112 | .help("Additional arguments are ignored") 113 | .hide(true) 114 | .multiple_occurrences(true), 115 | ) 116 | .arg( 117 | Arg::new("exit-code") 118 | .long("exit-code") 119 | .short('c') 120 | .takes_value(true) 121 | .value_name("CODE") 122 | .allow_hyphen_values(true) 123 | .default_value("0") 124 | .help("Terminate with the given exit code"), 125 | ) 126 | .arg( 127 | Arg::new("daemon") 128 | .long("daemon") 129 | .short('d') 130 | .help("Daemonize the process after launching"), 131 | ) 132 | .arg( 133 | Arg::new("no-interrupt") 134 | .long("no-interrupt") 135 | .short('I') 136 | .help("Do not terminate when receiving SIGINT/SIGTERM signals"), 137 | ) 138 | .arg( 139 | Arg::new("prefix") 140 | .long("prefix") 141 | .short('p') 142 | .takes_value(true) 143 | .value_name("PREFIX") 144 | .default_value("lucid") 145 | .help("Prefix all messages with the given string"), 146 | ) 147 | .arg( 148 | Arg::new("verbose") 149 | .long("verbose") 150 | .short('v') 151 | .help("Be noisy"), 152 | ) 153 | .arg( 154 | Arg::new("quiet") 155 | .long("quiet") 156 | .short('q') 157 | .conflicts_with("verbose") 158 | .help("Do not output anything"), 159 | ) 160 | .arg( 161 | Arg::new("stderr") 162 | .long("stderr") 163 | .short('e') 164 | .help("Print all messages to stderr"), 165 | ); 166 | 167 | let matches = app.get_matches(); 168 | 169 | let sleeping_duration = match matches.value_of("duration") { 170 | None => None, 171 | Some(duration) => Some( 172 | duration 173 | .parse::() 174 | .map_err(|_| LucidError::DurationParseError) 175 | .and_then(duration_from_float)?, 176 | ), 177 | }; 178 | 179 | let verbosity_level = if matches.is_present("verbose") { 180 | VerbosityLevel::Verbose 181 | } else if matches.is_present("quiet") { 182 | VerbosityLevel::Quiet 183 | } else { 184 | VerbosityLevel::Normal 185 | }; 186 | 187 | let no_interrupt = matches.is_present("no-interrupt"); 188 | 189 | let prefix = matches.value_of("prefix").unwrap_or("lucid"); 190 | 191 | let exit_code = matches 192 | .value_of("exit-code") 193 | .and_then(|c| c.parse::().ok()) 194 | .unwrap_or(0i32); 195 | 196 | let stdout = io::stdout(); 197 | let stderr = io::stderr(); 198 | let mut output = OutputHandler::new( 199 | stdout.lock(), 200 | stderr.lock(), 201 | prefix, 202 | verbosity_level, 203 | matches.is_present("stderr"), 204 | ); 205 | 206 | if matches.is_present("daemon") { 207 | output.print_verbose("Daemonizing.."); 208 | unistd::daemon(true, true).map_err(|_| LucidError::FailedToDaemonize)?; 209 | } 210 | 211 | // Print status information 212 | output.print_verbose(&format!( 213 | "getcwd() = {}", 214 | unistd::getcwd() 215 | .map(|p| p.to_string_lossy().into_owned()) 216 | .map(|s| format!("\"{}\"", s)) 217 | .unwrap_or_else(|_| "".into()) 218 | )); 219 | output.print_verbose(&format!("getpid() = {}", unistd::getpid())); 220 | 221 | match sleeping_duration { 222 | None => { 223 | output.print("Going to sleep forever"); 224 | } 225 | Some(sleeping_duration) => { 226 | output.print(&format!( 227 | "Going to sleep for {}", 228 | duration_as_str(&sleeping_duration) 229 | )); 230 | } 231 | } 232 | 233 | let start_time = time::Instant::now(); 234 | 235 | // Set up signal handler 236 | let running = Arc::new(AtomicBool::new(true)); 237 | let r = running.clone(); 238 | ctrlc::set_handler(move || { 239 | r.store(false, Ordering::SeqCst); 240 | }) 241 | .expect("Error while setting up signal handler."); 242 | 243 | // Main loop 244 | let cycle_time = time::Duration::from_millis(100); 245 | loop { 246 | let since_start = start_time.elapsed(); 247 | 248 | if !running.load(Ordering::SeqCst) { 249 | if no_interrupt { 250 | output.print("Ignoring termination signal."); 251 | running.store(true, Ordering::SeqCst); 252 | } else { 253 | output.print("Caught termination signal - interrupting sleep."); 254 | break; 255 | } 256 | } 257 | 258 | if let Some(sleeping_duration) = sleeping_duration { 259 | if since_start >= sleeping_duration { 260 | break; 261 | } 262 | 263 | if since_start + cycle_time > sleeping_duration { 264 | if sleeping_duration > since_start { 265 | thread::sleep(sleeping_duration - since_start); 266 | } else { 267 | break; 268 | } 269 | } else { 270 | thread::sleep(cycle_time); 271 | } 272 | } else { 273 | thread::sleep(cycle_time); 274 | } 275 | 276 | output.print_verbose(&format!( 277 | "Still dreaming after {}", 278 | duration_as_str(&since_start) 279 | )); 280 | } 281 | 282 | output.print(&format!( 283 | "Woke up after {}", 284 | duration_as_str(&start_time.elapsed()) 285 | )); 286 | 287 | Ok(exit_code) 288 | } 289 | 290 | fn main() { 291 | let result = run(); 292 | match result { 293 | Err(err) => { 294 | eprintln!("Error: {}", err.message()); 295 | std::process::exit(1); 296 | } 297 | Ok(exit_code) => { 298 | std::process::exit(exit_code); 299 | } 300 | } 301 | } 302 | 303 | #[test] 304 | fn test_duration_from_float() { 305 | assert_eq!(Ok(time::Duration::from_secs(14)), duration_from_float(14.0)); 306 | assert_eq!( 307 | Ok(time::Duration::from_secs(14)), 308 | duration_from_float(14.0001) 309 | ); 310 | 311 | assert_eq!(Ok(time::Duration::from_secs(0)), duration_from_float(0.0)); 312 | 313 | assert_eq!( 314 | Ok(time::Duration::from_millis(12345)), 315 | duration_from_float(12.345) 316 | ); 317 | assert_eq!( 318 | Ok(time::Duration::from_millis(12345)), 319 | duration_from_float(12.3454) 320 | ); 321 | assert_eq!( 322 | Ok(time::Duration::from_millis(12346)), 323 | duration_from_float(12.3456) 324 | ); 325 | 326 | assert_eq!( 327 | Ok(time::Duration::from_millis(1)), 328 | duration_from_float(0.001) 329 | ); 330 | assert_eq!( 331 | Ok(time::Duration::from_millis(1100)), 332 | duration_from_float(1.1) 333 | ); 334 | 335 | assert_eq!(Err(LucidError::DurationNegative), duration_from_float(-1.2)); 336 | } 337 | 338 | #[test] 339 | fn test_verbosity_level() { 340 | assert!(VerbosityLevel::Normal > VerbosityLevel::Quiet); 341 | assert!(VerbosityLevel::Verbose > VerbosityLevel::Normal); 342 | assert!(VerbosityLevel::Verbose > VerbosityLevel::Quiet); 343 | } 344 | --------------------------------------------------------------------------------