├── .github └── workflows │ ├── docs.yml │ └── release-pipeline.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── banner.png ├── docs ├── assets │ ├── banner.png │ └── logo.png ├── contributing.md ├── features │ ├── bits.md │ ├── cpu.md │ ├── csv.md │ ├── disk.md │ ├── env.md │ ├── file.md │ ├── http.md │ ├── ip.md │ ├── json.md │ ├── math.md │ ├── mem.md │ ├── network.md │ ├── port.md │ ├── process.md │ ├── text.md │ └── time.md ├── index.md ├── quickstart.md └── support.md ├── install.ps1 ├── install.sh ├── mkdocs.yml ├── src ├── bits_commands.rs ├── bits_utils.rs ├── cpu_commands.rs ├── csv_commands.rs ├── csv_utils.rs ├── disk_commands.rs ├── env_commands.rs ├── file_commands.rs ├── format_utils.rs ├── graph_utils.rs ├── http_commands.rs ├── input_utils.rs ├── ip_commands.rs ├── ip_utils.rs ├── json_commands.rs ├── lib.rs ├── main.rs ├── math_commands.rs ├── math_utils.rs ├── mem_commands.rs ├── network_commands.rs ├── port_commands.rs ├── process_commands.rs ├── text_commands.rs └── time_commands.rs ├── tests ├── csv_sql_search_tests.rs └── math_exp_tests.rs └── vgsales.csv /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: deploy mkdocs 2 | on: 3 | push: 4 | branches: 5 | - main 6 | permissions: 7 | contents: write 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: Configure Git Credentials 14 | run: | 15 | git config user.name github-actions[bot] 16 | git config user.email 41898282+github-actions[bot]@users.noreply.github.com 17 | - uses: actions/setup-python@v5 18 | with: 19 | python-version: 3.x 20 | - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV 21 | - uses: actions/cache@v4 22 | with: 23 | key: mkdocs-material-${{ env.cache_id }} 24 | path: .cache 25 | restore-keys: | 26 | mkdocs-material- 27 | - run: pip install mkdocs-material 28 | - run: mkdocs gh-deploy --force -------------------------------------------------------------------------------- /.github/workflows/release-pipeline.yml: -------------------------------------------------------------------------------- 1 | name: Release Pipeline 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | permissions: 7 | contents: write 8 | 9 | jobs: 10 | # Build for Windows 11 | build_windows: 12 | runs-on: windows-latest # Use Windows runner 13 | 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v2 17 | 18 | - name: Set up Rust 19 | uses: actions-rs/toolchain@v1 20 | with: 21 | toolchain: stable 22 | override: true 23 | components: clippy 24 | 25 | - name: Build for Windows 26 | run: | 27 | cargo build --release 28 | mkdir -p output/win 29 | cp target/release/aid.exe output/win/aid-win.exe 30 | 31 | - name: Upload Windows artifact 32 | uses: actions/upload-artifact@v3 33 | with: 34 | name: windows-artifact 35 | path: output/win/aid-win.exe 36 | 37 | # Build for Linux x86_64 38 | build_linux: 39 | runs-on: ubuntu-latest 40 | 41 | steps: 42 | - name: Checkout code 43 | uses: actions/checkout@v2 44 | 45 | - name: Set up Rust 46 | uses: actions-rs/toolchain@v1 47 | with: 48 | toolchain: stable 49 | override: true 50 | components: clippy 51 | 52 | - name: Build for Linux 53 | run: | 54 | cargo build --release 55 | mkdir -p output/linux 56 | cp target/release/aid output/linux/aid-linux 57 | 58 | - name: Upload Linux artifact 59 | uses: actions/upload-artifact@v3 60 | with: 61 | name: linux-artifact 62 | path: output/linux/aid-linux 63 | 64 | # Build for macOS x86_64 65 | build_macos: 66 | runs-on: macos-latest 67 | 68 | steps: 69 | - name: Checkout code 70 | uses: actions/checkout@v2 71 | 72 | - name: Set up Rust 73 | uses: actions-rs/toolchain@v1 74 | with: 75 | toolchain: stable 76 | override: true 77 | components: clippy 78 | 79 | - name: Build for macOS 80 | run: | 81 | cargo build --release 82 | mkdir -p output/osx 83 | cp target/release/aid output/osx/aid-mac 84 | 85 | - name: Upload macOS artifact 86 | uses: actions/upload-artifact@v3 87 | with: 88 | name: macos-artifact 89 | path: output/osx/aid-mac 90 | 91 | # Build for macOS ARM (Apple Silicon) 92 | build_macos_arm: 93 | runs-on: macos-latest 94 | 95 | steps: 96 | - name: Checkout code 97 | uses: actions/checkout@v2 98 | 99 | - name: Set up Rust 100 | uses: actions-rs/toolchain@v1 101 | with: 102 | toolchain: stable 103 | override: true 104 | target: aarch64-apple-darwin # Add macOS ARM target 105 | components: clippy 106 | 107 | - name: Build for macOS ARM 108 | run: | 109 | cargo build --release --target aarch64-apple-darwin 110 | mkdir -p output/osx_arm 111 | cp target/aarch64-apple-darwin/release/aid output/osx_arm/aid-mac-arm 112 | 113 | - name: Upload macOS ARM artifact 114 | uses: actions/upload-artifact@v3 115 | with: 116 | name: macos-arm-artifact 117 | path: output/osx_arm/aid-mac-arm 118 | 119 | # Create a single GitHub release with all artifacts 120 | create_release: 121 | runs-on: ubuntu-latest 122 | needs: [build_windows, build_linux, build_macos, build_macos_arm] 123 | 124 | steps: 125 | - name: Checkout code 126 | uses: actions/checkout@v2 127 | 128 | # Download artifacts from previous jobs 129 | - name: Download Windows artifact 130 | uses: actions/download-artifact@v3 131 | with: 132 | name: windows-artifact 133 | 134 | - name: Download Linux artifact 135 | uses: actions/download-artifact@v3 136 | with: 137 | name: linux-artifact 138 | 139 | - name: Download macOS artifact 140 | uses: actions/download-artifact@v3 141 | with: 142 | name: macos-artifact 143 | 144 | - name: Download macOS ARM artifact 145 | uses: actions/download-artifact@v3 146 | with: 147 | name: macos-arm-artifact 148 | 149 | - name: Generate release tag 150 | id: tag 151 | run: | 152 | VERSION=0.1.11 153 | echo "::set-output name=release_tag::aid-${VERSION}" 154 | 155 | - name: Create GitHub release 156 | uses: softprops/action-gh-release@v1 157 | env: 158 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 159 | with: 160 | tag_name: ${{ steps.tag.outputs.release_tag }} 161 | files: | 162 | aid-win.exe 163 | aid-linux 164 | aid-mac 165 | aid-mac-arm 166 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | 16 | # RustRover 17 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 18 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 19 | # and can be added to the global gitignore or merged into this file. For a more nuclear 20 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 21 | #.idea/ 22 | req.json 23 | test.csv 24 | data.csv 25 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aid" 3 | version = "0.1.11" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | clap = { version = "4.5.20", features = ["derive"] } 8 | comfy-table = "7.1.1" 9 | num-traits = "0.2.19" 10 | ping = "0.5.2" 11 | rand = "0.8.5" 12 | reqwest = "0.12.8" 13 | serde = "1.0.210" 14 | serde_derive = "1.0.210" 15 | serde_json = "1.0.129" 16 | socket2 = "0.5.7" 17 | sysinfo = "0.32.0" 18 | tokio = { version = "1", features = ["full"] } 19 | csv = "1.3.0" 20 | nom = "7.1.3" 21 | regex = "1.11.0" 22 | base64 = "0.22.1" 23 | md-5 = "0.10.5" 24 | sha2 = "0.10.6" 25 | sha1 = "0.10.5" 26 | time = { version = "0.3", features = ["macros", "formatting", "local-offset"] } 27 | walkdir = "2.5.0" 28 | zip = "2.2.0" 29 | cron-descriptor = "=0.1.0" 30 | chrono = "0.4.38" 31 | uuid = { version = "1.3", features = ["v4"] } 32 | percent-encoding = "2.3.1" 33 | 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |

3 | 4 |
5 |

6 | A CLI toolkit featuring a variety of helpful utilities. 7 | 8 | ``` 9 | aid math plot --start -20 --end 20 --step 0.5 --exp "x * sin(1 - x)" 10 | | * 11 | * | ** 12 | *** | ** 13 | * * | *** 14 | * * | ** * ** 15 | * * ** | *** * * 16 | * * *** | * * * * 17 | ** * ** * | *** * * * * 18 | * * * * *** | * * * * * * 19 | * * * ** * ** | ** * * ** * * 20 | --*---*---*---*--**--******-**---*---*---*--*---*- 21 | * * ** * * | *** * * * * ** 22 | * * * **** | * * * * 23 | * * * ** | * * * * 24 | ** * * | *** ** * 25 | * *** | *** 26 | * ** | ** 27 | * | ** 28 | ** | 29 | ** | 30 | ``` 31 | ## Read the [docs](https://timmoth.github.io/aid-cli/) 32 | for all supported commands, parameters, and examples 33 | 34 | ## Installation 35 | 36 | Manual installation is simple, just download the release and add it to your PATH environment variable, if you'd like an even easier way i've added scripts to do it for you. 37 | 38 | ### Linux / Mac (apple silicon supported) 39 | ``` 40 | wget https://raw.githubusercontent.com/Timmoth/aid-cli/refs/heads/main/install.sh 41 | chmod +x install.sh 42 | sudo ./install.sh 43 | ``` 44 | ### Windows (powershell) 45 | ``` 46 | Invoke-WebRequest -Uri https://raw.githubusercontent.com/Timmoth/aid-cli/refs/heads/main/install.ps1 -OutFile install.ps1 47 | Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -Force 48 | .\install.ps1 49 | ``` 50 | 51 | ### Releases 52 | [Download the latest release](https://github.com/Timmoth/aid-cli/releases) 53 | 54 | ## Top level commands: 55 | ``` 56 | | command | description | 57 | | ----------- | --------------------------------------------------------- | 58 | | aid http | HTTP functions | 59 | | aid ip | IP information / scanning | 60 | | aid port | Port information / scanning | 61 | | aid cpu | System cpu information | 62 | | aid mem | System memory information | 63 | | aid disk | System disk information | 64 | | aid network | System network information | 65 | | aid json | JSON parsing / extraction functions | 66 | | aid csv | CSV search / transformation functions | 67 | | aid text | Text manipulation functions | 68 | | aid file | File info functions | 69 | | aid time | Time related functions | 70 | | aid bits | Bit manipulation functions | 71 | | aid math | Math functions | 72 | | aid process | Process monitoring functions | 73 | | aid env | Environment information | 74 | | aid help | Print this message or the help of the given subcommand(s) | 75 | ``` 76 | ## All commands: 77 | [More info](https://timmoth.github.io/aid-cli/) 78 | ``` 79 | | version | command | description | 80 | | ---------- | ---------------------- | ---------------------------------------------------------------------- | 81 | | [u] 0.1.3 | aid http req | Make a HTTP request | 82 | | [u] 0.1.3 | aid http serve | Start a dummy HTTP server | 83 | | [u] 0.1.3 | aid ip local | Show my local IP address | 84 | | [u] 0.1.3 | aid ip public | Show my public IP address | 85 | | [u] 0.1.3 | aid ip scan | Scan a specified IP address subnet for active ip addresses | 86 | | [u] 0.1.3 | aid ip status | Try to connect to the specified IP address | 87 | | [u] 0.1.3 | aid port status | Check if the specified port is 'open' or 'closed'. | 88 | | [u] 0.1.3 | aid port scan | Scan for open ports on a specified IP address | 89 | | [u] 0.1.3 | aid cpu info | Show CPU information | 90 | | [u] 0.1.6 | aid cpu usage | Monitor CPU usage | 91 | | [u] 0.1.6 | aid mem usage | Monitor memory usage | 92 | | [u] 0.1.3 | aid disk info | Show disk information | 93 | | [u] 0.1.3 | aid network info | Show network information | 94 | | [u] 0.1.3 | aid network usage | Display network usage | 95 | | [a] 0.1.7 | aid process usage | Display process usage | 96 | | [u] 0.1.3 | aid json extract | Extract a property from JSON data | 97 | | [u] 0.1.3 | aid json jwt-decode | Decode a JWT | 98 | | [u] 0.1.3 | aid csv search | Sql search over csv | 99 | | [u] 0.1.3 | aid text base64-encode | encodes a base64 string | 100 | | [u] 0.1.3 | aid text base64-decode | decodes a base64 string | 101 | | [a] 0.1.10 | aid text url-encode | url encodes a string | 102 | | [a] 0.1.10 | aid text url-decode | decodes a url encoded string | 103 | | [u] 0.1.3 | aid text lines | reads and prints lines from a text file | 104 | | [a] 0.1.10 | aid text guid | Generates a random GUID | 105 | | [a] 0.1.11 | aid text replace | Find and replace text using a regex pattern in the given input string. | 106 | | [a] 0.1.11 | aid text count | Count occurrences of a regex pattern in the given input string. | 107 | | [u] 0.1.3 | aid file info | prints file metadata | 108 | | [u] 0.1.3 | aid file md5 | calculates the files Md5 checksum | 109 | | [u] 0.1.3 | aid file sha1 | calculates the files Sha1 checksum | 110 | | [u] 0.1.3 | aid file sha256 | calculates the files Sha256 checksum | 111 | | [a] 0.1.4 | aid file zip | zips the files in the source directory | 112 | | [u] 0.1.9 | aid time unix | Display unix timestamp | 113 | | [u] 0.1.9 | aid time dt | Display the datetime | 114 | | [a] 0.1.9 | aid time chron | Describes a chron job | 115 | | [a] 0.1.9 | aid time count-down | Starts a countdown timer | 116 | | [a] 0.1.8 | aid bits eval | Bitwise expression evaluation / conversion / information | 117 | | [u] 0.1.10 | aid math eval | Evaluates a math expression | 118 | | [u] 0.1.10 | aid math plot | Plot a math expression | 119 | | [a] 0.1.10 | aid env vars | Filter / Display environment variables | 120 | 121 | [a] added in version x.x.x 122 | [u] updated in version x.x.x 123 | [p] patched in version x.x.x 124 | [d] deprecated in version x.x.x 125 | ``` 126 | 127 | ### Build 128 | If you'd like to build the latest version from source: 129 | ``` 130 | //Install rust https://www.rust-lang.org/tools/install 131 | git clone https://github.com/Timmoth/aid-cli 132 | cd aid-cli 133 | cargo build --release 134 | .\target\release\aid.exe 135 | ``` 136 | -------------------------------------------------------------------------------- /banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timmoth/aid-cli/dffe698894e09c95d134ca3db96ca46865d09870/banner.png -------------------------------------------------------------------------------- /docs/assets/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timmoth/aid-cli/dffe698894e09c95d134ca3db96ca46865d09870/docs/assets/banner.png -------------------------------------------------------------------------------- /docs/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timmoth/aid-cli/dffe698894e09c95d134ca3db96ca46865d09870/docs/assets/logo.png -------------------------------------------------------------------------------- /docs/contributing.md: -------------------------------------------------------------------------------- 1 | ## Contributing 🙏 2 | 3 | Contributions are welcome! Here’s how you can get involved: 4 | 5 | 1. **Fork the repository**: Click the "Fork" button at the top right of this page. 6 | 2. **Clone your fork**: 7 | ```bash 8 | git clone https://github.com/Timmoth/aid-cli.git 9 | ``` 10 | 3. **Create a branch**: Make your changes in a new branch. 11 | ```bash 12 | git checkout -b my-feature-branch 13 | ``` 14 | 4. **Commit your changes**: 15 | ```bash 16 | git commit -m 'Add some feature' 17 | ``` 18 | 5. **Push to the branch**: 19 | ```bash 20 | git push origin my-feature-branch 21 | ``` 22 | 6. **Open a pull request**: Describe your changes and submit your PR. 23 | -------------------------------------------------------------------------------- /docs/features/bits.md: -------------------------------------------------------------------------------- 1 | ### aid bits eval 2 | ``` 3 | aid bits eval Evaluates a bitwise expression, converts base, visualize binary / display info 4 | -i, --info Output the bitboard representation. 5 | -c, --chess Output the chess bitboard representation. 6 | -b, --bin Output the result in binary. 7 | --hex Output the result in hex. 8 | 9 | -----input----- 10 | aid bits eval 0b1001 11 | -----output----- 12 | 9 13 | 14 | -----input----- 15 | aid bits eval --hex 0b10101001 16 | -----output----- 17 | a9 18 | 19 | -----input----- 20 | aid bits eval --bin 0xa9 21 | -----output----- 22 | 10101001 23 | 24 | -----input----- 25 | aid bits eval --bin '0xa9 << 2 | (0b1010 & 7)' 26 | -----output----- 27 | 1010100110 28 | 29 | -----input----- 30 | aid bits eval --info '!((0b11110000 ^ 0b00001111) << 8)' 31 | -----output----- 32 | FF | 1 1 1 1 1 1 1 1 33 | FF | 1 1 1 1 1 1 1 1 34 | FF | 1 1 1 1 1 1 1 1 35 | FF | 1 1 1 1 1 1 1 1 36 | FF | 1 1 1 1 1 1 1 1 37 | FF | 1 1 1 1 1 1 1 1 38 | 00 | 0 0 0 0 0 0 0 0 39 | FF | 1 1 1 1 1 1 1 1 40 | ----------------------- 41 | 7 6 5 4 3 2 1 0 42 | dec: 18446744073709486335 43 | hex: FFFFFFFFFFFF00FF 44 | bin: 1111111111111111111111111111111111111111111111110000000011111111 45 | lsb: 0 46 | msb: 63 47 | set bits: 56 48 | 49 | -----input----- 50 | aid bits eval --chess '!(0xFEFEFEFEFEFEFEFE << 8)' 51 | -----output----- 52 | 8 | 1 0 0 0 0 0 0 0 53 | 7 | 1 0 0 0 0 0 0 0 54 | 6 | 1 0 0 0 0 0 0 0 55 | 5 | 1 0 0 0 0 0 0 0 56 | 4 | 1 0 0 0 0 0 0 0 57 | 3 | 1 0 0 0 0 0 0 0 58 | 2 | 1 0 0 0 0 0 0 0 59 | 1 | 1 1 1 1 1 1 1 1 60 | ----------------------- 61 | A B C D E F G H 62 | dec: 72340172838076927 63 | hex: 1010101010101FF 64 | bin: 100000001000000010000000100000001000000010000000111111111 65 | lsb: 0 66 | msb: 56 67 | set bits: 15 68 | ``` -------------------------------------------------------------------------------- /docs/features/cpu.md: -------------------------------------------------------------------------------- 1 | ### aid cpu info 2 | ``` 3 | aid cpu info Show CPU information 4 | -j, --json Output CPU information in JSON format. 5 | 6 | -----input----- 7 | aid cpu info 8 | 9 | -----output----- 10 | AMD Ryzen 9 5900HX with Radeon Graphics 11 | ``` 12 | 13 | ### aid cpu usage 14 | ``` 15 | aid cpu usage Monitor CPU usage 16 | -w, --watch Continuously monitor CPU usage. 17 | -j, --json Output CPU usage in JSON format. 18 | -p, --plot Plot total % cpu usage. 19 | 20 | -----input----- 21 | aid cpu usage -w -p 22 | 23 | -----output----- 24 | total: 6.0%, cpus: 18.6%, 2.3%, 12.7%, 1.6%, 11.2%, 7.0%, 8.7%, 1.5%, 6.5%, 1.4%, 5.0%, 1.9%, 6.1%, 1.9%, 3.7%, 5.4% 25 | plot: total cpu % 26 | X range: [27, 56] 27 | Y range: [4.8092117, 23.36589] 28 | * 29 | 30 | * 31 | * 32 | * 33 | 34 | * * 35 | * * 36 | * 37 | * * 38 | * 39 | *** * * 40 | * * * * 41 | ** * * 42 | * * **** * 43 | ** * ***** 44 | ***** ** * ** * 45 | * ** * *** * ** * 46 | **** ** ** * * ** ** *** 47 | ** *** ************** 48 | ``` -------------------------------------------------------------------------------- /docs/features/csv.md: -------------------------------------------------------------------------------- 1 | ### aid csv search 2 | ``` 3 | aid csv search Sql search over csv 4 | -s, --sql Sql query e.g SELECT 'first name',age FROM people.csv WHERE age >= 25 AND age < 30 ORDER BY 'age' ASC. 5 | -o, --output Output file path. 6 | 7 | -----input----- 8 | aid csv search -s "csv search -s "SELECT Platform,COUNT(Name) FROM vgsales.csv GROUP BY Platform ORDER BY COUNT(Name) DESC" 9 | -----output----- 10 | Platform,COUNT(Name) 11 | DS,2163 12 | PS2,2161 13 | PS3,1329 14 | Wii,1325 15 | X360,1265 16 | PSP,1213 17 | PS,1196 18 | PC,960 19 | XB,824 20 | GBA,822 21 | GC,556 22 | 3DS,509 23 | PSV,413 24 | PS4,336 25 | ``` 26 | -------------------------------------------------------------------------------- /docs/features/disk.md: -------------------------------------------------------------------------------- 1 | ### aid disk info 2 | ``` 3 | aid disk info Show disk information 4 | -j, --json Output disk information in JSON format. 5 | 6 | -----input----- 7 | aid disk info -j 8 | -----output----- 9 | [ 10 | { 11 | "available_gb": 52.2, 12 | "total_gb": 936.2, 13 | "percent_free": 5.6, 14 | "file_system": "NTFS", 15 | "mount_point": "C:\\", 16 | "removable": false, 17 | "kind": "SSD" 18 | } 19 | ] 20 | ``` -------------------------------------------------------------------------------- /docs/features/env.md: -------------------------------------------------------------------------------- 1 | ### aid env vars 2 | 3 | ``` 4 | aid env vars Filter / Display environment variables 5 | -k, --kfilter filter the results by a key regex. 6 | -v, --vfilter filter the results by a value regex. 7 | 8 | -----input----- 9 | aid env vars 10 | -----output----- 11 | CommonProgramFiles: 12 | C:\Program Files\Common Files 13 | CommonProgramFiles(x86): 14 | C:\Program Files (x86)\Common Files 15 | CommonProgramW6432: 16 | C:\Program Files\Common Files 17 | JAVA_HOME: 18 | C:\Program Files\Java\jdk-21\bin 19 | ProgramData: 20 | C:\ProgramData 21 | ProgramFiles: 22 | C:\Program Files 23 | ProgramFiles(x86): 24 | C:\Program Files (x86) 25 | 26 | -----input----- 27 | aid env vars -k program 28 | -----output----- 29 | CommonProgramFiles: 30 | C:\Program Files\Common Files 31 | CommonProgramFiles(x86): 32 | C:\Program Files (x86)\Common Files 33 | CommonProgramW6432: 34 | C:\Program Files\Common Files 35 | ProgramData: 36 | C:\ProgramData 37 | ProgramFiles: 38 | C:\Program Files 39 | ProgramFiles(x86): 40 | C:\Program Files (x86) 41 | 42 | -----input----- 43 | aid env vars -k program -v common 44 | -----output----- 45 | CommonProgramFiles: 46 | C:\Program Files\Common Files 47 | CommonProgramFiles(x86): 48 | C:\Program Files (x86)\Common Files 49 | CommonProgramW6432: 50 | C:\Program Files\Common Files 51 | ``` 52 | 53 | ### aid time count-down 54 | 55 | ``` 56 | aid time count-down Starts a countdown timer 57 | 58 | -----input----- 59 | aid time count-down 10:30 60 | -----output----- 61 | Time left: 10 minutes 30 seconds 62 | ``` 63 | 64 | ### aid time unix 65 | ``` 66 | aid time unix Display unix timestamp 67 | -d, --dt Use the specified datetime. 68 | -m, --milli Output the timestamp as unix milliseconds. 69 | 70 | -----input----- 71 | aid time unix 72 | -----output----- 73 | 1729546694 74 | 75 | -----input----- 76 | aid time unix -m 77 | -----output----- 78 | 1729715103358 79 | 80 | -----input----- 81 | aid time unix -d 'Fri, 14 Jul 2017 02:40:00 +0000' 82 | -----output----- 83 | 1500000000 84 | ``` 85 | 86 | ### aid time dt 87 | ``` 88 | aid time dt Display the datetime 89 | -l, --local Use the local datetime. 90 | -u, --unix Use the specified unix second timestamp. 91 | -r, --rfc Output the datetime in Rfc3339 format. 92 | 93 | -----input----- 94 | aid time dt 95 | -----output----- 96 | 2024-10-21 21:38:34 97 | 98 | -----input----- 99 | aid time dt -l 100 | -----output----- 101 | 2024-10-21 22:38:14 102 | 103 | -----input----- 104 | aid time dt -u 1729546694 105 | -----output----- 106 | 2024-10-21 21:38:14 107 | 108 | -----input----- 109 | aid time dt --rfc 110 | -----output----- 111 | 2024-10-23T20:38:00.086663200+00:00 112 | ``` -------------------------------------------------------------------------------- /docs/features/file.md: -------------------------------------------------------------------------------- 1 | ### aid file info 2 | ``` 3 | aid file info prints file metadata 4 | -f, --file file path. 5 | 6 | -----input----- 7 | aid file info -f .\README.md 8 | -----output----- 9 | Type: File 10 | Size: 5582 bytes 11 | Permissions: Read-only: false 12 | Modified: 2024-10-21 20:52:25.0 +00:00:00 13 | Accessed: 2024-10-21 20:52:25.0 +00:00:00 14 | Created: 2024-10-18 20:45:22.0 +00:00:00 15 | ``` 16 | 17 | ### aid file md5 18 | ``` 19 | aid file md5 calculates the files Md5 checksum 20 | -f, --file file path. 21 | 22 | -----input----- 23 | aid file md5 -f .\README.md 24 | -----output----- 25 | 7d417daec5c2663c7a224297d1fc9d95 26 | ``` 27 | 28 | ### aid file sha1 29 | ``` 30 | aid file sha1 calculates the files Sha1 checksum 31 | -f, --file file path. 32 | 33 | -----input----- 34 | aid file sha1 -f .\README.md 35 | -----output----- 36 | aaaa9aa5119da904c84eca8b0a1db46947732737 37 | ``` 38 | 39 | ### aid file sha256 40 | ``` 41 | aid file sha256 calculates the files Sha256 checksum 42 | -f, --file file path. 43 | 44 | -----input----- 45 | aid file sha256 -f .\README.md 46 | -----output----- 47 | 06e033f7ca19a5ef5a1be805a41dec6bd5b36cfdd231d6d3959373d6d4fe7ae7 48 | ``` 49 | 50 | ### aid file zip 51 | ``` 52 | aid file zip zips the files in the source directory 53 | -d, --dir file path. 54 | -f, --file output zip file. 55 | 56 | -----input----- 57 | aid file zip -dir ./src ./src.zip 58 | -----output----- 59 | Successfully zipped directory './src' 60 | ``` 61 | -------------------------------------------------------------------------------- /docs/features/http.md: -------------------------------------------------------------------------------- 1 | ### aid http req 2 | ``` 3 | aid http req Make a HTTP request 4 | -m, --method Specify the HTTP method (e.g., GET, POST). 5 | -u, --url Specify the URL for the HTTP request. 6 | -c, --config Path to a configuration file for the request. Specify: method, url, body, headers in json format. 7 | -o, --output If specified saves http response body to a file at the given path. 8 | 9 | -----input----- 10 | aid http req -m GET -u https://dog-api.kinduff.com/api/facts 11 | -----output----- 12 | { 13 | "facts": [ 14 | "The U.S. has the highest dog population in the world. France has the second highest." 15 | ], 16 | "success": true 17 | } 18 | ``` 19 | 20 | ### aid http serve 21 | ``` 22 | aid http serve Start a HTTP server (GET: 0.0.0.0:80 -> 'Hello, World!') 23 | -p, --port Specify the port for the HTTP server (default is 80). [default: 80] 24 | 25 | -----input----- 26 | aid http serve 27 | -----output----- 28 | Server listening on http://0.0.0.0:80 29 | ``` -------------------------------------------------------------------------------- /docs/features/ip.md: -------------------------------------------------------------------------------- 1 | ### aid ip local 2 | ``` 3 | aid ip local Show my local IP address 4 | -j, --json Output the local IP address in JSON format. 5 | 6 | -----input----- 7 | aid ip local 8 | -----output----- 9 | 192.168.0.10 10 | ``` 11 | 12 | ### aid ip public 13 | ``` 14 | aid ip public Show my public IP address 15 | -j, --json Output the local IP address in JSON format. 16 | 17 | -----input----- 18 | aid ip public 19 | -----output----- 20 | 1.2.3.4 21 | ``` 22 | ### aid ip scan 23 | ``` 24 | aid ip scan Scan a specified IP address subnet for active ip addresses 25 | -i, --ip The IP subnet to scan. If not provided, the local subnet will be used. [default: ] 26 | -j, --json Output scan results in JSON format. 27 | 28 | -----input----- 29 | aid ip scan 30 | -----output----- 31 | 1.2.3.0 32 | 1.2.3.2 33 | 1.2.3.4 34 | ``` 35 | ### aid ip status 36 | ``` 37 | aid ip status Try to connect to the specified IP address 38 | -i, --ip The IP address to check the status of. 39 | -j, --json Output status in JSON format. 40 | 41 | -----input----- 42 | aid ip status -i 1.2.3.2 43 | -----output----- 44 | online 45 | ``` -------------------------------------------------------------------------------- /docs/features/json.md: -------------------------------------------------------------------------------- 1 | ### aid json jwt-decode 2 | ``` 3 | aid json jwt-decode Decode a JWT 4 | 5 | -----input----- 6 | aid json jwt-decode eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjM5OWZkM2E5MmI3YTJiNDZjMzQzMDNiOTViOGNhMmMyIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNzI5NTQ2MzUxfQ.BSvGKZUlSPDWtnVjeJG45eUz1JqYZbBYVKPp4EiV23gs8hE92LvnlxnfnZP-QNfb1JTFCPikQKmkAhp5QInTDg 7 | -----output----- 8 | { 9 | "header": { 10 | "alg": "ES256", 11 | "kid": "399fd3a92b7a2b46c34303b95b8ca2c2", 12 | "typ": "JWT" 13 | }, 14 | "payload": { 15 | "iat": 1729546351, 16 | "name": "John Doe", 17 | "sub": "1234567890" 18 | } 19 | } 20 | ``` -------------------------------------------------------------------------------- /docs/features/math.md: -------------------------------------------------------------------------------- 1 | ### aid math eval 2 | ``` 3 | aid math eval Evaluates a math expression 4 | 5 | -----input----- 6 | aid math eval "sin(pi / 2) * 8 ^ e" 7 | 8 | -----output----- 9 | 285.0054081607272 10 | ``` 11 | 12 | ### aid math plot 13 | ``` 14 | aid math plot Plots a math expression 15 | --start Start x coord. 16 | --end End x coord. 17 | -s, --step x step size. 18 | 19 | -----input----- 20 | aid math plot --start -20 --end 20 --step 0.5 "x * sin(1 - x)" 21 | 22 | -----output----- 23 | expression: x * sin(1 - x) 24 | X range: [-20, 20] 25 | Y range: [-19.43818, 18.049082] 26 | | * 27 | * | ** 28 | *** | ** 29 | * * | *** 30 | * * | ** * ** 31 | * * ** | *** * * 32 | * * *** | * * * * 33 | ** * ** * | *** * * * * 34 | * * * * *** | * * * * * * 35 | * * * ** * ** | ** * * ** * * 36 | --*---*---*---*--**--******-**---*---*---*--*---*- 37 | * * ** * * | *** * * * * ** 38 | * * * **** | * * * * 39 | * * * ** | * * * * 40 | ** * * | *** ** * 41 | * *** | *** 42 | * ** | ** 43 | * | ** 44 | ** | 45 | ** | 46 | ``` -------------------------------------------------------------------------------- /docs/features/mem.md: -------------------------------------------------------------------------------- 1 | ### aid mem usage 2 | ``` 3 | aid mem usage Monitor memory usage 4 | -w, --watch Continuously monitor memory usage. 5 | -j, --json Output memory usage in JSON format. 6 | -p, --plot Plot total % mem usage. 7 | -----input----- 8 | aid mem usage -w -p 9 | -----output----- 10 | total: 15.4GB, used: 13.3GB (86.6%), free: 2.1GB (13.4%) 11 | plot: used memory % 12 | X range: [15, 44] 13 | Y range: [86.5, 87.1] 14 | * * 15 | * ** 16 | *** * 17 | ** * 18 | * * 19 | * 20 | * 21 | ** ** * 22 | * *** ** 23 | * * ** *** 24 | ***** ***** **** * 25 | * ** * 26 | ** * * 27 | ****** ******* 28 | * 29 | * 30 | * ******** 31 | * ** 32 | * * 33 | ** 34 | ``` -------------------------------------------------------------------------------- /docs/features/network.md: -------------------------------------------------------------------------------- 1 | ### aid network info 2 | ``` 3 | aid network info Show network information 4 | -j, --json Output network information in JSON format. 5 | 6 | -----input----- 7 | aid network info -j 8 | -----output----- 9 | [ 10 | { 11 | "name": "WiFi", 12 | "transmitted": 0.2, 13 | "received": 1.5, 14 | "mac": "00:00:00:00:00:00" 15 | } 16 | ] 17 | ``` 18 | 19 | ### aid network usage 20 | ``` 21 | aid network usage Display network usage 22 | -w, --watch Continuously monitor network usage. 23 | 24 | -----input----- 25 | aid network usage -w 26 | -----output----- 27 | WiFi: rx 1.05 kB/s, tx 0 B/s 28 | WiFi-QoS Packet Scheduler-0000: rx 1.05 kB/s, tx 0 B/s 29 | ``` -------------------------------------------------------------------------------- /docs/features/port.md: -------------------------------------------------------------------------------- 1 | ### aid port status 2 | ``` 3 | aid port status Check if the specified port is 'open' or 'closed'. 4 | -i, --ip The IP address to check (optional). 5 | -p The port number to check the status of. 6 | -j, --json Output port status in JSON format. 7 | 8 | -----input----- 9 | aid port status -i 192.168.0.1 -p 80 10 | -----output----- 11 | open 12 | ``` 13 | ### aid port scan 14 | ``` 15 | aid port scan Scan for open ports on a specified IP address 16 | -i, --ip The IP address to scan (optional). 17 | -j, --json Output scan results in JSON format. 18 | 19 | -----input----- 20 | aid port scan -i 192.168.0.1 21 | -----output----- 22 | 80 23 | 443 24 | 22 25 | ``` -------------------------------------------------------------------------------- /docs/features/process.md: -------------------------------------------------------------------------------- 1 | ### aid process usage 2 | ``` 3 | aid process usage Display process information. 4 | -f, --filter filter the results by process name regex. 5 | -s, --sort Sort the results by [cpu, mem, disk] 6 | -l, --limit Limit the number of results to display. 7 | -w, --watch Continuously monitor the processes. 8 | 9 | -----input----- 10 | aid process usage -w -s mem -l 10 -f host 11 | -----output----- 12 | [pid] [cpu %] [mem] [disk read] [disk write] name 13 | [5640] 1.20 % 44 MB 0 B/s 0 B/s svchost.exe 14 | [1568] 0.00 % 42 MB 0 B/s 0 B/s svchost.exe 15 | [5632] 0.00 % 30 MB 0 B/s 0 B/s svchost.exe 16 | [9348] 0.00 % 20 MB 0 B/s 0 B/s svchost.exe 17 | [14736] 0.00 % 20 MB 0 B/s 0 B/s svchost.exe 18 | [9388] 0.00 % 19 MB 0 B/s 0 B/s svchost.exe 19 | [9244] 0.00 % 19 MB 0 B/s 0 B/s sihost.exe 20 | [8396] 0.00 % 19 MB 0 B/s 0 B/s svchost.exe 21 | [1736] 0.00 % 17 MB 0 B/s 0 B/s svchost.exe 22 | [37596] 0.00 % 16 MB 0 B/s 0 B/s taskhostw.exe 23 | ``` -------------------------------------------------------------------------------- /docs/features/text.md: -------------------------------------------------------------------------------- 1 | ### aid text base64-encode 2 | ``` 3 | aid text base64-encode encodes a base64 string 4 | 5 | -----input----- 6 | aid text base64-encode "Hello, world!" 7 | -----output----- 8 | SGVsbG8sIHdvcmxkIQ== 9 | ``` 10 | ### aid text base64-decode 11 | ``` 12 | aid text base64-decode decodes a base64 string 13 | 14 | -----input----- 15 | aid text base64-decode SGVsbG8sIHdvcmxkIQ== 16 | -----output----- 17 | Hello, world! 18 | ``` 19 | ### aid text guid 20 | ``` 21 | aid text guid Generates a random GUID 22 | 23 | -----input----- 24 | aid text guid 25 | -----output----- 26 | af6b8756-32fb-4630-bfe4-264c9a476273 27 | ``` 28 | 29 | ### aid text url-encode 30 | ``` 31 | aid text url-encode url encodes a string 32 | 33 | -----input----- 34 | aid text url-encode hello, world! 35 | -----output----- 36 | hello%20world%21 37 | ``` 38 | 39 | ### aid text url-decode 40 | ``` 41 | aid text url-decode decodes a url encoded string 42 | 43 | -----input----- 44 | aid text url-decode hello%20world%21 45 | -----output----- 46 | hello world! 47 | ``` 48 | 49 | ### aid text lines 50 | ``` 51 | aid text lines reads and prints lines from a text file 52 | -i, --input Input text file to search. 53 | -s, --start first line to print 54 | -e, --end last line to print 55 | -f, --first number of lines from the start of the file to print 56 | -l, --last number of lines from the end of the file to print 57 | 58 | -----input----- 59 | aid text lines -i .\README.md -l 10 60 | -----output----- 61 | | aid file zip | zips the files in the source directory | 62 | | aid time unix | Display unix timestamp | 63 | | aid time dt | Display the datetime | 64 | | aid bits board | Display the number in bitboard representation | 65 | | aid bits to-bin | Converts a number to it's binary representation | 66 | | aid bits to-dec | Converts a number to it's decimal representation | 67 | | aid bits to-hex | Converts a number to it's hex representation | 68 | | aid math eval | Evaluates a math expression | 69 | | aid math plot | Plot a math expression | 70 | ``` 71 | 72 | ### aid text replace 73 | ``` 74 | aid text replace Find and replace text using a regex pattern in the given input string. 75 | -m, --match Regex pattern to match substrings to replace. 76 | -r, --replace Replacement string for matched substrings. 77 | 78 | -----input----- 79 | aid text replace -m dog -r rock "The quick brown fox jumps over the lazy dog." 80 | -----output----- 81 | The quick brown fox jumps over the lazy rock. 82 | ``` 83 | 84 | ### aid text count 85 | ``` 86 | aid text count Count occurrences of a regex pattern in the given input string. 87 | -m, --match Regex pattern to match and count in the input. 88 | 89 | -----input----- 90 | aid text count -m in "The rain in Spain stays mainly in the plain." 91 | -----output----- 92 | 6 93 | ``` -------------------------------------------------------------------------------- /docs/features/time.md: -------------------------------------------------------------------------------- 1 | ### aid time chron 2 | 3 | ``` 4 | aid time chron Describes a chron job 5 | 6 | -----input----- 7 | aid time chron 0 30 3 ? * 2#3 * 8 | -----output----- 9 | At 3:30 AM, on the third Tuesday of the month 10 | ``` 11 | 12 | ### aid time count-down 13 | 14 | ``` 15 | aid time count-down Starts a countdown timer 16 | 17 | -----input----- 18 | aid time count-down 10:30 19 | -----output----- 20 | Time left: 10 minutes 30 seconds 21 | ``` 22 | 23 | ### aid time unix 24 | ``` 25 | aid time unix Display unix timestamp 26 | -d, --dt Use the specified datetime. 27 | -m, --milli Output the timestamp as unix milliseconds. 28 | 29 | -----input----- 30 | aid time unix 31 | -----output----- 32 | 1729546694 33 | 34 | -----input----- 35 | aid time unix -m 36 | -----output----- 37 | 1729715103358 38 | 39 | -----input----- 40 | aid time unix -d 'Fri, 14 Jul 2017 02:40:00 +0000' 41 | -----output----- 42 | 1500000000 43 | ``` 44 | 45 | ### aid time dt 46 | ``` 47 | aid time dt Display the datetime 48 | -l, --local Use the local datetime. 49 | -u, --unix Use the specified unix second timestamp. 50 | -r, --rfc Output the datetime in Rfc3339 format. 51 | 52 | -----input----- 53 | aid time dt 54 | -----output----- 55 | 2024-10-21 21:38:34 56 | 57 | -----input----- 58 | aid time dt -l 59 | -----output----- 60 | 2024-10-21 22:38:14 61 | 62 | -----input----- 63 | aid time dt -u 1729546694 64 | -----output----- 65 | 2024-10-21 21:38:14 66 | 67 | -----input----- 68 | aid time dt --rfc 69 | -----output----- 70 | 2024-10-23T20:38:00.086663200+00:00 71 | ``` -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | ![Banner](assets/banner.png) 2 | 3 | ## Top level commands: 4 | ``` 5 | | command | description | 6 | | ----------- | --------------------------------------------------------- | 7 | | aid http | HTTP functions | 8 | | aid ip | IP information / scanning | 9 | | aid port | Port information / scanning | 10 | | aid cpu | System cpu information | 11 | | aid mem | System memory information | 12 | | aid disk | System disk information | 13 | | aid network | System network information | 14 | | aid json | JSON parsing / extraction functions | 15 | | aid csv | CSV search / transformation functions | 16 | | aid text | Text manipulation functions | 17 | | aid file | File info functions | 18 | | aid time | Time related functions | 19 | | aid bits | Bit manipulation functions | 20 | | aid math | Math functions | 21 | | aid process | Process monitoring functions | 22 | | aid env | Environment information | 23 | | aid help | Print this message or the help of the given subcommand(s) | 24 | ``` 25 | ## All commands: 26 | 27 | ``` 28 | | version | command | description | 29 | | ---------- | ---------------------- | ---------------------------------------------------------------------- | 30 | | [u] 0.1.3 | aid http req | Make a HTTP request | 31 | | [u] 0.1.3 | aid http serve | Start a dummy HTTP server | 32 | | [u] 0.1.3 | aid ip local | Show my local IP address | 33 | | [u] 0.1.3 | aid ip public | Show my public IP address | 34 | | [u] 0.1.3 | aid ip scan | Scan a specified IP address subnet for active ip addresses | 35 | | [u] 0.1.3 | aid ip status | Try to connect to the specified IP address | 36 | | [u] 0.1.3 | aid port status | Check if the specified port is 'open' or 'closed'. | 37 | | [u] 0.1.3 | aid port scan | Scan for open ports on a specified IP address | 38 | | [u] 0.1.3 | aid cpu info | Show CPU information | 39 | | [u] 0.1.6 | aid cpu usage | Monitor CPU usage | 40 | | [u] 0.1.6 | aid mem usage | Monitor memory usage | 41 | | [u] 0.1.3 | aid disk info | Show disk information | 42 | | [u] 0.1.3 | aid network info | Show network information | 43 | | [u] 0.1.3 | aid network usage | Display network usage | 44 | | [a] 0.1.7 | aid process usage | Display process usage | 45 | | [u] 0.1.3 | aid json extract | Extract a property from JSON data | 46 | | [u] 0.1.3 | aid json jwt-decode | Decode a JWT | 47 | | [u] 0.1.3 | aid csv search | Sql search over csv | 48 | | [u] 0.1.3 | aid text base64-encode | encodes a base64 string | 49 | | [u] 0.1.3 | aid text base64-decode | decodes a base64 string | 50 | | [a] 0.1.10 | aid text url-encode | url encodes a string | 51 | | [a] 0.1.10 | aid text url-decode | decodes a url encoded string | 52 | | [u] 0.1.3 | aid text lines | reads and prints lines from a text file | 53 | | [a] 0.1.10 | aid text guid | Generates a random GUID | 54 | | [a] 0.1.11 | aid text replace | Find and replace text using a regex pattern in the given input string. | 55 | | [a] 0.1.11 | aid text count | Count occurrences of a regex pattern in the given input string. | 56 | | [u] 0.1.3 | aid file info | prints file metadata | 57 | | [u] 0.1.3 | aid file md5 | calculates the files Md5 checksum | 58 | | [u] 0.1.3 | aid file sha1 | calculates the files Sha1 checksum | 59 | | [u] 0.1.3 | aid file sha256 | calculates the files Sha256 checksum | 60 | | [a] 0.1.4 | aid file zip | zips the files in the source directory | 61 | | [u] 0.1.9 | aid time unix | Display unix timestamp | 62 | | [u] 0.1.9 | aid time dt | Display the datetime | 63 | | [a] 0.1.9 | aid time chron | Describes a chron job | 64 | | [a] 0.1.9 | aid time count-down | Starts a countdown timer | 65 | | [a] 0.1.8 | aid bits eval | Bitwise expression evaluation / conversion / information | 66 | | [u] 0.1.10 | aid math eval | Evaluates a math expression | 67 | | [u] 0.1.10 | aid math plot | Plot a math expression | 68 | | [a] 0.1.10 | aid env vars | Filter / Display environment variables | 69 | 70 | [a] added in version x.x.x 71 | [u] updated in version x.x.x 72 | [p] patched in version x.x.x 73 | [d] deprecated in version x.x.x 74 | ``` 75 | 76 | # Support 🛟 77 | 78 | Need help? Ping me on [linkedin](https://www.linkedin.com/in/timmoth/) and I'd be more then happy to jump on a call to debug, help configure or answer any questions. 79 | -------------------------------------------------------------------------------- /docs/quickstart.md: -------------------------------------------------------------------------------- 1 | ## Installation 2 | 3 | Manual installation is simple, just download the release and add it to your PATH environment variable, if you'd like an even easier way i've added scripts to do it for you. 4 | 5 | ### Linux / Mac 6 | ``` 7 | wget https://raw.githubusercontent.com/Timmoth/aid-cli/refs/heads/main/install.sh 8 | chmod +x install.sh 9 | sudo ./install.sh 10 | ``` 11 | ### Windows (powershell) 12 | ``` 13 | Invoke-WebRequest -Uri https://raw.githubusercontent.com/Timmoth/aid-cli/refs/heads/main/install.ps1 -OutFile install.ps1 14 | Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -Force 15 | .\install.ps1 16 | ``` 17 | 18 | ### Releases 19 | [Download the latest release](https://github.com/Timmoth/aid-cli/releases) 20 | 21 | ### Build 22 | If you'd like to build the latest version from source: 23 | ``` 24 | //Install rust https://www.rust-lang.org/tools/install 25 | git clone https://github.com/Timmoth/aid-cli 26 | cd aid-cli 27 | cargo build --release 28 | .\target\release\aid.exe 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/support.md: -------------------------------------------------------------------------------- 1 | Need help? Ping me on [linkedin](https://www.linkedin.com/in/timmoth/) and I'd be more then happy to jump on a call to debug, help configure or answer any questions. 2 | -------------------------------------------------------------------------------- /install.ps1: -------------------------------------------------------------------------------- 1 | # Set the download URL and target path 2 | $downloadUrl = "https://github.com/Timmoth/aid-cli/releases/download/aid-0.1.11/aid-win.exe" 3 | $installDir = "C:\Program Files\AidCLI" 4 | $filename = "aid.exe" 5 | 6 | # Create the installation directory if it doesn't exist 7 | if (-not (Test-Path -Path $installDir)) { 8 | Write-Host "Creating install directory..." 9 | New-Item -ItemType Directory -Path $installDir 10 | } 11 | 12 | # Download the file using Invoke-WebRequest 13 | $destination = Join-Path -Path $installDir -ChildPath $filename 14 | Write-Host "Downloading aid-win.exe..." 15 | Invoke-WebRequest -Uri $downloadUrl -OutFile $destination 16 | 17 | if (-not (Test-Path -Path $destination)) { 18 | Write-Host "Download failed! File not found." 19 | exit 1 20 | } 21 | 22 | Write-Host "Download successful! File saved to $destination" 23 | 24 | # Check if the installation directory is already in the PATH 25 | $envPath = [System.Environment]::GetEnvironmentVariable("Path", [System.EnvironmentVariableTarget]::Machine) 26 | if ($envPath -notlike "*$installDir*") { 27 | Write-Host "Adding $installDir to system PATH..." 28 | 29 | # Add the directory to the PATH 30 | $newPath = "$envPath;$installDir" 31 | [System.Environment]::SetEnvironmentVariable("Path", $newPath, [System.EnvironmentVariableTarget]::Machine) 32 | 33 | Write-Host "$installDir successfully added to system PATH" 34 | } else { 35 | Write-Host "$installDir is already in the system PATH" 36 | } 37 | 38 | Write-Host "Installation complete! You can now use the 'aid' command from any terminal." 39 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Determine the operating system (Linux or macOS) and architecture (x86_64 or ARM64) 4 | OS="$(uname)" 5 | ARCH="$(uname -m)" 6 | 7 | if [[ "$OS" == "Linux" ]]; then 8 | if [[ "$ARCH" == "x86_64" ]]; then 9 | URL="https://github.com/Timmoth/aid-cli/releases/download/aid-0.1.11/aid-linux" 10 | else 11 | echo "Error: Unsupported architecture for Linux." 12 | exit 1 13 | fi 14 | elif [[ "$OS" == "Darwin" ]]; then 15 | if [[ "$ARCH" == "x86_64" ]]; then 16 | URL="https://github.com/Timmoth/aid-cli/releases/download/aid-0.1.11/aid-mac" 17 | # Check if running on an x86 mac with Rosetta 18 | if [[ $(sysctl -n sysctl.proc_translated) == "1" ]]; then 19 | echo "Running on Apple Silicon with Rosetta. Consider downloading the ARM64 version." 20 | fi 21 | elif [[ "$ARCH" == "arm64" ]]; then 22 | URL="https://github.com/Timmoth/aid-cli/releases/download/aid-0.1.11/aid-mac-arm" 23 | else 24 | echo "Error: Unsupported architecture for macOS." 25 | exit 1 26 | fi 27 | else 28 | echo "Error: Unsupported operating system." 29 | exit 1 30 | fi 31 | 32 | INSTALL_DIR="/usr/local/bin" 33 | FILENAME="aid" 34 | 35 | # Check if curl or wget is installed for downloading 36 | if command -v curl >/dev/null 2>&1; then 37 | DOWNLOADER="curl -L -o" 38 | elif command -v wget >/dev/null 2>&1; then 39 | DOWNLOADER="wget -O" 40 | else 41 | echo "Error: Neither curl nor wget is installed." 42 | exit 1 43 | fi 44 | 45 | # Download the file 46 | echo "Downloading aid from $URL..." 47 | $DOWNLOADER "$INSTALL_DIR/$FILENAME" "$URL" 48 | 49 | # Verify if the download was successful 50 | if [ ! -f "$INSTALL_DIR/$FILENAME" ]; then 51 | echo "Error: Download failed." 52 | exit 1 53 | fi 54 | 55 | echo "Download successful! File saved to $INSTALL_DIR/$FILENAME" 56 | 57 | # Make the file executable 58 | echo "Making $FILENAME executable..." 59 | chmod +x "$INSTALL_DIR/$FILENAME" 60 | 61 | # Check if the install directory is already in PATH 62 | if ! echo "$PATH" | grep -q "$INSTALL_DIR"; then 63 | echo "Adding $INSTALL_DIR to PATH..." 64 | 65 | # Add the directory to the shell profile for persistent PATH updates 66 | if [ -f "$HOME/.bashrc" ]; then 67 | echo "export PATH=\$PATH:$INSTALL_DIR" >> "$HOME/.bashrc" 68 | source "$HOME/.bashrc" 69 | elif [ -f "$HOME/.bash_profile" ]; then 70 | echo "export PATH=\$PATH:$INSTALL_DIR" >> "$HOME/.bash_profile" 71 | source "$HOME/.bash_profile" 72 | elif [ -f "$HOME/.zshrc" ]; then 73 | echo "export PATH=\$PATH:$INSTALL_DIR" >> "$HOME/.zshrc" 74 | source "$HOME/.zshrc" 75 | else 76 | echo "Could not find shell profile. Please add $INSTALL_DIR to your PATH manually." 77 | fi 78 | else 79 | echo "$INSTALL_DIR is already in PATH." 80 | fi 81 | 82 | echo "Installation complete! You can now run 'aid' from any terminal." 83 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Aid-CLI Docs 2 | site_url: https://timmoth.github.io/aid-cli/ 3 | site_description: A CLI toolkit featuring a variety of helpful utilities. 4 | site_author: Timmoth 5 | repo_url: https://github.com/Timmoth/aid-cli/ 6 | edit_uri: edit/main/docs 7 | 8 | theme: 9 | name: readthedocs 10 | highlightjs: true 11 | logo: assets/logo.png 12 | nav: 13 | - Overview ✅: index.md 14 | - Quick start ▶️: quickstart.md 15 | - Support 🛟: support.md 16 | - Contributing 🙏: contributing.md 17 | - Features: 18 | - http: features/http.md 19 | - ip: features/ip.md 20 | - port: features/port.md 21 | - cpu: features/cpu.md 22 | - mem: features/mem.md 23 | - disk: features/disk.md 24 | - network: features/network.md 25 | - process: features/process.md 26 | - json: features/json.md 27 | - csv: features/csv.md 28 | - text: features/text.md 29 | - file: features/file.md 30 | - time: features/time.md 31 | - bits: features/bits.md 32 | - math: features/math.md 33 | markdown_extensions: 34 | - tables 35 | - admonition 36 | - pymdownx.details 37 | - pymdownx.superfences 38 | -------------------------------------------------------------------------------- /src/bits_commands.rs: -------------------------------------------------------------------------------- 1 | use crate::bits_utils; 2 | 3 | pub fn evaluate(board: bool, chess_board: bool, binary: bool, hex: bool, expression: String) { 4 | let (_, parsed_expr) = bits_utils::parse_expr(&expression).unwrap(); 5 | let result = parsed_expr.eval(); 6 | if board || chess_board { 7 | bitboard(chess_board, None, Some(result), None) 8 | } else { 9 | if binary { 10 | println!("{:b}", result); 11 | } else if hex { 12 | println!("{:x}", result); 13 | } else { 14 | println!("{}", result); 15 | } 16 | } 17 | } 18 | 19 | fn bitboard(chess_board: bool, binary: Option, decimal: Option, hex: Option) { 20 | let num: u64 = if let Some(v) = decimal { 21 | v 22 | } else if let Some(v) = hex { 23 | u64::from_str_radix(&v, 16).expect("failed parsing hex string") 24 | } else if let Some(b) = binary { 25 | u64::from_str_radix(&b, 2).expect("failed parsing binary string") 26 | } else { 27 | 0 28 | }; 29 | 30 | for rank in (0..8).rev() { 31 | let byte = ((num >> (rank * 8)) & 0xFF) as u8; 32 | if chess_board{ 33 | print!("{} | ", rank + 1); 34 | }else{ 35 | print!("{:02X} | ", byte); 36 | } 37 | 38 | if chess_board { 39 | for file in 0..8 { 40 | let bit = (num >> (rank * 8 + file)) & 1; 41 | print!("{} ", bit); 42 | } 43 | } else { 44 | for file in (0..8).rev() { 45 | let bit = (num >> (rank * 8 + file)) & 1; 46 | print!("{} ", bit); 47 | } 48 | } 49 | 50 | println!(); 51 | } 52 | 53 | if chess_board { 54 | println!(" -----------------------"); 55 | println!(" A B C D E F G H"); 56 | }else{ 57 | println!(" -----------------------"); 58 | println!(" 7 6 5 4 3 2 1 0"); 59 | } 60 | 61 | println!("dec: {}", num); 62 | println!("hex: {:X}", num); 63 | println!("bin: {:b}", num); 64 | println!("lsb: {}", num.trailing_zeros()); 65 | if num.leading_zeros() >= 64 { 66 | println!("msb: {}", 64); 67 | } else { 68 | println!("msb: {}", 63 - num.leading_zeros()); 69 | } 70 | println!("set bits: {}", num.count_ones()); 71 | } -------------------------------------------------------------------------------- /src/bits_utils.rs: -------------------------------------------------------------------------------- 1 | use nom::{ 2 | branch::alt, 3 | bytes::complete::tag, 4 | character::complete::{char, digit1, space0}, 5 | combinator::{map, map_res}, 6 | sequence::{delimited, preceded}, 7 | IResult, 8 | }; 9 | use std::str::FromStr; 10 | 11 | #[derive(Debug)] 12 | pub enum Expr { 13 | Num(u64), 14 | BinOp(Box, BinOp, Box), 15 | UnOp(UnOp, Box), 16 | } 17 | 18 | #[derive(Debug)] 19 | pub enum BinOp { 20 | And, 21 | Or, 22 | Xor, 23 | Shl, 24 | Shr, 25 | } 26 | 27 | #[derive(Debug)] 28 | pub enum UnOp { 29 | Not, 30 | } 31 | 32 | // Parsing numbers: decimal, hex, and binary 33 | fn parse_number(input: &str) -> IResult<&str, u64> { 34 | alt(( 35 | map_res(preceded(tag("0x"), nom::character::complete::hex_digit1), |s: &str| { 36 | u64::from_str_radix(s, 16) 37 | }), 38 | map_res(preceded(tag("0b"), nom::character::complete::digit1), |s: &str| { 39 | u64::from_str_radix(s, 2) 40 | }), 41 | map_res(digit1, |s: &str| u64::from_str(s)), 42 | ))(input) 43 | } 44 | 45 | // Parsing parenthesized expressions 46 | fn parse_parentheses(input: &str) -> IResult<&str, Expr> { 47 | delimited( 48 | delimited(space0, char('('), space0), 49 | |input| parse_expression(input), // Call the main expression parser 50 | delimited(space0, char(')'), space0), 51 | )(input) 52 | } 53 | 54 | // Primary expressions: either numbers or parentheses 55 | fn parse_primary(input: &str) -> IResult<&str, Expr> { 56 | alt((parse_parentheses, map(parse_number, Expr::Num)))(input) 57 | } 58 | 59 | // Parsing unary operator (NOT) 60 | fn parse_unop(input: &str) -> IResult<&str, UnOp> { 61 | delimited(space0, map(tag("!"), |_| UnOp::Not), space0)(input) 62 | } 63 | 64 | // Function to parse unary expressions 65 | fn parse_unary_expr(input: &str) -> IResult<&str, Expr> { 66 | let (input, op) = parse_unop(input)?; 67 | let (input, expr) = parse_primary(input)?; 68 | Ok((input, Expr::UnOp(op, Box::new(expr)))) 69 | } 70 | 71 | // Binary operator precedence levels 72 | fn get_precedence(op: &BinOp) -> u8 { 73 | match op { 74 | BinOp::Shl | BinOp::Shr => 3, 75 | BinOp::And => 2, 76 | BinOp::Xor => 1, 77 | BinOp::Or => 0, 78 | } 79 | } 80 | 81 | // Parsing binary operators 82 | fn parse_binop(input: &str) -> IResult<&str, BinOp> { 83 | alt(( 84 | map(tag("&"), |_| BinOp::And), 85 | map(tag("|"), |_| BinOp::Or), 86 | map(tag("^"), |_| BinOp::Xor), 87 | map(tag("<<"), |_| BinOp::Shl), 88 | map(tag(">>"), |_| BinOp::Shr), 89 | ))(input) 90 | } 91 | 92 | // Parse expressions with precedence climbing 93 | fn parse_expression(input: &str) -> IResult<&str, Expr> { 94 | let (input, mut lhs) = alt((parse_unary_expr, parse_primary))(input)?; // Allow unary expressions first 95 | 96 | let mut input = input; 97 | loop { 98 | let (next_input, _) = space0(input)?; 99 | 100 | // Attempt to parse a binary operator 101 | if let Ok((next_input, op)) = parse_binop(next_input) { 102 | let precedence = get_precedence(&op); 103 | let (next_input, _) = space0(next_input)?; 104 | let (next_input, mut rhs) = alt((parse_unary_expr, parse_primary))(next_input)?; // Allow unary expressions on rhs 105 | 106 | // Check for next higher precedence operators 107 | while let Ok((next_next_input, next_op)) = parse_binop(next_input) { 108 | let next_precedence = get_precedence(&next_op); 109 | if next_precedence > precedence { 110 | let (next_next_input, higher_rhs) = parse_expression(next_next_input)?; 111 | rhs = Expr::BinOp(Box::new(rhs), next_op, Box::new(higher_rhs)); 112 | input = next_next_input; // Update input 113 | } else { 114 | input = next_next_input; // Exit if no higher precedence 115 | break; 116 | } 117 | } 118 | 119 | lhs = Expr::BinOp(Box::new(lhs), op, Box::new(rhs)); 120 | input = next_input; // Update input for the next iteration 121 | } else { 122 | break; // Exit loop if no binary operator found 123 | } 124 | } 125 | 126 | Ok((input, lhs)) 127 | } 128 | 129 | // Parsing the expression (with support for unary and binary operators) 130 | pub fn parse_expr(input: &str) -> IResult<&str, Expr> { 131 | parse_expression(input) 132 | } 133 | 134 | // Implement evaluation of the expressions 135 | impl Expr { 136 | pub fn eval(&self) -> u64 { 137 | match self { 138 | Expr::Num(n) => *n, 139 | Expr::BinOp(lhs, op, rhs) => { 140 | let lhs_val = lhs.eval(); 141 | let rhs_val = rhs.eval(); 142 | match op { 143 | BinOp::And => lhs_val & rhs_val, 144 | BinOp::Or => lhs_val | rhs_val, 145 | BinOp::Xor => lhs_val ^ rhs_val, 146 | BinOp::Shl => lhs_val << rhs_val, 147 | BinOp::Shr => lhs_val >> rhs_val, 148 | } 149 | } 150 | Expr::UnOp(op, expr) => { 151 | let val = expr.eval(); 152 | match op { 153 | UnOp::Not => !val, 154 | } 155 | } 156 | } 157 | } 158 | } -------------------------------------------------------------------------------- /src/cpu_commands.rs: -------------------------------------------------------------------------------- 1 | use serde_derive::Serialize; 2 | use std::{collections::HashSet, time::Duration}; 3 | use sysinfo::System; 4 | 5 | use crate::{format_utils, graph_utils}; 6 | 7 | #[derive(Serialize, Debug)] 8 | struct CpuInfo { 9 | name: String, 10 | } 11 | 12 | pub fn system_cpu_info(json: bool) { 13 | let s = sysinfo::System::new_all(); 14 | 15 | let unique_vendor_ids: HashSet = 16 | s.cpus().iter().map(|c| c.brand().to_string()).collect(); 17 | let unique_vendor_ids_vec: Vec = unique_vendor_ids.into_iter().collect(); 18 | 19 | if json { 20 | let output: Vec = unique_vendor_ids_vec 21 | .iter() 22 | .map(|v| CpuInfo { 23 | name: v.to_string(), 24 | }) 25 | .collect(); 26 | format_utils::print_json(&output); 27 | } else { 28 | let joined_vendor_ids = unique_vendor_ids_vec.join(", "); 29 | println!("{}", joined_vendor_ids); 30 | } 31 | } 32 | 33 | #[derive(Serialize, Debug)] 34 | struct CpuUsage { 35 | total: f64, 36 | core_usage: Vec, 37 | } 38 | 39 | pub async fn system_cpu_usage(watch: bool, json: bool, plot: bool) { 40 | let mut system = System::new_all(); 41 | 42 | if watch { 43 | let mut points: Vec<(f32, f32)> = Vec::new(); 44 | let mut i:f32 = 0.0; 45 | loop { 46 | system.refresh_cpu_all(); 47 | let total: f32 = system.global_cpu_usage(); 48 | if points.len() >= 30 { 49 | // Remove the oldest point (optional, only if you want a rolling window) 50 | points.remove(0); // or points.drain(..1); for potentially more efficient operation 51 | } 52 | points.push((i, total)); 53 | i += 1.0; 54 | 55 | let usage = CpuUsage { 56 | total: format_utils::round_to_one_decimal(total), 57 | core_usage: system 58 | .cpus() 59 | .iter() 60 | .map(|c| format_utils::round_to_one_decimal(c.cpu_usage())) 61 | .collect(), 62 | }; 63 | 64 | format_utils::clear_terminal(); 65 | if json { 66 | format_utils::print_json(&usage); 67 | }else{ 68 | println!( 69 | "total: {}, cpus: {}", 70 | format_utils::format_percent(total), 71 | system 72 | .cpus() 73 | .iter() 74 | .map(|c| format_utils::format_percent(c.cpu_usage())) 75 | .collect::>() 76 | .join(", ") 77 | ); 78 | } 79 | 80 | if plot{ 81 | // Plot the chart 82 | let title: &str = "total cpu %"; 83 | graph_utils::plot_chart(&points, String::from(title)); 84 | } 85 | 86 | tokio::time::sleep(Duration::from_millis(500)).await; 87 | } 88 | } else { 89 | system.refresh_cpu_all(); 90 | let total: f32 = system.global_cpu_usage(); 91 | 92 | println!( 93 | "total: {}, cpus: {}", 94 | format_utils::format_percent(total), 95 | system 96 | .cpus() 97 | .iter() 98 | .map(|c| format_utils::format_percent(c.cpu_usage())) 99 | .collect::>() 100 | .join(", ") 101 | ); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/csv_commands.rs: -------------------------------------------------------------------------------- 1 | use std::{fs::File, time::Instant}; 2 | 3 | use csv::Writer; 4 | 5 | use crate::csv_utils; 6 | 7 | pub async fn sql_search(sql: String, output_path: Option) { 8 | 9 | let start = Instant::now(); 10 | 11 | // Parse the SQL query 12 | let parsed = match csv_utils::parse_sql(&sql) { 13 | Ok(result) => result, 14 | Err(e) => { 15 | eprintln!("Failed to parse SQL query: {}", e); 16 | return; // Early exit on error 17 | } 18 | }; 19 | 20 | let parse_time = start.elapsed(); 21 | let start = Instant::now(); 22 | 23 | let query = parsed.1; 24 | 25 | // Load the CSV file 26 | let (headers, records) = match csv_utils::load_csv(&query.table) { 27 | Ok(result) => result, 28 | Err(e) => { 29 | eprintln!("Failed to load CSV file '{}': {}", query.table, e); 30 | return; // Early exit on error 31 | } 32 | }; 33 | 34 | let rows = records.len(); 35 | let load_time = start.elapsed(); 36 | let start = Instant::now(); 37 | 38 | // Apply the query 39 | let (result_headers, result_rows) = csv_utils::apply_query(&headers, records, &query); 40 | let query_time = start.elapsed(); 41 | let result_row_count = result_rows.len(); 42 | 43 | output_results(result_rows, &result_headers, &output_path); 44 | 45 | if output_path.is_some() { 46 | // Log the query statistics 47 | println!("Parsed {:?} in {:?}", query, parse_time); 48 | println!("Loaded {} rows in {:?}", rows, load_time); 49 | println!("Found {} rows in {:?}", result_row_count, query_time); 50 | } 51 | } 52 | 53 | 54 | fn output_results(results: Vec>, headers: &Vec, output_path: &Option) { 55 | if results.is_empty() { 56 | println!("No results found."); 57 | return; 58 | } 59 | 60 | match output_path { 61 | Some(path) => { 62 | match File::create(&path) { 63 | Ok(file) => { 64 | let mut wtr = Writer::from_writer(file); 65 | 66 | // Write headers to the CSV if provided 67 | if let Err(e) = wtr.write_record(headers) { 68 | eprintln!("Failed to write headers to file: {}", e); 69 | return; 70 | } 71 | 72 | // Write each row of results to the CSV 73 | for row in results.iter() { 74 | if let Err(e) = wtr.write_record(row) { 75 | eprintln!("Failed to write row to file: {}", e); 76 | return; 77 | } 78 | } 79 | 80 | if let Err(e) = wtr.flush() { 81 | eprintln!("Failed to flush writer: {}", e); 82 | } 83 | 84 | println!("Results successfully written to {}", path); 85 | } 86 | Err(e) => { 87 | eprintln!("Failed to create file {}: {}", path, e); 88 | } 89 | } 90 | } 91 | 92 | None => { 93 | // Print headers if they are available 94 | println!("{}", headers.join(",")); 95 | 96 | 97 | // Print each row of results 98 | for row in results.iter() { 99 | println!("{}", row.join(",")); 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/disk_commands.rs: -------------------------------------------------------------------------------- 1 | use sysinfo::Disks; 2 | use serde_derive::Serialize; 3 | 4 | use crate::format_utils; 5 | 6 | #[derive(Serialize, Debug)] 7 | struct DiskInfoOutput { 8 | available_gb: f64, 9 | total_gb: f64, 10 | percent_free: f64, 11 | file_system: String, 12 | mount_point: String, 13 | removable: bool, 14 | kind: String, 15 | } 16 | 17 | pub async fn system_disk_info(json: bool) { 18 | 19 | let disks = Disks::new_with_refreshed_list(); 20 | let mut outputs: Vec = Vec::new(); 21 | for disk in &disks { 22 | outputs.push(DiskInfoOutput{ 23 | available_gb: format_utils::bytes_to_gb(disk.available_space()), 24 | total_gb: format_utils::bytes_to_gb(disk.total_space()), 25 | percent_free: format_utils::percent(disk.available_space(), disk.total_space()), 26 | file_system: String::from(disk.file_system().to_str().unwrap_or_default()), 27 | mount_point: String::from(disk.mount_point().to_str().unwrap_or_default()), 28 | removable: disk.is_removable(), 29 | kind: disk.kind().to_string(), 30 | }); 31 | } 32 | 33 | if json { 34 | format_utils::print_json(&outputs); 35 | }else{ 36 | for o in outputs{ 37 | println!("available: {}GB ({}%), total: {}GB, file_system: {}, mount_point: {}, removable: {}, king: {}", o.available_gb, o.percent_free, o.total_gb, o.file_system, o.mount_point, o.removable, o.kind) 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/env_commands.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | use std::env; 3 | use std::env::consts::OS; 4 | 5 | use regex::Regex; 6 | 7 | pub fn print_env_vars(key_filter: Option, value_filter: Option) { 8 | let mut env_vars: BTreeMap> = BTreeMap::new(); 9 | let separator = if OS == "windows" { ';' } else { ':' }; 10 | 11 | let key_regex = key_filter.map(|kf| Regex::new(&kf).unwrap()); 12 | let value_regex = value_filter.map(|vf| Regex::new(&vf).unwrap()); 13 | 14 | for (key, value) in env::vars() { 15 | if let Some(ref kr) = key_regex { 16 | if !kr.is_match(&key.to_lowercase()) { 17 | continue; 18 | } 19 | } 20 | 21 | let split_values: Vec = value 22 | .split(separator) 23 | .map(|s| s.trim().to_string()) 24 | .filter(|v| !v.is_empty()) // Filter out empty values 25 | .collect(); 26 | 27 | let filtered_values: Vec = if let Some(ref vr) = value_regex { 28 | split_values 29 | .into_iter() 30 | .filter(|v| vr.is_match(&v.to_lowercase())) 31 | .collect() 32 | } else { 33 | split_values 34 | }; 35 | 36 | if !filtered_values.is_empty() { 37 | env_vars.insert(key, filtered_values); 38 | } 39 | } 40 | 41 | for (key, values) in env_vars { 42 | println!("{}:", key); 43 | for value in values { 44 | println!("\t{}", value); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/file_commands.rs: -------------------------------------------------------------------------------- 1 | use md5::{Digest as Md5Digest, Md5}; 2 | use sha1::Sha1; 3 | use sha2::Sha256; 4 | use std::fs::File; 5 | use std::io::{self, BufReader, Read}; 6 | use std::path::Path; 7 | use std::time::UNIX_EPOCH; 8 | use time::OffsetDateTime; 9 | use std::io::Write; 10 | use zip::ZipWriter; 11 | use zip::write::{FileOptions, SimpleFileOptions}; 12 | use walkdir::WalkDir; 13 | 14 | #[cfg(unix)] 15 | use std::os::unix::fs::PermissionsExt; 16 | 17 | pub fn zip_directory(dir_path: String, zip_file_path: String) { 18 | // Create a new ZIP file 19 | let zip_file = File::create(&zip_file_path); 20 | if let Err(e) = zip_file { 21 | eprintln!("Error creating zip file '{}': {}", zip_file_path, e); 22 | return; 23 | } 24 | let zip_file = zip_file.unwrap(); 25 | 26 | let mut zip_writer = ZipWriter::new(zip_file); 27 | let options = SimpleFileOptions::default() 28 | .compression_method(zip::CompressionMethod::Deflated) 29 | .unix_permissions(0o755); 30 | 31 | // Walk through the directory 32 | for entry in WalkDir::new(&dir_path) { 33 | let entry = entry.unwrap(); // Handle errors with unwrap; you may want to log them instead 34 | let path = entry.path(); 35 | 36 | // Only add files to the zip 37 | if path.is_file() { 38 | // Get relative path for the ZIP entry 39 | let relative_path = path.strip_prefix(&dir_path).unwrap_or(path); 40 | if let Err(e) = zip_writer.start_file(relative_path.to_string_lossy(), options) { 41 | eprintln!("Error adding file to zip '{}': {}", relative_path.display(), e); 42 | continue; // Skip to the next file 43 | } 44 | 45 | let file_reader = File::open(path); 46 | match file_reader { 47 | Ok(mut reader) => { 48 | if let Err(e) = io::copy(&mut reader, &mut zip_writer) { 49 | eprintln!("Error writing file to zip '{}': {}", relative_path.display(), e); 50 | } 51 | } 52 | Err(e) => { 53 | eprintln!("Error opening file '{}': {}", path.display(), e); 54 | } 55 | } 56 | } 57 | } 58 | 59 | if let Err(e) = zip_writer.finish() { 60 | eprintln!("Error finalizing zip file: {}", e); 61 | } else { 62 | println!("Successfully zipped directory '{}'", dir_path); 63 | } 64 | } 65 | 66 | pub fn file_info(file_path: String) { 67 | let path = Path::new(&file_path); 68 | 69 | // Check if file exists 70 | if !path.exists() { 71 | println!("File does not exist."); 72 | return; 73 | } 74 | 75 | // Get metadata 76 | let metadata = path.metadata().expect("Failed reading file metadata"); 77 | 78 | // File type: Directory or file 79 | if metadata.is_dir() { 80 | println!("Type: Directory"); 81 | } else if metadata.is_file() { 82 | println!("Type: File"); 83 | } else if metadata.file_type().is_symlink() { 84 | println!("Type: Symlink"); 85 | } else { 86 | println!("Type: Other"); 87 | } 88 | 89 | println!("Size: {} bytes", metadata.len()); 90 | 91 | // Permissions (cross-platform, but more detailed on Unix) 92 | #[cfg(unix)] 93 | println!("Permissions: {:o}", metadata.permissions().mode()); 94 | #[cfg(not(unix))] 95 | println!( 96 | "Permissions: Read-only: {}", 97 | metadata.permissions().readonly() 98 | ); 99 | 100 | // Timestamps 101 | let modified_time = metadata 102 | .modified() 103 | .expect("Failed reading date modified") 104 | .duration_since(UNIX_EPOCH) 105 | .expect("Failed reading date modified") 106 | .as_secs(); 107 | let accessed_time = metadata 108 | .accessed() 109 | .expect("Failed reading date accessed.") 110 | .duration_since(UNIX_EPOCH) 111 | .expect("Failed reading date accessed.") 112 | .as_secs(); 113 | 114 | // On some platforms, creation time may not be available 115 | let created_time = metadata.created().ok().map_or_else( 116 | || "Not available".to_string(), 117 | |created| { 118 | let created_secs = OffsetDateTime::from_unix_timestamp(created.duration_since(UNIX_EPOCH).unwrap().as_secs() as i64).expect("Failed converting date accessed."); 119 | format!("{}", created_secs) 120 | }, 121 | ); 122 | 123 | let modified = OffsetDateTime::from_unix_timestamp(modified_time as i64).expect("Failed converting date modified."); 124 | let accessed = OffsetDateTime::from_unix_timestamp(accessed_time as i64).expect("Failed converting date accessed."); 125 | 126 | println!("Modified: {}", modified); 127 | println!("Accessed: {}", accessed); 128 | println!("Created: {}", created_time); 129 | } 130 | 131 | pub fn md5_checksum(file_path: String) { 132 | let file = File::open(&file_path).expect("Unable to open file"); 133 | let mut reader = BufReader::new(file); 134 | let mut md5_hasher = Md5::new(); 135 | let mut buffer = [0; 1024]; // 1KB buffer 136 | 137 | loop { 138 | let bytes_read = reader.read(&mut buffer).expect("Failed reading file data"); 139 | 140 | if bytes_read == 0 { 141 | break; // EOF reached 142 | } 143 | 144 | md5_hasher.update(&buffer[..bytes_read]); 145 | } 146 | 147 | let md5_result = md5_hasher.finalize(); 148 | println!("{:x}", md5_result); 149 | } 150 | 151 | pub fn sha1_checksum(file_path: String) { 152 | let file = File::open(&file_path).expect("Unable to open file"); 153 | let mut reader = BufReader::new(file); 154 | let mut sha1_hasher = Sha1::new(); 155 | let mut buffer = [0; 1024]; // 1KB buffer 156 | 157 | loop { 158 | let bytes_read = reader.read(&mut buffer).expect("Failed reading file data"); 159 | 160 | if bytes_read == 0 { 161 | break; // EOF reached 162 | } 163 | 164 | sha1_hasher.update(&buffer[..bytes_read]); 165 | } 166 | 167 | let sha1_result = sha1_hasher.finalize(); 168 | println!("{:x}", sha1_result); 169 | } 170 | 171 | pub fn sha256_checksum(file_path: String) { 172 | let file = File::open(&file_path).expect("Unable to open file"); 173 | let mut reader = BufReader::new(file); 174 | let mut sha256_hasher = Sha256::new(); 175 | let mut buffer = [0; 1024]; // 1KB buffer 176 | 177 | loop { 178 | let bytes_read = reader.read(&mut buffer).expect("Failed reading file data"); 179 | 180 | if bytes_read == 0 { 181 | break; // EOF reached 182 | } 183 | 184 | sha256_hasher.update(&buffer[..bytes_read]); 185 | } 186 | 187 | let sha256_result = sha256_hasher.finalize(); 188 | println!("{:x}", sha256_result); 189 | } -------------------------------------------------------------------------------- /src/format_utils.rs: -------------------------------------------------------------------------------- 1 | use num_traits::ToPrimitive; 2 | use serde::Serialize; 3 | 4 | pub fn round_to_one_decimal(num: T) -> f64 { 5 | if let Some(n) = num.to_f64() { 6 | (n * 10.0).round() / 10.0 7 | } else { 8 | 0.0 9 | } 10 | } 11 | 12 | const GB: u64 = 1 << 30; 13 | 14 | pub fn percent(a: T, b: U) -> f64 { 15 | let a_f64 = a.to_f64().unwrap_or(0.0); 16 | let b_f64 = b.to_f64().unwrap_or(0.0); 17 | 18 | if b_f64 > 0.0 { 19 | round_to_one_decimal((a_f64 / b_f64) * 100.0) 20 | } else { 21 | 0.0 // Avoid division by zero 22 | } 23 | } 24 | 25 | pub fn bytes_to_gb(num: T) -> f64 { 26 | if let Some(n) = num.to_f64() { 27 | let gb = n / GB as f64; 28 | (gb * 10.0).round() / 10.0 29 | } else { 30 | // Handle the case where the conversion fails (e.g., for non-numeric types) 31 | 0.0 32 | } 33 | } 34 | 35 | pub fn format_bytes(bytes: T) -> String { 36 | 37 | let bytes_f64 = bytes.to_f64().unwrap_or(0.0); 38 | 39 | const GB: u64 = 1 << 30; // 1 GiB in bytes 40 | const MB: u64 = 1 << 20; // 1 MiB in bytes 41 | 42 | if bytes_f64 as u64 >= GB { 43 | format!("{:.1}GB", bytes_f64 as f64 / GB as f64) 44 | } else { 45 | format!("{:.1}MB", bytes_f64 as f64 / MB as f64) 46 | } 47 | } 48 | 49 | pub fn format_percent(a: T) -> String { 50 | let a_f64 = a.to_f64().unwrap_or(0.0); 51 | return format!("{:.1}%",a_f64 );// Avoid division by zero 52 | } 53 | 54 | pub fn calculate_format_percent(a: T, b: U) -> String { 55 | let a_f64 = a.to_f64().unwrap_or(0.0); 56 | let b_f64 = b.to_f64().unwrap_or(0.0); 57 | if b_f64 > 0.0 { 58 | return format!("{:.1}%", (a_f64 / b_f64) * 100.0); 59 | } else { 60 | return format!("{:.1}%",0.0 );// Avoid division by zero 61 | }; 62 | } 63 | 64 | pub fn clear_terminal(){ 65 | print!("\x1B[2J\x1B[3J\x1B[H"); 66 | } 67 | 68 | pub fn print_json(body: &T) { 69 | let json = serde_json::to_string_pretty(body).expect("Failed to convert to JSON"); 70 | println!("{}", json); 71 | } -------------------------------------------------------------------------------- /src/graph_utils.rs: -------------------------------------------------------------------------------- 1 | use crate::math_utils; 2 | use std::cmp::{max, min}; 3 | 4 | /// Interpolate between two points (x0, y0) and (x1, y1), and generate intermediate points. 5 | fn interpolate(p1: (f32, f32), p2: (f32, f32), num_points: usize) -> Vec<(f32, f32)> { 6 | let mut interpolated_points = Vec::new(); 7 | 8 | for i in 0..=num_points { 9 | let t = i as f32 / num_points as f32; 10 | let x = p1.0 * (1.0 - t) + p2.0 * t; 11 | let y = p1.1 * (1.0 - t) + p2.1 * t; 12 | interpolated_points.push((x, y)); 13 | } 14 | 15 | interpolated_points 16 | } 17 | 18 | pub fn plot_chart(points: &Vec<(f32, f32)>, expression: String) { 19 | let width = 50; 20 | let height = 20; 21 | 22 | // Find min and max values for x and y to set the chart bounds 23 | let (min_x, max_x) = points.iter().fold( 24 | (f32::INFINITY, f32::NEG_INFINITY), 25 | |(min_x, max_x), &(x, _)| (min_x.min(x), max_x.max(x)), 26 | ); 27 | 28 | let (min_y, max_y) = points.iter().fold( 29 | (f32::INFINITY, f32::NEG_INFINITY), 30 | |(min_y, max_y), &(_, y)| (min_y.min(y), max_y.max(y)), 31 | ); 32 | 33 | let axis_x = if min_y <= 0.0 && max_y >= 0.0 { 34 | Some((0.0 - min_y) / (max_y - min_y) * (height - 1) as f32) 35 | } else if max_y < 0.0 { 36 | Some(height as f32 - 1.0) 37 | // x-axis at the top 38 | } else { 39 | Some(-1.0) 40 | // x-axis at the bottom 41 | }; 42 | 43 | let axis_y = if min_x <= 0.0 && max_x >= 0.0 { 44 | Some(((0.0 - min_x) / (max_x - min_x) * (width - 1) as f32) as f32) 45 | } else if max_x < 0.0 { 46 | // y-axis at the left 47 | Some(width as f32 - 1.0) 48 | } else { 49 | Some(-1.0) 50 | // y-axis at the right 51 | }; 52 | 53 | println!("plot: {}", expression); 54 | println!("X range: [{}, {}]", min_x, max_x); 55 | println!("Y range: [{}, {}]", min_y, max_y); 56 | // Create a 2D grid initialized with spaces 57 | let mut grid = vec![vec![' '; width]; height]; 58 | 59 | // Draw the axis 60 | if let Some(axis_x) = axis_x { 61 | if axis_x >= 0.0 { 62 | let xaxis = axis_x as usize; 63 | if height > xaxis { 64 | for i in 0..width { 65 | grid[height - 1 - xaxis][i] = '-'; // x-axis (horizontal) 66 | } 67 | } 68 | } 69 | } 70 | 71 | if let Some(axis_y) = axis_y { 72 | if axis_y >= 0.0 { 73 | let axisy = axis_y as usize; 74 | for i in 0..height { 75 | grid[i][axisy] = '|'; // y-axis (vertical) 76 | if let Some(axis_x) = axis_x { 77 | if axis_x >= 0.0 { 78 | let xaxis = axis_x as usize; 79 | 80 | grid[height - 1 - xaxis][axisy] = '+'; // Intersection of x and y axes 81 | } 82 | } 83 | } 84 | } 85 | } 86 | 87 | // Plot interpolated points 88 | for pair in points.windows(2) { 89 | let interpolated_points = interpolate(pair[0], pair[1], 10); // Interpolating with 10 points between each pair 90 | for &(x, y) in &interpolated_points { 91 | let scaled_x = ((x - min_x) / (max_x - min_x) * (width - 1) as f32) as usize; 92 | let scaled_y = ((y - min_y) / (max_y - min_y) * (height - 1) as f32) as usize; 93 | 94 | if height > scaled_y { 95 | grid[height - 1 - scaled_y][scaled_x] = '*'; 96 | } 97 | } 98 | } 99 | 100 | // Print the grid 101 | for row in &grid { 102 | println!("{}", row.iter().collect::()); 103 | } 104 | } 105 | 106 | pub fn generate_points(start: f32, end: f32, step: f32, expression: &String) -> Vec<(f32, f32)> { 107 | let exp = math_utils::parse_expr(&expression).unwrap(); 108 | 109 | let mut points = Vec::new(); 110 | 111 | let mut x = start; 112 | while x <= end { 113 | let y = math_utils::evaluate(&exp.1, x as f64) as f32; 114 | points.push((x, y)); 115 | x += step; 116 | } 117 | 118 | points 119 | } 120 | -------------------------------------------------------------------------------- /src/http_commands.rs: -------------------------------------------------------------------------------- 1 | use reqwest::Method; 2 | use tokio::fs::File; 3 | use tokio::io::{AsyncReadExt, AsyncWriteExt}; 4 | use serde_derive::Deserialize; 5 | use serde_json::{from_str, Value}; 6 | use reqwest::{Client, header::{HeaderMap, HeaderName, HeaderValue}}; 7 | use core::str; 8 | use std::str::FromStr; 9 | use tokio::net::TcpListener; 10 | 11 | #[derive(Deserialize, Debug)] 12 | struct HttpRequestConfig { 13 | method: Option, 14 | url: Option, 15 | headers: Option, 16 | body: Option, 17 | } 18 | 19 | async fn read_config_file(file_path: &str) -> Result { 20 | let mut file = File::open(file_path).await.map_err(|e| format!("Failed to open file: {}", e))?; 21 | let mut contents = String::new(); 22 | file.read_to_string(&mut contents).await.map_err(|e| format!("Failed to read file: {}", e))?; 23 | serde_json::from_str(&contents).map_err(|e| format!("Failed to deserialize JSON: {}", e)) 24 | } 25 | 26 | // Helper function to build request headers 27 | fn build_headers(header_map: Option) -> Result { 28 | let mut headers = HeaderMap::new(); 29 | if let Some(headers_object) = header_map.and_then(|h| h.as_object().cloned()) { 30 | for (key, value) in headers_object { 31 | let header_key = HeaderName::from_str(&key).map_err(|_| format!("Invalid header key: {}", key))?; 32 | let value_str = value.as_str().ok_or(format!("Header value for '{}' is not a string", key))?; 33 | let header_value = HeaderValue::from_str(value_str).map_err(|_| format!("Invalid header value for '{}': {}", key, value_str))?; 34 | headers.insert(header_key, header_value); 35 | } 36 | } 37 | Ok(headers) 38 | } 39 | 40 | // Helper function to handle the request method and URL logic 41 | fn parse_method_and_url(config: &mut HttpRequestConfig, method: Option, url: Option) -> Result<(Method, String), String> { 42 | if let Some(u) = url { 43 | if !u.is_empty() { 44 | config.url = Some(u); 45 | } 46 | } 47 | 48 | let url = config.url.clone().ok_or("No valid URL provided.".to_string())?; 49 | 50 | if let Some(m) = method { 51 | if !m.is_empty() { 52 | config.method = Some(m); 53 | } 54 | } 55 | 56 | let method = config.method.clone().ok_or("No valid HTTP method provided.".to_string())?; 57 | let method = Method::from_str(&method).map_err(|_| format!("Invalid HTTP method: {}", method))?; 58 | 59 | Ok((method, url)) 60 | } 61 | 62 | pub async fn http_request( 63 | method: Option, 64 | url: Option, 65 | config_path: Option, 66 | output: Option 67 | ) { 68 | let mut config = HttpRequestConfig { 69 | method: None, 70 | url: None, 71 | headers: None, 72 | body: None, 73 | }; 74 | 75 | // Load configuration from file, if provided 76 | if let Some(c) = config_path { 77 | if let Err(e) = read_config_file(&c).await.map(|cfg| config = cfg) { 78 | eprintln!("Error reading config: {}", e); 79 | return; 80 | } 81 | } 82 | 83 | // Parse the method and URL 84 | let (method, url) = match parse_method_and_url(&mut config, method, url) { 85 | Ok((method, url)) => (method, url), 86 | Err(e) => { 87 | eprintln!("{}", e); 88 | return; 89 | } 90 | }; 91 | 92 | // Build the request 93 | let client = Client::new(); 94 | let mut builder = client.request(method, &url); 95 | 96 | // Add headers if provided 97 | if let Ok(headers) = build_headers(config.headers) { 98 | builder = builder.headers(headers); 99 | } else { 100 | eprintln!("Error building headers"); 101 | return; 102 | } 103 | 104 | // Add body if provided 105 | if let Some(body) = config.body { 106 | if let Ok(body_str) = serde_json::to_string(&body) { 107 | builder = builder.body(body_str); 108 | } else { 109 | eprintln!("Failed to serialize body"); 110 | return; 111 | } 112 | } 113 | 114 | // Send the request and handle the response 115 | match builder.send().await { 116 | Ok(response) => handle_response(response, output).await, 117 | Err(e) => eprintln!("Http request failed: {}", e), 118 | } 119 | } 120 | 121 | // Helper function to handle HTTP response 122 | async fn handle_response(response: reqwest::Response, output: Option) { 123 | let status = response.status(); 124 | if status.is_success() { 125 | if let Some(output) = output { 126 | if let Ok(bytes) = response.bytes().await { 127 | if let Ok(mut file) = File::create(&output).await { 128 | let _ = file.write_all(&bytes).await; 129 | println!("Downloaded file to: {}", output); 130 | } else { 131 | eprintln!("Failed to write the output file."); 132 | } 133 | } 134 | } else { 135 | if let Ok(text) = response.text().await { 136 | let payload = from_str(&text).unwrap_or(Value::Null); 137 | let json_output = serde_json::to_string_pretty(&payload).unwrap(); 138 | println!("{}", json_output); 139 | } else { 140 | eprintln!("Failed to read response."); 141 | } 142 | } 143 | } else { 144 | eprintln!("Request failed with status: {}", status); 145 | 146 | // Attempt to read and print the error body if available 147 | if let Ok(error_body) = response.text().await { 148 | let payload = from_str(&error_body).unwrap_or(Value::Null); 149 | let json_output = serde_json::to_string_pretty(&payload).unwrap(); 150 | println!("{}", json_output); 151 | } else { 152 | eprintln!("Failed to read the error response body."); 153 | } 154 | } 155 | } 156 | 157 | 158 | pub async fn http_serve(port: u16) { 159 | let addr = format!("0.0.0.0:{}", port); 160 | let listener = TcpListener::bind(&addr).await.unwrap(); 161 | println!("Server listening on http://{}", addr); 162 | 163 | // Accept incoming connections in a loop 164 | loop { 165 | let (mut stream, _) = listener.accept().await.unwrap(); 166 | tokio::spawn(async move { 167 | handle_client(&mut stream).await; 168 | }); 169 | } 170 | } 171 | 172 | async fn handle_client(stream: &mut tokio::net::TcpStream) { 173 | let mut buffer = [0; 1024]; 174 | match stream.read(&mut buffer).await { 175 | Ok(0) => return, // Connection closed 176 | Ok(_) => { 177 | if let Ok(request) = str::from_utf8(&buffer) { 178 | println!("Received request: {}", request); 179 | let response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nHello, World!"; 180 | 181 | if let Err(e) = stream.write_all(response.as_bytes()).await { 182 | eprintln!("Failed to send response: {}", e); 183 | } 184 | 185 | if let Err(e) = stream.flush().await { 186 | eprintln!("Failed to flush stream: {}", e); 187 | } 188 | } 189 | } 190 | Err(e) => { 191 | eprintln!("Failed to read from stream: {}", e); 192 | } 193 | } 194 | 195 | // Close the connection 196 | if let Err(e) = stream.shutdown().await { 197 | eprintln!("Failed to shutdown stream: {}", e); 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/input_utils.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Read}; 2 | 3 | pub fn args_or_readline(args: Vec) -> String{ 4 | if args.is_empty() { 5 | // If no arguments are provided, read from stdin 6 | let mut input = String::new(); 7 | io::stdin().read_line(&mut input).expect("Failed to read from stdin"); 8 | input.trim().to_string() // Trim any whitespace from stdin input 9 | } else { 10 | // Concatenate provided arguments with spaces 11 | args.join(" ") 12 | } 13 | } -------------------------------------------------------------------------------- /src/ip_commands.rs: -------------------------------------------------------------------------------- 1 | use reqwest; 2 | use serde_derive::Serialize; 3 | use tokio::task::JoinHandle; 4 | use std::net::Ipv4Addr; 5 | use std::str::FromStr; 6 | use std::time::Duration; 7 | use std::time::Instant; 8 | 9 | use crate::format_utils; 10 | use crate::ip_utils; 11 | 12 | #[derive(Serialize, Debug)] 13 | struct IpResponse { 14 | ip: String, 15 | } 16 | 17 | #[derive(Serialize, Debug)] 18 | struct MultiIpResponse { 19 | ips: Vec, 20 | } 21 | 22 | #[derive(Serialize, Debug)] 23 | struct OnlineResponse { 24 | online: bool, 25 | } 26 | 27 | pub async fn ip_local(json: bool) { 28 | match ip_utils::get_local_ip().await { 29 | Ok(addr) => { 30 | if json { 31 | format_utils::print_json(&IpResponse { 32 | ip: addr.to_string(), 33 | }); 34 | } else { 35 | println!("{}", addr) 36 | } 37 | } 38 | Err(e) => eprintln!("Failed to get local address: {}", e), 39 | } 40 | } 41 | 42 | pub async fn ip_public(json: bool) { 43 | // Send a request to an external service that returns the public IP 44 | let response = match reqwest::get("https://api.ipify.org").await { 45 | Ok(s) => s, 46 | Err(e) => { 47 | eprintln!("Http request failed: {}", e); 48 | return; 49 | } 50 | }; 51 | 52 | match response.text().await { 53 | Ok(ip_addr) => { 54 | if json { 55 | format_utils::print_json(&IpResponse { ip: ip_addr }); 56 | } else { 57 | println!("{}", ip_addr) 58 | } 59 | } 60 | Err(e) => println!("{}", e), 61 | } 62 | } 63 | 64 | pub async fn ip_scan(ip_str: Option, json: bool) { 65 | let timeout = Duration::from_millis(100); 66 | 67 | let ip_str = ip_utils::to_ip_or_local(ip_str).await; 68 | 69 | let ping_tasks: Vec>> = (0..255) 70 | .filter_map(|i| { 71 | // Split the IP into segments 72 | let mut segments: Vec = ip_str.split('.').map(|s| s.to_string()).collect(); 73 | 74 | // Make sure it's a valid IPv4 address (i.e., has 4 segments) 75 | if segments.len() != 4 { 76 | eprintln!("Invalid IP format: {}", ip_str); 77 | return None; 78 | } 79 | 80 | // Replace the last segment with `i` 81 | segments[3] = i.to_string(); 82 | 83 | // Join the segments back into a string 84 | let target_ip_str = segments.join("."); 85 | 86 | // Parse the modified string back into an Ipv4Addr 87 | let target_ip = match Ipv4Addr::from_str(&target_ip_str) { 88 | Ok(ip) => ip, 89 | Err(_) => return None, // Skip invalid IPs 90 | }; 91 | 92 | let start = Instant::now(); 93 | 94 | // Create a task for each ping 95 | Some(tokio::spawn(async move { 96 | match ip_utils::ping(target_ip, timeout).await { 97 | Ok(_) => Some((target_ip, start.elapsed())), 98 | Err(_) => None, 99 | } 100 | })) 101 | }) 102 | .collect(); 103 | 104 | let mut ips: Vec = Vec::new(); 105 | // Await all tasks and filter successful pings 106 | for task in ping_tasks { 107 | if let Ok(Some(result)) = task.await { 108 | ips.push(result.0.to_string()); 109 | } 110 | } 111 | 112 | if json { 113 | format_utils::print_json(&MultiIpResponse { ips: ips }); 114 | } else { 115 | println!("{}", ips.join("\n\r")) 116 | } 117 | } 118 | 119 | pub async fn ip_status(ip_str: String, json: bool) { 120 | let ip = match Ipv4Addr::from_str(&ip_str) { 121 | Ok(s) => s, 122 | Err(e) => { 123 | eprintln!("Failed to bind udp socket: {}", e); 124 | return; 125 | } 126 | }; 127 | 128 | let timeout = Duration::from_millis(100); // Timeout for each port scan 129 | 130 | if json { 131 | match ip_utils::ping(ip, timeout).await { 132 | Ok(_) => format_utils::print_json(&OnlineResponse { online: true }), 133 | Err(_) => format_utils::print_json(&OnlineResponse { online: false }), 134 | } 135 | } else { 136 | match ip_utils::ping(ip, timeout).await { 137 | Ok(_) => println!("online"), 138 | Err(_) => println!("offline"), 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/ip_utils.rs: -------------------------------------------------------------------------------- 1 | use socket2::{Domain, Protocol, Socket, Type}; 2 | use std::mem::MaybeUninit; 3 | use std::net::SocketAddr; 4 | use std::net::{IpAddr, Ipv4Addr}; 5 | use std::time::Duration; 6 | use tokio::net::{TcpStream, UdpSocket}; 7 | use tokio::time::timeout; 8 | 9 | // ICMP Echo Request Type and Code 10 | const ICMP_ECHO_REQUEST: u8 = 8; 11 | const ICMP_ECHO_REPLY: u8 = 0; 12 | 13 | fn checksum(data: &[u8]) -> u16 { 14 | let mut sum = 0u32; 15 | let chunks = data.chunks_exact(2); 16 | for chunk in chunks { 17 | sum += u16::from_be_bytes([chunk[0], chunk[1]]) as u32; 18 | } 19 | if let Some(&byte) = data.chunks_exact(2).remainder().first() { 20 | sum += (byte as u32) << 8; 21 | } 22 | while (sum >> 16) != 0 { 23 | sum = (sum & 0xFFFF) + (sum >> 16); 24 | } 25 | !(sum as u16) 26 | } 27 | 28 | fn build_icmp_packet(sequence: u16) -> Vec { 29 | let mut packet = vec![0u8; 8]; // ICMP Header is 8 bytes 30 | packet[0] = ICMP_ECHO_REQUEST; // Type (Echo Request) 31 | packet[1] = 0; // Code 32 | packet[2] = 0; // Checksum (placeholder) 33 | packet[3] = 0; 34 | packet[4] = (sequence >> 8) as u8; // Identifier 35 | packet[5] = (sequence & 0xff) as u8; 36 | packet[6] = 0; // Sequence number 37 | packet[7] = sequence as u8; 38 | 39 | let checksum_val = checksum(&packet); 40 | packet[2] = (checksum_val >> 8) as u8; 41 | packet[3] = (checksum_val & 0xff) as u8; 42 | 43 | packet 44 | } 45 | 46 | pub async fn ping(target_ip: Ipv4Addr, timeout: Duration) -> Result<(), std::io::Error> { 47 | // Create a raw socket for ICMP (requires root/admin privileges) 48 | let socket = Socket::new(Domain::IPV4, Type::RAW, Some(Protocol::ICMPV4))?; 49 | 50 | // Set a timeout for the socket 51 | socket.set_read_timeout(Some(timeout))?; 52 | 53 | // Destination address 54 | let socket_addr = socket2::SockAddr::from(std::net::SocketAddrV4::new(target_ip, 0)); 55 | 56 | // Build an ICMP echo request packet 57 | let icmp_packet = build_icmp_packet(1); // Sequence number 1 58 | 59 | // Send the ICMP packet 60 | socket.send_to(&icmp_packet, &socket_addr)?; 61 | 62 | // Buffer to receive the reply 63 | let mut buf = [const { MaybeUninit::::uninit() }; 32]; 64 | 65 | // Receive the ICMP echo reply 66 | match socket.recv_from(&mut buf) { 67 | Ok((size, _)) => { 68 | // SAFETY: We know `size` bytes were written to the buffer, so we can safely assume these bytes are initialized 69 | let initialized_buf = 70 | unsafe { std::slice::from_raw_parts(buf.as_ptr() as *const u8, size) }; 71 | 72 | if size >= 20 && initialized_buf[20] == ICMP_ECHO_REPLY { 73 | return Ok(()); 74 | } else { 75 | return Err(std::io::Error::new( 76 | std::io::ErrorKind::Other, 77 | "Received non-echo reply packet", 78 | )); 79 | } 80 | } 81 | Err(e) => { 82 | // Forward the error instead of printing it 83 | return Err(e); 84 | } 85 | } 86 | } 87 | 88 | pub async fn can_connect( 89 | ip: Ipv4Addr, 90 | port: u16, 91 | timeout_duration: Duration, 92 | ) -> Result { 93 | // Define the socket address (IP + port) 94 | let address = SocketAddr::new(IpAddr::V4(ip), port); 95 | 96 | // Attempt to connect with a timeout 97 | let result = timeout(timeout_duration, TcpStream::connect(address)).await; 98 | 99 | match result { 100 | Ok(Ok(_)) => Ok(true), // Connection succeeded 101 | Ok(Err(e)) => Err(e), // Connection failed 102 | Err(_) => Ok(false), // Timeout error 103 | } 104 | } 105 | 106 | pub async fn get_local_ip() -> Result { 107 | let socket = UdpSocket::bind("0.0.0.0:0").await?; 108 | socket.connect("8.8.8.8:80").await?; 109 | let socket_addr = socket.local_addr()?; 110 | return Ok(socket_addr.ip()); 111 | } 112 | 113 | pub async fn to_ip_or_local(ip: Option) -> String { 114 | // Check if the provided IP is Some and non-empty 115 | if let Some(t) = ip { 116 | if !t.is_empty() { 117 | return t; // Return the provided IP 118 | } 119 | } 120 | 121 | // If the provided IP is None or empty, get the local IP 122 | match get_local_ip().await { 123 | Ok(addr) => addr.to_string(), // Return the local address as a string 124 | Err(e) => { 125 | eprintln!("Failed to get local address: {}", e); 126 | String::from("127.0.0.1") // Return a default value if an error occurs 127 | } 128 | } 129 | } -------------------------------------------------------------------------------- /src/json_commands.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Read}; 2 | use serde_derive::Serialize; 3 | use serde_json::{from_str, Value}; 4 | use base64::prelude::*; 5 | use std::str; 6 | use base64::engine::general_purpose::URL_SAFE; 7 | 8 | pub async fn json_extract(property: String) { 9 | let mut input = String::new(); 10 | io::stdin().read_to_string(&mut input).expect("Failed to read from stdin"); 11 | let json: Value = serde_json::from_str(&input).expect("Failed to parse JSON"); 12 | 13 | let filter: &str = &format!("/{}", property); 14 | match json.pointer(filter) { 15 | Some(value) => match value { 16 | Value::String(s) => println!("{}", s), // Print raw string if it's a JSON string 17 | _ => println!("{}", value), // Print normally for non-string values 18 | }, 19 | None => eprintln!("Field not found in the provided JSON"), 20 | } 21 | } 22 | 23 | #[derive(Serialize)] 24 | struct JwtDecoded { 25 | header: Value, 26 | payload: Value, 27 | } 28 | 29 | pub fn json_decode_jwt(token: &str) { 30 | let parts: Vec<&str> = token.split('.').collect(); 31 | if parts.len() != 3 { 32 | println!("Invalid token format"); 33 | return; 34 | } 35 | 36 | let header = parts[0]; 37 | let payload = parts[1]; 38 | 39 | // Decode the header 40 | let decoded_header = decode_jwt_part(header).unwrap_or_else(|err| { 41 | println!("Error decoding header: {}", err); 42 | "".to_string() 43 | }); 44 | 45 | // Decode the payload 46 | let decoded_payload = decode_jwt_part(payload).unwrap_or_else(|err| { 47 | println!("Error decoding payload: {}", err); 48 | "".to_string() 49 | }); 50 | 51 | // Create a struct to hold the decoded values 52 | let decoded_jwt = JwtDecoded { 53 | header: from_str(&decoded_header).unwrap_or(Value::Null), 54 | payload: from_str(&decoded_payload).unwrap_or(Value::Null), 55 | }; 56 | 57 | // Serialize the struct to JSON 58 | let json_output = serde_json::to_string_pretty(&decoded_jwt).unwrap(); 59 | 60 | // Print the JSON output 61 | println!("{}", json_output); 62 | } 63 | 64 | 65 | fn decode_jwt_part(part: &str) -> Result { 66 | // Ensure correct padding for Base64 URL encoding 67 | let mut padding = String::new(); 68 | let missing_padding = 4 - (part.len() % 4); 69 | if missing_padding != 4 { 70 | padding.push_str(&"=".repeat(missing_padding)); 71 | } 72 | 73 | // Create the Base64 URL string with proper padding 74 | let base64_url_encoded = format!("{}{}", part, padding); 75 | 76 | // Decode the Base64 string 77 | match URL_SAFE.decode(&base64_url_encoded) { 78 | Ok(decoded_bytes) => { 79 | // Convert bytes to string 80 | str::from_utf8(&decoded_bytes) 81 | .map(|s| s.to_string()) 82 | .map_err(|e| e.to_string()) 83 | } 84 | Err(e) => Err(format!("Base64 decode error: {}", e)), 85 | } 86 | } -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod csv_utils; 2 | pub mod cpu_commands; 3 | pub mod format_utils; 4 | pub mod ip_commands; 5 | pub mod ip_utils; 6 | pub mod mem_commands; 7 | pub mod port_commands; 8 | pub mod disk_commands; 9 | pub mod network_commands; 10 | pub mod http_commands; 11 | pub mod json_commands; 12 | pub mod csv_commands; 13 | pub mod text_commands; 14 | pub mod file_commands; 15 | pub mod time_commands; 16 | pub mod bits_commands; 17 | pub mod math_utils; 18 | pub mod math_commands; 19 | pub mod graph_utils; 20 | pub mod process_commands; 21 | pub mod bits_utils; 22 | pub mod input_utils; 23 | pub mod env_commands; -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use aid::{ 2 | bits_commands, cpu_commands, csv_commands, disk_commands, env_commands, file_commands, 3 | http_commands, input_utils, ip_commands, json_commands, math_commands, mem_commands, 4 | network_commands, port_commands, process_commands, text_commands, time_commands, 5 | }; 6 | use clap::{Parser, Subcommand}; 7 | 8 | #[derive(Parser)] 9 | #[command(author, version, about, long_about = None)] 10 | struct Args { 11 | #[command(subcommand)] 12 | cmd: Commands, 13 | } 14 | 15 | #[derive(Subcommand, Debug, Clone)] 16 | enum Commands { 17 | #[command(subcommand, about = "HTTP functions")] 18 | Http(HttpCommands), 19 | #[command(subcommand, about = "IP information / scanning")] 20 | Ip(IpCommands), 21 | #[command(subcommand, about = "Port information / scanning")] 22 | Port(PortCommands), 23 | #[command(subcommand, about = "System cpu information")] 24 | Cpu(CpuCommands), 25 | #[command(subcommand, about = "System memory information")] 26 | Mem(MemoryCommands), 27 | #[command(subcommand, about = "System disk information")] 28 | Disk(DiskCommands), 29 | #[command(subcommand, about = "System network information")] 30 | Network(NetworkCommands), 31 | #[command(subcommand, about = "JSON parsing / extraction functions")] 32 | Json(JsonCommands), 33 | #[command(subcommand, about = "CSV searching / filtering")] 34 | Csv(CsvCommands), 35 | #[command(subcommand, about = "Text manipulation functions")] 36 | Text(TextCommands), 37 | #[command(subcommand, about = "File information")] 38 | File(FileCommands), 39 | #[command(subcommand, about = "Time related functions")] 40 | Time(TimeCommands), 41 | #[command(subcommand, about = "Math functions")] 42 | Math(MathCommands), 43 | #[command(subcommand, about = "Bit manipulation functions")] 44 | Bits(BitsCommands), 45 | #[command(subcommand, about = "Process monitoring functions")] 46 | Process(ProcessCommands), 47 | #[command(subcommand, about = "Environment information")] 48 | Env(EnvCommands), 49 | } 50 | #[derive(Subcommand, Debug, Clone)] 51 | enum IpCommands { 52 | #[command(about = "Show my local IP address")] 53 | Local { 54 | #[arg(short = 'j', long = "json", action = clap::ArgAction::SetTrue, 55 | help = "Output the local IP address in JSON format.")] 56 | json: bool, 57 | }, 58 | 59 | #[command(about = "Show my public IP address")] 60 | Public { 61 | #[arg(short = 'j', long = "json", action = clap::ArgAction::SetTrue, 62 | help = "Output the public IP address in JSON format.")] 63 | json: bool, 64 | }, 65 | 66 | #[command(about = "Scan a specified IP address subnet for active ip addresses")] 67 | Scan { 68 | #[arg( 69 | short = 'i', 70 | long = "ip", 71 | default_value = "", 72 | help = "The IP subnet to scan. If not provided, the local subnet will be used." 73 | )] 74 | ip: Option, 75 | 76 | #[arg(short = 'j', long = "json", action = clap::ArgAction::SetTrue, 77 | help = "Output scan results in JSON format.")] 78 | json: bool, 79 | }, 80 | 81 | #[command(about = "Try to connect to the specified IP address")] 82 | Status { 83 | #[arg( 84 | short = 'i', 85 | long = "ip", 86 | help = "The IP address to check the status of." 87 | )] 88 | ip: String, 89 | 90 | #[arg(short = 'j', long = "json", action = clap::ArgAction::SetTrue, 91 | help = "Output status in JSON format.")] 92 | json: bool, 93 | }, 94 | } 95 | 96 | #[derive(Subcommand, Debug, Clone)] 97 | enum PortCommands { 98 | #[command(about = "Check if the specified port is 'open' or 'closed'.")] 99 | Status { 100 | #[arg(short = 'i', long = "ip", help = "The IP address to check (optional).")] 101 | ip: Option, 102 | 103 | #[arg(short = 'p', help = "The port number to check the status of.")] 104 | port: u16, 105 | 106 | #[arg(short = 'j', long = "json", action = clap::ArgAction::SetTrue, 107 | help = "Output port status in JSON format.")] 108 | json: bool, 109 | }, 110 | 111 | #[command(about = "Scan for open ports on a specified IP address")] 112 | Scan { 113 | #[arg(short = 'i', long = "ip", help = "The IP address to scan (optional).")] 114 | ip: Option, 115 | 116 | #[arg(short = 'j', long = "json", action = clap::ArgAction::SetTrue, 117 | help = "Output scan results in JSON format.")] 118 | json: bool, 119 | }, 120 | } 121 | 122 | #[derive(Subcommand, Debug, Clone)] 123 | enum CpuCommands { 124 | #[command(about = "Show CPU information")] 125 | Info { 126 | #[arg(short = 'j', long = "json", action = clap::ArgAction::SetTrue, 127 | help = "Output CPU information in JSON format.")] 128 | json: bool, 129 | }, 130 | 131 | #[command(about = "Monitor CPU usage")] 132 | Usage { 133 | #[arg(short = 'w', long = "watch", action = clap::ArgAction::SetTrue, 134 | help = "Continuously monitor CPU usage.")] 135 | watch: bool, 136 | 137 | #[arg(short = 'j', long = "json", action = clap::ArgAction::SetTrue, 138 | help = "Output CPU usage in JSON format.")] 139 | json: bool, 140 | 141 | #[arg(short = 'p', long = "plot", action = clap::ArgAction::SetTrue, 142 | help = "Plot total % CPU usage.")] 143 | plot: bool, 144 | }, 145 | } 146 | 147 | #[derive(Subcommand, Debug, Clone)] 148 | enum MemoryCommands { 149 | #[command(about = "Monitor memory usage")] 150 | Usage { 151 | #[arg(short = 'w', long = "watch", action = clap::ArgAction::SetTrue, 152 | help = "Continuously monitor memory usage.")] 153 | watch: bool, 154 | 155 | #[arg(short = 'j', long = "json", action = clap::ArgAction::SetTrue, 156 | help = "Output memory usage in JSON format.")] 157 | json: bool, 158 | 159 | #[arg(short = 'p', long = "plot", action = clap::ArgAction::SetTrue, 160 | help = "Plot total % mem usage.")] 161 | plot: bool, 162 | }, 163 | } 164 | 165 | #[derive(Subcommand, Debug, Clone)] 166 | enum DiskCommands { 167 | #[command(about = "Show disk information")] 168 | Info { 169 | #[arg(short = 'j', long = "json", action = clap::ArgAction::SetTrue, 170 | help = "Output disk information in JSON format.")] 171 | json: bool, 172 | }, 173 | } 174 | 175 | #[derive(Subcommand, Debug, Clone)] 176 | enum NetworkCommands { 177 | #[command(about = "Show network information")] 178 | Info { 179 | #[arg(short = 'j', long = "json", action = clap::ArgAction::SetTrue, 180 | help = "Output network information in JSON format.")] 181 | json: bool, 182 | }, 183 | #[command(about = "Monitor network usage")] 184 | Usage { 185 | #[arg(short = 'w', long = "watch", action = clap::ArgAction::SetTrue, 186 | help = "Continuously monitor network usage.")] 187 | watch: bool, 188 | }, 189 | } 190 | 191 | #[derive(Subcommand, Debug, Clone)] 192 | enum HttpCommands { 193 | #[command(about = "Make a HTTP request")] 194 | Req { 195 | #[arg( 196 | short = 'm', 197 | long = "method", 198 | help = "Specify the HTTP method (e.g., GET, POST)." 199 | )] 200 | method: Option, 201 | 202 | #[arg( 203 | short = 'u', 204 | long = "url", 205 | help = "Specify the URL for the HTTP request." 206 | )] 207 | url: Option, 208 | 209 | #[arg( 210 | short = 'c', 211 | long = "config", 212 | help = "Path to a configuration file for the request. Specify: method, url, body, headers in json format." 213 | )] 214 | config: Option, 215 | 216 | #[arg( 217 | short = 'o', 218 | long = "output", 219 | help = "If specified saves http response body to a file at the given path." 220 | )] 221 | output: Option, 222 | }, 223 | 224 | #[command(about = "Start a HTTP server (GET: 0.0.0.0:80 -> 'Hello, World!')")] 225 | Serve { 226 | #[arg( 227 | short = 'p', 228 | long = "port", 229 | default_value = "80", 230 | help = "Specify the port for the HTTP server (default is 80)." 231 | )] 232 | port: u16, 233 | }, 234 | } 235 | 236 | #[derive(Subcommand, Debug, Clone)] 237 | enum JsonCommands { 238 | #[command(about = "Extract a property from JSON data")] 239 | Extract { 240 | #[arg( 241 | short = 'p', 242 | long = "prop", 243 | help = "Specify the property to extract from the JSON." 244 | )] 245 | property: String, 246 | }, 247 | #[command(about = "Decode a JWT")] 248 | JwtDecode { 249 | #[arg(trailing_var_arg = true)] 250 | c_args: Vec, 251 | }, 252 | } 253 | 254 | #[derive(Subcommand, Debug, Clone)] 255 | enum CsvCommands { 256 | #[command(about = "Sql search over csv")] 257 | Search { 258 | #[arg(short = 's', long = "sql", help = "Sql query.")] 259 | sql: String, 260 | #[arg(short = 'o', long = "output", help = "Output file path.")] 261 | output: Option, 262 | }, 263 | } 264 | 265 | #[derive(Subcommand, Debug, Clone)] 266 | enum TextCommands { 267 | #[command(about = "base64 encode")] 268 | Base64Encode { 269 | #[arg(trailing_var_arg = true)] 270 | c_args: Vec, 271 | }, 272 | 273 | #[command(about = "base64 decode")] 274 | Base64Decode { 275 | #[arg(trailing_var_arg = true)] 276 | c_args: Vec, 277 | }, 278 | #[command(about = "url encodes a string")] 279 | UrlEncode { 280 | #[arg(trailing_var_arg = true)] 281 | c_args: Vec, 282 | }, 283 | #[command(about = "decodes a url encoded string")] 284 | UrlDecode { 285 | #[arg(trailing_var_arg = true)] 286 | c_args: Vec, 287 | }, 288 | #[command(about = "search a text file for lines that match a regex")] 289 | Regex { 290 | #[arg(short = 'f', long = "file", help = "Input text file to search.")] 291 | file: String, 292 | #[arg(short = 'r', long = "regex", help = "regex search pattern.")] 293 | regex: String, 294 | }, 295 | 296 | #[command(about = "print the specified range of lines.")] 297 | Lines { 298 | #[arg(short = 'i', long = "input", help = "Input text file to search.")] 299 | file: String, 300 | #[arg(short = 's', long = "start", help = "first line to print")] 301 | start: Option, 302 | #[arg(short = 'e', long = "end", help = "last line to print")] 303 | end: Option, 304 | #[arg( 305 | short = 'f', 306 | long = "first", 307 | help = "number of lines from the start of the file to print" 308 | )] 309 | head: Option, 310 | #[arg( 311 | short = 'l', 312 | long = "last", 313 | help = "number of lines from the end of the file to print" 314 | )] 315 | tail: Option, 316 | }, 317 | 318 | #[command(about = "Generates a random guid")] 319 | Guid, 320 | #[command(about = "Find and replace text using a regex pattern in the given input string.")] 321 | Replace { 322 | #[arg( 323 | short = 'm', 324 | long = "match", 325 | help = "Regex pattern to match substrings to replace." 326 | )] 327 | pattern: String, 328 | 329 | #[arg( 330 | short = 'r', 331 | long = "replace", 332 | help = "Replacement string for matched substrings." 333 | )] 334 | replace: String, 335 | 336 | #[arg( 337 | trailing_var_arg = true, 338 | )] 339 | c_args: Vec, 340 | }, 341 | 342 | #[command(about = "Count occurrences of a regex pattern in the given input string.")] 343 | Count { 344 | #[arg( 345 | short = 'm', 346 | long = "match", 347 | help = "Regex pattern to match and count in the input." 348 | )] 349 | pattern: String, 350 | 351 | #[arg( 352 | trailing_var_arg = true, 353 | )] 354 | c_args: Vec, 355 | }, 356 | } 357 | 358 | #[derive(Subcommand, Debug, Clone)] 359 | enum FileCommands { 360 | #[command(about = "prints file metadata")] 361 | Info { 362 | #[arg(short = 'f', long = "file", help = "Input file.")] 363 | file: String, 364 | }, 365 | #[command(about = "calculate a files Md5 checksum")] 366 | Md5 { 367 | #[arg(short = 'f', long = "file", help = "Input file.")] 368 | file: String, 369 | }, 370 | #[command(about = "calculate a files Sha1 checksum")] 371 | Sha1 { 372 | #[arg(short = 'f', long = "file", help = "Input file.")] 373 | file: String, 374 | }, 375 | #[command(about = "calculate a files Sha256 checksum")] 376 | Sha256 { 377 | #[arg(short = 'f', long = "file", help = "Input file.")] 378 | file: String, 379 | }, 380 | 381 | #[command(about = "zips the files in the source directory")] 382 | Zip { 383 | #[arg(short = 'd', long = "dir", help = "source directory.")] 384 | dir: String, 385 | #[arg(short = 'f', long = "file", help = "output zip file.")] 386 | file: String, 387 | }, 388 | } 389 | 390 | #[derive(Subcommand, Debug, Clone)] 391 | enum TimeCommands { 392 | #[command(about = "Display unix timestamp")] 393 | Unix { 394 | #[arg(short = 'm', long = "milli", action = clap::ArgAction::SetTrue, 395 | help = "Output the timestamp as unix milliseconds.")] 396 | milli: bool, 397 | #[arg(short = 'd', long = "dt", help = "Use the specified datetime.")] 398 | dt: Option, 399 | }, 400 | #[command(about = "Display the datetime")] 401 | Dt { 402 | #[arg(short = 'l', long = "local", action = clap::ArgAction::SetTrue, 403 | help = "Use the local datetime.")] 404 | local: bool, 405 | #[arg( 406 | short = 'u', 407 | long = "unix", 408 | help = "Use the specified unix second timestamp." 409 | )] 410 | unix: Option, 411 | #[arg(short = 'r', long = "rfc", action = clap::ArgAction::SetTrue, 412 | help = "Output the datetime in Rfc3339 format.")] 413 | rfc: bool, 414 | }, 415 | #[command(about = "Describes a chron job")] 416 | Chron { 417 | #[arg(trailing_var_arg = true)] 418 | c_args: Vec, 419 | }, 420 | #[command(about = "Start a countdown timer for the given minutes / seconds")] 421 | CountDown { 422 | #[arg(trailing_var_arg = true)] 423 | c_args: Vec, 424 | }, 425 | } 426 | 427 | #[derive(Subcommand, Debug, Clone)] 428 | enum MathCommands { 429 | #[command(about = "Evaluates a math expression")] 430 | Eval { 431 | #[arg(trailing_var_arg = true)] 432 | c_args: Vec, 433 | }, 434 | #[command(about = "Plot a math expression")] 435 | Plot { 436 | #[arg(long = "start", help = "Start x coord.", allow_hyphen_values = true)] 437 | start_x: f32, 438 | #[arg(long = "end", help = "End x coord.", allow_hyphen_values = true)] 439 | end_x: f32, 440 | #[arg( 441 | short = 's', 442 | long = "step", 443 | help = "x step size.", 444 | allow_hyphen_values = true 445 | )] 446 | step_x: f32, 447 | #[arg(trailing_var_arg = true)] 448 | c_args: Vec, 449 | }, 450 | } 451 | 452 | #[derive(Subcommand, Debug, Clone)] 453 | enum BitsCommands { 454 | #[command( 455 | about = "Evaluates a bitwise expression, converts base, visualize binary / display info" 456 | )] 457 | Eval { 458 | #[arg( 459 | short = 'i', 460 | long = "info", 461 | help = "Output the bitboard representation." 462 | )] 463 | board: bool, 464 | #[arg( 465 | short = 'c', 466 | long = "chess", 467 | help = "Output the chess bitboard representation." 468 | )] 469 | chess_board: bool, 470 | #[arg(short = 'b', long = "bin", help = "Output the result in binary.")] 471 | binary: bool, 472 | #[arg(long = "hex", help = "Output the result in hex.")] 473 | hex: bool, 474 | #[arg(trailing_var_arg = true)] 475 | c_args: Vec, 476 | }, 477 | } 478 | 479 | #[derive(Subcommand, Debug, Clone)] 480 | enum ProcessCommands { 481 | #[command(about = "Display process usage")] 482 | Usage { 483 | #[arg( 484 | short = 'f', 485 | long = "filter", 486 | help = "filter the results by process name regex." 487 | )] 488 | filter: Option, 489 | #[arg( 490 | short = 's', 491 | long = "sort", 492 | help = "Sort the results by [cpu, mem, disk]" 493 | )] 494 | sort_by: Option, 495 | #[arg( 496 | short = 'l', 497 | long = "limit", 498 | help = "Limit the number of results to display." 499 | )] 500 | limit: Option, 501 | #[arg(short = 'w', long = "watch", help = "Continuously monitor the processes.", action = clap::ArgAction::SetTrue)] 502 | watch: bool, 503 | }, 504 | } 505 | 506 | #[derive(Subcommand, Debug, Clone)] 507 | enum EnvCommands { 508 | #[command(about = "Filter / Display environment variables")] 509 | Vars { 510 | #[arg( 511 | short = 'k', 512 | long = "kfilter", 513 | help = "filter the results by a key regex." 514 | )] 515 | kfilter: Option, 516 | #[arg( 517 | short = 'v', 518 | long = "vfilter", 519 | help = "filter the results by a value regex." 520 | )] 521 | vfilter: Option, 522 | }, 523 | } 524 | 525 | #[tokio::main] 526 | async fn main() { 527 | let args = Args::parse(); 528 | match args.cmd { 529 | Commands::Ip(command) => match command { 530 | IpCommands::Local { json } => ip_commands::ip_local(json).await, 531 | IpCommands::Public { json } => ip_commands::ip_public(json).await, 532 | IpCommands::Scan { ip, json } => ip_commands::ip_scan(ip, json).await, 533 | IpCommands::Status { ip, json } => ip_commands::ip_status(ip, json).await, 534 | }, 535 | Commands::Port(command) => match command { 536 | PortCommands::Scan { ip, json } => port_commands::port_scan(ip, json).await, 537 | PortCommands::Status { ip, port, json } => { 538 | port_commands::port_status(ip, port, json).await 539 | } 540 | }, 541 | Commands::Cpu(sub_command) => match sub_command { 542 | CpuCommands::Info { json } => cpu_commands::system_cpu_info(json), 543 | CpuCommands::Usage { watch, json, plot } => { 544 | cpu_commands::system_cpu_usage(watch, json, plot).await 545 | } 546 | }, 547 | Commands::Mem(sub_command) => match sub_command { 548 | MemoryCommands::Usage { watch, json, plot } => { 549 | mem_commands::system_mem_usage(watch, json, plot).await 550 | } 551 | }, 552 | Commands::Disk(sub_command) => match sub_command { 553 | DiskCommands::Info { json } => disk_commands::system_disk_info(json).await, 554 | }, 555 | Commands::Network(sub_command) => match sub_command { 556 | NetworkCommands::Info { json } => network_commands::system_network_info(json).await, 557 | NetworkCommands::Usage { watch } => network_commands::system_network_usage(watch), 558 | }, 559 | Commands::Http(sub_command) => match sub_command { 560 | HttpCommands::Req { 561 | method, 562 | url, 563 | config, 564 | output, 565 | } => http_commands::http_request(method, url, config, output).await, 566 | HttpCommands::Serve { port } => http_commands::http_serve(port).await, 567 | }, 568 | 569 | Commands::Json(sub_command) => match sub_command { 570 | JsonCommands::Extract { property } => json_commands::json_extract(property).await, 571 | JsonCommands::JwtDecode { c_args } => { 572 | json_commands::json_decode_jwt(&input_utils::args_or_readline(c_args)) 573 | } 574 | }, 575 | 576 | Commands::Csv(sub_command) => match sub_command { 577 | CsvCommands::Search { sql, output } => csv_commands::sql_search(sql, output).await, 578 | }, 579 | 580 | Commands::Text(sub_command) => match sub_command { 581 | TextCommands::Base64Encode { c_args } => { 582 | text_commands::base64_encode(input_utils::args_or_readline(c_args)) 583 | } 584 | TextCommands::Base64Decode { c_args } => { 585 | text_commands::base64_decode(input_utils::args_or_readline(c_args)) 586 | } 587 | TextCommands::Regex { file, regex } => text_commands::regex_search(file, regex), 588 | TextCommands::Lines { 589 | file, 590 | start, 591 | end, 592 | head, 593 | tail, 594 | } => text_commands::print_lines(file, start, end, head, tail), 595 | TextCommands::Guid => { 596 | text_commands::guid() 597 | } 598 | TextCommands::UrlEncode {c_args} => { 599 | text_commands::url_encode(&input_utils::args_or_readline(c_args)) 600 | } 601 | TextCommands::UrlDecode {c_args} => { 602 | text_commands::url_decode(&input_utils::args_or_readline(c_args)) 603 | }, 604 | TextCommands::Replace {pattern, replace, c_args} => { 605 | text_commands::replace(input_utils::args_or_readline(c_args), &pattern, &replace) 606 | }, 607 | TextCommands::Count {pattern, c_args} => { 608 | text_commands::count(input_utils::args_or_readline(c_args), &pattern) 609 | } 610 | }, 611 | 612 | Commands::File(sub_command) => match sub_command { 613 | FileCommands::Info { file } => file_commands::file_info(file), 614 | FileCommands::Md5 { file } => file_commands::md5_checksum(file), 615 | FileCommands::Sha1 { file } => file_commands::sha1_checksum(file), 616 | FileCommands::Sha256 { file } => file_commands::sha256_checksum(file), 617 | FileCommands::Zip { dir, file } => file_commands::zip_directory(dir, file), 618 | }, 619 | Commands::Time(sub_command) => match sub_command { 620 | TimeCommands::Unix { milli, dt } => time_commands::unix_timestamp(milli, dt), 621 | TimeCommands::Dt { local, rfc, unix } => time_commands::date_time(local, rfc, unix), 622 | TimeCommands::Chron { c_args } => { 623 | time_commands::chron_tostring(input_utils::args_or_readline(c_args)) 624 | } 625 | TimeCommands::CountDown { c_args } => { 626 | time_commands::countdown(input_utils::args_or_readline(c_args)) 627 | } 628 | }, 629 | Commands::Math(sub_command) => match sub_command { 630 | MathCommands::Eval { c_args } => { 631 | math_commands::evaluate(input_utils::args_or_readline(c_args)) 632 | } 633 | MathCommands::Plot { 634 | start_x, 635 | end_x, 636 | step_x, 637 | c_args, 638 | } => math_commands::plot( 639 | start_x, 640 | end_x, 641 | step_x, 642 | input_utils::args_or_readline(c_args), 643 | ), 644 | }, 645 | Commands::Bits(sub_command) => match sub_command { 646 | BitsCommands::Eval { 647 | board, 648 | chess_board, 649 | binary, 650 | hex, 651 | c_args, 652 | } => bits_commands::evaluate( 653 | board, 654 | chess_board, 655 | binary, 656 | hex, 657 | input_utils::args_or_readline(c_args), 658 | ), 659 | }, 660 | Commands::Process(sub_command) => match sub_command { 661 | ProcessCommands::Usage { 662 | filter, 663 | sort_by, 664 | limit, 665 | watch, 666 | } => process_commands::system_process_info(filter, sort_by, limit, watch), 667 | }, 668 | Commands::Env(sub_command) => match sub_command { 669 | EnvCommands::Vars { kfilter, vfilter } => { 670 | env_commands::print_env_vars(kfilter, vfilter) 671 | } 672 | }, 673 | } 674 | } 675 | -------------------------------------------------------------------------------- /src/math_commands.rs: -------------------------------------------------------------------------------- 1 | use crate::{graph_utils, math_utils}; 2 | pub fn evaluate(expression: String) { 3 | match math_utils::parse_expr(&expression) { 4 | Ok((_, ast)) => { 5 | println!("{}", math_utils::evaluate(&ast, 0.0)); 6 | } 7 | Err(e) => println!("Error parsing expression: {:?}", e), 8 | } 9 | } 10 | 11 | pub fn plot(startx: f32, endx: f32, step: f32, expression: String) { 12 | // Example points 13 | let points = graph_utils::generate_points(startx, endx, step, &expression); 14 | // Plot the chart 15 | graph_utils::plot_chart(&points, expression); 16 | } -------------------------------------------------------------------------------- /src/math_utils.rs: -------------------------------------------------------------------------------- 1 | use nom::{ 2 | branch::alt, 3 | bytes::complete::tag, 4 | character::complete::{char, digit1, multispace0}, 5 | combinator::{map, opt, recognize}, 6 | sequence::{delimited, preceded, tuple}, 7 | IResult, 8 | }; 9 | use std::str::FromStr; 10 | 11 | // Define the AST for the expressions 12 | #[derive(Debug)] 13 | pub enum Expr { 14 | Number(f64), 15 | Variable, // New variant for the variable x 16 | NegateOp(Box), 17 | AbsOp(Box), 18 | BinaryOp(Box, Op, Box), 19 | TrigOp(TrigFunc, Box), 20 | Log2Op(Box), 21 | Log10Op(Box), 22 | Ln(Box), 23 | Sqrt(Box), 24 | Pi, 25 | E, 26 | } 27 | 28 | #[derive(Debug)] 29 | pub enum Op { 30 | Add, 31 | Sub, 32 | Mul, 33 | Div, 34 | Pow, 35 | Mod, 36 | } 37 | 38 | #[derive(Debug)] 39 | pub enum TrigFunc { 40 | Sin, 41 | aSin, 42 | Sinh, 43 | Cos, 44 | aCos, 45 | Cosh, 46 | Tan, 47 | aTan, 48 | Tanh, 49 | } 50 | 51 | // A parser for floating-point numbers 52 | fn parse_number(input: &str) -> IResult<&str, Expr> { 53 | let (input, number_str) = recognize(tuple(( 54 | preceded(multispace0, digit1), // integer part 55 | opt(preceded(char('.'), digit1)), // optional fractional part 56 | )))(input)?; 57 | 58 | let number = f64::from_str(number_str.trim()).unwrap(); 59 | Ok((input, Expr::Number(number))) 60 | } 61 | 62 | // A parser for unary operations (e.g., negation) 63 | fn parse_negate(input: &str) -> IResult<&str, Expr> { 64 | let (input, _) = preceded(multispace0, char('-'))(input)?; 65 | let (input, expr) = preceded(multispace0, parse_factor)(input)?; 66 | Ok((input, Expr::NegateOp(Box::new(expr)))) 67 | } 68 | 69 | fn parse_abs(input: &str) -> IResult<&str, Expr> { 70 | let (input, _) = preceded(multispace0, tag("abs"))(input)?; 71 | let (input, expr) = preceded(multispace0, parse_factor)(input)?; 72 | Ok((input, Expr::AbsOp(Box::new(expr)))) 73 | } 74 | 75 | // A parser for pi (π) 76 | fn parse_pi(input: &str) -> IResult<&str, Expr> { 77 | preceded(multispace0, map(tag("pi"), |_| Expr::Pi))(input) 78 | } 79 | 80 | // A parser for e (Euler's number) 81 | fn parse_e(input: &str) -> IResult<&str, Expr> { 82 | preceded(multispace0, map(tag("e"), |_| Expr::E))(input) 83 | } 84 | 85 | // A parser for the variable 'x' 86 | fn parse_variable(input: &str) -> IResult<&str, Expr> { 87 | preceded(multispace0, map(tag("x"), |_| Expr::Variable))(input) 88 | } 89 | 90 | // A parser for trigonometric functions 91 | fn parse_trig_func(input: &str) -> IResult<&str, Expr> { 92 | let (input, trig_func) = preceded( 93 | multispace0, 94 | alt(( 95 | map(tag("sinh"), |_| TrigFunc::Sinh), 96 | map(tag("sin"), |_| TrigFunc::Sin), 97 | map(tag("asin"), |_| TrigFunc::aSin), 98 | map(tag("cos"), |_| TrigFunc::Cos), 99 | map(tag("cosh"), |_| TrigFunc::Cosh), 100 | map(tag("acos"), |_| TrigFunc::aCos), 101 | map(tag("tanh"), |_| TrigFunc::Tanh), 102 | map(tag("tan"), |_| TrigFunc::Tan), 103 | map(tag("atan"), |_| TrigFunc::aTan), 104 | )), 105 | )(input)?; 106 | 107 | let (input, expr) = preceded(multispace0, delimited(char('('), parse_expr, char(')')))(input)?; 108 | Ok((input, Expr::TrigOp(trig_func, Box::new(expr)))) 109 | } 110 | 111 | fn parse_log2_func(input: &str) -> IResult<&str, Expr> { 112 | let (input, _) = preceded(multispace0, tag("log2"))(input)?; 113 | 114 | let (input, expr) = preceded(multispace0, delimited(char('('), parse_expr, char(')')))(input)?; 115 | Ok((input, Expr::Log2Op(Box::new(expr)))) 116 | } 117 | 118 | fn parse_log10_func(input: &str) -> IResult<&str, Expr> { 119 | let (input, _) = preceded(multispace0, tag("log10"))(input)?; 120 | 121 | let (input, expr) = preceded(multispace0, delimited(char('('), parse_expr, char(')')))(input)?; 122 | Ok((input, Expr::Log2Op(Box::new(expr)))) 123 | } 124 | 125 | fn parse_ln_func(input: &str) -> IResult<&str, Expr> { 126 | let (input, _) = preceded(multispace0, tag("ln"))(input)?; 127 | 128 | let (input, expr) = preceded(multispace0, delimited(char('('), parse_expr, char(')')))(input)?; 129 | Ok((input, Expr::Ln(Box::new(expr)))) 130 | } 131 | 132 | fn parse_sqrt_func(input: &str) -> IResult<&str, Expr> { 133 | let (input, _) = preceded(multispace0, tag("sqrt"))(input)?; 134 | 135 | let (input, expr) = preceded(multispace0, delimited(char('('), parse_expr, char(')')))(input)?; 136 | Ok((input, Expr::Ln(Box::new(expr)))) 137 | } 138 | 139 | // A parser for multiplication, division, and other operators 140 | fn parse_term(input: &str) -> IResult<&str, Expr> { 141 | let (input, mut left) = parse_factor(input)?; // Start with parsing a factor 142 | let mut input = input; 143 | 144 | while let Ok((next_input, op)) = parse_mul_div_op(input) { 145 | let (next_input, right) = parse_factor(next_input)?; // Continue parsing factors for terms 146 | left = Expr::BinaryOp(Box::new(left), op, Box::new(right)); 147 | input = next_input; 148 | } 149 | 150 | Ok((input, left)) 151 | } 152 | // A parser for basic factors: numbers, parentheses, 'x', etc. 153 | fn parse_factor(input: &str) -> IResult<&str, Expr> { 154 | alt(( 155 | delimited( 156 | preceded(multispace0, char('(')), 157 | parse_expr, 158 | preceded(multispace0, char(')')), 159 | ), 160 | parse_number, 161 | parse_variable, 162 | parse_negate, 163 | parse_abs, 164 | parse_pi, 165 | parse_e, 166 | parse_trig_func, 167 | parse_log2_func, 168 | parse_log10_func, 169 | parse_ln_func, 170 | parse_sqrt_func 171 | ))(input) 172 | } 173 | 174 | // A parser for addition and subtraction 175 | fn parse_op(input: &str) -> IResult<&str, Op> { 176 | preceded( 177 | multispace0, 178 | alt((map(tag("+"), |_| Op::Add), map(tag("-"), |_| Op::Sub))), 179 | )(input) 180 | } 181 | 182 | // A parser for multiplication, division, and exponentiation 183 | fn parse_mul_div_op(input: &str) -> IResult<&str, Op> { 184 | preceded( 185 | multispace0, 186 | alt(( 187 | map(tag("*"), |_| Op::Mul), 188 | map(tag("/"), |_| Op::Div), 189 | map(tag("^"), |_| Op::Pow), 190 | map(tag("%"), |_| Op::Mod), 191 | )), 192 | )(input) 193 | } 194 | 195 | // A parser for binary operations (addition, subtraction, etc.) 196 | pub fn parse_expr(input: &str) -> IResult<&str, Expr> { 197 | let (input, mut left) = parse_term(input)?; 198 | let mut input = input; 199 | 200 | while let Ok((next_input, op)) = parse_op(input) { 201 | let (next_input, right) = parse_term(next_input)?; 202 | left = Expr::BinaryOp(Box::new(left), op, Box::new(right)); 203 | input = next_input; 204 | } 205 | 206 | Ok((input, left)) 207 | } 208 | 209 | // Main evaluation function 210 | pub fn evaluate(expr: &Expr, x_value: f64) -> f64 { 211 | match expr { 212 | Expr::Number(value) => *value, 213 | Expr::Variable => x_value, // Substitute variable 'x' with its value 214 | Expr::Pi => std::f64::consts::PI, 215 | Expr::E => std::f64::consts::E, 216 | Expr::NegateOp(inner) => -evaluate(inner, x_value), 217 | Expr::AbsOp(inner) => evaluate(inner, x_value).abs(), 218 | Expr::BinaryOp(left, op, right) => { 219 | let left_value = evaluate(left, x_value); 220 | let right_value = evaluate(right, x_value); 221 | match op { 222 | Op::Add => left_value + right_value, 223 | Op::Sub => left_value - right_value, 224 | Op::Mul => left_value * right_value, 225 | Op::Div => left_value / right_value, 226 | Op::Pow => left_value.powf(right_value), 227 | Op::Mod => left_value % right_value, 228 | } 229 | } 230 | Expr::Log2Op(arg) => { 231 | let arg_value = evaluate(arg, x_value); 232 | arg_value.log2() 233 | } 234 | Expr::Log10Op(arg) => { 235 | let arg_value = evaluate(arg, x_value); 236 | arg_value.log10() 237 | } 238 | Expr::Ln(arg) => { 239 | let arg_value = evaluate(arg, x_value); 240 | arg_value.ln() 241 | } 242 | Expr::Sqrt(arg) => { 243 | let arg_value = evaluate(arg, x_value); 244 | arg_value.sqrt() 245 | } 246 | Expr::TrigOp(func, arg) => { 247 | let arg_value = evaluate(arg, x_value); 248 | match func { 249 | TrigFunc::Sin => arg_value.sin(), 250 | TrigFunc::aSin => arg_value.asin(), 251 | TrigFunc::Sinh => arg_value.sinh(), 252 | 253 | TrigFunc::Cos => arg_value.cos(), 254 | TrigFunc::aCos => arg_value.acos(), 255 | TrigFunc::Cosh => arg_value.cosh(), 256 | 257 | TrigFunc::Tan => arg_value.tan(), 258 | TrigFunc::aTan => arg_value.atan(), 259 | TrigFunc::Tanh => arg_value.tanh(), 260 | } 261 | } 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /src/mem_commands.rs: -------------------------------------------------------------------------------- 1 | use serde_derive::Serialize; 2 | use std::time::Duration; 3 | use sysinfo::System; 4 | 5 | use crate::{format_utils, graph_utils}; 6 | 7 | pub async fn system_mem_usage(watch: bool, json: bool, plot: bool) { 8 | let mut system = System::new_all(); 9 | 10 | if watch { 11 | let mut points: Vec<(f32, f32)> = Vec::new(); 12 | let mut i:f32 = 0.0; 13 | loop { 14 | system.refresh_memory(); 15 | let used_memory = system.used_memory(); 16 | let total_memory = system.total_memory(); 17 | let free_memory = system.free_memory(); 18 | let used_percentage = format_utils::percent(used_memory, total_memory); 19 | 20 | if points.len() >= 30 { 21 | // Remove the oldest point (optional, only if you want a rolling window) 22 | points.remove(0); // or points.drain(..1); for potentially more efficient operation 23 | } 24 | points.push((i, used_percentage as f32)); 25 | i += 1.0; 26 | 27 | 28 | format_utils::clear_terminal(); 29 | if json { 30 | let mem_usage = MemUsage { 31 | total_gb: format_utils::bytes_to_gb(total_memory), 32 | used_gb: format_utils::bytes_to_gb(used_memory), 33 | used_percent: used_percentage, 34 | free_gb: format_utils::bytes_to_gb(free_memory), 35 | free_percent: format_utils::percent(free_memory, total_memory), 36 | }; 37 | format_utils::print_json(&mem_usage); 38 | } else { 39 | println!( 40 | "total: {}, used: {} ({}), free: {} ({})", 41 | format_utils::format_bytes(total_memory), 42 | format_utils::format_bytes(used_memory), 43 | format_utils::calculate_format_percent(used_memory, total_memory), 44 | format_utils::format_bytes(free_memory), 45 | format_utils::calculate_format_percent(free_memory, total_memory) 46 | ); 47 | } 48 | 49 | if plot{ 50 | // Plot the chart 51 | let title: &str = "used memory %"; 52 | graph_utils::plot_chart(&points, String::from(title)); 53 | } 54 | 55 | tokio::time::sleep(Duration::from_millis(500)).await; 56 | } 57 | } else { 58 | system.refresh_memory(); 59 | let used_memory = system.used_memory(); 60 | let total_memory = system.total_memory(); 61 | let free_memory = system.free_memory(); 62 | 63 | if json { 64 | let mem_usage = MemUsage { 65 | total_gb: format_utils::bytes_to_gb(total_memory), 66 | used_gb: format_utils::bytes_to_gb(used_memory), 67 | used_percent: format_utils::percent(used_memory, total_memory), 68 | free_gb: format_utils::bytes_to_gb(free_memory), 69 | free_percent: format_utils::percent(free_memory, total_memory), 70 | }; 71 | format_utils::print_json(&mem_usage); 72 | } else { 73 | println!( 74 | "total: {}, used: {} ({}), free: {} ({})", 75 | format_utils::format_bytes(total_memory), 76 | format_utils::format_bytes(used_memory), 77 | format_utils::calculate_format_percent(used_memory, total_memory), 78 | format_utils::format_bytes(free_memory), 79 | format_utils::calculate_format_percent(free_memory, total_memory) 80 | ); 81 | } 82 | } 83 | } 84 | 85 | #[derive(Serialize, Debug)] 86 | struct MemUsage { 87 | total_gb: f64, 88 | used_gb: f64, 89 | used_percent: f64, 90 | free_gb: f64, 91 | free_percent: f64, 92 | } 93 | -------------------------------------------------------------------------------- /src/network_commands.rs: -------------------------------------------------------------------------------- 1 | use serde_derive::Serialize; 2 | use sysinfo::Networks; 3 | 4 | use crate::format_utils; 5 | use std::thread::sleep; 6 | use std::time::{Duration, Instant}; 7 | 8 | // Helper function to format bytes to human-readable units (kB, MB, GB) 9 | fn format_bytes(bytes: u64) -> String { 10 | let kilobyte = 1024.0; 11 | let megabyte = kilobyte * 1024.0; 12 | let gigabyte = megabyte * 1024.0; 13 | 14 | if bytes as f64 >= gigabyte { 15 | format!("{:.2} GB/s", bytes as f64 / gigabyte) 16 | } else if bytes as f64 >= megabyte { 17 | format!("{:.2} MB/s", bytes as f64 / megabyte) 18 | } else if bytes as f64 >= kilobyte { 19 | format!("{:.2} kB/s", bytes as f64 / kilobyte) 20 | } else { 21 | format!("{} B/s", bytes) 22 | } 23 | } 24 | 25 | pub fn system_network_usage(watch: bool) { 26 | 27 | let mut networks = Networks::new_with_refreshed_list(); 28 | 29 | let mut last_instant = Instant::now(); 30 | 31 | loop { 32 | sleep(Duration::from_secs(1)); 33 | 34 | networks.refresh(); 35 | let current_instant = Instant::now(); 36 | let elapsed_time = current_instant.duration_since(last_instant).as_secs_f64(); 37 | 38 | format_utils::clear_terminal(); 39 | for (interface_name, data) in networks.iter() { 40 | let received = data.received(); 41 | let transmitted = data.transmitted(); 42 | 43 | // Calculate speeds 44 | let receive_speed = received as f64 / elapsed_time; // Bytes per second 45 | let transmit_speed = transmitted as f64 / elapsed_time; // Bytes per second 46 | 47 | // Output the receive/transmit speeds in a human-readable format 48 | println!("{}: rx {}, tx {}", interface_name, format_bytes(receive_speed as u64), format_bytes(transmit_speed as u64)); 49 | } 50 | 51 | last_instant = current_instant; 52 | if !watch{ 53 | return; 54 | } 55 | } 56 | } 57 | 58 | #[derive(Serialize, Debug)] 59 | struct NetworkInfoOutput { 60 | name: String, 61 | transmitted: f64, 62 | received: f64, 63 | mac: String, 64 | } 65 | 66 | pub async fn system_network_info(json: bool) { 67 | 68 | let networks = Networks::new_with_refreshed_list(); 69 | 70 | let mut outputs: Vec = Vec::new(); 71 | for (interface_name, data) in &networks { 72 | outputs.push(NetworkInfoOutput{ 73 | name: interface_name.clone(), 74 | transmitted: format_utils::bytes_to_gb(data.total_transmitted()), 75 | received: format_utils::bytes_to_gb(data.total_received()), 76 | mac: data.mac_address().to_string(), 77 | }); 78 | } 79 | 80 | if json { 81 | format_utils::print_json(&outputs); 82 | }else{ 83 | for o in outputs{ 84 | println!("name: {}, transmitted: {}GB, received: {}GB, mac: {}", o.name, o.transmitted, o.received, o.mac); 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /src/port_commands.rs: -------------------------------------------------------------------------------- 1 | use std::net::Ipv4Addr; 2 | use std::str::FromStr; 3 | use std::time::Duration; 4 | use serde_derive::Serialize; 5 | use crate::ip_utils; 6 | 7 | use crate::format_utils; 8 | 9 | #[derive(Serialize, Debug)] 10 | struct OpenResponse { 11 | open: bool, 12 | } 13 | 14 | #[derive(Serialize, Debug)] 15 | struct MultiPortResponse { 16 | ports: Vec, 17 | } 18 | 19 | pub async fn port_scan(ip_str: Option, json: bool) { 20 | 21 | let ip_str = ip_utils::to_ip_or_local(ip_str).await; 22 | 23 | let ip = match Ipv4Addr::from_str(&ip_str) { 24 | Ok(s) => s, 25 | Err(e) => { 26 | eprintln!("Failed parsing Ip address {}", e); 27 | return; 28 | } 29 | }; 30 | 31 | let timeout = Duration::from_millis(100); // Timeout for each port scan 32 | 33 | let ping_tasks: Vec<_> = (1..9999) 34 | .map(|i: u16| { 35 | tokio::spawn(async move { 36 | match ip_utils::can_connect(ip, i, timeout).await { 37 | Ok(is_open) => { 38 | if is_open { 39 | Some(i) 40 | } else { 41 | None 42 | } 43 | } 44 | Err(_) => None, 45 | } 46 | }) 47 | }) 48 | .collect(); 49 | 50 | let mut ports: Vec = Vec::new(); 51 | for task in ping_tasks { 52 | if let Ok(Some(result)) = task.await { 53 | ports.push(result); 54 | } 55 | } 56 | 57 | if json { 58 | format_utils::print_json(&MultiPortResponse { ports: ports }); 59 | } else { 60 | let port_strings: Vec = ports.iter().map(|p| p.to_string()).collect(); 61 | println!("{}", port_strings.join("\n\r")) 62 | } 63 | } 64 | 65 | pub async fn port_status(ip_str: Option, port: u16, json: bool) { 66 | 67 | let ip_str = ip_utils::to_ip_or_local(ip_str).await; 68 | 69 | let ip = match Ipv4Addr::from_str(&ip_str) { 70 | Ok(s) => s, 71 | Err(e) => { 72 | eprintln!("Failed to bind udp socket: {}", e); 73 | return; 74 | } 75 | }; 76 | 77 | let timeout = Duration::from_millis(100); 78 | 79 | if json { 80 | match ip_utils::can_connect(ip, port, timeout).await { 81 | Ok(is_open) => format_utils::print_json(&OpenResponse { open: is_open }), 82 | Err(_) => format_utils::print_json(&OpenResponse { open: false }), 83 | } 84 | } else { 85 | match ip_utils::can_connect(ip, port, timeout).await { 86 | Ok(_) => println!("open"), 87 | Err(_) => println!("closed"), 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/process_commands.rs: -------------------------------------------------------------------------------- 1 | use regex::Regex; 2 | use std::thread::sleep; 3 | use std::time::{Duration, Instant}; 4 | use sysinfo::{Pid, System}; 5 | 6 | use crate::format_utils; 7 | 8 | pub fn system_process_info( 9 | name_filter: Option, 10 | sort_by: Option, 11 | limit: Option, 12 | watch: bool, 13 | ) { 14 | let mut sys = System::new_all(); 15 | 16 | let name_regex = name_filter 17 | .as_ref() 18 | .map(|filter| Regex::new(filter).unwrap()); 19 | 20 | loop { 21 | sys.refresh_all(); 22 | 23 | // Collect process info 24 | let mut process_info: Vec<(Pid, &sysinfo::Process)> = sys 25 | .processes() 26 | .iter() 27 | .filter(|(_, process)| { 28 | // Filter processes by name if regex is provided 29 | if let Some(ref regex) = name_regex { 30 | regex.is_match(&process.name().to_str().unwrap_or_default()) 31 | } else { 32 | true 33 | } 34 | }) 35 | .map(|(pid, process)| (*pid, process)) 36 | .collect(); 37 | 38 | // Sort processes based on the specified criteria 39 | match sort_by.as_deref() { 40 | Some("cpu") => { 41 | process_info.sort_by(|a, b| b.1.cpu_usage().partial_cmp(&a.1.cpu_usage()).unwrap()) 42 | } 43 | Some("mem") => { 44 | process_info.sort_by(|a, b| b.1.memory().partial_cmp(&a.1.memory()).unwrap()) 45 | } 46 | Some("disk") => process_info.sort_by(|a, b| { 47 | let disk_a = a.1.disk_usage(); 48 | let disk_b = b.1.disk_usage(); 49 | (disk_b.read_bytes + disk_b.written_bytes) 50 | .partial_cmp(&(disk_a.read_bytes + disk_a.written_bytes)) 51 | .unwrap() 52 | }), 53 | Some("name") | None => process_info.sort_by(|a, b| a.1.name().cmp(b.1.name())), 54 | _ => eprintln!("Invalid sort option provided. Defaulting to sort by name."), 55 | } 56 | 57 | let last_instant = Instant::now(); 58 | 59 | format_utils::clear_terminal(); 60 | println!( 61 | "{:<5}\t{:>6}\t{:>10}\t{:>12}\t{:>12}\tname", 62 | "[pid]", "[cpu %]", "[mem]", "[disk read]", "[disk write]" 63 | ); 64 | 65 | for (pid, process) in process_info.iter().take(limit.unwrap_or(usize::MAX)) { 66 | let disk_usage = process.disk_usage(); 67 | 68 | let elapsed_time = last_instant.elapsed().as_secs_f64(); 69 | let read_speed = if elapsed_time > 0.0 { 70 | disk_usage.read_bytes as f64 / elapsed_time // Bytes per second 71 | } else { 72 | 0.0 73 | }; 74 | let write_speed = if elapsed_time > 0.0 { 75 | disk_usage.written_bytes as f64 / elapsed_time // Bytes per second 76 | } else { 77 | 0.0 78 | }; 79 | 80 | // Display process information 81 | println!( 82 | "[{:>5}]\t{:<6.2}%\t{:>10}\t{:>12}\t{:>12}\t{}\t", 83 | pid, 84 | process.cpu_usage(), 85 | format_memory(process.memory()), 86 | format_disk_speed(read_speed), 87 | format_disk_speed(write_speed), 88 | process.name().to_str().unwrap() 89 | ); 90 | } 91 | 92 | // If not watching, break out of the loop after one display 93 | if !watch { 94 | break; 95 | } 96 | 97 | sleep(Duration::from_secs(1)); 98 | } 99 | } 100 | 101 | fn format_memory(bytes: u64) -> String { 102 | let kilobyte = 1024.0; 103 | let megabyte = kilobyte * 1024.0; 104 | let gigabyte = megabyte * 1024.0; 105 | 106 | if bytes as f64 >= gigabyte { 107 | format!("{:.0} GB", bytes as f64 / gigabyte) 108 | } else if bytes as f64 >= megabyte { 109 | format!("{:.0} MB", bytes as f64 / megabyte) 110 | } else if bytes as f64 >= kilobyte { 111 | format!("{:.0} kB", bytes as f64 / kilobyte) 112 | } else { 113 | format!("{} B", bytes) 114 | } 115 | } 116 | 117 | fn format_disk_speed(bytes_per_sec: f64) -> String { 118 | let kilobyte = 1024.0; 119 | let megabyte = kilobyte * 1024.0; 120 | let gigabyte = megabyte * 1024.0; 121 | 122 | if bytes_per_sec >= gigabyte { 123 | format!("{:.0} GB/s", bytes_per_sec / gigabyte) 124 | } else if bytes_per_sec >= megabyte { 125 | format!("{:.0} MB/s", bytes_per_sec / megabyte) 126 | } else if bytes_per_sec >= kilobyte { 127 | format!("{:.0} kB/s", bytes_per_sec / kilobyte) 128 | } else { 129 | format!("{:.0} B/s", bytes_per_sec) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/text_commands.rs: -------------------------------------------------------------------------------- 1 | use base64::prelude::*; 2 | use percent_encoding::{percent_decode, utf8_percent_encode, NON_ALPHANUMERIC}; 3 | use regex::Regex; 4 | use uuid::Uuid; 5 | use std::{fs::File, io::{self, BufRead}, str}; 6 | use base64::engine::general_purpose::URL_SAFE; 7 | 8 | pub fn base64_encode(input: String) { 9 | // Encode the input string as bytes into Base64 URL format 10 | let encoded = URL_SAFE.encode(input.as_bytes()); 11 | // Print the Base64 encoded string 12 | println!("{}", encoded); 13 | } 14 | 15 | pub fn base64_decode(input: String) { 16 | // Ensure correct padding for Base64 URL encoding 17 | let mut padding = String::new(); 18 | let missing_padding = 4 - (input.len() % 4); 19 | if missing_padding != 4 { 20 | padding.push_str(&"=".repeat(missing_padding)); 21 | } 22 | 23 | // Create the Base64 URL string with proper padding 24 | let base64_encoded = format!("{}{}", input, padding); 25 | 26 | // Decode the Base64 string 27 | match URL_SAFE.decode(&base64_encoded) { 28 | Ok(decoded_bytes) => { 29 | // Convert bytes to string 30 | let decoded_string = str::from_utf8(&decoded_bytes) 31 | .map(|s| s.to_string()) 32 | .map_err(|e| e.to_string()); 33 | 34 | if let Ok(decoded_string) = decoded_string{ 35 | println!("{}", decoded_string); 36 | }else if let Err(e) = decoded_string{ 37 | eprint!("{}", e); 38 | } 39 | } 40 | Err(e) => eprintln!("Base64 decode error: {}", e), 41 | } 42 | } 43 | 44 | pub fn regex_search(filename: String, regex: String){ 45 | let file = File::open(filename); 46 | if let Ok(file) = file{ 47 | let reader = io::BufReader::new(file); 48 | let re: Regex = Regex::new(regex.as_str()).unwrap(); 49 | 50 | for (index, line) in reader.lines().enumerate() { 51 | if let Ok(line) = line{ 52 | if re.is_match(&line) { 53 | println!("{}: {}", index + 1, line); 54 | } 55 | } 56 | } 57 | } 58 | } 59 | 60 | pub fn print_lines( 61 | filename: String, 62 | start: Option, 63 | end: Option, 64 | head: Option, 65 | tail: Option 66 | ) { 67 | // Check for conflicting options 68 | if (head.is_some() || tail.is_some()) && (start.is_some() || end.is_some()) { 69 | eprintln!("Error: Cannot specify both head/tail and start/end ranges."); 70 | return; 71 | } 72 | if head.is_some() && tail.is_some() { 73 | eprintln!("Error: Cannot specify both head and tail."); 74 | return; 75 | } 76 | 77 | // Open the file 78 | let file = File::open(&filename).expect("Unable to open file"); 79 | let reader = io::BufReader::new(file); 80 | 81 | // Handle head: print first N lines 82 | if let Some(n) = head { 83 | for (i, line) in reader.lines().enumerate() { 84 | if i >= n { 85 | break; 86 | } 87 | if let Ok(line) = line { 88 | println!("{}", line); 89 | } 90 | } 91 | return; 92 | } 93 | 94 | // If tail is specified, we need to scan the file and count lines first 95 | if let Some(n) = tail { 96 | let mut lines: Vec = Vec::new(); 97 | for line in reader.lines() { 98 | if let Ok(line) = line { 99 | lines.push(line); 100 | } 101 | } 102 | let total_lines = lines.len(); 103 | let start_tail_idx = total_lines.saturating_sub(n); 104 | for line in lines.iter().skip(start_tail_idx) { 105 | println!("{}", line); 106 | } 107 | return; 108 | } 109 | 110 | // Handle start/end range without loading the entire file into memory 111 | let start_idx = start.unwrap_or(0); // If start is None, default to the beginning 112 | let end_idx = end.unwrap_or(usize::MAX); // If end is None, default to the max usize 113 | 114 | for (index, line) in reader.lines().enumerate() { 115 | if index < start_idx { 116 | continue; 117 | } 118 | if index > end_idx { 119 | break; 120 | } 121 | if let Ok(line) = line { 122 | println!("{}", line); 123 | } 124 | } 125 | } 126 | 127 | pub fn guid(){ 128 | println!("{}", Uuid::new_v4()); 129 | } 130 | 131 | pub fn url_encode(input: &String) { 132 | println!("{}", utf8_percent_encode(input, NON_ALPHANUMERIC).to_string()); 133 | } 134 | 135 | pub fn url_decode(input: &String) { 136 | println!("{}",percent_decode(input.as_bytes()) 137 | .decode_utf8_lossy() 138 | .to_string()); 139 | } 140 | 141 | pub fn replace(source: String, pattern: &str, replace: &str) { 142 | let re = Regex::new(pattern).expect("Invalid regex pattern"); 143 | let result = re.replace_all(&source, replace).to_string(); 144 | 145 | println!("{}", result); 146 | } 147 | 148 | pub fn count(source: String, pattern: &str) { 149 | let re = Regex::new(pattern).expect("Invalid regex pattern"); 150 | let match_count = re.find_iter(&source).count(); 151 | 152 | println!("{}", match_count); 153 | } -------------------------------------------------------------------------------- /src/time_commands.rs: -------------------------------------------------------------------------------- 1 | use std::thread::sleep; 2 | use std::time::{Duration, Instant}; 3 | use chrono::{DateTime, Local, NaiveDateTime, TimeZone, Utc}; 4 | use cron_descriptor::cronparser::cron_expression_descriptor; 5 | use time::format_description::well_known::Rfc3339; 6 | use time::macros; 7 | use time::OffsetDateTime; 8 | 9 | use crate::format_utils; 10 | 11 | pub fn unix_timestamp(milli: bool, dt: Option) { 12 | let now = match dt { 13 | Some(ref datetime_str) => { 14 | // Try parsing with multiple formats 15 | match DateTime::parse_from_rfc3339(datetime_str) 16 | .or_else(|_| DateTime::parse_from_rfc2822(datetime_str)) 17 | .or_else(|_| DateTime::parse_from_str(datetime_str, "%Y-%m-%d %H:%M:%S %z")) 18 | .or_else(|_| DateTime::parse_from_str(datetime_str, "%Y-%m-%d %H:%M:%S")) 19 | { 20 | Ok(parsed_dt) => parsed_dt, 21 | Err(_) => { 22 | println!("Could not parse the provided datetime string."); 23 | return; 24 | } 25 | } 26 | } 27 | None => Utc::now().into(), 28 | }; 29 | 30 | if milli { 31 | let timestamp_milliseconds = now.timestamp_millis(); 32 | println!("{}", timestamp_milliseconds); 33 | } else { 34 | let timestamp_seconds = now.timestamp(); 35 | println!("{}", timestamp_seconds); 36 | } 37 | } 38 | 39 | 40 | pub fn date_time(local: bool, rfc: bool, unix: Option) { 41 | 42 | // If a Unix timestamp is provided, parse it 43 | let date_time: DateTime = if let Some(unix_time) = unix { 44 | DateTime::from_timestamp(unix_time as i64, 0).unwrap() 45 | } else { 46 | Utc::now() // Use the current time if no Unix timestamp is provided 47 | }; 48 | 49 | if local { 50 | // Convert UTC time to local time 51 | if rfc { 52 | let formatted = date_time.with_timezone(&Local).to_rfc3339(); // Use chrono's RFC 3339 method 53 | println!("{}", formatted); 54 | } else { 55 | let formatted = date_time.with_timezone(&Local).format("%Y-%m-%d %H:%M:%S").to_string(); // Custom format 56 | println!("{}", formatted); 57 | } 58 | } else { 59 | if rfc { 60 | let formatted = date_time.to_rfc3339(); // Use chrono's RFC 3339 method 61 | println!("{}", formatted); 62 | } else { 63 | let formatted = date_time.format("%Y-%m-%d %H:%M:%S").to_string(); // Custom format 64 | println!("{}", formatted); 65 | } 66 | }; 67 | } 68 | 69 | 70 | pub fn chron_tostring(cron_expr: String) { 71 | let description = cron_expression_descriptor::get_description_cron(&cron_expr); 72 | println!("{}", description); 73 | } 74 | 75 | pub fn countdown(input: String) { 76 | // Parse input into minutes and seconds 77 | let countdown_time = match parse_duration(&input) { 78 | Some(duration) => duration, 79 | None => { 80 | println!("Please provide a valid time in the format MM:SS or just seconds."); 81 | return; 82 | } 83 | }; 84 | 85 | // Countdown loop 86 | let mut total_seconds = countdown_time.as_secs(); 87 | while total_seconds > 0 { 88 | format_utils::clear_terminal(); // Assume you have this utility for clearing the terminal 89 | let minutes = total_seconds / 60; 90 | let seconds = total_seconds % 60; 91 | 92 | // Display the remaining time in a readable format 93 | if minutes > 1 { 94 | if seconds == 1 { 95 | println!("Time left: {} minutes 1 second", minutes); 96 | } else if seconds == 0 { 97 | println!("Time left: {} minutes", minutes); 98 | } else { 99 | println!("Time left: {} minutes {} seconds", minutes, seconds); 100 | } 101 | } else if minutes == 1 { 102 | if seconds == 1 { 103 | println!("Time left: 1 minute 1 second"); 104 | } else if seconds == 0 { 105 | println!("Time left: 1 minute"); 106 | } else { 107 | println!("Time left: 1 minute {} seconds", seconds); 108 | } 109 | } else { 110 | if seconds == 1 { 111 | println!("Time left: 1 second"); 112 | } else if seconds == 0 { 113 | println!("Time left: 0 seconds"); 114 | } else { 115 | println!("Time left: {} seconds", seconds); 116 | } 117 | } 118 | 119 | sleep(Duration::from_secs(1)); 120 | total_seconds -= 1; 121 | } 122 | 123 | // Flashing "Time's up" message 124 | let mut flash = true; 125 | loop { 126 | format_utils::clear_terminal(); 127 | if flash { 128 | println!("Time's up!"); 129 | } else { 130 | println!(" "); 131 | } 132 | flash = !flash; 133 | sleep(Duration::from_millis(500)); 134 | } 135 | } 136 | 137 | // Function to parse input string into a Duration 138 | fn parse_duration(input: &str) -> Option { 139 | // Try to parse in "MM:SS" format 140 | if let Some((minutes, seconds)) = input.split_once(':') { 141 | if let (Ok(min), Ok(sec)) = (minutes.parse::(), seconds.parse::()) { 142 | return Some(Duration::from_secs(min * 60 + sec)); 143 | } 144 | } 145 | 146 | // If no ":" found, treat the input as just seconds 147 | if let Ok(seconds) = input.parse::() { 148 | return Some(Duration::from_secs(seconds)); 149 | } 150 | 151 | // Invalid input 152 | None 153 | } 154 | -------------------------------------------------------------------------------- /tests/csv_sql_search_tests.rs: -------------------------------------------------------------------------------- 1 | use aid::csv_utils; 2 | use std::io; 3 | 4 | pub fn load_and_query(sql: &str) -> Result<(Vec, Vec>), io::Error> { 5 | // Use the input SQL parameter instead of hardcoded SQL 6 | let parsed = match csv_utils::parse_sql(sql) { 7 | Ok(result) => result, 8 | Err(e) => { 9 | return Err(io::Error::new( 10 | io::ErrorKind::InvalidData, 11 | format!("Failed to parse SQL query: {}", e), 12 | )) 13 | } 14 | }; 15 | 16 | let query = parsed.1; 17 | 18 | // Load the CSV file 19 | let (headers, records) = match csv_utils::load_csv(&query.table) { 20 | Ok(result) => result, 21 | Err(e) => { 22 | eprintln!(); 23 | return Err(io::Error::new( 24 | io::ErrorKind::InvalidData, 25 | format!("Failed to load CSV file '{}': {}", query.table, e), 26 | )); 27 | } 28 | }; 29 | 30 | // Apply the query 31 | Ok(csv_utils::apply_query(&headers, records, &query)) 32 | } 33 | 34 | #[cfg(test)] 35 | mod tests { 36 | use super::*; 37 | 38 | #[test] 39 | fn select_all() { 40 | let result = load_and_query("SELECT * FROM ./vgsales.csv"); 41 | 42 | if let Err(e) = &result { 43 | eprint!("{}", e); 44 | assert!(!result.is_err()); 45 | } 46 | 47 | if let Ok((headers, data)) = result { 48 | assert_eq!( 49 | vec![ 50 | "Rank", 51 | "Name", 52 | "Platform", 53 | "Year", 54 | "Genre", 55 | "Publisher", 56 | "NA_Sales", 57 | "EU_Sales", 58 | "JP_Sales", 59 | "Other_Sales", 60 | "Global_Sales" 61 | ], 62 | headers 63 | ); 64 | assert_eq!( 65 | vec![ 66 | "1", 67 | "Wii Sports", 68 | "Wii", 69 | "2006", 70 | "Sports", 71 | "Nintendo", 72 | "41.49", 73 | "29.02", 74 | "3.77", 75 | "8.46", 76 | "82.74" 77 | ], 78 | data[0] 79 | ); 80 | assert_eq!( 81 | vec![ 82 | "16600", 83 | "Spirits & Spells", 84 | "GBA", 85 | "2003", 86 | "Platform", 87 | "Wanadoo", 88 | "0.01", 89 | "0", 90 | "0", 91 | "0", 92 | "0.01" 93 | ], 94 | data[16597] 95 | ); 96 | assert_eq!(16598, data.len()); 97 | } 98 | } 99 | 100 | #[test] 101 | fn count_all() { 102 | let result = load_and_query("SELECT COUNT(*) FROM ./vgsales.csv"); 103 | 104 | if let Err(e) = &result { 105 | eprint!("{}", e); 106 | assert!(!result.is_err()); 107 | } 108 | 109 | if let Ok((headers, data)) = result { 110 | assert_eq!(vec!["COUNT(*)"], headers); 111 | assert_eq!(vec!["16598"], data[0]); 112 | assert_eq!(1, data.len()); 113 | } 114 | } 115 | 116 | #[test] 117 | fn select_multi_column() { 118 | let result = load_and_query( 119 | "SELECT Year,Name FROM ./vgsales.csv WHERE Platform = 'Wii' AND Year = 2015", 120 | ); 121 | 122 | if let Err(e) = &result { 123 | eprint!("{}", e); 124 | assert!(!result.is_err()); 125 | } 126 | 127 | if let Ok((headers, data)) = result { 128 | assert_eq!(vec!["Year", "Name"], headers); 129 | assert_eq!(vec!["2015", "Just Dance 2016"], data[0]); 130 | assert_eq!(vec!["2015", "Monster High: New Ghoul in School"], data[3]); 131 | assert_eq!(4, data.len()); 132 | } 133 | } 134 | 135 | #[test] 136 | fn select_distinct() { 137 | let result = load_and_query("SELECT DISTINCT Platform FROM vgsales.csv WHERE Publisher LIKE 'Nin%' ORDER BY Platform DESC"); 138 | 139 | if let Err(e) = &result { 140 | eprint!("{}", e); 141 | assert!(!result.is_err()); 142 | } 143 | 144 | if let Ok((headers, data)) = result { 145 | assert_eq!(vec!["Platform"], headers); 146 | assert_eq!(vec!["WiiU"], data[0]); 147 | assert_eq!(vec!["3DS"], data[9]); 148 | assert_eq!(10, data.len()); 149 | } 150 | } 151 | 152 | #[test] 153 | fn select_between() { 154 | let result = load_and_query("SELECT DISTINCT Platform FROM vgsales.csv WHERE Year BETWEEN 2005 AND 2006 ORDER BY Platform ASC"); 155 | 156 | if let Err(e) = &result { 157 | eprint!("{}", e); 158 | assert!(!result.is_err()); 159 | } 160 | 161 | if let Ok((headers, data)) = result { 162 | assert_eq!(vec!["Platform"], headers); 163 | assert_eq!(vec!["DS"], data[0]); 164 | assert_eq!(vec!["XB"], data[9]); 165 | assert_eq!(10, data.len()); 166 | } 167 | } 168 | 169 | #[test] 170 | fn select_filter_or() { 171 | let result = load_and_query("SELECT DISTINCT Publisher FROM vgsales.csv WHERE Name LIKE 'Call of Duty%' OR Name LIKE 'Battlefield%' ORDER BY Publisher ASC"); 172 | 173 | if let Err(e) = &result { 174 | eprint!("{}", e); 175 | assert!(!result.is_err()); 176 | } 177 | 178 | if let Ok((headers, data)) = result { 179 | assert_eq!(vec!["Publisher"], headers); 180 | assert_eq!(vec!["Activision"], data[0]); 181 | assert_eq!(vec!["Electronic Arts"], data[1]); 182 | assert_eq!(2, data.len()); 183 | } 184 | } 185 | 186 | #[test] 187 | fn select_filter_and_or() { 188 | let result = load_and_query("SELECT Name FROM vgsales.csv WHERE Year = 1984 AND (Genre = 'Puzzle' OR Genre = 'Racing') ORDER BY Year DESC"); 189 | 190 | if let Err(e) = &result { 191 | eprint!("{}", e); 192 | assert!(!result.is_err()); 193 | } 194 | 195 | if let Ok((headers, data)) = result { 196 | assert_eq!(vec!["Name"], headers); 197 | assert_eq!(vec!["Excitebike"], data[0]); 198 | assert_eq!(vec!["Beamrider"], data[5]); 199 | assert_eq!(6, data.len()); 200 | } 201 | } 202 | #[test] 203 | fn select_filter_not() { 204 | let result = load_and_query("SELECT DISTINCT Platform FROM vgsales.csv WHERE Name LIKE 'Call of Duty%' AND NOT (Platform = 'PC' OR Platform = 'DS') ORDER BY Platform DESC"); 205 | 206 | if let Err(e) = &result { 207 | eprint!("{}", e); 208 | assert!(!result.is_err()); 209 | } 210 | 211 | if let Ok((headers, data)) = result { 212 | assert_eq!(vec!["Platform"], headers); 213 | assert_eq!(vec!["XOne"], data[0]); 214 | assert_eq!(vec!["GC"], data[10]); 215 | assert_eq!(11, data.len()); 216 | } 217 | } 218 | 219 | #[test] 220 | fn select_group_by() { 221 | let result = load_and_query( 222 | "SELECT Genre, COUNT(Name) FROM vgsales.csv GROUP BY Genre ORDER BY COUNT(Name) DESC", 223 | ); 224 | 225 | if let Err(e) = &result { 226 | eprint!("{}", e); 227 | assert!(!result.is_err()); 228 | } 229 | 230 | if let Ok((headers, data)) = result { 231 | assert_eq!(vec!["Genre", "COUNT(Name)"], headers); 232 | assert_eq!(vec!["Action", "3316"], data[0]); 233 | assert_eq!(vec!["Puzzle", "582"], data[11]); 234 | assert_eq!(12, data.len()); 235 | } 236 | } 237 | 238 | #[test] 239 | fn select_aggregate() { 240 | let result = load_and_query("SELECT Genre,COUNT(Name),SUM(Global_Sales),MIN(Global_Sales),MAX(Global_Sales),AVG(Global_Sales) FROM vgsales.csv GROUP BY Genre ORDER BY SUM(Global_Sales) DESC"); 241 | 242 | if let Err(e) = &result { 243 | eprint!("{}", e); 244 | assert!(!result.is_err()); 245 | } 246 | 247 | if let Ok((headers, data)) = result { 248 | assert_eq!( 249 | vec![ 250 | "Genre", 251 | "COUNT(Name)", 252 | "SUM(Global_Sales)", 253 | "MIN(Global_Sales)", 254 | "MAX(Global_Sales)", 255 | "AVG(Global_Sales)" 256 | ], 257 | headers 258 | ); 259 | assert_eq!( 260 | vec![ 261 | "Action", 262 | "3316", 263 | "1751.1799999999691", 264 | "0.01", 265 | "8.24", 266 | "0.5281001206272524" 267 | ], 268 | data[0] 269 | ); 270 | assert_eq!( 271 | vec![ 272 | "Strategy", 273 | "681", 274 | "175.1200000000004", 275 | "0.01", 276 | "5.45", 277 | "0.2571512481644646" 278 | ], 279 | data[11] 280 | ); 281 | assert_eq!(12, data.len()); 282 | } 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /tests/math_exp_tests.rs: -------------------------------------------------------------------------------- 1 | use aid::math_utils; 2 | 3 | #[cfg(test)] 4 | mod tests { 5 | use core::f64; 6 | 7 | use super::*; 8 | 9 | #[test] 10 | fn addition() { 11 | let expression = "1 + 2"; 12 | let parse_result = math_utils::parse_expr(expression); 13 | if let Err(e) = &parse_result { 14 | eprint!("{}", e); 15 | assert!(!parse_result.is_err()); 16 | } 17 | 18 | let (_, ast) = parse_result.unwrap(); 19 | let eval = math_utils::evaluate(&ast, 0.0); 20 | assert_eq!(3.0, eval); 21 | } 22 | 23 | #[test] 24 | fn subtraction() { 25 | let expression = "1 - 2"; 26 | let parse_result = math_utils::parse_expr(expression); 27 | if let Err(e) = &parse_result { 28 | eprint!("{}", e); 29 | assert!(!parse_result.is_err()); 30 | } 31 | 32 | let (_, ast) = parse_result.unwrap(); 33 | let eval = math_utils::evaluate(&ast, 0.0); 34 | assert_eq!(-1.0, eval); 35 | } 36 | 37 | #[test] 38 | fn multiplication() { 39 | let expression = "1 * 2"; 40 | let parse_result = math_utils::parse_expr(expression); 41 | if let Err(e) = &parse_result { 42 | eprint!("{}", e); 43 | assert!(!parse_result.is_err()); 44 | } 45 | 46 | let (_, ast) = parse_result.unwrap(); 47 | let eval = math_utils::evaluate(&ast, 0.0); 48 | assert_eq!(2.0, eval); 49 | } 50 | 51 | #[test] 52 | fn division() { 53 | let expression = "1 / 2"; 54 | let parse_result = math_utils::parse_expr(expression); 55 | if let Err(e) = &parse_result { 56 | eprint!("{}", e); 57 | assert!(!parse_result.is_err()); 58 | } 59 | 60 | let (_, ast) = parse_result.unwrap(); 61 | let eval = math_utils::evaluate(&ast, 0.0); 62 | assert_eq!(0.5, eval); 63 | } 64 | 65 | #[test] 66 | fn exponent() { 67 | let expression = "2 ^ 3"; 68 | let parse_result = math_utils::parse_expr(expression); 69 | if let Err(e) = &parse_result { 70 | eprint!("{}", e); 71 | assert!(!parse_result.is_err()); 72 | } 73 | 74 | let (_, ast) = parse_result.unwrap(); 75 | let eval = math_utils::evaluate(&ast, 0.0); 76 | assert_eq!(8.0, eval); 77 | } 78 | 79 | #[test] 80 | fn sin() { 81 | let expression = "sin(pi)"; 82 | let parse_result = math_utils::parse_expr(expression); 83 | if let Err(e) = &parse_result { 84 | eprint!("{}", e); 85 | assert!(!parse_result.is_err()); 86 | } 87 | 88 | let (_, ast) = parse_result.unwrap(); 89 | let eval = math_utils::evaluate(&ast, 0.0); 90 | let expected = 0.0; 91 | assert!((eval - expected).abs() < f64::EPSILON); 92 | } 93 | 94 | #[test] 95 | fn tan() { 96 | let expression = "tan(pi/4)"; 97 | let parse_result = math_utils::parse_expr(expression); 98 | if let Err(e) = &parse_result { 99 | eprint!("{}", e); 100 | assert!(!parse_result.is_err()); 101 | } 102 | 103 | let (_, ast) = parse_result.unwrap(); 104 | let eval = math_utils::evaluate(&ast, 0.0); 105 | let expected = 1.0; 106 | assert!((eval - expected).abs() < f64::EPSILON); 107 | } 108 | 109 | #[test] 110 | fn cos() { 111 | let expression = "cos(pi)"; 112 | let parse_result = math_utils::parse_expr(expression); 113 | if let Err(e) = &parse_result { 114 | eprint!("{}", e); 115 | assert!(!parse_result.is_err()); 116 | } 117 | 118 | let (_, ast) = parse_result.unwrap(); 119 | let eval = math_utils::evaluate(&ast, 0.0); 120 | let expected = -1.0; 121 | assert!((eval - expected).abs() < f64::EPSILON); 122 | } 123 | 124 | #[test] 125 | fn parentheses() { 126 | let expression = "2 * (4 - 2)"; 127 | let parse_result = math_utils::parse_expr(expression); 128 | if let Err(e) = &parse_result { 129 | eprint!("{}", e); 130 | assert!(!parse_result.is_err()); 131 | } 132 | 133 | let (_, ast) = parse_result.unwrap(); 134 | let eval = math_utils::evaluate(&ast, 0.0); 135 | let expected = 4.0; 136 | assert!((eval - expected).abs() < f64::EPSILON); 137 | } 138 | 139 | #[test] 140 | fn negative() { 141 | let expression = "-2 * (4 - 2)"; 142 | let parse_result = math_utils::parse_expr(expression); 143 | if let Err(e) = &parse_result { 144 | eprint!("{}", e); 145 | assert!(!parse_result.is_err()); 146 | } 147 | 148 | let (_, ast) = parse_result.unwrap(); 149 | let eval = math_utils::evaluate(&ast, 0.0); 150 | let expected = -4.0; 151 | assert!((eval - expected).abs() < f64::EPSILON); 152 | } 153 | 154 | #[test] 155 | fn modulo() { 156 | let expression = "7 % 3"; 157 | let parse_result = math_utils::parse_expr(expression); 158 | if let Err(e) = &parse_result { 159 | eprint!("{}", e); 160 | assert!(!parse_result.is_err()); 161 | } 162 | 163 | let (_, ast) = parse_result.unwrap(); 164 | let eval = math_utils::evaluate(&ast, 0.0); 165 | let expected = 1.0; 166 | assert!((eval - expected).abs() < f64::EPSILON); 167 | } 168 | 169 | #[test] 170 | fn formula() { 171 | let expression = "e ^ (pi / 2)"; 172 | let parse_result = math_utils::parse_expr(expression); 173 | if let Err(e) = &parse_result { 174 | eprint!("{}", e); 175 | assert!(!parse_result.is_err()); 176 | } 177 | 178 | let (_, ast) = parse_result.unwrap(); 179 | let eval = math_utils::evaluate(&ast, 0.0); 180 | let expected = 4.810477380965351; 181 | println!("{}", eval); 182 | assert!((eval - expected).abs() < f64::EPSILON); 183 | } 184 | } 185 | --------------------------------------------------------------------------------