[
  {
    "path": ".github/workflows/release.yml",
    "content": "name: release\non:\n  push:\n  workflow_dispatch:\nenv:\n  CARGO_INCREMENTAL: 0\npermissions:\n  contents: write\njobs:\n  release:\n    name: ${{ matrix.target }}\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - os: ubuntu-latest\n            target: x86_64-unknown-linux-musl\n            deb: true\n          - os: ubuntu-latest\n            target: arm-unknown-linux-musleabihf\n          - os: ubuntu-latest\n            target: armv7-unknown-linux-musleabihf\n            deb: true\n          - os: ubuntu-latest\n            target: aarch64-unknown-linux-musl\n            deb: true\n          - os: ubuntu-latest\n            target: i686-unknown-linux-musl\n            deb: true\n          - os: ubuntu-latest\n            target: aarch64-linux-android\n          - os: macos-latest\n            target: x86_64-apple-darwin\n          - os: macos-latest\n            target: aarch64-apple-darwin\n          - os: windows-latest\n            target: x86_64-pc-windows-msvc\n          - os: windows-latest\n            target: i686-pc-windows-msvc\n          - os: windows-latest\n            target: aarch64-pc-windows-msvc\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n\n      - name: Get version\n        id: get_version\n        uses: SebRollen/toml-action@v1.2.0\n        with:\n          file: Cargo.toml\n          field: package.version\n\n      - name: Install Rust\n        uses: actions-rs/toolchain@v1\n        with:\n          toolchain: stable\n          profile: minimal\n          override: true\n          target: ${{ matrix.target }}\n\n      - name: Setup cache\n        uses: Swatinem/rust-cache@v2.7.3\n        with:\n          key: ${{ matrix.target }}\n\n      - name: Install cross\n        if: ${{ runner.os == 'Linux' }}\n        uses: actions-rs/cargo@v1\n        with:\n          command: install\n          args: --color=always --git=https://github.com/cross-rs/cross.git --locked --rev=02bf930e0cb0c6f1beffece0788f3932ecb2c7eb --verbose cross\n\n      - name: Build binary\n        uses: actions-rs/cargo@v1\n        env:\n          POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }}\n          DOCS_KEY: ${{ secrets.DOCS_KEY }}\n        with:\n          command: build\n          args: --release --target=${{ matrix.target }} --color=always --verbose\n          use-cross: ${{ runner.os == 'Linux' }}\n\n      - name: Install cargo-deb\n        if: ${{ matrix.deb == true }}\n        uses: actions-rs/install@v0.1\n        with:\n          crate: cargo-deb\n\n      - name: Build deb\n        if: ${{ matrix.deb == true }}\n        uses: actions-rs/cargo@v1\n        with:\n          command: deb\n          args: --no-build --no-strip --output=. --target=${{ matrix.target }}\n\n      - name: Package (*nix)\n        if: ${{ runner.os != 'Windows' }}\n        run: |\n          tar -cv LICENSE README.md \\\n            -C target/${{ matrix.target }}/release/ vpm |\n            gzip --best > \\\n            vpm-${{ steps.get_version.outputs.value }}-${{ matrix.target }}.tar.gz\n\n      - name: Create variable files\n        if: ${{ runner.os == 'Windows' }}\n        run: |\n          echo \"${{ steps.get_version.outputs.value }}\" > version.txt\n          echo \"${{ matrix.target }}\" >> target.txt\n\n      - name: Package (Windows)\n        if: ${{ runner.os == 'Windows' }}\n        uses: Minionguyjpro/Inno-Setup-Action@v1.2.4\n        with:\n          path: installer.iss\n          options: /O+\n\n      - name: Upload artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: ${{ matrix.target }}\n          path: |\n            *.deb\n            *.tar.gz\n            *.zip\n            *.exe\n\n      - name: Create release\n        if: |\n          github.ref == 'refs/heads/main' && startsWith(github.event.head_commit.message, 'chore(release)')\n        uses: softprops/action-gh-release@v2\n        with:\n          draft: true\n          files: |\n            *.deb\n            *.tar.gz\n            *.zip\n            *.exe\n          name: ${{ steps.get_version.outputs.value }}\n          tag_name: ''\n"
  },
  {
    "path": ".gitignore",
    "content": "# Generated by Cargo\n# will have compiled files and executables\ndebug/\ntarget/\n\n# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries\n# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html\nCargo.lock\n\n# These are backup files generated by rustfmt\n**/*.rs.bk\n\n# MSVC Windows builds of rustc generate these, which store debugging information\n*.pdb\n\n# RustRover\n#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can\n#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore\n#  and can be added to the global gitignore or merged into this file.  For a more nuclear\n#  option (not recommended) you can uncomment the following to ignore the entire idea folder.\n#.idea/\n\nvpm_modules/\nVpm.toml\nvpm.toml\nvpm.lock\n.svlangserver/\n.env\n\n.DS_Store\n.vscode/launch.json\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing Guidelines\n\nFirst off, thank you for considering contributing to the Verilog Package Manager (VPM). It's people like you that make VPM such a great tool.\n\nFollowing these guidelines helps to communicate that you respect the time of the developers managing and developing this open source project. In return, they should reciprocate that respect in addressing your issue, assessing changes, and helping you finalize your pull requests.\n\n## Getting started\nAnybody is welcome to contribute—we encourage anybody who is interested in this project to join the VPM discord. We'll discuss upcoming changes, user suggesions, and roadmaps.\n\n_For something that is bigger than a one or two line fix:_\n1. Create your own fork of the code\n2. Do the changes in your fork (be sure to follow the code style of the project)\n3. If you like the change and think the project could use it, send a pull request indicating that you have a CLA on file (for these larger fixes, try to include an update on the discord as well)\n\n_For small or \"obvious\" fixes..._\n* Small contributions such as fixing spelling errors, where the content is small enough to not be considered intellectual property, can be submitted by a contributor as a patch\n\n**As a rule of thumb, changes are obvious fixes if they do not introduce any new functionality or creative thinking.** As long as the change does not affect functionality, some likely examples include the following:\n- Spelling / grammar fixes\n- Typo correction, white space and formatting changes\n- Comment clean up\n- Bug fixes that change default return values or error codes stored in constants\n- Adding logging messages or debugging output\n- Changes to ‘metadata’ files like Gemfile, .gitignore, build scripts, etc.\n- Moving source files from one directory or package to another\n\n## How to report a bug\n**Security Disclosure**\nIf you find a security vulnerability, do **NOT** open an issue. Email jag.maddipatla@gmail.com or sathvik.redrouthu@gmail.com instead. Any security issues should be submitted here directly.\nIn order to determine whether you are dealing with a security issue, ask yourself these two questions:\n* Can I access something that's not mine, or something I shouldn't have access to?\n* Can I disable something for other people?\nIf the answer to either of those two questions are \"yes\", then you're probably dealing with a security issue. Note that even if you answer \"no\" to both questions, you may still be dealing with a security issue, so if you're unsure, just email us.\n\n## Code review process\nOnce you submit a contribution, it will be signed off by either @Jag-M or @sathvikr prior to being implemented. Interested contributors should join our discord to get commit access.\nWe also hold weekly triage meetings in a public google meet that all contributors/interested persons may join. Any community feedback will be implemented as soon as possible (usually within a couple of hours).\n\n## Philosophy\nOur philosophy is to provide robust tooling to make chip design as intuitive as possible.\n\nIf you find yourself wishing for a feature that doesn't exist in VPM, you are probably not alone. There are bound to be others out there with similar needs. Many of the features that VPM has today have been added because our users saw the need. Open an issue on our issues list on GitHub which describes the feature you would like to see, why you need it, and how it should work.\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\ndescription = \"A powerful package manager for Verilog projects, streamlining IP core management and accelerating hardware design workflows\"\ndocumentation = \"https://github.com/getinstachip/vpm#readme\"\nhomepage = \"https://getinstachip.com\"\nrepository = \"https://github.com/getinstachip/vpm\"\nname = \"vpm\"\nversion = \"0.2.18\"\nedition = \"2021\"\nlicense = \"MIT\"\ncopyright = \"Copyright (c) 2024 Instachip\"\nauthors = [\"Instachip <team@getinstachip.com>\"]\n\n[dependencies]\nclap = { version = \"4.5.13\", features = [\"derive\"] }\ntokio = { version = \"1.39.2\", features = [\"full\"] }\nopenssl = { version = \"0.10\", features = [\"vendored\"] }\nreqwest = { version = \"0.12.5\", features = [\"json\", \"blocking\"] }\nserde = { version = \"1.0.208\", features = [\"derive\"] }\ntree-sitter-verilog = { git = \"https://github.com/tree-sitter/tree-sitter-verilog\" }\ntree-sitter = \"0.20.6\"\nanyhow = \"1.0.86\"\nserde_json = \"1.0.125\"\nwalkdir = \"2.5.0\"\nonce_cell = \"1.19.0\"\ntempfile = \"3.12.0\"\ncargo-lock = \"9.0.0\"\nfastrand = \"2.1.1\"\nfancy-regex = \"0.13.0\"\ndialoguer = \"0.11.0\"\nfuzzy-matcher = \"0.3.7\"\nindicatif = \"0.17.8\"\nwhich = \"6.0.3\"\nregex = \"1.10.6\"\ntoml_edit = \"0.22.20\"\nimara-diff = \"0.1.7\"\nuuid = { version = \"1.10.0\", features = [\"v7\"] }\ndirectories = \"5.0.1\"\nring = \"0.17.8\"\nbase64 = \"0.22.1\"\nhex = \"0.4.3\"\nrand = \"0.8.5\"\nsha2 = \"0.10.8\"\nsys-info = \"0.9.1\"\n\n[build-dependencies]\ncc=\"*\"\n"
  },
  {
    "path": "LICENSE.md",
    "content": "# MIT License\n\nCopyright (c) 2024 Instachip\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Verilog Package Manager (VPM)\n\nVPM is a powerful package manager for Verilog projects, currently being piloted at Stanford and UC Berkeley. It's designed to streamline the management, reuse, and communication of IP cores and dependencies in hardware design workflows, significantly accelerating your design process.\n\n## Features\n\n- **Module Management**: Easily include, update, and remove modules in your project.\n- **Documentation Generation**: Automatically create comprehensive documentation for your Verilog modules.\n- **Dependency Handling**: Manage project dependencies with ease.\n- **Simulation Support**: Simulate your Verilog files directly through VPM.\n- **Tool Integration**: Seamlessly install and set up open-source tools for your project.\n- **File Generation**: Automatically generate necessary files like .f, .svh, .xcd, and .tcl.\n\n## Installation\n\nVPM is designed for easy installation with no additional dependencies. \n\n### Default Installation (Linux/MacOS):\n```bash\ncurl -f https://getinstachip.com/install.sh | sh\n```\n\n### Default Installation (Windows):\n1. Download the `.zip` file matching your Windows architecture from the [latest release page](https://github.com/getinstachip/vpm/releases/latest)\n2. Extract and run the `.exe` file\n\nIf installation doesn't work, try the following:\n\n### Linux alternative:\nWe support Snap\n\n```bash\nsnap download instachip-vpm\nalias vpm='instachip-vpm.vpm'\n```\n\n### MacOS alternative:\n```bash\nbrew tap getinstachip/vpm\nbrew install vpm\n```\n\nAfter installation, the vpm command will be available in any terminal.\n\n## Commands\n\n- `vpm include <path_to_module.sv>`: Include any module from a repo (and all its submodules).\n- `vpm docs <module.sv>`: Generate documentation for any module (highlighting bugs and edge cases)\n- `vpm install <tool>`: Auto-integrate an open-source tool without manual setup\n- `vpm update <module.sv>`: Update module to the latest version\n- `vpm remove <module.sv>`: Remove a module from your project\n- `vpm list`: List all modules in our standard library\n- `vpm dotf <module.sv>`:  Generate a `.f` filelist when exporting your project\n- `vpm sim <module.sv> <testbench.sv>`: Simulate Verilog module using iVerilog\n  \n### vpm include\nInclude a module or repository in your project.\n\nThis command:\n- Downloads the specified module or repository\n- Analyzes the module hierarchy\n- Includes all necessary submodules and generates appropriate header files\n- Updates the vpm.toml file with new module details\n\nThis command comes in two forms:\n1. Include a module and all its submodules:\n```bash\nvpm include <URL_TO_TOP_MODULE.sv>\n```\n`URL_TO_TOP_MODULE`: Full GitHub URL to the top module to include. The URL should come in the format of `https://github.com/<AUTHOR_NAME>/<REPO_NAME>/blob/branch/<PATH_TO_MODULE.sv>`.\n\nExample:\n```bash\nvpm include https://github.com/ZipCPU/zipcpu/blob/master/rtl/core/prefetch.v\n```\n\n![](https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExY3Jmbmw0NWlva3F2bHdyY2h0NGZwNGlvNXRjZTY2bXB4ODRzOXd6eiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/KwHCr2ifmIZzSkpfjv/giphy.gif)\n\n2. Include a repository:\n```bash\nvpm include --repo <AUTHOR_NAME/REPO_NAME>\n```\n\nPress tab to select multiple modules and press ENTER to install. If no modules are selected, all modules in the repository will be installed.\n\nExample:\n```bash\nvpm include --repo ZipCPU/zipcpu\n```\n![](https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExMG5uaHJ1N2twd2JiY2pucjlwbjNjNm02NjRycDlocDF5bnB2eHNvYiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/QJ2sIDYIftEgu5uNAg/giphy.gif)\n### vpm docs\nGenerate comprehensive documentation for a module.\n\nThis command generates a Markdown README file containing:\n- Overview and module description\n- Pinout diagram\n- Table of ports\n- Table of parameters\n- Important implementation details\n- Simulation output and GTKWave waveform details (Coming soon!)\n- List of any major bugs or caveats if they exist\n\n```bash\nvpm docs <MODULE.sv>\n```\n\n`<MODULE>`: Name of the module to generate documentation for. Include the file extension.\n\n`[URL]`: Optional URL of the repository to generate documentation for. If not specified, VPM will assume the module is local, and will search for the module in the vpm_modules directory.\n\nExamples:\n```bash\nvpm docs pfcache.v\nvpm docs pfcache.v https://github.com/ZipCPU/zipcpu\n```\n![](https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExOXc5NWpmYnV5eGxtYzRud2tid3poYTZyYXEwdmpqaGF3MjZwdW5leiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/C8nFHwNq0qBpXRF9pP/giphy.gif)\n\n### vpm update\nUpdate a package to the latest version.\n\nThis command:\n- Checks for the latest version of the specified module\n- Downloads and replaces the current version with the latest\n- Updates all dependencies and submodules\n- Modifies the vpm.toml file to reflect the changes\n\n```bash\nvpm update <PACKAGE_PATH>\n```\n\n`<PACKAGE_PATH>`: Full module path of the package to update\n\nExample:\n```bash\nvpm update my_project/modules/counter\n```\n\n### vpm remove\nRemove a package from your project.\n\nThis command:\n- Removes the specified module from your project\n- Updates the vpm.toml file to remove the module entry\n- Cleans up any orphaned dependencies\n\n```bash\nvpm remove <PACKAGE_PATH>\n```\n\n`<PACKAGE_PATH>`: Full module path of the package to remove\n\nExample:\n```bash\nvpm remove my_project/modules/unused_module\n```\n\n### vpm dotf\nGenerate a .f file list for a Verilog or SystemVerilog module.\n\n```bash\nvpm dotf <PATH_TO_TOP_MODULE>\n```\n\n`<PATH_TO_TOP_MODULE>`: Path to the top module to generate the file list for. File should be local.\n\nExample:\n```bash\nvpm dotf ./vpm_modules/pfcache/fwb_master.v\n```\n![](https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExMHhkdjQ1bnl0cTA3cW1lOHVuNjkxaW1ydzFndXNnaDZlMHFiMWRpNSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/mafBT4PURloV52oLFP/giphy.gif)\n\nThis command:\n- Analyzes the specified top module\n- Identifies all submodules and dependencies\n- Generates a .f file containing all necessary file paths\n- Includes all locally scoped defines for submodules\n\n### vpm install\nInstall and set up an open-source tool for integration into your project.\n\nThis command:\n- Downloads the specified tool\n- Configures the tool for your system\n- Integrates it with your VPM project setup\n\n```bash\nvpm install <TOOL_NAME>\n```\n`<TOOL_NAME>`: Name of the tool to install\n\nExample:\n```bash\nvpm install verilator\n```\n![](https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExNjFhc2t1ZTBwM29xdm10dThubWN3ZGhvOWhjeXJjNnQ0dWVqd2szdSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/737P65RSHVlu2dxXVu/giphy.gif)\n\nCurrently supported tools:\n- Verilator\n- Chipyard\n- OpenROAD\n- Edalize\n- Icarus Verilog\n\nComing soon:\n- Yosys (with support for ABC)\n- RISC-V GNU Toolchain\n\n### vpm sim\nSimulate Verilog files.\n\nThis command:\n- Compiles the specified Verilog files\n- Runs the simulation\n- Provides output and analysis of the simulation results\n\n```bash\nvpm sim <VERILOG_FILES>...\n```\n`<VERILOG_FILES>`: List of Verilog files to simulate using Icarus Verilog.\n\nExample:\n```bash\nvpm sim testbench.v module1.v module2.v\n```\n![](https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExcnhiaDNwZmRhazVlODAxanlqaW1yaXdpazVmNTVwanJ4c2V3a3RscSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/6ImXOh4OVsjrWYrikf/giphy.gif)\n\n### vpm list\nList all modules in VPM's standard library.\n\nThis command displays all available modules in the standard Verilog library, including:\n- Common modules\n- RISC-V modules\n\n```bash\nvpm list\n```\n\n### vpm config\nConfigure VPM settings.\n\nThis command allows you to enable or disable anonymous usage data collection.\n\n```bash\nvpm config <OPTION> <VALUE>\n```\n\nOPTIONS:\n- analytics (true/false): Enable or disable anonymous usage data collection.\n\nExample:\n```bash\nvpm config --analytics true\n```\n\n## Configuration\n\nVPM uses a `vpm.toml` file for project configuration. This file allows you to specify project properties, dependencies, and custom settings.\n\nExample vpm.toml file:\n```toml\n[library]\nname = \"my_cpu\"\nversion = \"0.3.5\"\ndescription = \"A basic CPU.\"\n\n[dependencies]\n\"https://github.com/ZipCPU/zipcpu\" = { modules = [\"alu\", \"register_file\"], commit = \"1234567890abcdef\" }\n```\n\n### Support and Contribution\nFor issues, feature requests, or contributions, please email sathvikr@getinstachip.com or create a GitHub Issue. Please read our CONTRIBUTING.md file for guidelines on how to contribute to VPM.\n\n### License\nVPM is released under the MIT License.\n\n### Acknowledgements\n\nWe'd like to thank our early adopters for their valuable feedback and support in developing VPM:\n\n- [Kasun Buddhi](https://www.linkedin.com/in/kasunbuddhi/)\n- [Krishna](https://www.linkedin.com/in/krishna-verilog/)\n- [Yash](https://www.linkedin.com/in/yash-verilog/)\n- [Max Korbel](https://www.linkedin.com/in/maxkorbel/)\n- [Samuel](https://www.linkedin.com/in/samuel-verilog/)\n- [Biplab Das](https://www.linkedin.com/in/biplab-das-7b9870165/)\n- [Nikhil Raju](https://www.linkedin.com/in/nikhil-raju-verilog/)\n\nAnd many more contributors who have helped shape VPM.\n"
  },
  {
    "path": "install.sh",
    "content": "#!/bin/sh\n# shellcheck shell=dash\n# shellcheck disable=SC3043 # Assume `local` extension\n\n# The official vpm installer.\n#\n# It runs on Unix shells like {a,ba,da,k,z}sh. It uses the common `local`\n# extension. Note: Most shells limit `local` to 1 var per line, contra bash.\n\nmain() {\n    # The version of ksh93 that ships with many illumos systems does not support the \"local\"\n    # extension. Print a message rather than fail in subtle ways later on:\n    if [ \"${KSH_VERSION-}\" = 'Version JM 93t+ 2010-03-05' ]; then\n        err 'the installer does not work with this ksh93 version; please try bash'\n    fi\n\n    set -u\n\n    parse_args \"$@\"\n\n    local _arch\n    _arch=\"${ARCH:-$(ensure get_architecture)}\"\n    assert_nz \"${_arch}\" \"arch\"\n    echo \"Detected architecture: ${_arch}\"\n\n    local _bin_name\n    case \"${_arch}\" in\n    *windows*) _bin_name=\"vpm.exe\" ;;\n    *) _bin_name=\"vpm\" ;;\n    esac\n\n    # Create and enter a temporary directory.\n    local _tmp_dir\n    _tmp_dir=\"$(mktemp -d)\" || err \"mktemp: could not create temporary directory\"\n    cd \"${_tmp_dir}\" || err \"cd: failed to enter directory: ${_tmp_dir}\"\n\n    # Download and extract vpm.\n    local _package\n    _package=\"$(ensure download_vpm \"${_arch}\")\"\n    assert_nz \"${_package}\" \"package\"\n    echo \"Downloaded package: ${_package}\"\n    case \"${_package}\" in\n    *.tar.gz)\n        need_cmd tar\n        ensure tar -xf \"${_package}\"\n        ;;\n    *.zip)\n        need_cmd unzip\n        ensure unzip -oq \"${_package}\"\n        ;;\n    *)\n        err \"unsupported package format: ${_package}\"\n        ;;\n    esac\n\n    # Install binary.\n    ensure try_sudo mkdir -p -- \"${BIN_DIR}\"\n    ensure try_sudo cp -- \"${_bin_name}\" \"${BIN_DIR}/${_bin_name}\"\n    ensure try_sudo chmod +x \"${BIN_DIR}/${_bin_name}\"\n    echo \"Installed vpm to ${BIN_DIR}\"\n\n    # Print success message and check $PATH.\n    echo \"\"\n    echo \"vpm is installed!\"\n    if ! echo \":${PATH}:\" | grep -Fq \":${BIN_DIR}:\"; then\n        echo \"Note: ${BIN_DIR} is not on your \\$PATH. vpm will not work unless it is added to \\$PATH.\"\n    fi\n}\n\n# Parse the arguments passed and set variables accordingly.\nparse_args() {\n    # BIN_DIR_DEFAULT=\"${HOME}/.local/bin\"\n    BIN_DIR_DEFAULT=/usr/local/bin\n    # MAN_DIR_DEFAULT=\"${HOME}/.local/share/man\"\n    SUDO_DEFAULT=\"sudo\"\n\n    BIN_DIR=\"${BIN_DIR_DEFAULT}\"\n    # MAN_DIR=\"${MAN_DIR_DEFAULT}\"\n    SUDO=\"${SUDO_DEFAULT}\"\n\n    while [ \"$#\" -gt 0 ]; do\n        case \"$1\" in\n        --arch) ARCH=\"$2\" && shift 2 ;;\n        --arch=*) ARCH=\"${1#*=}\" && shift 1 ;;\n        --bin-dir) BIN_DIR=\"$2\" && shift 2 ;;\n        --bin-dir=*) BIN_DIR=\"${1#*=}\" && shift 1 ;;\n        # --man-dir) MAN_DIR=\"$2\" && shift 2 ;;\n        # --man-dir=*) MAN_DIR=\"${1#*=}\" && shift 1 ;;\n        --sudo) SUDO=\"$2\" && shift 2 ;;\n        --sudo=*) SUDO=\"${1#*=}\" && shift 1 ;;\n        -h | --help) usage && exit 0 ;;\n        *) err \"Unknown option: $1\" ;;\n        esac\n    done\n}\n\nusage() {\n    # heredocs are not defined in POSIX.\n    local _text_heading _text_reset\n    _text_heading=\"$(tput bold || true 2>/dev/null)$(tput smul || true 2>/dev/null)\"\n    _text_reset=\"$(tput sgr0 || true 2>/dev/null)\"\n\n    local _arch\n    _arch=\"$(get_architecture || true)\"\n\n    echo \"\\\n${_text_heading}vpm installer${_text_reset}\nInstachip <team@getinstachip.com>\nhttps://github.com/getinstachip/vpm\n\nFetches and installs vpm. If vpm is already installed, it will be updated to the latest version.\n\n${_text_heading}Usage:${_text_reset}\n  install.sh [OPTIONS]\n\n${_text_heading}Options:${_text_reset}\n      --arch     Override the architecture identified by the installer [current: ${_arch}]\n      --bin-dir  Override the installation directory [default: ${BIN_DIR_DEFAULT}]\n      # --man-dir  Override the manpage installation directory [default: ${MAN_DIR_DEFAULT}]\n      --sudo     Override the command used to elevate to root privileges [default: ${SUDO_DEFAULT}]\n  -h, --help     Print help\"\n}\n\ndownload_vpm() {\n    local _arch=\"$1\"\n\n    if check_cmd curl; then\n        _dld=curl\n    elif check_cmd wget; then\n        _dld=wget\n    else\n        need_cmd 'curl or wget'\n    fi\n    need_cmd grep\n\n    local _releases_url=\"https://api.github.com/repos/getinstachip/vpm/releases/latest\"\n    local _releases\n    case \"${_dld}\" in\n    curl) _releases=\"$(curl -sL \"${_releases_url}\")\" ||\n        err \"curl: failed to download ${_releases_url}\" ;;\n    wget) _releases=\"$(wget -qO- \"${_releases_url}\")\" ||\n        err \"wget: failed to download ${_releases_url}\" ;;\n    *) err \"unsupported downloader: ${_dld}\" ;;\n    esac\n    (echo \"${_releases}\" | grep -q 'API rate limit exceeded') &&\n        err \"you have exceeded GitHub's API rate limit. Please try again later, or use a different installation method: https://github.com/getinstachip/vpm/#installation\"\n\n    local _package_url\n    _package_url=\"$(echo \"${_releases}\" | grep \"browser_download_url\" | cut -d '\"' -f 4 | grep -- \"${_arch}\")\" ||\n        err \"vpm has not yet been packaged for your architecture (${_arch}), please file an issue: https://github.com/getinstachip/vpm/issues\"\n\n    local _ext\n    case \"${_package_url}\" in\n    *.tar.gz) _ext=\"tar.gz\" ;;\n    *.zip) _ext=\"zip\" ;;\n    *) err \"unsupported package format: ${_package_url}\" ;;\n    esac\n\n    local _package=\"vpm.${_ext}\"\n    case \"${_dld}\" in\n    curl) _releases=\"$(curl -sLo \"${_package}\" \"${_package_url}\")\" || err \"curl: failed to download ${_package_url}\" ;;\n    wget) _releases=\"$(wget -qO \"${_package}\" \"${_package_url}\")\" || err \"wget: failed to download ${_package_url}\" ;;\n    *) err \"unsupported downloader: ${_dld}\" ;;\n    esac\n\n    echo \"${_package}\"\n}\n\ntry_sudo() {\n    if \"$@\" >/dev/null 2>&1; then\n        return 0\n    fi\n\n    need_sudo\n    \"${SUDO}\" \"$@\"\n}\n\nneed_sudo() {\n    if ! check_cmd \"${SUDO}\"; then\n        err \"\\\ncould not find the command \\`${SUDO}\\` needed to get permissions for install.\n\nIf you are on Windows, please run your shell as an administrator, then rerun this script.\nOtherwise, please run this script as root, or install \\`sudo\\`.\"\n    fi\n\n    if ! \"${SUDO}\" -v; then\n        err \"sudo permissions not granted, aborting installation\"\n    fi\n}\n\n# The below functions have been extracted with minor modifications from the\n# Rustup install script:\n#\n#   https://github.com/rust-lang/rustup/blob/4c1289b2c3f3702783900934a38d7c5f912af787/rustup-init.sh\n\nget_architecture() {\n    local _ostype _cputype _bitness _arch _clibtype\n    _ostype=\"$(uname -s)\"\n    _cputype=\"$(uname -m)\"\n    _clibtype=\"musl\"\n\n    if [ \"${_ostype}\" = Linux ]; then\n        if [ \"$(uname -o || true)\" = Android ]; then\n            _ostype=Android\n        fi\n    fi\n\n    if [ \"${_ostype}\" = Darwin ] && [ \"${_cputype}\" = i386 ]; then\n        # Darwin `uname -m` lies\n        if sysctl hw.optional.x86_64 | grep -q ': 1'; then\n            _cputype=x86_64\n        fi\n    fi\n\n    if [ \"${_ostype}\" = SunOS ]; then\n        # Both Solaris and illumos presently announce as \"SunOS\" in \"uname -s\"\n        # so use \"uname -o\" to disambiguate.  We use the full path to the\n        # system uname in case the user has coreutils uname first in PATH,\n        # which has historically sometimes printed the wrong value here.\n        if [ \"$(/usr/bin/uname -o || true)\" = illumos ]; then\n            _ostype=illumos\n        fi\n\n        # illumos systems have multi-arch userlands, and \"uname -m\" reports the\n        # machine hardware name; e.g., \"i86pc\" on both 32- and 64-bit x86\n        # systems.  Check for the native (widest) instruction set on the\n        # running kernel:\n        if [ \"${_cputype}\" = i86pc ]; then\n            _cputype=\"$(isainfo -n)\"\n        fi\n    fi\n\n    case \"${_ostype}\" in\n    Android)\n        _ostype=linux-android\n        ;;\n    Linux)\n        check_proc\n        _ostype=unknown-linux-${_clibtype}\n        _bitness=$(get_bitness)\n        ;;\n    FreeBSD)\n        _ostype=unknown-freebsd\n        ;;\n    NetBSD)\n        _ostype=unknown-netbsd\n        ;;\n    DragonFly)\n        _ostype=unknown-dragonfly\n        ;;\n    Darwin)\n        _ostype=apple-darwin\n        ;;\n    illumos)\n        _ostype=unknown-illumos\n        ;;\n    MINGW* | MSYS* | CYGWIN* | Windows_NT)\n        _ostype=pc-windows-msvc\n        ;;\n    *)\n        err \"unrecognized OS type: ${_ostype}\"\n        ;;\n    esac\n\n    case \"${_cputype}\" in\n    i386 | i486 | i686 | i786 | x86)\n        _cputype=i686\n        ;;\n    xscale | arm)\n        _cputype=arm\n        if [ \"${_ostype}\" = \"linux-android\" ]; then\n            _ostype=linux-androideabi\n        fi\n        ;;\n    armv6l)\n        _cputype=arm\n        if [ \"${_ostype}\" = \"linux-android\" ]; then\n            _ostype=linux-androideabi\n        else\n            _ostype=\"${_ostype}eabihf\"\n        fi\n        ;;\n    armv7l | armv8l)\n        _cputype=armv7\n        if [ \"${_ostype}\" = \"linux-android\" ]; then\n            _ostype=linux-androideabi\n        else\n            _ostype=\"${_ostype}eabihf\"\n        fi\n        ;;\n    aarch64 | arm64)\n        _cputype=aarch64\n        ;;\n    x86_64 | x86-64 | x64 | amd64)\n        _cputype=x86_64\n        ;;\n    mips)\n        _cputype=$(get_endianness mips '' el)\n        ;;\n    mips64)\n        if [ \"${_bitness}\" -eq 64 ]; then\n            # only n64 ABI is supported for now\n            _ostype=\"${_ostype}abi64\"\n            _cputype=$(get_endianness mips64 '' el)\n        fi\n        ;;\n    ppc)\n        _cputype=powerpc\n        ;;\n    ppc64)\n        _cputype=powerpc64\n        ;;\n    ppc64le)\n        _cputype=powerpc64le\n        ;;\n    s390x)\n        _cputype=s390x\n        ;;\n    riscv64)\n        _cputype=riscv64gc\n        ;;\n    *)\n        err \"unknown CPU type: ${_cputype}\"\n        ;;\n    esac\n\n    # Detect 64-bit linux with 32-bit userland\n    if [ \"${_ostype}\" = unknown-linux-musl ] && [ \"${_bitness}\" -eq 32 ]; then\n        case ${_cputype} in\n        x86_64)\n            # 32-bit executable for amd64 = x32\n            if is_host_amd64_elf; then {\n                err \"x32 userland is unsupported\"\n            }; else\n                _cputype=i686\n            fi\n            ;;\n        mips64)\n            _cputype=$(get_endianness mips '' el)\n            ;;\n        powerpc64)\n            _cputype=powerpc\n            ;;\n        aarch64)\n            _cputype=armv7\n            if [ \"${_ostype}\" = \"linux-android\" ]; then\n                _ostype=linux-androideabi\n            else\n                _ostype=\"${_ostype}eabihf\"\n            fi\n            ;;\n        riscv64gc)\n            err \"riscv64 with 32-bit userland unsupported\"\n            ;;\n        *) ;;\n        esac\n    fi\n\n    # Detect armv7 but without the CPU features Rust needs in that build,\n    # and fall back to arm.\n    # See https://github.com/rust-lang/rustup.rs/issues/587.\n    if [ \"${_ostype}\" = \"unknown-linux-musleabihf\" ] && [ \"${_cputype}\" = armv7 ]; then\n        if ensure grep '^Features' /proc/cpuinfo | grep -q -v neon; then\n            # At least one processor does not have NEON.\n            _cputype=arm\n        fi\n    fi\n\n    _arch=\"${_cputype}-${_ostype}\"\n    echo \"${_arch}\"\n}\n\nget_bitness() {\n    need_cmd head\n    # Architecture detection without dependencies beyond coreutils.\n    # ELF files start out \"\\x7fELF\", and the following byte is\n    #   0x01 for 32-bit and\n    #   0x02 for 64-bit.\n    # The printf builtin on some shells like dash only supports octal\n    # escape sequences, so we use those.\n    local _current_exe_head\n    _current_exe_head=$(head -c 5 /proc/self/exe)\n    if [ \"${_current_exe_head}\" = \"$(printf '\\177ELF\\001')\" ]; then\n        echo 32\n    elif [ \"${_current_exe_head}\" = \"$(printf '\\177ELF\\002')\" ]; then\n        echo 64\n    else\n        err \"unknown platform bitness\"\n    fi\n}\n\nget_endianness() {\n    local cputype=\"$1\"\n    local suffix_eb=\"$2\"\n    local suffix_el=\"$3\"\n\n    # detect endianness without od/hexdump, like get_bitness() does.\n    need_cmd head\n    need_cmd tail\n\n    local _current_exe_endianness\n    _current_exe_endianness=\"$(head -c 6 /proc/self/exe | tail -c 1)\"\n    if [ \"${_current_exe_endianness}\" = \"$(printf '\\001')\" ]; then\n        echo \"${cputype}${suffix_el}\"\n    elif [ \"${_current_exe_endianness}\" = \"$(printf '\\002')\" ]; then\n        echo \"${cputype}${suffix_eb}\"\n    else\n        err \"unknown platform endianness\"\n    fi\n}\n\nis_host_amd64_elf() {\n    need_cmd head\n    need_cmd tail\n    # ELF e_machine detection without dependencies beyond coreutils.\n    # Two-byte field at offset 0x12 indicates the CPU,\n    # but we're interested in it being 0x3E to indicate amd64, or not that.\n    local _current_exe_machine\n    _current_exe_machine=$(head -c 19 /proc/self/exe | tail -c 1)\n    [ \"${_current_exe_machine}\" = \"$(printf '\\076')\" ]\n}\n\ncheck_proc() {\n    # Check for /proc by looking for the /proc/self/exe link.\n    # This is only run on Linux.\n    if ! test -L /proc/self/exe; then\n        err \"unable to find /proc/self/exe. Is /proc mounted? Installation cannot proceed without /proc.\"\n    fi\n}\n\nneed_cmd() {\n    if ! check_cmd \"$1\"; then\n        err \"need '$1' (command not found)\"\n    fi\n}\n\ncheck_cmd() {\n    command -v -- \"$1\" >/dev/null 2>&1\n}\n\n# Run a command that should never fail. If the command fails execution\n# will immediately terminate with an error showing the failing\n# command.\nensure() {\n    if ! \"$@\"; then err \"command failed: $*\"; fi\n}\n\nassert_nz() {\n    if [ -z \"$1\" ]; then err \"found empty string: $2\"; fi\n}\n\nerr() {\n    echo \"Error: $1\" >&2\n    exit 1\n}\n\n# This is put in braces to ensure that the script does not run until it is\n# downloaded completely.\n{\n    main \"$@\" || exit 1\n}\n"
  },
  {
    "path": "installer.iss",
    "content": "#define VerFile = FileOpen(\"version.txt\")\n#define MyAppVersion = FileRead(VerFile)\n#expr FileClose(VerFile)\n#undef VerFile\n\n#define TarFile FileOpen(\"target.txt\")\n#define MyTarget FileRead(TarFile)\n#expr FileClose(TarFile)\n#undef TarFile\n\n#define MyAppName \"VPM\"\n#define MyAppPublisher \"Instachip\"\n#define MyAppURL \"https://getinstachip.com/\"\n\n[Setup]\nAppId={{E3D813B5-C9DB-4FC0-957C-9D06371B378E}\nAppName={#MyAppName}\nAppVersion={#MyAppVersion}\nAppPublisher={#MyAppPublisher}\nAppPublisherURL={#MyAppURL}\nAppSupportURL={#MyAppURL}\nAppUpdatesURL={#MyAppURL}\nCreateAppDir=yes\nPrivilegesRequired=lowest\nPrivilegesRequiredOverridesAllowed=dialog\nCompression=lzma\nSolidCompression=yes\nWizardStyle=modern\nDefaultDirName={autopf}\\{#MyAppName}\nDisableDirPage=no\nDirExistsWarning=no\nOutputBaseFilename=vpm-installer-{#MyAppVersion}-{#MyTarget}\nOutputDir=.\n\n[Languages]\nName: \"english\"; MessagesFile: \"compiler:Default.isl\"\nName: \"armenian\"; MessagesFile: \"compiler:Languages\\Armenian.isl\"\nName: \"brazilianportuguese\"; MessagesFile: \"compiler:Languages\\BrazilianPortuguese.isl\"\nName: \"bulgarian\"; MessagesFile: \"compiler:Languages\\Bulgarian.isl\"\nName: \"catalan\"; MessagesFile: \"compiler:Languages\\Catalan.isl\"\nName: \"corsican\"; MessagesFile: \"compiler:Languages\\Corsican.isl\"\nName: \"czech\"; MessagesFile: \"compiler:Languages\\Czech.isl\"\nName: \"danish\"; MessagesFile: \"compiler:Languages\\Danish.isl\"\nName: \"dutch\"; MessagesFile: \"compiler:Languages\\Dutch.isl\"\nName: \"finnish\"; MessagesFile: \"compiler:Languages\\Finnish.isl\"\nName: \"french\"; MessagesFile: \"compiler:Languages\\French.isl\"\nName: \"german\"; MessagesFile: \"compiler:Languages\\German.isl\"\nName: \"hebrew\"; MessagesFile: \"compiler:Languages\\Hebrew.isl\"\nName: \"hungarian\"; MessagesFile: \"compiler:Languages\\Hungarian.isl\"\nName: \"icelandic\"; MessagesFile: \"compiler:Languages\\Icelandic.isl\"\nName: \"italian\"; MessagesFile: \"compiler:Languages\\Italian.isl\"\nName: \"japanese\"; MessagesFile: \"compiler:Languages\\Japanese.isl\"\nName: \"korean\"; MessagesFile: \"compiler:Languages\\Korean.isl\"\nName: \"norwegian\"; MessagesFile: \"compiler:Languages\\Norwegian.isl\"\nName: \"polish\"; MessagesFile: \"compiler:Languages\\Polish.isl\"\nName: \"portuguese\"; MessagesFile: \"compiler:Languages\\Portuguese.isl\"\nName: \"russian\"; MessagesFile: \"compiler:Languages\\Russian.isl\"\nName: \"slovak\"; MessagesFile: \"compiler:Languages\\Slovak.isl\"\nName: \"slovenian\"; MessagesFile: \"compiler:Languages\\Slovenian.isl\"\nName: \"spanish\"; MessagesFile: \"compiler:Languages\\Spanish.isl\"\nName: \"turkish\"; MessagesFile: \"compiler:Languages\\Turkish.isl\"\nName: \"ukrainian\"; MessagesFile: \"compiler:Languages\\Ukrainian.isl\"\n\n[Files]\nSource: \"target\\{#MyTarget}\\release\\vpm.exe\"; DestDir: \"{app}\"; Flags: ignoreversion\nSource: \"README.md\"; DestDir: \"{app}\"; Flags: ignoreversion\n\n[Tasks]\nName: addtopath; Description: \"Add application directory to PATH\"; Flags: checkedonce\n\n[Code]\nconst\n    ModPathName = 'modifypath';\n    ModPathType = 'user';\n\nfunction ModPathDir(): TArrayOfString;\nbegin\n    SetArrayLength(Result, 1);\n    Result[0] := ExpandConstant('{app}');\nend;\n\nprocedure ModPath();\nvar\n    oldpath: string;\n    newpath: string;\n    updatepath: boolean;\nbegin\n    if not RegQueryStringValue(HKEY_CURRENT_USER, 'Environment', 'Path', oldpath) then\n        oldpath := '';\n\n    updatepath := true;\n\n    if (Pos(';' + UpperCase(ExpandConstant('{app}')) + ';', ';' + UpperCase(oldpath) + ';') > 0) then\n        updatepath := false\n    else if (Pos(';' + UpperCase(ExpandConstant('{app}')) + '\\;', ';' + UpperCase(oldpath) + ';') > 0) then\n        updatepath := false;\n\n    if (updatepath) then begin\n        newpath := oldpath;\n        if (Pos(';', oldpath) > 0) then\n            newpath := newpath + ';' + ExpandConstant('{app}')\n        else\n            newpath := ExpandConstant('{app}');\n\n        if RegWriteStringValue(HKEY_CURRENT_USER, 'Environment', 'Path', newpath) then\n        begin\n            StringChangeEx(oldpath, ';', #13#10, True);\n            StringChangeEx(newpath, ';', #13#10, True);\n            Log('Old PATH:' + #13#10 + oldpath);\n            Log('New PATH:' + #13#10 + newpath);\n            RegWriteStringValue(HKEY_CURRENT_USER, 'Environment', 'Path', newpath);\n        end else\n            Log('Error: Failed to modify PATH');\n    end;\nend;\n\nprocedure CurStepChanged(CurStep: TSetupStep);\nbegin\n    if (CurStep = ssPostInstall) and WizardIsTaskSelected('addtopath') then\n        ModPath();\nend;\n\nprocedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);\nvar\n    oldpath: string;\n    newpath: string;\n    pathArr: TArrayOfString;\n    i: Integer;\nbegin\n    if (CurUninstallStep = usUninstall) then begin\n        if RegQueryStringValue(HKEY_CURRENT_USER, 'Environment', 'Path', oldpath) then begin\n            oldpath := oldpath + ';';\n            i := 0;\n            while (Pos(';', oldpath) > 0) do begin\n                SetArrayLength(pathArr, i + 1);\n                pathArr[i] := Copy(oldpath, 0, Pos(';', oldpath) - 1);\n                oldpath := Copy(oldpath, Pos(';', oldpath) + 1, Length(oldpath));\n                i := i + 1;\n\n                if (pathArr[i - 1] <> ExpandConstant('{app}')) then begin\n                    if (newpath = '') then\n                        newpath := pathArr[i - 1]\n                    else\n                        newpath := newpath + ';' + pathArr[i - 1];\n                end;\n            end;\n\n            RegWriteStringValue(HKEY_CURRENT_USER, 'Environment', 'Path', newpath);\n        end;\n    end;\nend;\n\n[UninstallDelete]\nType: filesandordirs; Name: \"{app}\""
  },
  {
    "path": "src/cmd/cmd.rs",
    "content": "use clap::Parser;\n\n#[derive(Debug, Parser)]\n#[clap(\n    about = \"VPM - Verilog Package Manager\",\n    author,\n    version,\n    propagate_version = true,\n    disable_help_subcommand = true,\n    after_help = \"Run 'vpm <COMMAND> --help' for more information on a specific command.\"\n)]\npub enum Cmd {\n    #[command(\n        about = \"vpm include <MODULE_URL> [--repo] [--riscv] [--commit <HASH>] // Add a module or repository to your project\",\n        long_about = \"Include a module with one command. VPM's internal parser will identify and configure any subdependencies.\"\n    )]\n    Include(Include),\n\n    #[command(\n        about = \"vpm update <MODULE_PATH> // Update a module to its latest version\",\n        long_about = \"Update a specific module to its latest version. This command checks for updates to the specified module and applies them if available.\"\n    )]\n    Update(Update),\n\n    #[command(\n        about = \"vpm remove <PACKAGE_PATH> // Remove a package from your project\",\n        long_about = \"Remove a package from your project. This command uninstalls the specified package and removes it from your project's dependencies, helping you maintain a clean and efficient project structure.\"\n    )]\n    Remove(Remove),\n\n    #[command(\n        about = \"vpm dotf <TOP_MODULE_PATH> // Generate a .f filelist for a module\",\n        long_about = \"Generate a filelist (.f file) for a top module and all its submodules.\"\n    )]\n    Dotf(Dotf),\n\n    #[command(\n        about = \"vpm docs <MODULE_PATH> [--url <URL>] // Generate documentation for a module\",\n        long_about = \"Generate documentation for a module. This command creates comprehensive documentation for the specified module, including descriptions of inputs, outputs, and functionality. It supports both local modules and those hosted on remote repositories.\"\n    )]\n    Docs(Docs),\n\n    #[command(\n        about = \"vpm install <TOOL_NAME> // Install a specified tool\",\n        long_about = \"Install a specified tool. VPM automates the build process and installs missing subdependencies. Support for powerful linters, Icarus Verilog, Verilator, Yosys, GTKWave, the RISC-V toolchain, and more.\"\n    )]\n    Install(Install),\n\n    #[command(\n        about = \"vpm list // List all available modules in the project\",\n        long_about = \"List all available modules in the current project. This command provides an overview of all modules currently included in your project, helping you keep track of your dependencies and project structure.\"\n    )]\n    List(List),\n\n    #[command(\n        about = \"vpm sim <FILE_PATHS>... // Simulate Verilog files\",\n        long_about = \"Simulate one or more Verilog files. This command runs simulations on the specified Verilog files, allowing you to test and verify the behavior of your designs before synthesis or implementation.\"\n    )]\n    Sim(Sim),\n\n    #[command(\n        about = \"vpm synth <TOP_MODULE_PATH> // Synthesize a top module\",\n        long_about = \"Synthesize a top module. This command performs synthesis on the specified top module, converting your RTL design into a gate-level netlist. Supports synthesis for:\n    • Board-agnostic (default)\n    • Xilinx FPGAs\n    • Altera FPGAs (coming soon)\n    • Custom board files (coming soon)\n    \"\n    )]\n    Synth(Synth),\n\n    #[command(\n        about = \"vpm load <TOP_MODULE_PATH> // Load a top module onto a target device\",\n        long_about = \"Load a top module onto a target device. This command programs the synthesized design onto the specified hardware, allowing you to test your design on actual FPGA or ASIC hardware.\"\n    )]\n    Load(Load),\n\n    #[command(\n        about = \"vpm run <PROGRAM> // Execute a specified program\",\n        long_about = \"Run a specified program. This command executes the given program, which can be useful for running custom scripts, tools, or compiled designs as part of your Verilog development workflow.\"\n    )]\n    Run(Run),\n\n    #[command(\n        about = \"vpm upgrade // Upgrade VPM to the latest version\",\n        long_about = \"Upgrade VPM to the latest version available.\"\n    )]\n    Upgrade(Upgrade),\n\n    #[command(\n        about = \"vpm config <KEY> <VALUE> // Configure VPM settings\",\n        long_about = \"Configure VPM settings. This command allows you to set various options and preferences for VPM, such as enabling or disabling analytics.\"\n    )]\n    Config(Config),\n}\n\n#[derive(Debug, Parser)]\npub struct Upgrade {}\n\n#[derive(Debug, Parser)]\npub struct Include {\n    #[arg(long, short, help = \"If this flag is set, the URL will be treated as a full repository. If not set, the URL will be treated as a single module.\")]\n    pub repo: bool,\n    #[arg(help = \"GitHub URL of the module to include. This should point to a single .v or .sv file in GitHub. If --repo is set, <URL> should not be a full repository URL, but rather 'AUTHOR_NAME/REPO_NAME'\")]\n    pub url: String,\n    #[arg(long, help = \"Include RISC-V specific modules. Use this flag when including modules designed specifically for RISC-V architectures.\")]\n    pub riscv: bool,\n    #[arg(long, help = \"Commit hash of the module to include. This should be a valid commit hash from the module's repository.\")]\n    pub commit: Option<String>,\n}\n\n#[derive(Debug, Parser)]\npub struct Update {\n    #[arg(help = \"Full module path of the module to update. This should be the complete path to the module file within your project structure.\")]\n    pub module_path: String,\n    #[arg(long, help = \"Update to the given commit hash. If not set, the latest commit hash will be used.\")]\n    pub commit: Option<String>,\n}\n\n#[derive(Debug, Parser)]\npub struct Remove {\n    #[arg(help = \"Full module path of the package to remove. This should be the complete path to the package directory within your project structure.\")]\n    pub package_path: String,\n}\n\n#[derive(Debug, Parser)]\npub struct Dotf {\n    #[arg(help = \"Path to the top module to generate a filelist for. This should be the complete path to the top module file within your project structure.\")]\n    pub path_to_top_module: String,\n}\n\n#[derive(Debug, Parser)]\npub struct Docs {\n    #[arg(help = \"Path of the module to generate documentation for. This should be the path to the module file within your project structure, starting with 'vpm_modules/'.\")]\n    pub module_path: String,\n    #[arg(long, help = \"If this flag is set, the module path will be treated as a link to a .v or .sv file in a GitHub repository. If not set, the path will be treated as a local file path.\")]\n    pub from_repo: bool,\n    #[arg(long, help = \"Generate documentation in offline mode for code security.\")]\n    pub offline: bool,\n}\n\n#[derive(Debug, Parser)]\npub struct Install {\n    #[arg(help = \"Name of the tool to install. This should be a valid tool name recognized by VPM. Available options:\n    • verilator: A fast Verilog/SystemVerilog simulator\n    • iverilog: Icarus Verilog, a Verilog simulation and synthesis tool\n    • yosys: Open-source Verilog synthesis suite\n    • gtkwave: Waveform viewer for simulation results\n    • verible: SystemVerilog parser, style linter, and formatter\n    • edalize: One-stop library for interfacing EDA tools\n    • riscv-gnu-toolchain: GNU toolchain for RISC-V, including GCC compiler and associated tools\")]\n    pub tool_name: String,\n}\n\n#[derive(Debug, Parser)]\npub struct Sim {\n    #[arg(help = \"List of paths to .v or .sv files you want to simulate. Include '_tb' in the testbench file name; otherwise, a base testbench and waveform will be generated.\")]\n    pub verilog_files: Vec<String>,\n    #[arg(long, help = \"Generate waveform output. If set, the simulation will produce waveform data and open it in GTKWave.\")]\n    pub waveform: bool,\n    pub folder: Option<String>,\n}\n\n#[derive(Debug, Parser)]\npub struct List {}\n\n#[derive(Debug, Parser)]\npub struct Synth {\n    #[arg(help = \"Top module path to synthesize. This should be the path to the main module of your design that you want to synthesize.\")]\n    pub top_module_path: String,\n    #[arg(long, help = \"Set this flag if you're working with a RISC-V based design.\")]\n    pub riscv: bool,\n    #[arg(long, help = \"Path to RISC-V core. Required if --riscv is set. This should be the path to your RISC-V core implementation.\")]\n    pub core_path: Option<String>,\n    #[arg(long, help = \"Specify target board. Use this to optimize the synthesis for a specific FPGA board. Current options:\n    • xilinx: Optimize for Xilinx FPGA boards\n    • altera: Optimize for Altera FPGA boards (coming soon)\n    • custom: Use a custom board file (coming soon)\")]\n    pub board: Option<String>,\n    #[arg(long, help = \"Generate synthesis script. If set, the command will produce a Yosys synthesis script instead of running the synthesis directly.\")]\n    pub gen_yosys_script: bool,\n}\n\n#[derive(Debug, Parser)]\npub struct Load {\n    #[arg(help = \"Path to the top module to load. This should be the path to the synthesized netlist or bitstream file.\")]\n    pub top_module_path: String,\n    #[arg(help = \"Path to the .xcd constraint file. This file should contain timing and placement constraints for your design.\")]\n    pub constraints_path: String,\n    #[arg(long, help = \"Use RISC-V toolchain. Set this flag if you're working with a RISC-V based design and need to use RISC-V specific tools for loading.\")]\n    pub riscv: bool,\n}\n\n#[derive(Debug, Parser)]\npub struct Run {\n    #[arg(help = \"Path to the program to run. This can be a compiled binary, a script, or any executable file.\")]\n    pub program_path: String,\n    #[arg(long, help = \"Use RISC-V toolchain. Set this flag if you're running a program compiled for RISC-V architecture and need to use RISC-V specific tools or emulators.\")]\n    pub riscv: bool,\n}\n\n#[derive(Debug, Parser)]\npub struct Config {\n    #[arg(long, help = \"Enable or disable anonymous usage data collection. Set to false to opt-out of data collection.\")]\n    pub analytics: Option<bool>,\n}\n"
  },
  {
    "path": "src/cmd/config.rs",
    "content": "use crate::cmd::{Execute, Config};\nuse crate::config_man::set_analytics;\nuse anyhow::Result;\n\nimpl Execute for Config {\n    async fn execute(&self) -> Result<()> {\n        if self.analytics.is_some() {\n            set_analytics(self.analytics.unwrap())?;\n            println!(\"Analytics set to: {}\", self.analytics.unwrap());\n        }\n        Ok(())\n    }\n}"
  },
  {
    "path": "src/cmd/docs.rs",
    "content": "use anyhow::{Result, Context, anyhow};\nuse reqwest::Client;\nuse std::path::PathBuf;\nuse serde_json::json;\nuse std::fs;\nuse indicatif::{ProgressBar, ProgressStyle};\nuse std::process::{Command, Stdio};\n\nuse crate::cmd::{Execute, Docs};\nuse crate::config_man::{decrypt_docs_count, encrypt_docs_count};\n\nimpl Execute for Docs {\n    async fn execute(&self) -> Result<()> {\n        let docs_count = decrypt_docs_count()?;\n        if docs_count >= 10 {\n            println!(\"You have used all your documentation generation credits. Consider upgrading to VPM Pro for unlimited and betterdocumentation generation.\");\n            return Ok(());\n        }\n\n        if self.from_repo {\n            let content = fetch_module_content(&self.module_path).await\n                .context(\"Failed to fetch module content. Please check your internet connection and ensure the provided URL is correct.\")?;\n            let file_name = self.module_path.split('/').last().unwrap_or(&self.module_path);\n            let folder_name = file_name.split('.').next().unwrap_or(file_name);\n            let destination = PathBuf::from(\"./vpm_modules\").join(folder_name);\n            fs::create_dir_all(&destination)\n                .context(\"Failed to create destination directory. Please check if you have write permissions in the current directory.\")?;\n            if self.offline {\n                generate_docs_offline(&self.module_path, &content, Some(destination.join(format!(\"{}_README.md\", folder_name)))).await\n                    .context(\"Failed to generate documentation offline. Please check the module content and try again.\")?;\n            } else {\n                generate_docs(&self.module_path, &content, Some(destination.join(format!(\"{}_README.md\", folder_name)))).await\n                    .context(\"Failed to generate documentation. Please check the module content and try again.\")?;\n            }\n        } else {\n            let full_module_path = PathBuf::from(&self.module_path);\n            \n            if full_module_path.exists() {\n                let content = fs::read_to_string(&full_module_path)\n                    .with_context(|| format!(\"Failed to read module file: {}. Please ensure you have read permissions for this file.\", full_module_path.display()))?;\n                println!(\"Generating documentation for local module '{}'\", self.module_path);\n                let readme_path = full_module_path.with_file_name(format!(\"{}_README.md\", full_module_path.file_stem().unwrap().to_str().unwrap()));\n                if self.offline {\n                    generate_docs_offline(&self.module_path, &content, Some(readme_path)).await\n                        .context(\"Failed to generate documentation offline for the local module. Please check the module content and try again.\")?;\n                } else {\n                    generate_docs(&self.module_path, &content, Some(readme_path)).await\n                        .context(\"Failed to generate documentation for the local module. Please check the module content and try again.\")?;\n                }\n            } else {\n                return Err(anyhow!(\"Module '{}' not found in vpm_modules. Please provide a URL to a repository containing the module, or ensure the module exists in the correct location.\", self.module_path));\n            }\n        }\n        encrypt_docs_count(docs_count + 1)?;\n        println!(\"Documentation generated successfully. You have used {} of your 10 credits.\", docs_count + 1);\n        Ok(())\n    }\n}\n\nasync fn fetch_module_content(url: &str) -> Result<String> {\n    let client = reqwest::Client::new();\n\n    // Extract the raw content URL\n    let raw_url = url.replace(\"github.com\", \"raw.githubusercontent.com\")\n                     .replace(\"/blob/\", \"/\");\n\n    println!(\"Fetching content from URL: {}\", raw_url);\n\n    // Fetch the content\n    let response = client.get(&raw_url).send().await?;\n\n    if !response.status().is_success() {\n        return Err(anyhow::anyhow!(\"Failed to fetch module content: HTTP {}\", response.status()));\n    }\n\n    let content = response.text().await?;\n\n    Ok(content)\n}\n\nfn format_text(text: &str) -> String {\n    text.replace(\"\\\\n\", \"\\n\")\n        .replace(\"\\\\'\", \"'\")\n        .replace(\"\\\\\\\"\", \"\\\"\")\n        .replace(\"\\\\\\\\\", \"\\\\\")\n}\n\nasync fn generate_docs(module_path: &str, content: &str, full_module_path: Option<PathBuf>) -> Result<()> {\n    let pb = ProgressBar::new(100);\n    pb.set_style(ProgressStyle::default_bar()\n        .template(\"{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta}) {msg}\")\n        .unwrap_or_else(|_| ProgressStyle::default_bar())\n        .progress_chars(\"#>-\"));\n    \n    pb.set_position(33);\n    pb.set_message(\"Generating documentation...\");\n\n    let client = Client::new();\n    let api_url = \"https://bmniatl2bh.execute-api.us-east-1.amazonaws.com/dev/getApiKey\";\n    let response = client.post(api_url)\n        .header(\"Content-Type\", \"application/json\")\n        .json(&json!({ \"code\": content }))\n        .send().await\n        .context(\"Failed to send request to documentation generation API. Please check your internet connection and try again.\")?;\n\n    let documentation = format_text(&response.text().await\n        .context(\"Failed to read response from documentation generation API. The API might be experiencing issues. Please try again later.\")?);\n\n    pb.set_position(66);\n    pb.set_message(\"Writing documentation to file...\");\n\n    let readme_path = if let Some(path) = full_module_path {\n        path\n    } else {\n        let module_name = module_path.rsplit('/').next().unwrap_or(module_path);\n        let dir = PathBuf::from(\"./vpm_modules\").join(module_name).parent().unwrap().to_path_buf();\n        fs::create_dir_all(&dir)\n            .with_context(|| format!(\"Failed to create directory: {}. Please ensure you have write permissions in this location.\", dir.display()))?;\n        dir.join(format!(\"{}_README.md\", module_name))\n    };\n    tokio::fs::write(&readme_path, documentation).await\n        .with_context(|| format!(\"Failed to write documentation to file: {}. Please ensure you have write permissions in this location.\", readme_path.display()))?;\n    \n    pb.set_position(100);\n    pb.finish_with_message(format!(\"Documentation for {} written to {}\", module_path, readme_path.display()));\n\n    Ok(())\n}\n\nasync fn generate_docs_offline(module_path: &str, content: &str, full_module_path: Option<PathBuf>) -> Result<()> {\n    let pb = ProgressBar::new(100);\n    pb.set_style(ProgressStyle::default_bar()\n        .template(\"{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta}) {msg}\")\n        .unwrap_or_else(|_| ProgressStyle::default_bar())\n        .progress_chars(\"#>-\"));\n    \n    pb.set_position(33);\n    pb.set_message(\"Generating documentation offline...\");\n\n    // Check if Ollama is installed\n    if !Command::new(\"ollama\").arg(\"--version\").output().is_ok() {\n        pb.set_message(\"Ollama not found. Installing...\");\n        \n        // Install Ollama\n        let install_status = if cfg!(target_os = \"macos\") {\n            Command::new(\"brew\").args(&[\"install\", \"ollama\"]).status()\n        } else if cfg!(target_os = \"linux\") {\n            Command::new(\"curl\").args(&[\"-fsSL\", \"https://ollama.ai/install.sh\", \"|\", \"sh\"]).status()\n        } else {\n            return Err(anyhow::anyhow!(\"Unsupported operating system for Ollama installation\"));\n        };\n\n        if let Err(e) = install_status {\n            return Err(anyhow::anyhow!(\"Failed to install Ollama: {}\", e));\n        }\n\n        pb.set_message(\"Ollama installed successfully\");\n    }\n\n    // Start Ollama server in the background\n    let mut ollama_serve = Command::new(\"ollama\")\n        .arg(\"serve\")\n        .stdout(Stdio::null())\n        .stderr(Stdio::null())\n        .spawn()\n        .context(\"Failed to start Ollama server. Make sure it's installed and in your PATH.\")?;\n\n    // Give the server a moment to start up\n    tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;\n\n    // Prepare the Ollama command\n    let ollama_output = Command::new(\"ollama\")\n        .arg(\"run\")\n        .arg(\"codellama\")\n        .arg(\"Generate documentation for the following Verilog module:\")\n        .arg(content)\n        .output()\n        .context(\"Failed to execute Ollama. Make sure it's installed and in your PATH.\")?;\n\n    // Stop the Ollama server\n    ollama_serve.kill().context(\"Failed to stop Ollama server\")?;\n\n    if !ollama_output.status.success() {\n        return Err(anyhow::anyhow!(\"Ollama command failed: {}\", String::from_utf8_lossy(&ollama_output.stderr)));\n    }\n\n    let documentation = String::from_utf8(ollama_output.stdout)\n        .context(\"Failed to parse Ollama output as UTF-8\")?;\n\n    pb.set_position(66);\n    pb.set_message(\"Writing documentation to file...\");\n\n    let readme_path = if let Some(path) = full_module_path {\n        path\n    } else {\n        let module_name = module_path.rsplit('/').next().unwrap_or(module_path);\n        let dir = PathBuf::from(\"./vpm_modules\").join(module_name).parent().unwrap().to_path_buf();\n        fs::create_dir_all(&dir)\n            .with_context(|| format!(\"Failed to create directory: {}. Please ensure you have write permissions in this location.\", dir.display()))?;\n        dir.join(format!(\"{}_README.md\", module_name))\n    };\n    tokio::fs::write(&readme_path, documentation).await\n        .with_context(|| format!(\"Failed to write documentation to file: {}. Please ensure you have write permissions in this location.\", readme_path.display()))?;\n    \n    pb.set_position(100);\n    pb.finish_with_message(format!(\"Documentation for {} written to {}\", module_path, readme_path.display()));\n\n    Ok(())\n}\n"
  },
  {
    "path": "src/cmd/dotf.rs",
    "content": "use anyhow::Result;\nuse std::fs;\nuse std::path::{Path, PathBuf};\nuse std::io::Write;\n\nuse crate::cmd::{Execute, Dotf};\n\nimpl Execute for Dotf {\n    async fn execute(&self) -> Result<()> {\n        // Clear the .f file if it already exists\n        let top_module_file = Path::new(&self.path_to_top_module).file_name().and_then(|f| f.to_str()).unwrap_or(\"\");\n        let top_module_dir = Path::new(&self.path_to_top_module).with_extension(\"\").to_str().unwrap_or(\"\").to_string();\n        let filelist_name = format!(\"{}.f\", top_module_file.trim_end_matches(\".sv\").trim_end_matches(\".v\"));\n        let filelist_path = PathBuf::from(\"vpm_modules\").join(&top_module_dir).join(&filelist_name);\n\n        if filelist_path.exists() {\n            fs::write(&filelist_path, \"\")?;\n        }\n        let _ = append_modules_to_filelist(&self.path_to_top_module, true);\n        Ok(())\n    }\n}\n\npub fn append_modules_to_filelist(top_module_path: &str, sub: bool) -> Result<()> {\n    let vpm_modules_dir = PathBuf::from(\"./vpm_modules\");\n    let mut visited_modules: Vec<String> = Vec::new();\n\n    let top_module_file = Path::new(top_module_path).file_name().and_then(|f| f.to_str()).unwrap_or(\"\");\n    let top_module_dir = Path::new(top_module_path).with_extension(\"\").to_str().unwrap_or(\"\").to_string();\n    let filelist_name = format!(\"{}.f\", top_module_file.trim_end_matches(\".sv\").trim_end_matches(\".v\"));\n    let filelist_path = PathBuf::from(\"vpm_modules\").join(&top_module_dir).join(&filelist_name);\n\n    let mut filepaths = Vec::new();\n    let mut f_statements = Vec::new();\n    let mut define_statements = Vec::new();\n\n    append_module(&vpm_modules_dir, top_module_file, top_module_file, &mut visited_modules, sub, &filelist_path, &mut filepaths, &mut f_statements, &mut define_statements)?;\n\n    // Write all filepaths together\n    let mut file = fs::OpenOptions::new()\n        .append(true)\n        .create(true)\n        .open(&filelist_path)?;\n\n    // Add +incdir+ statement\n    file.write_all(format!(\"+incdir+{}\\n\\n\", vpm_modules_dir.join(&top_module_dir).display()).as_bytes())?;\n\n    for filepath in filepaths {\n        file.write_all(format!(\"{}\\n\", filepath).as_bytes())?;\n    }\n\n    file.write_all(b\"\\n\")?;\n\n    // Write all unique define statements together\n    let mut unique_defines: std::collections::HashSet<String> = std::collections::HashSet::new();\n    for define in define_statements {\n        unique_defines.insert(define);\n    }\n    for define in unique_defines {\n        file.write_all(format!(\"{}\\n\", define).as_bytes())?;\n    }\n\n    file.write_all(b\"\\n\")?;\n\n    // Write all -f statements together\n    for f_statement in f_statements {\n        file.write_all(format!(\"{}\\n\", f_statement).as_bytes())?;\n    }\n\n    Ok(())\n}\n\nfn append_module(\n    dir: &Path,\n    module: &str,\n    top_module: &str,\n    visited_modules: &mut Vec<String>,\n    sub: bool,\n    filelist_path: &Path,\n    filepaths: &mut Vec<String>,\n    f_statements: &mut Vec<String>,\n    define_statements: &mut Vec<String>,\n) -> Result<()> {\n    for entry in fs::read_dir(dir)? {\n        let entry = entry?;\n        let path = entry.path();\n        if path.is_file() && path.file_name().map_or(false, |name| name == module) {\n            let module_path = path.to_str().unwrap_or_default();\n            let contents = fs::read_to_string(&path)?;\n\n            filepaths.push(module_path.to_string());\n\n            // Check for `define macros and module boundaries\n            let mut in_ifdef_block = false;\n            let mut in_module = false;\n            let mut module_defines = Vec::new();\n            let mut current_module_name = String::new();\n\n            for line in contents.lines() {\n                let trimmed_line = line.trim();\n                if trimmed_line.starts_with(\"module\") {\n                    in_module = true;\n                    current_module_name = trimmed_line.split_whitespace().nth(1).unwrap_or(\"\").to_string();\n                } else if trimmed_line.starts_with(\"endmodule\") {\n                    in_module = false;\n                    if !module_defines.is_empty() {\n                        if current_module_name == top_module.trim_end_matches(\".v\") {\n                            // Add defines to the top module's .f file\n                            define_statements.extend(module_defines.clone());\n                        } else {\n                            let submodule_filelist_name = format!(\"{}.f\", current_module_name);\n                            let submodule_filelist_path = dir.join(&submodule_filelist_name);\n                            let mut submodule_file = fs::OpenOptions::new()\n                                .append(true)\n                                .create(true)\n                                .open(&submodule_filelist_path)?;\n                            for define in &module_defines {\n                                submodule_file.write_all(format!(\"{}\\n\", define).as_bytes())?;\n                            }\n                            f_statements.push(format!(\"-f {}\", submodule_filelist_path.to_str().unwrap_or_default()));\n                        }\n                    }\n                    module_defines.clear();\n                } else if trimmed_line.starts_with(\"`ifdef\") {\n                    in_ifdef_block = true;\n                    let parts: Vec<&str> = trimmed_line.split_whitespace().collect();\n                    if parts.len() >= 2 {\n                        let macro_name = parts[1];\n                        let define = format!(\"+define+{}\", macro_name);\n                        if !in_module {\n                            if !define_statements.contains(&define) {\n                                define_statements.push(define.clone());\n                            }\n                        } else {\n                            module_defines.push(define);\n                        }\n                    }\n                } else if trimmed_line.starts_with(\"`endif\") {\n                    in_ifdef_block = false;\n                } else if !in_ifdef_block && trimmed_line.starts_with(\"`define\") {\n                    let parts: Vec<&str> = trimmed_line.split_whitespace().collect();\n                    let define = if parts.len() >= 3 {\n                        let macro_name = parts[1];\n                        let macro_value = parts[2..].join(\" \");\n                        format!(\"+define+{}={}\", macro_name, macro_value)\n                    } else if parts.len() == 2 {\n                        let macro_name = parts[1];\n                        format!(\"+define+{}\", macro_name)\n                    } else {\n                        continue;\n                    };\n                    if !in_module {\n                        if !define_statements.contains(&define) {\n                            define_statements.push(define.clone());\n                        }\n                    } else {\n                        module_defines.push(define);\n                    }\n                }\n            }\n\n            if sub {\n                let mut parser = tree_sitter::Parser::new();\n                parser\n                    .set_language(tree_sitter_verilog::language())\n                    .expect(\"Error loading Verilog grammar\");\n\n                if let Some(tree) = parser.parse(&contents, None) {\n                    let root_node = tree.root_node();\n                    find_module_instantiations(\n                        root_node,\n                        top_module,\n                        &contents,\n                        visited_modules,\n                        sub,\n                        filelist_path,\n                        filepaths,\n                        f_statements,\n                        define_statements)?;\n                }\n            }\n\n            return Ok(());\n        } else if path.is_dir() {\n            append_module(\n                &path,\n                module,\n                top_module,\n                visited_modules,\n                sub,\n                filelist_path,\n                filepaths,\n                f_statements,\n                define_statements)?;\n        }\n    }\n\n    Ok(())\n}\n\nfn find_module_instantiations(\n    root_node: tree_sitter::Node,\n    top_module: &str,\n    contents: &str,\n    visited_modules: &mut Vec<String>,\n    sub: bool,\n    filelist_path: &Path,\n    filepaths: &mut Vec<String>,\n    f_statements: &mut Vec<String>,\n    define_statements: &mut Vec<String>,\n) -> Result<()> {\n    let mut cursor = root_node.walk();\n    for child in root_node.children(&mut cursor) {\n        if child.kind().contains(\"instantiation\") {\n            if let Some(first_child) = child.child(0) {\n                if let Ok(module) = first_child.utf8_text(contents.as_bytes()) {\n                    let module_name_v = format!(\"{}.v\", module);\n                    let module_name_sv = format!(\"{}.sv\", module);\n                    if !visited_modules.contains(&module_name_v) && !visited_modules.contains(&module_name_sv) {\n                        visited_modules.push(module_name_v.clone());\n                        visited_modules.push(module_name_sv.clone());\n                        append_module(\n                            &PathBuf::from(\"./vpm_modules\"),\n                            &module_name_v,\n                            top_module,\n                            visited_modules,\n                            sub,\n                            filelist_path,\n                            filepaths,\n                            f_statements,\n                            define_statements)?;\n                        append_module(\n                            &PathBuf::from(\"./vpm_modules\"),\n                            &module_name_sv,\n                            top_module,\n                            visited_modules,\n                            sub,\n                            filelist_path,\n                            filepaths,\n                            f_statements,\n                            define_statements)?;\n                    }\n                }\n            }\n        }\n\n        find_module_instantiations(\n            child,\n            top_module,\n            contents,\n            visited_modules,\n            sub,\n            filelist_path,\n            filepaths,\n            f_statements,\n            define_statements)?;\n    }\n    \n    Ok(())\n}\n"
  },
  {
    "path": "src/cmd/include.rs",
    "content": "use std::collections::HashSet;\nuse std::env::current_dir;\nuse std::path::{Path, PathBuf};\nuse std::{fs, process::Command};\nuse anyhow::{Context, Result};\nuse once_cell::sync::Lazy;\nuse tree_sitter::{Node, Parser, Query, QueryCursor};\nuse crate::cmd::{Execute, Include};\nuse crate::toml::{add_dependency, add_top_module};\nuse walkdir::{DirEntry, WalkDir};\nuse fancy_regex::Regex;\n\nuse dialoguer::{theme::ColorfulTheme, MultiSelect};\nuse fuzzy_matcher::FuzzyMatcher;\nuse fuzzy_matcher::skim::SkimMatcherV2;\nuse std::io::{self, Write};\nuse indicatif::{ProgressBar, ProgressStyle};\n\nimpl Execute for Include {\n    async fn execute(&self) -> Result<()> {\n        println!(\"Including from: '{}'\", self.url);\n        let repo_name = name_from_url(&self.url);\n        let tmp_path = PathBuf::from(\"/tmp\").join(repo_name);\n        let commit = if self.commit.is_none() {\n            Some(get_head_commit_hash(&self.url)?)\n        } else {\n            self.commit.clone()\n        };\n        if self.repo {\n            include_entire_repo(&self.url, &tmp_path, self.riscv, commit.as_deref())?\n        } else {\n            include_single_module(&self.url, self.riscv, commit.as_deref())?\n        }\n        Ok(())\n    }\n}\n\npub fn get_head_commit_hash(url: &str) -> Result<String> {\n    let github_url = if url.starts_with(\"https://github.com/\") {\n        url.to_string()\n    } else {\n        format!(\"https://github.com/{}\", url)\n    };\n\n    let (repo_url, _) = github_url.rsplit_once(\"/blob/\").unwrap_or((&github_url, \"\"));\n\n    let output = Command::new(\"git\")\n        .args([\"ls-remote\", repo_url, \"HEAD\"])\n        .output()?;\n\n    if output.status.success() {\n        let stdout = String::from_utf8(output.stdout)?;\n        let hash = stdout.split_whitespace().next().unwrap_or(\"\").to_string();\n        if !hash.is_empty() {\n            Ok(hash[..7].to_string())  // Return only the first 7 characters (short hash)\n        } else {\n            Err(anyhow::anyhow!(\"Failed to get HEAD commit hash: Empty hash returned\"))\n        }\n    } else {\n        let stderr = String::from_utf8_lossy(&output.stderr);\n        Err(anyhow::anyhow!(\"Failed to get HEAD commit hash: {}\", stderr))\n    }\n}\n\nfn include_entire_repo(url: &str, tmp_path: &PathBuf, riscv: bool, commit_hash: Option<&str>) -> Result<()> {\n    let url = format!(\"https://github.com/{}\", url);\n    println!(\"Full GitHub URL: {}@{}\", url, commit_hash.unwrap_or(\"HEAD\"));\n    include_repo_from_url(&url, \"/tmp/\", commit_hash)?;\n    add_dependency(&url)?;\n\n    let files = get_files(&tmp_path.to_str().unwrap_or_default());\n    let items = get_relative_paths(&files, tmp_path);\n\n    let selected_items = select_modules(&items)?;\n\n    process_selected_modules(&url, tmp_path, &selected_items, riscv, commit_hash)?;\n\n    fs::remove_dir_all(tmp_path)?;\n    print_success_message(&url, &selected_items);\n    Ok(())\n}\n\nfn include_single_module(url: &str, riscv: bool, commit_hash: Option<&str>) -> Result<()> {\n    let repo_url = get_github_repo_url(url).unwrap();\n    include_repo_from_url(&repo_url, \"/tmp/\", commit_hash)?;\n    add_dependency(&repo_url)?;\n    println!(\"Repo URL: {}@{}\", repo_url, commit_hash.unwrap_or(\"HEAD\"));\n    let module_path = get_component_path_from_github_url(url).unwrap_or_default();\n    println!(\"Including module: {}\", module_path);\n    include_module_from_url(&module_path, &repo_url, riscv, commit_hash)?;\n    println!(\"Successfully installed module: {}\", module_path);\n    Ok(())\n}\n\nfn get_files(directory: &str) -> Vec<String> {\n    WalkDir::new(directory)\n        .into_iter()\n        .filter_map(|entry| {\n            entry.ok().and_then(|e| {\n                if e.file_type().is_file() {\n                    Some(e.path().to_string_lossy().into_owned())\n                } else {\n                    None\n                }\n            })\n        })\n        .collect()\n}\n\nfn get_relative_paths(files: &[String], tmp_path: &PathBuf) -> Vec<String> {\n    files.iter()\n        .map(|file| file.strip_prefix(&tmp_path.to_string_lossy().as_ref())\n            .unwrap_or(file)\n            .trim_start_matches('/')\n            .to_string())\n        .collect()\n}\n\nfn select_modules(items: &[String]) -> Result<HashSet<String>> {\n    let matcher = SkimMatcherV2::default();\n    let mut selected_items: HashSet<String> = HashSet::new();\n\n    loop {\n        print!(\"Enter module name (or press Enter to finish): \");\n        io::stdout().flush()?;\n\n        let mut query = String::new();\n        io::stdin().read_line(&mut query)?;\n        query = query.trim().to_string();\n\n        if query.is_empty() {\n            break;\n        }\n\n        let filtered_items: Vec<String> = items\n            .iter()\n            .filter(|&item| matcher.fuzzy_match(item, &query).is_some())\n            .cloned()\n            .collect();\n\n        let selection = MultiSelect::with_theme(&ColorfulTheme::default())\n            .with_prompt(\"Toggle items to include with the space bar. Hit enter to start a new search\")\n            .items(&filtered_items)\n            .interact()?;\n\n        for i in &selected_items {\n            println!(\"- {}\", i);\n        }\n\n        selected_items.extend(selection.iter().map(|&i| filtered_items[i].clone()));\n    }\n\n    print!(\"\\x1B[2J\\x1B[1;1H\");\n    Ok(selected_items)\n}\n\nfn process_selected_modules(url: &str, tmp_path: &PathBuf, selected_items: &HashSet<String>, riscv: bool, commit_hash: Option<&str>) -> Result<()> {\n    for item in selected_items {\n        let displayed_path = item.strip_prefix(tmp_path.to_string_lossy().as_ref()).unwrap_or(item).trim_start_matches('/');\n        println!(\"Including module: {}\", displayed_path);\n        \n        let full_path = tmp_path.join(displayed_path);\n        let module_path = full_path.strip_prefix(tmp_path).unwrap_or(&full_path).to_str().unwrap().trim_start_matches('/');\n        println!(\"Module path: {}\", module_path);\n\n        include_module_from_url(module_path, url, riscv, commit_hash)?;\n    }\n\n    if selected_items.is_empty() {\n        println!(\"No modules selected. Including entire repository.\");\n        include_repo_from_url(url, \"./\", commit_hash)?;\n    }\n\n    Ok(())\n}\n\nfn print_success_message(url: &str, selected_items: &HashSet<String>) {\n    if !selected_items.is_empty() {\n        let installed_modules = selected_items.iter()\n            .map(|item| item.to_string())\n            .collect::<Vec<String>>()\n            .join(\", \");\n        println!(\"Successfully installed module(s): {}\", installed_modules);\n    } else {\n        println!(\"Successfully installed repository '{}'.\", name_from_url(url));\n    }\n}\n\nfn name_from_url(url: &str) -> &str {\n    url.rsplit('/').find(|&s| !s.is_empty()).unwrap_or_default()\n}\n\nfn get_component_path_from_github_url(url: &str) -> Option<String> {\n    let parts: Vec<&str> = url.split(\"/\").collect();\n    if parts.len() < 8 || !url.starts_with(\"https://github.com/\") {\n        return None;\n    }\n\n    Some(parts[7..].join(\"/\"))\n}\n\nfn get_github_repo_url(url: &str) -> Option<String> {\n    let parts: Vec<&str> = url.split('/').collect();\n    if parts.len() < 5 || !url.starts_with(\"https://github.com/\") {\n        return None;\n    }\n\n    Some(format!(\"https://github.com/{}/{}\", parts[3], parts[4]))\n}\n\nfn is_full_filepath(path: &str) -> bool {\n    path.contains('/') || path.contains('\\\\')\n}\n\nfn filepath_to_dir_entry(filepath: PathBuf) -> Result<DirEntry> {\n    WalkDir::new(filepath)\n        .min_depth(0)\n        .max_depth(0)\n        .into_iter()\n        .next()\n        .ok_or_else(|| anyhow::anyhow!(\"Failed to create DirEntry\"))?\n        .context(\"Failed to create DirEntry\")\n}\n\nfn generate_top_v_content(module_path: &str) -> Result<String> {\n    println!(\"Generating top.v file for RISC-V in {}\", module_path);\n    let module_content = fs::read_to_string(module_path)?;\n\n    let mut top_content = String::new();\n    top_content.push_str(\"// Auto-generated top.v file for RISC-V\\n\\n\");\n\n    // Use regex to find module declaration\n    let module_re = regex::Regex::new(r\"module\\s+(\\w+)\\s*(?:#\\s*\\(([\\s\\S]*?)\\))?\\s*\\(([\\s\\S]*?)\\);\").unwrap();\n    if let Some(captures) = module_re.captures(&module_content) {\n        let module_name = captures.get(1).unwrap().as_str();\n        println!(\"Module name: {}\", module_name);\n\n        // Extract parameters\n        let params = captures.get(2).map_or(Vec::new(), |m| {\n            m.as_str().lines()\n                .map(|line| line.trim())\n                .filter(|line| !line.is_empty())\n                .collect()\n        });\n\n        // Extract ports\n        let ports: Vec<&str> = captures.get(3).unwrap().as_str()\n            .lines()\n            .map(|line| line.trim())\n            .filter(|line| !line.is_empty())\n            .collect();\n\n        // Generate top module ports\n        top_content.push_str(\"module top (\\n\");\n        for port in &ports {\n            top_content.push_str(&format!(\"    {}\\n\", port));\n        }\n        top_content.push_str(\");\\n\\n\");\n\n        // Instantiate the module\n        top_content.push_str(&format!(\"{} #(\\n\", module_name));\n        for param in params.iter() {\n            if let Some((name, value)) = param.split_once('=') {\n                let name = name.trim().trim_start_matches(\"parameter\").trim();\n                let name = name.split_whitespace().last().unwrap_or(name);\n                let value = value.trim().trim_end_matches(',');\n                top_content.push_str(&format!(\"    .{}({}),\\n\", name, value));\n            }\n        }\n        top_content.push_str(\") cpu (\\n\");\n\n        // Connect ports\n        let port_re = regex::Regex::new(r\"(input|output|inout)\\s+(?:wire|reg)?\\s*(?:\\[.*?\\])?\\s*(\\w+)\").unwrap();\n        for (i, port) in ports.iter().enumerate() {\n            if let Some(port_captures) = port_re.captures(port) {\n                let port_name = port_captures.get(2).unwrap().as_str();\n                top_content.push_str(&format!(\"    .{}({}){}\\n\", port_name, port_name, if i < ports.len() - 1 { \",\" } else { \"\" }));\n            }\n        }\n        top_content.push_str(\");\\n\\n\");\n\n        top_content.push_str(\"endmodule\\n\");\n        return Ok(top_content);\n    }\n\n    Err(anyhow::anyhow!(\"No module declaration found in the file\"))\n}\n\nfn generate_xdc_content(module_path: &str) -> Result<String> {\n    println!(\"Generating constraints.xdc file for Xilinx Artix-7 board in {}\", module_path);\n    let module_content = fs::read_to_string(module_path)?;\n\n    let mut xdc_content = String::new();\n    xdc_content.push_str(\"## Auto-generated constraints.xdc file for Xilinx Artix-7 board\\n\\n\");\n\n    // Use regex to find all ports\n    let port_re = regex::Regex::new(r\"(?m)^\\s*(input|output|inout)\\s+(?:wire|reg)?\\s*(?:\\[.*?\\])?\\s*(\\w+)\").unwrap();\n    let mut ports = Vec::new();\n\n    for captures in port_re.captures_iter(&module_content) {\n        let port_type = captures.get(1).unwrap().as_str();\n        let port_name = captures.get(2).unwrap().as_str();\n        ports.push((port_type, port_name));\n    }\n\n    // Define pin mappings (you may need to adjust these based on your specific board)\n    let pin_mappings = [\n        (\"clk\", \"E3\"),\n        (\"resetn\", \"C12\"),\n        (\"trap\", \"D10\"),\n        (\"mem_valid\", \"C11\"),\n        (\"mem_instr\", \"C10\"),\n        (\"mem_ready\", \"A10\"),\n        (\"mem_addr[0]\", \"A8\"),\n        (\"mem_wdata[0]\", \"C5\"),\n        (\"mem_wstrb[0]\", \"C6\"),\n        (\"mem_rdata[0]\", \"D5\"),\n    ];\n\n    // Generate constraints for each port\n    for (_port_type, port_name) in ports {\n        if let Some((_, pin)) = pin_mappings.iter().find(|&&(p, _)| p == port_name) {\n            let iostandard = if port_name == \"clk\" { \"LVCMOS33\" } else { \"LVCMOS33\" };\n            xdc_content.push_str(&format!(\"set_property -dict {{ PACKAGE_PIN {} IOSTANDARD {} }} [get_ports {{ {} }}]\\n\", pin, iostandard, port_name));\n        } else {\n            println!(\"Warning: No pin mapping found for port: {}\", port_name);\n        }\n    }\n\n    // Add clock constraint\n    if let Some((_, _clk_pin)) = pin_mappings.iter().find(|&&(p, _)| p == \"clk\") {\n        xdc_content.push_str(&format!(\"\\n## Clock signal\\n\"));\n        xdc_content.push_str(&format!(\"create_clock -period 10.000 -name sys_clk_pin -waveform {{0.000 5.000}} -add [get_ports {{ clk }}]\\n\"));\n    } else {\n        println!(\"Warning: No clock signal found. XDC file may be incomplete.\");\n        xdc_content.push_str(\"\\n## Warning: No clock signal found. Please add clock constraints manually.\\n\");\n    }\n\n    Ok(xdc_content)\n}\n\npub fn include_module_from_url(module_path: &str, url: &str, riscv: bool, commit_hash: Option<&str>) -> Result<()> {\n    let package_name = name_from_url(url);\n\n    include_repo_from_url(url, \"/tmp/\", commit_hash)?;\n    let destination = \"./\";\n    process_module(package_name, module_path, destination.to_owned(), &mut HashSet::new(), url, true, commit_hash)?;\n\n    let module_path = Path::new(&destination).join(Path::new(module_path).file_name().unwrap());\n    anyhow::ensure!(module_path.exists(), \"Module file not found in the destination folder\");\n\n    if riscv {\n        let top_v_content = generate_top_v_content(&module_path.to_str().unwrap())?;\n        fs::write(format!(\"{}/top.v\", destination), top_v_content)?;\n        println!(\"Created top.v file for RISC-V in {}\", destination);\n        // Generate .xdc file for Xilinx Artix-7 board\n        let xdc_content = generate_xdc_content(&format!(\"{}/top.v\", destination))?;\n        fs::write(format!(\"{}/constraints.xdc\", destination), xdc_content)?;\n        println!(\"Created constraints.xdc file for Xilinx Artix-7 board in {}\", destination);\n    }\n    add_top_module(url, current_dir()?.join(module_path.file_name().unwrap()).to_str().unwrap(), commit_hash.unwrap_or(\"\"))?;\n    \n    Ok(())\n}\n\npub fn process_module(package_name: &str, module: &str, destination: String, visited: &mut HashSet<String>, url: &str, is_top_module: bool, commit_hash: Option<&str>) -> Result<HashSet<String>> {\n    // println!(\"Processing module: {}\", module);\n    let module_name = module.strip_suffix(\".v\").or_else(|| module.strip_suffix(\".sv\")).unwrap_or(module);\n    let module_with_ext = if module.ends_with(\".v\") || module.ends_with(\".sv\") {\n        module.to_string()\n    } else {\n        format!(\"{}.v\", module_name)\n    };\n    if !visited.insert(module_with_ext.clone()) {\n        return Ok(HashSet::new());\n    }\n\n    let tmp_path = PathBuf::from(\"/tmp\").join(package_name);\n    let file_path = tmp_path.join(&module_with_ext);\n\n    let target_path = PathBuf::from(&destination);\n\n    println!(\"Including submodule '{}'\", module_with_ext);\n\n    let mut processed_modules = HashSet::new();\n\n    if is_full_filepath(&module_with_ext) {\n        // println!(\"Full filepath detected for module '{}'\", module_with_ext);\n        let dir_entry = filepath_to_dir_entry(file_path)?;\n        process_file(&dir_entry, &target_path.to_str().unwrap(), module, url, visited, is_top_module)?;\n        processed_modules.insert(module_with_ext.clone());\n    } else {\n        // println!(\"Full filepath not detected for module '{}'\", module_with_ext);\n        process_non_full_filepath(module_name, &tmp_path, &target_path, url, visited, is_top_module, &mut processed_modules)?;\n    }\n\n    let submodules = download_and_process_submodules(package_name, module, &destination, url, visited, is_top_module, commit_hash)?;\n    processed_modules.extend(submodules);\n\n    Ok(processed_modules)\n}\n\nfn process_non_full_filepath(module_name: &str, tmp_path: &PathBuf, target_path: &PathBuf, url: &str, visited: &mut HashSet<String>, is_top_module: bool, processed_modules: &mut HashSet<String>) -> Result<()> {\n    let matching_entries = find_matching_entries(module_name, tmp_path);\n    println!(\"Found {} matching entries for module '{}'\", matching_entries.len(), module_name);\n    if matching_entries.is_empty() {\n        println!(\"No matching files found for module '{}'. Skipping...\", module_name);\n    } else if matching_entries.len() == 1 {\n        let dir_entry = filepath_to_dir_entry(matching_entries[0].clone())?;\n        process_file(&dir_entry, target_path.to_str().unwrap(), module_name, url, visited, is_top_module)?;\n        processed_modules.insert(format!(\"{}.v\", module_name));\n    } else {\n        process_multiple_matches(matching_entries, target_path, module_name, url, visited, is_top_module, processed_modules)?;\n    }\n\n    Ok(())\n}\n\nfn find_matching_entries(module_name: &str, tmp_path: &PathBuf) -> Vec<PathBuf> {\n    WalkDir::new(tmp_path)\n        .into_iter()\n        .filter_map(Result::ok)\n        .filter(|entry| {\n            entry.file_name().to_str() == Some(&format!(\"{}.sv\", module_name)) || \n            entry.file_name().to_str() == Some(&format!(\"{}.v\", module_name))\n        })\n        .map(|entry| entry.path().to_path_buf())\n        .collect()\n}\n\nfn process_multiple_matches(matching_entries: Vec<PathBuf>, target_path: &PathBuf, module_name: &str, url: &str, visited: &mut HashSet<String>, is_top_module: bool, processed_modules: &mut HashSet<String>) -> Result<()> {\n    println!(\"Multiple modules found for '{}'. Please choose:\", module_name);\n    for (i, entry) in matching_entries.iter().enumerate() {\n        println!(\"{}: {}\", i + 1, entry.display());\n    }\n\n    let mut choice = String::new();\n    std::io::stdin().read_line(&mut choice)?;\n    let index: usize = choice.trim().parse()?;\n\n    if index > 0 && index <= matching_entries.len() {\n        let dir_entry = filepath_to_dir_entry(matching_entries[index - 1].clone())?;\n        process_file(&dir_entry, target_path.to_str().unwrap(), module_name, url, visited, is_top_module)?;\n        processed_modules.insert(format!(\"{}.v\", module_name));\n    } else {\n        anyhow::bail!(\"Invalid choice\");\n    }\n\n    Ok(())\n}\n\nfn process_file(entry: &DirEntry, destination: &str, module_path: &str, url: &str, visited: &mut HashSet<String>, is_top_module: bool) -> Result<()> {\n    let target_path = PathBuf::from(destination);\n    let extension = entry.path().extension().and_then(|s| s.to_str()).unwrap_or(\"v\");\n    fs::copy(entry.path(), &target_path.join(entry.file_name()))?;\n\n    let contents = fs::read_to_string(entry.path())?;\n    let mut parser = Parser::new();\n    parser.set_language(tree_sitter_verilog::language())?;\n    let tree = parser.parse(&contents, None).context(\"Failed to parse file\")?;\n\n    let header_content = generate_headers(tree.root_node(), &contents)?;\n    let module_name = Path::new(module_path)\n        .file_stem()\n        .and_then(|s| s.to_str())\n        .unwrap_or(module_path);\n    let module_name_with_ext = if !module_name.ends_with(\".v\") && !module_name.ends_with(\".sv\") {\n        format!(\"{}.{}\", module_name, extension)\n    } else {\n        module_name.to_string()\n    };\n    let header_filename = format!(\"{}.{}\", module_name.strip_suffix(\".v\").unwrap_or(module_name), if extension == \"sv\" { \"svh\" } else { \"vh\" });\n    let headers_dir = target_path.join(\"headers\");\n    fs::create_dir_all(&headers_dir)?;\n    fs::write(headers_dir.join(&header_filename), header_content)?;\n    println!(\"Generating header file: {}\", target_path.join(&header_filename).to_str().unwrap());\n\n    let full_module_path = target_path.join(&module_name_with_ext);\n    update_lockfile(&full_module_path, url, &contents, visited, is_top_module)?;\n\n    Ok(())\n}\n\nfn download_and_process_submodules(package_name: &str, module_path: &str, destination: &str, url: &str, visited: &mut HashSet<String>, _is_top_module: bool, commit_hash: Option<&str>) -> Result<HashSet<String>> {\n    let module_name = Path::new(module_path)\n        .file_stem()\n        .and_then(|s| s.to_str())\n        .unwrap_or(module_path);\n    // println!(\"Processing submodule: {}\", module_path);\n    let module_name_with_ext = if module_path.ends_with(\".sv\") {\n        format!(\"{}.sv\", module_name)\n    } else if module_path.ends_with(\".v\") {\n        format!(\"{}.v\", module_name)\n    } else {\n        module_path.to_string()\n    };\n\n    let full_module_path = PathBuf::from(destination).join(&module_name_with_ext);\n    // println!(\"Full module path: {}\", full_module_path.display());\n    let contents = match fs::read_to_string(&full_module_path) {\n        Ok(c) => c,\n        Err(e) => {\n            println!(\"Warning: Failed to read file {}: {}. Skipping this module.\", full_module_path.display(), e);\n            return Ok(HashSet::new());\n        }\n    };\n    \n    let mut parser = Parser::new();\n    if let Err(e) = parser.set_language(tree_sitter_verilog::language()) {\n        eprintln!(\"Warning: Failed to set parser language: {}. Skipping submodule processing.\", e);\n        return Ok(HashSet::new());\n    }\n\n    let submodules = match get_submodules(&contents) {\n        Ok(s) => s,\n        Err(e) => {\n            eprintln!(\"Warning: Failed to get submodules from {}: {}. Continuing without submodules.\", full_module_path.display(), e);\n            HashSet::new()\n        }\n    };\n\n    let mut all_submodules = HashSet::new();\n\n    for submodule in submodules {\n        let submodule_with_ext = if submodule.ends_with(\".v\") || submodule.ends_with(\".sv\") {\n            submodule.to_string()\n        } else {\n            let parent_extension = Path::new(&module_name_with_ext)\n                .extension()\n                .and_then(|ext| ext.to_str())\n                .unwrap_or(\"v\");\n            format!(\"{}.{}\", &submodule, parent_extension)\n        };\n        if !visited.contains(&submodule_with_ext) {\n            let submodule_destination = PathBuf::from(destination);\n            if let Err(e) = fs::create_dir_all(&submodule_destination) {\n                eprintln!(\"Warning: Failed to create directory {}: {}. Skipping this submodule.\", submodule_destination.display(), e);\n                continue;\n            }\n            \n            match process_module(\n                package_name,\n                &submodule_with_ext,\n                submodule_destination.to_str().unwrap().to_string(),\n                visited,\n                &url,\n                false,\n                commit_hash.clone()\n            ) {\n                Ok(processed_submodules) => {\n                    all_submodules.insert(submodule_with_ext.clone());\n                    all_submodules.extend(processed_submodules);\n                },\n                Err(e) => {\n                    eprintln!(\"Warning: Failed to process submodule {}: {}. Skipping this submodule.\", submodule_with_ext, e);\n                    continue;\n                }\n            }\n\n            let full_submodule_path = submodule_destination.join(&submodule_with_ext);\n            if let Err(e) = update_lockfile(&full_submodule_path, &url, &contents, visited, false) {\n                eprintln!(\"Warning: Failed to update lockfile for {}: {}. Continuing without updating lockfile.\", full_submodule_path.display(), e);\n            }\n        }\n    }\n\n    Ok(all_submodules)\n}\n\nfn update_lockfile(full_path: &PathBuf, url: &str, contents: &str, visited: &HashSet<String>, is_top_module: bool) -> Result<()> {\n    let mut lockfile = fs::read_to_string(\"vpm.lock\").unwrap_or_default();\n    let module_entry = if is_top_module {\n        format!(\"[[package]]\\nfull_path = \\\"{}\\\"\\nsource = \\\"{}\\\"\\nparents = []\\n\", full_path.display(), url)\n    } else {\n        format!(\"[[package]]\\nfull_path = \\\"{}\\\"\\nsource = \\\"{}\\\"\\n\", full_path.display(), url)\n    };\n\n    let mut parser = Parser::new();\n    parser.set_language(tree_sitter_verilog::language())?;\n    let submodules = get_submodules(contents)?;\n    let submodules_vec: Vec<String> = submodules.into_iter().collect();\n\n    if !lockfile.contains(&format!(\"full_path = \\\"{}\\\"\", full_path.display())) {\n        let formatted_submodules = submodules_vec.iter()\n            .map(|s| format!(\"  \\\"{}\\\",\", s))\n            .collect::<Vec<_>>()\n            .join(\"\\n\");\n        lockfile.push_str(&format!(\"\\n{}\\nsubmodules = [\\n{}\\n]\\n\", module_entry, formatted_submodules));\n    } else {\n        update_submodules(&mut lockfile, &module_entry, &submodules_vec);\n    }\n\n    for submodule in &submodules_vec {\n        if !visited.contains(submodule) {\n            let submodule_path = full_path.parent().unwrap().join(submodule);\n            if let Some(existing_entry) = lockfile.find(&format!(\"\\n[[package]]\\nfull_path = \\\"{}\\\"\", submodule_path.display())) {\n                let parent_start = lockfile[existing_entry..].find(\"parents = [\").map(|i| existing_entry + i);\n                if let Some(start) = parent_start {\n                    let end = lockfile[start..].find(']').map(|i| start + i + 1).unwrap_or(lockfile.len());\n                    let current_parents = lockfile[start..end].to_string();\n                    let new_parents = if current_parents.contains(&full_path.display().to_string()) {\n                        current_parents\n                    } else {\n                        format!(\"{}  \\\"{}\\\",\\n]\", &current_parents[..current_parents.len() - 1], full_path.display())\n                    };\n                    lockfile.replace_range(start..end, &new_parents);\n                }\n            } else {\n                let submodule_entry = format!(\"\\n[[package]]\\nfull_path = \\\"{}\\\"\\nsource = \\\"{}\\\"\\nparents = [\\n  \\\"{}\\\",\\n]\\nsubmodules = []\\n\", submodule_path.display(), url, full_path.display());\n                lockfile.push_str(&submodule_entry);\n            }\n        }\n    }\n\n    fs::write(\"vpm.lock\", lockfile)?;\n    Ok(())\n}\n\nfn update_submodules(lockfile: &mut String, module_entry: &str, submodules: &[String]) {\n    if let Some(start) = lockfile.find(module_entry).and_then(|pos| lockfile[pos..].find(\"submodules = [\").map(|offset| pos + offset)) {\n        let end = lockfile[start..].find(']').map(|pos| start + pos + 1).unwrap_or(lockfile.len());\n        let new_modules = format!(\"submodules = [\\n{}\\n]\", submodules.iter().map(|m| format!(\"  \\\"{}\\\",\", m)).collect::<Vec<_>>().join(\"\\n\"));\n        lockfile.replace_range(start..end, &new_modules);\n    }\n}\n\npub fn generate_headers(root_node: Node, contents: &str) -> Result<String> {\n    static QUERY: Lazy<Query> = Lazy::new(|| {\n        Query::new(\n            tree_sitter_verilog::language(),\n            \"(module_declaration\n                (module_header\n                    (module_keyword)\n                    (simple_identifier) @module_name)\n                (module_nonansi_header\n                    (parameter_port_list)? @params\n                    (list_of_ports) @ports)\n            )\n            (module_declaration\n                (module_header\n                    (module_keyword)\n                    (simple_identifier) @module_name)\n                (module_ansi_header\n                    (parameter_port_list)? @params\n                    (list_of_port_declarations)? @ports)\n            )\",\n        )\n        .expect(\"Failed to create query\")\n    });\n\n    let mut query_cursor = QueryCursor::new();\n    let matches = query_cursor.matches(&QUERY, root_node, contents.as_bytes());\n\n    let mut header_content = String::new();\n\n    for match_ in matches {\n        let mut module_name = \"\";\n        let mut params = \"\";\n        let mut ports = \"\";\n\n        for capture in match_.captures {\n            let capture_text = &contents[capture.node.byte_range()];\n            match capture.index {\n                0 => module_name = capture_text,\n                1 => params = capture_text,\n                2 => ports = capture_text,\n                _ => {}\n            }\n        }\n        \n        header_content.push_str(&format!(\n            \"`ifndef {}_H\\n`define {}_H\\n\\n\",\n            module_name.to_uppercase(),\n            module_name.to_uppercase()\n        ));\n\n        if !params.is_empty() {\n            header_content.push_str(&format!(\n                \"// Parameters\\n{}\\n\\n\",\n                params.trim()\n            ));\n        }\n\n        if !ports.is_empty() {\n            header_content.push_str(&format!(\n                \"// Ports\\n{}\\n\\n\",\n                ports.trim()\n            ));\n        }\n\n        header_content.push_str(&format!(\n            \"// Module: {}\\n// TODO: Add module description\\n\\n`endif // {}_H\\n\\n\",\n            module_name,\n            module_name.to_uppercase()\n        ));\n    }\n\n    Ok(header_content)\n}\n\n\npub fn get_submodules(contents: &str) -> Result<HashSet<String>> {\n    static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(\n        r\"(?mi)^\\s*(?!(always(_comb|_ff|_latch)?|assert|assign|assume|begin|case|cover|else|end(case|function|generate|module|primitive|table|task)?|enum|for|forever|function|generate|if|initial|input|int|localparam|logic|module|negedge|output|param(eter)?|posedge|primitive|real|reg|repeat|table|task|time|timescale|typedef|while|wire))(\\w+)\\s*(?:#\\([\\s.\\w(\\[\\-:\\]\\),{'}`/+!~@#$%^&*=<>?]+\\))?\\s*[\\w\\[:\\]]+\\s*(?:\\([\\s.\\w(\\[\\-:\\]\\),{'}`/+!~@#$%^&*=<>?|]+\\));\"\n    ).unwrap());\n    let submodules: HashSet<String> = REGEX\n        .captures_iter(contents) // Iterate over captures\n        .map(|caps| caps.unwrap().get(0).unwrap().as_str()) // Extract the matched string\n        .map(|s| s.split_whitespace().next().unwrap().to_string()) // Split and get submodule name\n        .collect(); // Collect into a HashSet\n    // for submodule in &submodules {\n        // println!(\"Found submodule: {}\", submodule);\n    // }\n    Ok(submodules)\n}\n\npub fn include_repo_from_url(url: &str, location: &str, commit_hash: Option<&str>) -> Result<()> {\n    let repo_path = Path::new(location).join(name_from_url(url));\n    let pb = ProgressBar::new_spinner();\n    pb.set_style(ProgressStyle::default_spinner().template(\"{spinner} {msg}\").unwrap());\n    pb.set_message(\"Reading repository...\");\n    pb.enable_steady_tick(std::time::Duration::from_millis(100));\n    clone_repo(url, &repo_path, commit_hash)?;\n    pb.finish_with_message(\"Reading repository complete\");\n    Ok(())\n}\n\npub fn clone_repo(url: &str, repo_path: &Path, commit_hash: Option<&str>) -> Result<()> {\n    if repo_path.exists() {\n        fs::remove_dir_all(repo_path)?;\n    }\n    Command::new(\"git\")\n        .args([ \"clone\", \"--depth\", \"1\", \"--single-branch\", \"--jobs\", \"4\",\n            url, repo_path.to_str().unwrap_or_default(),\n        ])\n        .stdout(std::process::Stdio::null())\n        .stderr(std::process::Stdio::null())\n        .status()\n        .with_context(|| format!(\"Failed to clone repository from URL: '{}'\", url))?;\n    if let Some(hash) = commit_hash {\n        Command::new(\"git\")\n            .args([ \"-C\", repo_path.to_str().unwrap_or_default(), \"checkout\", hash ])\n            .stdout(std::process::Stdio::null())\n            .stderr(std::process::Stdio::null())\n            .status()\n            .with_context(|| format!(\"Failed to checkout commit hash: '{}'\", hash))?;\n    }\n    Ok(())\n}"
  },
  {
    "path": "src/cmd/install.rs",
    "content": "use anyhow::{Context, Result};\nuse std::process::Command;\nuse std::path::Path;\nuse std::env;\nuse std::fs::OpenOptions;\nuse std::io::Write;\n\nuse crate::cmd::{Execute, Install};\n\nimpl Execute for Install {\n    async fn execute(&self) -> Result<()> {\n        match self.tool_name.as_str() {\n            \"verilator\" => {\n                println!(\"Installing Verilator...\");\n                install_verilator()?;\n            },\n            \"icarus-verilog\" => {\n                println!(\"Installing Icarus Verilog...\");\n                install_icarus_verilog()?;\n            },\n            \"chipyard\" => {\n                println!(\"Installing Chipyard...\");\n                install_chipyard()?;\n            },\n            \"openroad\" => {\n                println!(\"Installing OpenROAD...\");\n                install_openroad()?;\n            },\n            \"edalize\" => {\n                println!(\"Installing Edalize...\");\n                install_edalize()?;\n            },\n            \"yosys\" => {\n                println!(\"Installing Yosys...\");\n                install_yosys()?;\n            },\n            \"riscv\" => {\n                println!(\"Installing RISC-V toolchain...\");\n                install_riscv()?;\n            },\n            \"nextpnr\" => {\n                println!(\"Installing NextPNR...\");\n                install_nextpnr()?;\n            },\n            \"project-xray\" => {\n                println!(\"Installing Project XRay...\");\n                install_xray()?;\n            },\n            _ => {\n                println!(\"Tool '{}' is not recognized for installation.\", self.tool_name);\n            }\n        }\n\n        Ok(())\n    }\n}\n\nfn has_sudo_access() -> bool {\n    let output = Command::new(\"sudo\")\n        .arg(\"-n\")\n        .arg(\"true\")\n        .output()\n        .expect(\"Failed to execute sudo command\");\n    output.status.success()\n}\n\nfn install_verilator() -> Result<()> {\n    println!(\"Installing Verilator...\");\n\n    #[cfg(target_os = \"macos\")]\n    {\n        println!(\"Running on macOS...\");\n        // Install Verilator using Homebrew on macOS\n        let status = Command::new(\"brew\")\n            .arg(\"install\")\n            .arg(\"verilator\")\n            .status()\n            .context(\"Failed to install Verilator using Homebrew\")?;\n\n        if !status.success() {\n            println!(\"Failed to install Verilator on macOS.\");\n            return Ok(());\n        }\n    }\n\n    #[cfg(target_os = \"linux\")]\n    {\n        println!(\"Running on Linux...\");\n        \n        if has_sudo_access() {\n            // Install Verilator using package manager\n            if !is_arch_distro() {\n                // Install Verilator using apt-get on non-Arch Linux\n                let status = Command::new(\"sudo\")\n                    .args(&[\"apt-get\", \"update\"])\n                    .status()\n                    .context(\"Failed to update package lists\")?;\n\n                if !status.success() {\n                    println!(\"Failed to update package lists on Linux.\");\n                    return Ok(());\n                }\n\n                let status = Command::new(\"sudo\")\n                    .args(&[\"apt-get\", \"install\", \"-y\", \"verilator\"])\n                    .status()\n                    .context(\"Failed to install Verilator using apt-get\")?;\n\n                if !status.success() {\n                    println!(\"Failed to install Verilator on Linux.\");\n                    return Ok(());\n                }\n            } else {\n                // Install Verilator using pacman on Arch Linux\n                let status = Command::new(\"sudo\")\n                    .args(&[\"pacman\", \"-Syu\", \"--noconfirm\", \"verilator\"])\n                    .status()\n                    .context(\"Failed to install Verilator using pacman\")?;\n\n                if !status.success() {\n                    println!(\"Failed to install Verilator on Arch Linux.\");\n                    return Ok(());\n                }\n            }\n        } else {\n            println!(\"No sudo access. Installing Verilator from source...\");\n            install_verilator_from_source()?;\n        }\n    }\n\n    #[cfg(not(any(target_os = \"macos\", target_os = \"linux\")))]\n    {\n        println!(\"Unsupported operating system. Please install Verilator manually.\");\n        return Ok(());\n    }\n\n    println!(\"Verilator installed successfully.\");\n    Ok(())\n}\n\nfn install_verilator_from_source() -> Result<()> {\n    // Create a directory for the installation\n    let install_dir = Path::new(&std::env::var(\"HOME\")?).join(\"verilator\");\n    std::fs::create_dir_all(&install_dir)?;\n\n    // Clone the repository\n    Command::new(\"git\")\n        .args(&[\"clone\", \"https://github.com/verilator/verilator\"])\n        .current_dir(&install_dir)\n        .status()\n        .context(\"Failed to clone Verilator repository\")?;\n\n    let source_dir = install_dir.join(\"verilator\");\n\n    // Configure with custom prefix\n    Command::new(\"autoconf\")\n        .current_dir(&source_dir)\n        .status()\n        .context(\"Failed to run autoconf for Verilator\")?;\n\n    Command::new(\"./configure\")\n        .arg(format!(\"--prefix={}\", install_dir.display()))\n        .current_dir(&source_dir)\n        .status()\n        .context(\"Failed to configure Verilator\")?;\n\n    // Build\n    Command::new(\"make\")\n        .current_dir(&source_dir)\n        .status()\n        .context(\"Failed to build Verilator\")?;\n\n    // Install\n    Command::new(\"make\")\n        .arg(\"install\")\n        .current_dir(&source_dir)\n        .status()\n        .context(\"Failed to install Verilator\")?;\n\n    // Add installation directory to PATH\n    println!(\"Verilator installed successfully in {}.\", install_dir.display());\n    println!(\"Please add the following line to your shell configuration file (e.g., .bashrc or .zshrc):\");\n    println!(\"export PATH=$PATH:{}/bin\", install_dir.display());\n\n    Ok(())\n}\n\nfn install_icarus_verilog() -> Result<()> {\n    println!(\"Installing Icarus Verilog...\");\n\n    #[cfg(target_os = \"macos\")]\n    {\n        println!(\"Running on macOS...\");\n        // Install Icarus Verilog using Homebrew on macOS\n        let status = Command::new(\"brew\")\n            .arg(\"install\")\n            .arg(\"icarus-verilog\")\n            .status()\n            .context(\"Failed to install Icarus Verilog using Homebrew\")?;\n\n        if !status.success() {\n            println!(\"Failed to install Icarus Verilog on macOS.\");\n            return Ok(());\n        }\n    }\n\n    #[cfg(target_os = \"linux\")]\n    {\n        if !is_arch_distro() {\n            println!(\"Running on Linux...\");\n            // Install Icarus Verilog using apt-get on Linux\n            let status = Command::new(\"sudo\")\n                .arg(\"apt-get\")\n                .arg(\"update\")\n                .status()\n                .context(\"Failed to update package lists\")?;\n\n            if !status.success() {\n                println!(\"Failed to update package lists on Linux.\");\n                return Ok(());\n            }\n\n            let status = Command::new(\"sudo\")\n                .arg(\"apt-get\")\n                .arg(\"install\")\n                .arg(\"-y\")\n                .arg(\"iverilog\")\n                .status()\n                .context(\"Failed to install Icarus Verilog using apt-get\")?;\n\n            if !status.success() {\n                println!(\"Failed to install Icarus Verilog on Linux using Apt-Get.\");\n                let install_dir = Path::new(&std::env::var(\"HOME\")?).join(\"icarus_verilog\");\n    std::fs::create_dir_all(&install_dir)?;\n\n\n    Command::new(\"git\")\n        .args(&[\"clone\", \"https://github.com/steveicarus/iverilog.git\"])\n        .current_dir(&install_dir)\n        .status()\n        .context(\"Failed to clone Icarus Verilog repository\")?;\n\n    let source_dir = install_dir.join(\"iverilog\");\n\n\n    Command::new(\"sh\")\n        .arg(\"autoconf.sh\")\n        .current_dir(&source_dir)\n        .status()\n        .context(\"Failed to generate configure script\")?;\n\n\n    Command::new(\"./configure\")\n        .arg(format!(\"--prefix={}\", install_dir.display()))\n        .current_dir(&source_dir)\n        .status()\n        .context(\"Failed to configure Icarus Verilog\")?;\n\n\n    Command::new(\"make\")\n        .current_dir(&source_dir)\n        .status()\n        .context(\"Failed to build Icarus Verilog\")?;\n\n\n    Command::new(\"make\")\n        .arg(\"install\")\n        .current_dir(&source_dir)\n        .status()\n        .context(\"Failed to install Icarus Verilog\")?;\n\n\n    println!(\"Icarus Verilog installed successfully.\");\n    println!(\"Please add the following line to your shell configuration file (e.g., .bashrc or .zshrc):\");\n    println!(\"export PATH=$PATH:{}/bin\", install_dir.display());\n\n                return Ok(());\n            } else {\n                \n        } else {\n            println!(\"Running on Arch Linux...\");\n            // Install Icarus Verilog using pacman on Arch Linux\n            let status = Command::new(\"sudo\")\n                .arg(\"pacman\")\n                .arg(\"-Syu\")\n                .arg(\"--noconfirm\")\n                .arg(\"iverilog\")\n                .status()\n                .context(\"Failed to install Icarus Verilog using pacman\")?;\n\n            if !status.success() {\n                println!(\"Failed to install Icarus Verilog on Arch Linux.\");\n                return Ok(());\n            }\n        }\n    }\n\n    #[cfg(not(any(target_os = \"macos\", target_os = \"linux\")))]\n    {\n        println!(\"Unsupported operating system. Please install Icarus Verilog manually.\");\n        return Ok(());\n    }\n\n    println!(\"Icarus Verilog installed successfully.\");\n    Ok(())\n}\n\nfn install_chipyard() -> Result<()> {\n    println!(\"Installing Chipyard...\");\n\n    // Define the installation directory\n    let install_dir = Path::new(\"/usr/local/bin\");\n\n    // Download Chipyard binary\n    let status = Command::new(\"curl\")\n        .args(&[\"-L\", \"https://github.com/ucb-bar/chipyard/releases/latest/download/chipyard\", \"-o\", install_dir.join(\"chipyard\").to_str().unwrap()])\n        .status()\n        .context(\"Failed to download Chipyard binary\")?;\n\n    if !status.success() {\n        println!(\"Failed to download Chipyard binary.\");\n        return Ok(());\n    }\n\n    // Make the binary executable\n    let status = Command::new(\"chmod\")\n        .args(&[\"+x\", install_dir.join(\"chipyard\").to_str().unwrap()])\n        .status()\n        .context(\"Failed to make Chipyard binary executable\")?;\n\n    if !status.success() {\n        println!(\"Failed to make Chipyard binary executable.\");\n        return Ok(());\n    }\n\n    println!(\"Chipyard installed successfully.\");\n    Ok(())\n}\n\nfn install_edalize() -> Result<()> {\n    println!(\"Installing Edalize...\");\n\n    let (_python_cmd, pip_cmd) = if check_command(\"python3\") {\n        (\"python3\", \"pip3\")\n    } else if check_command(\"python\") {\n        (\"python\", \"pip\")\n    } else {\n        println!(\"Neither Python 3 nor Python 2 is installed. Please install Python before proceeding.\");\n        return Ok(());\n    };\n\n    if !check_command(pip_cmd) {\n        println!(\"{} is not installed. Please install pip before proceeding.\", pip_cmd);\n        return Ok(());\n    }\n\n    // Install Edalize\n    let status = Command::new(pip_cmd)\n        .arg(\"install\")\n        .arg(\"--user\")\n        .arg(\"edalize\")\n        .status()\n        .context(\"Failed to install Edalize using pip\")?;\n\n    if !status.success() {\n        println!(\"Failed to install Edalize.\");\n        return Ok(());\n    }\n\n    // Install FuseSoC\n    let status = Command::new(pip_cmd)\n        .arg(\"install\")\n        .arg(\"--user\")\n        .arg(\"fusesoc\")\n        .status()\n        .context(\"Failed to install FuseSoC using pip\")?;\n\n    if !status.success() {\n        println!(\"Failed to install FuseSoC.\");\n        return Ok(());\n    }\n\n    println!(\"Edalize installed successfully.\");\n    Ok(())\n}\n\nfn check_command(cmd: &str) -> bool {\n    Command::new(cmd)\n        .arg(\"--version\")\n        .output()\n        .is_ok()\n}\n\nfn install_openroad() -> Result<()> {\n    println!(\"Installing OpenROAD...\");\n\n    #[cfg(target_os = \"linux\")]\n    {\n        println!(\"Running on Linux...\");\n        // Install OpenROAD using apt on Linux\n        let status = Command::new(\"sudo\")\n            .arg(\"apt\")\n            .arg(\"update\")\n            .status()\n            .context(\"Failed to update package lists\")?;\n\n        if !status.success() {\n            println!(\"Failed to update package lists on Linux.\");\n            return Ok(());\n        }\n\n        let status = Command::new(\"sudo\")\n            .arg(\"apt\")\n            .arg(\"install\")\n            .arg(\"-y\")\n            .arg(\"openroad\")\n            .status()\n            .context(\"Failed to install OpenROAD using apt\")?;\n\n        if !status.success() {\n            println!(\"Failed to install OpenROAD on Linux.\");\n            return Ok(());\n        }\n    }\n\n    #[cfg(target_os = \"macos\")]\n    {\n        println!(\"Running on macOS...\");\n        // Install OpenROAD using Homebrew on macOS\n        let status = Command::new(\"brew\")\n            .arg(\"install\")\n            .arg(\"openroad/openroad/openroad\")\n            .status()\n            .context(\"Failed to install OpenROAD using Homebrew\")?;\n\n        if !status.success() {\n            println!(\"Failed to install OpenROAD on macOS.\");\n            return Ok(());\n        }\n    }\n\n    #[cfg(not(any(target_os = \"macos\", target_os = \"linux\")))]\n    {\n        println!(\"Unsupported operating system. Please install OpenROAD manually.\");\n        return Ok(());\n    }\n\n    println!(\"OpenROAD installed successfully.\");\n    Ok(())\n}\n\nfn install_yosys() -> Result<()> {\n    println!(\"Installing Yosys and ABC...\");\n\n    #[cfg(target_os = \"macos\")]\n    {\n        println!(\"Running on macOS...\");\n        // Install Yosys using Homebrew on macOS\n        let status = Command::new(\"brew\")\n            .arg(\"install\")\n            .arg(\"yosys\")\n            .status()\n            .context(\"Failed to install Yosys using Homebrew\")?;\n\n        if !status.success() {\n            println!(\"Failed to install Yosys on macOS.\");\n            return Ok(());\n        }\n\n        // Install ABC by git cloning and making\n        if !Path::new(\"/usr/local/bin/abc\").exists() {\n            println!(\"Installing ABC...\");\n            let status = Command::new(\"git\")\n                .args(&[\"clone\", \"https://github.com/berkeley-abc/abc.git\"])\n                .status()\n                .context(\"Failed to clone ABC repository\")?;\n\n            if !status.success() {\n                println!(\"Failed to clone ABC repository.\");\n                return Ok(());\n            }\n\n            let status = Command::new(\"make\")\n                .current_dir(\"abc\")\n                .status()\n                .context(\"Failed to make ABC\")?;\n\n            if !status.success() {\n                println!(\"Failed to make ABC.\");\n                return Ok(());\n            }\n\n            let status = Command::new(\"sudo\")\n                .args(&[\"mv\", \"abc/abc\", \"/usr/local/bin/\"])\n                .status()\n                .context(\"Failed to move ABC to /usr/local/bin/\")?;\n\n            if !status.success() {\n                println!(\"Failed to move ABC to /usr/local/bin/.\");\n                return Ok(());\n            }\n\n            println!(\"ABC installed successfully.\");\n        } else {\n            println!(\"ABC is already installed.\");\n        }\n    }\n    println!(\"Yosys and ABC installed successfully.\");\n    Ok(())\n}\n\nfn install_riscv() -> Result<()> {\n    println!(\"Installing RISC-V toolchain...\");\n    Command::new(\"git\")\n        .args(&[\"clone\", \"--recursive\", \"https://github.com/riscv/riscv-gnu-toolchain.git\"])\n        .status()?;\n\n    // Change to the cloned directory\n    env::set_current_dir(\"riscv-gnu-toolchain\")?;\n\n    // Step 2: Install prerequisites (for Ubuntu/Debian)\n    Command::new(\"sudo\")\n        .args(&[\"apt-get\", \"install\", \"autoconf\", \"automake\", \"autotools-dev\", \"curl\", \"python3\", \"libmpc-dev\", \"libmpfr-dev\", \"libgmp-dev\", \"gawk\", \"build-essential\", \"bison\", \"flex\", \"texinfo\", \"gperf\", \"libtool\", \"patchutils\", \"bc\", \"zlib1g-dev\", \"libexpat-dev\"])\n        .status()?;\n\n    // Step 3: Create install directory\n    Command::new(\"sudo\")\n        .args(&[\"mkdir\", \"-p\", \"/opt/riscv\"])\n        .status()?;\n\n    // Step 4: Configure and build the toolchain\n    Command::new(\"./configure\")\n        .arg(\"--prefix=/opt/riscv\")\n        .status()?;\n\n    Command::new(\"sudo\")\n        .arg(\"make\")\n        .status()?;\n\n    // Step 5: Add the toolchain to PATH\n    let home = env::var(\"HOME\")?;\n    let bashrc_path = Path::new(&home).join(\".bashrc\");\n    let mut bashrc = OpenOptions::new()\n        .append(true)\n        .open(bashrc_path)?;\n\n    writeln!(bashrc, \"\\nexport PATH=$PATH:/opt/riscv/bin\")?;\n\n    // Step 6: Verify installation\n    Command::new(\"/opt/riscv/bin/riscv64-unknown-elf-gcc\")\n        .arg(\"--version\")\n        .status()?;\n\n    println!(\"RISC-V GNU toolchain installed successfully!\");\n    println!(\"Please restart your terminal or run 'source ~/.bashrc' to update your PATH.\");\n    Ok(())\n}\n\nfn install_nextpnr() -> Result<()> {\n    println!(\"Installing NextPNR...\");\n\n    // Install NextPNR using Homebrew on macOS\n    let status = Command::new(\"brew\")\n        .arg(\"install\")\n        .arg(\"nextpnr\")\n        .status()\n        .context(\"Failed to install NextPNR using Homebrew\")?;\n\n    if !status.success() {\n        println!(\"Failed to install NextPNR on macOS.\");\n        return Ok(());\n    }\n\n    println!(\"NextPNR installed successfully.\");\n    Ok(())\n}\n\nfn install_xray() -> Result<()> {\n    println!(\"Installing Project XRay...\");\n\n    // Install Project XRay using Homebrew on macOS\n    let status = Command::new(\"brew\")\n        .arg(\"install\")\n        .arg(\"xray\")\n        .status()\n        .context(\"Failed to install Project XRay using Homebrew\")?;\n\n    if !status.success() {\n        println!(\"Failed to install Project XRay on macOS.\");\n        return Ok(());\n    }\n\n    println!(\"Project XRay installed successfully.\");\n    Ok(())\n}\n\nfn is_arch_distro() -> bool {\n    Command::new(\"pacman\")\n        .arg(\"--version\")\n        .output()\n        .is_ok()\n}\n"
  },
  {
    "path": "src/cmd/list.rs",
    "content": "use anyhow::{Result, Context, anyhow};\nuse std::collections::HashSet;\nuse std::process::Command;\nuse crate::cmd::{Execute, List};\nuse tempfile::tempdir;\n\nconst STD_LIB_URL: &str = \"https://github.com/getinstachip/openchips\";\n\nimpl Execute for List {\n    async fn execute(&self) -> Result<()> {\n        match list_verilog_files() {\n            Ok(verilog_files) => {\n                println!(\"Available Verilog modules:\");\n                for file in verilog_files {\n                    println!(\"  {}\", file);\n                }\n                Ok(())\n            }\n            Err(e) => {\n                eprintln!(\"Error: Failed to list Verilog files. {}\", e);\n                eprintln!(\"Debug steps:\");\n                eprintln!(\"1. Check your internet connection\");\n                eprintln!(\"2. Ensure git is installed and accessible from the command line\");\n                eprintln!(\"3. Verify you have read permissions for the temporary directory\");\n                Err(e)\n            }\n        }\n    }\n}\n\nfn list_verilog_files() -> Result<Vec<String>> {\n    let temp_dir = tempdir().context(\"Failed to create temporary directory. Ensure you have write permissions in the system temp directory.\")?;\n    let repo_path = temp_dir.path();\n\n    // Clone the repository\n    let output = Command::new(\"git\")\n        .args([\n            \"clone\",\n            \"--depth\",\n            \"1\",\n            \"--single-branch\",\n            \"--jobs\",\n            \"4\",\n            STD_LIB_URL,\n            repo_path.to_str().unwrap_or_default(),\n        ])\n        .output()\n        .context(\"Failed to execute git command. Ensure git is installed and accessible from the command line.\")?;\n\n    if !output.status.success() {\n        return Err(anyhow!(\n            \"Git clone failed. Error: {}. This could be due to network issues, incorrect repository URL, or git configuration problems.\",\n            String::from_utf8_lossy(&output.stderr)\n        ));\n    }\n\n    let mut verilog_files = HashSet::new();\n\n    for entry in walkdir::WalkDir::new(repo_path)\n        .into_iter()\n        .filter_map(|e| e.ok())\n    {\n        if let Some(extension) = entry.path().extension() {\n            match extension.to_str() {\n                Some(\"v\") | Some(\"sv\") => {\n                    if let Some(file_name) = entry.path().file_stem() {\n                        verilog_files.insert(file_name.to_string_lossy().into_owned());\n                    }\n                }\n                _ => {}\n            }\n        }\n    }\n\n    if verilog_files.is_empty() {\n        Err(anyhow!(\"No Verilog files found in the repository. This could indicate an issue with the repository structure or content.\"))\n    } else {\n        Ok(verilog_files.into_iter().collect())\n    }\n}\n"
  },
  {
    "path": "src/cmd/load.rs",
    "content": "use anyhow::Result;\nuse std::path::Path;\nuse std::process::Command;\n\nuse crate::cmd::{Execute, Load};\n\nimpl Execute for Load {\n    async fn execute(&self) -> Result<()> {\n        let top_module_path = Path::new(&self.top_module_path);\n        let constraints_path = Path::new(&self.constraints_path);\n        if self.riscv {\n            load_xilinx(top_module_path, constraints_path)?;\n        } else {\n            unimplemented!(\"Non RISC-V loading not yet implemented\");\n        }\n        Ok(())\n    }\n}\n\nfn load_xilinx(edif_path: &Path, constraints_path: &Path) -> Result<()> {\n    let edif_path_str = edif_path.to_str().unwrap();\n    let constraints_path_str = constraints_path.to_str().unwrap();\n    \n    Command::new(\"yosys\")\n        .args(&[\"-p\", &format!(\"read_edif {}; write_json design.json\", edif_path_str)])\n        .status()?;\n\n    Command::new(\"nextpnr-xilinx\")\n        .args(&[\n            \"--chipdb\", \"vpm_modules/chipdb-xc7a35t.bin\",\n            \"--xdc\", constraints_path_str,\n            \"--json\", \"design.json\",\n            \"--write\", \"output.fasm\",\n            \"--device\", \"xc7a35tcsg324-1\"\n        ])\n        .status()?;\n\n    let fasm_output = Command::new(\"fasm2frames\")\n        .args(&[\"--part\", \"xc7a35tcsg324-1\", \"output.fasm\"])\n        .output()?;\n    std::fs::write(\"output.frames\", fasm_output.stdout)?;\n\n    Command::new(\"xc7frames2bit\")\n        .args(&[\n            \"--part_file\", \"vpm_modules/xc7a35tcsg324-1.yaml\",\n            \"--part_name\", \"xc7a35tcsg324-1\",\n            \"--frm_file\", \"output.frames\",\n            \"--output_file\", \"output.bit\"\n        ])\n        .status()?;\n\n    println!(\"Bitstream generated successfully: output.bit\");\n    Ok(())\n}"
  },
  {
    "path": "src/cmd/mod.rs",
    "content": "mod cmd;\nmod upgrade;\nmod include;\nmod update;\nmod remove;\nmod dotf;\nmod list;\nmod install;\nmod sim;\nmod docs;\nmod synth;\nmod run;\nmod load;\nmod config;\n\nuse anyhow::Result;\n\npub use crate::cmd::cmd::*;\n\nuse crate::config_man::send_event;\n\npub trait Execute {\n    async fn execute(&self) -> Result<()>;\n}\n\n\nimpl Execute for Cmd {\n    async fn execute(&self) -> Result<()> {\n        match self {\n            Cmd::Upgrade(cmd) => {\n                cmd.execute().await?;\n                send_event(\"upgrade\".to_string()).await?;\n                Ok(())\n            },\n            Cmd::Include(cmd) => {\n                cmd.execute().await?;\n                send_event(\"include\".to_string()).await?;\n                Ok(())\n            },\n            Cmd::Update(cmd) => {\n                cmd.execute().await?;\n                send_event(\"update\".to_string()).await?;\n                Ok(())\n            },\n            Cmd::Remove(cmd) => {\n                cmd.execute().await?;\n                send_event(\"remove\".to_string()).await?;\n                Ok(())\n            },\n            Cmd::Dotf(cmd) => {\n                cmd.execute().await?;\n                send_event(\"dotf\".to_string()).await?;\n                Ok(())\n            },\n            Cmd::Install(cmd) => {\n                cmd.execute().await?;\n                send_event(\"install\".to_string()).await?;\n                Ok(())\n            },\n            Cmd::List(cmd) => {\n                cmd.execute().await?;\n                send_event(\"list\".to_string()).await?;\n                Ok(())\n            },\n            Cmd::Sim(cmd) => {\n                cmd.execute().await?;\n                send_event(\"sim\".to_string()).await?;\n                Ok(())\n            },\n            Cmd::Docs(cmd) => {\n                cmd.execute().await?;\n                send_event(\"docs\".to_string()).await?;\n                Ok(())\n            },\n            Cmd::Synth(cmd) => {\n                cmd.execute().await?;\n                send_event(\"synth\".to_string()).await?;\n                Ok(())\n            },\n            Cmd::Load(cmd) => {\n                cmd.execute().await?;\n                send_event(\"load\".to_string()).await?;\n                Ok(())\n            },\n            Cmd::Run(cmd) => {\n                cmd.execute().await?;\n                send_event(\"run\".to_string()).await?;\n                Ok(())\n            },\n            Cmd::Config(cmd) => {\n                cmd.execute().await?;\n                send_event(\"config\".to_string()).await?;\n                Ok(())\n            },\n        }\n    }\n}\n"
  },
  {
    "path": "src/cmd/remove.rs",
    "content": "use std::fs;\nuse std::path::PathBuf;\nuse std::io::{self, Write};\n\nuse anyhow::{Result, anyhow};\n\nuse crate::cmd::{Execute, Remove};\nuse crate::toml::{remove_top_module, get_repo_links};\n\nimpl Execute for Remove {\n    async fn execute(&self) -> Result<()> {\n        remove_module(&self.package_path)?;\n        Ok(())\n    }\n}\n\nfn remove_module(module_path: &str) -> Result<()> {\n    let module_path = PathBuf::from(module_path);\n    if !module_path.exists() {\n        return Err(anyhow!(\"Module not found: {}\", module_path.display()));\n    }\n\n    let module_name = module_path.file_name().unwrap().to_str().unwrap();\n    \n    // Ask for y/n confirmation\n    print!(\"Are you sure you want to remove the module {}? (y/n): \", module_name);\n    io::stdout().flush()?;\n    let mut confirmation = String::new();\n    io::stdin().read_line(&mut confirmation)?;\n    if confirmation.trim().to_lowercase() != \"y\" {\n        return Ok(());\n    }\n\n    let repo_links = get_repo_links(module_name);\n\n    let repo_link = match repo_links.len() {\n        0 => return Err(anyhow!(\"No repository links found for module: {}\", module_name)),\n        1 => repo_links.into_iter().next().unwrap(),\n        _ => {\n            println!(\"Multiple repository links found for module: {}. Please choose the correct repository link.\", module_name);\n            for (i, link) in repo_links.iter().enumerate() {\n                println!(\"{}. {}\", i + 1, link);\n            }\n            \n            let mut choice = String::new();\n            print!(\"Enter your choice (1-{}): \", repo_links.len());\n            io::stdout().flush()?;\n            io::stdin().read_line(&mut choice)?;\n            let index: usize = choice.trim().parse().map_err(|_| anyhow!(\"Invalid choice\"))?;\n            \n            if index < 1 || index > repo_links.len() {\n                return Err(anyhow!(\"Invalid choice\"));\n            }\n            repo_links.iter().nth(index - 1)\n                .ok_or_else(|| anyhow!(\"Invalid choice\"))?\n                .to_string()\n        }\n    };\n\n    // Ask to enter the name of the module to confirm\n    print!(\"To confirm removal, please re-type \\\"{}\\\" (without the quotes): \", module_name);\n    io::stdout().flush()?;\n    let mut confirmation_name = String::new();\n    io::stdin().read_line(&mut confirmation_name)?;\n    if confirmation_name.trim() != module_name {\n        return Err(anyhow!(\"Module name does not match. Removal cancelled.\"));\n    }\n\n    fs::remove_file(&module_path)?;\n    // Remove the corresponding header file if it exists\n    let header_path = module_path.with_extension(\"vh\");\n    if header_path.exists() {\n        fs::remove_file(&header_path)?;\n        println!(\"Removed header file: {}\", header_path.display());\n    }\n    remove_top_module(&repo_link, module_name)?;    \n    println!(\"Removed module: {}\", module_path.display());\n\n    Ok(())\n}\n"
  },
  {
    "path": "src/cmd/run.rs",
    "content": "use anyhow::Result;\n\nuse crate::cmd::{Execute, Run};\n\nimpl Execute for Run {\n    async fn execute(&self) -> Result<()> {\n        Ok(())\n    }\n}"
  },
  {
    "path": "src/cmd/sim.rs",
    "content": "use anyhow::{Context, Result};\nuse std::process::Command;\nuse std::env;\nuse std::path::{Path, PathBuf};\nuse std::fs;\nuse fastrand;\nuse crate::cmd::{Execute, Sim};\nuse std::fs::File;\nuse std::io::{BufRead, BufReader};\nuse fancy_regex::Regex;\nuse walkdir::WalkDir;\n\nimpl Execute for Sim {\n    async fn execute(&self) -> Result<()> {\n        let output_path = if let Some(folder) = &self.folder {\n            compile_verilog_from_folder(folder)?\n        } else {\n            let mut verilog_files = self.verilog_files.clone();\n            if !testbench_exists(&verilog_files) {\n                generate_and_add_testbench(&mut verilog_files)?;\n            }\n            compile_verilog(&verilog_files)?\n        };\n\n        if self.waveform {\n            run_simulation_with_waveform(&output_path)?;\n        } else {\n            run_simulation(&output_path)?;\n        }\n\n        Ok(())\n    }\n}\n\nfn testbench_exists(verilog_files: &[String]) -> bool {\n    verilog_files.iter().any(|file| file.to_lowercase().contains(\"_tb.v\"))\n}\n\nfn generate_and_add_testbench(verilog_files: &mut Vec<String>) -> Result<()> {\n    if let Some(first_file) = verilog_files.first() {\n        let is_systemverilog = first_file.ends_with(\".sv\");\n        let testbench_content = generate_testbench(first_file, is_systemverilog)\n            .context(\"Failed to generate testbench. Please check if the Verilog file is valid.\")?;\n        let testbench_path = format!(\"{}_tb.{}\", first_file.trim_end_matches(if is_systemverilog { \".sv\" } else { \".v\" }), if is_systemverilog { \"sv\" } else { \"v\" });\n        fs::write(&testbench_path, testbench_content)\n            .context(\"Failed to write testbench file. Please check if you have write permissions.\")?;\n        // Remove comments from the original Verilog file\n        remove_comments_from_file(first_file)?;\n\n        verilog_files.push(testbench_path.clone());\n        println!(\"Generated testbench: {}\", testbench_path);\n        Ok(())\n    } else {\n        Err(anyhow::anyhow!(\"No Verilog files provided. Please specify at least one Verilog file.\"))\n    }\n}\n\npub fn compile_verilog_from_folder(folder: &str) -> Result<PathBuf> {\n    println!(\"Compiling Verilog files from folder: {}\", folder);\n    let verilog_files = collect_verilog_files(folder)?;\n\n    if verilog_files.is_empty() {\n        return Err(anyhow::anyhow!(\"No Verilog files found in the specified folder.\"));\n    }\n\n    let output_dir = Path::new(folder);\n    let random_output_name = generate_random_output_name();\n    let output_path = output_dir.join(&random_output_name);\n\n    let command_status = run_iverilog_command(output_path.to_str().unwrap(), &verilog_files)?;\n\n    if !command_status.success() {\n        return Err(anyhow::anyhow!(\"Compilation failed. Please check the error messages above.\"));\n    }\n\n    println!(\"Compilation successful. Output file: {:?}\", output_path);\n    Ok(output_path)\n}\n\nfn collect_verilog_files(folder: &str) -> Result<Vec<String>> {\n    let mut verilog_files = Vec::new();\n\n    for entry in WalkDir::new(folder).into_iter().filter_map(|e| e.ok()) {\n        let path = entry.path();\n        if path.is_file() && (path.extension() == Some(\"v\".as_ref()) || path.extension() == Some(\"sv\".as_ref())) {\n            verilog_files.push(path.to_string_lossy().into_owned());\n        }\n    }\n\n    Ok(verilog_files)\n}\n\nfn run_simulation_with_waveform(output_path: &Path) -> Result<()> {\n    println!(\"Running simulation with waveform...\");\n    println!(\"Output path: {:?}\", output_path);\n    let testbench_dir = output_path.parent().unwrap();\n    let vcd_path = testbench_dir.join(\"waveform.vcd\");\n    let current_dir = std::env::current_dir()\n        .context(\"Failed to get current directory. Please check your file system permissions.\")?;\n    std::env::set_current_dir(testbench_dir)\n        .context(\"Failed to change directory to testbench location. Please ensure the directory exists and you have necessary permissions.\")?;\n    \n    let mut cmd = Command::new(\"vvp\");\n    cmd.arg(output_path.file_name().unwrap());\n    cmd.arg(format!(\"-vcd={}\", vcd_path.file_name().unwrap().to_str().unwrap()));\n    \n    let output = cmd.output()\n        .context(\"Failed to run simulation with VCD output. Debug steps:\\n1. Ensure 'vvp' is installed: Run 'vvp --version' in terminal.\\n2. Check if 'vvp' is in your PATH: Run 'which vvp' (Unix) or 'where vvp' (Windows).\\n3. If not found, install Icarus Verilog or add its bin directory to your PATH.\")?;\n    \n    // Call gtkwave on the generated waveform file\n    println!(\"Opening waveform in GTKWave...\");\n    let _gtkwave_status = Command::new(\"gtkwave\")\n        .arg(\"waveform.vcd\")\n        .spawn()\n        .context(\"Failed to open GTKWave. Debug steps:\\n1. Ensure GTKWave is installed: Run 'gtkwave --version' in terminal.\\n2. Check if 'gtkwave' is in your PATH: Run 'which gtkwave' (Unix) or 'where gtkwave' (Windows).\\n3. If not found, install GTKWave or add its installation directory to your PATH.\")?;\n\n    // We don't wait for GTKWave to exit, as it's a GUI application\n    println!(\"GTKWave opened successfully. You can now view the waveform.\");\n    \n    std::env::set_current_dir(current_dir)\n        .context(\"Failed to change back to the original directory. This is unexpected, please check your file system.\")?;\n    \n    if !output.status.success() {\n        let error_message = String::from_utf8_lossy(&output.stderr);\n        return Err(anyhow::anyhow!(\"Simulation failed. Error details:\\n{}\\n\\nDebugging steps:\\n1. Check your Verilog code for syntax errors.\\n2. Ensure all module dependencies are correctly included.\\n3. Verify testbench inputs and timing.\\n4. Run the simulation without waveform generation to isolate the issue.\", error_message));\n    }\n    \n    println!(\"Generated waveform file: {}\", vcd_path.display());\n    println!(\"If GTKWave didn't open automatically, you can manually open the waveform file using GTKWave.\");\n    Ok(())\n}\n\npub fn generate_testbench(module_path: &str, is_systemverilog: bool) -> Result<String> {\n    println!(\"Generating testbench for module: {}\", module_path);\n\n    let (module_name, ports, parameters) = extract_module_info(module_path)?;\n\n    println!(\"Module name: {}\", module_name);\n    println!(\"Ports: {:?}\", ports);\n    println!(\"Parameters: {:?}\", parameters);\n\n    let mut testbench = String::new();\n    testbench.push_str(&generate_testbench_header(&module_name));\n    testbench.push_str(&declare_parameters(&parameters));\n    testbench.push_str(&declare_wires_for_ports(&ports, is_systemverilog));\n    testbench.push_str(&instantiate_module(&module_name, &ports, &parameters));\n    testbench.push_str(&generate_clock(&ports));\n    testbench.push_str(&generate_initial_block(&ports, is_systemverilog));\n    testbench.push_str(&generate_check_outputs_task(&ports, is_systemverilog));\n    testbench.push_str(\"endmodule\\n\");\n\n    Ok(testbench)\n}\n\nfn extract_module_info(module_path: &str) -> Result<(String, Vec<(String, Option<String>, String)>, Vec<(String, String)>)> {\n    let file = File::open(module_path)\n        .context(format!(\"Failed to open module file: {}. Please check if the file exists and you have read permissions.\", module_path))?;\n    let reader = BufReader::new(file);\n\n    let mut module_name = String::new();\n    let mut ports = Vec::new();\n    let mut parameters = Vec::new();\n    let mut in_module = false;\n    let module_regex = Regex::new(r\"module\\s+(\\w+)\\s*(?:\\(|#)\").unwrap();\n    let port_regex = Regex::new(r\"(input|output|inout)\\s+(?:reg|wire|logic)?\\s*(\\[.*?\\])?\\s*(\\w+)\").unwrap();\n    let parameter_regex = Regex::new(r\"parameter\\s+(\\w+)\\s*=\\s*([^,\\)]+)\").unwrap();\n    let inline_parameter_regex = Regex::new(r\"(\\w+)\\s*=\\s*([^,\\)]+)\").unwrap();\n\n    for line in reader.lines() {\n        let line = line?;\n        if !in_module {\n            if let Ok(Some(captures)) = module_regex.captures(&line) {\n                module_name = captures[1].to_string();\n                in_module = true;\n            }\n        } else {\n            for capture_result in port_regex.captures_iter(&line) {\n                if let Ok(capture) = capture_result {\n                    let direction = capture[1].to_string();\n                    let bus_width = capture.get(2).map(|m| m.as_str().to_string());\n                    let name = capture[3].to_string();\n                    ports.push((direction, bus_width, name));\n                }\n            }\n            for capture_result in parameter_regex.captures_iter(&line) {\n                if let Ok(capture) = capture_result {\n                    let name = capture[1].to_string();\n                    let value = capture[2].to_string();\n                    if let Some(existing) = parameters.iter_mut().find(|(n, _)| n == &name) {\n                        existing.1 = value;\n                    } else {\n                        parameters.push((name, value));\n                    }\n                }\n            }\n            for capture_result in inline_parameter_regex.captures_iter(&line) {\n                if let Ok(capture) = capture_result {\n                    let name = capture[1].to_string();\n                    let value = capture[2].to_string();\n                    if !parameters.iter().any(|(n, _)| n == &name) {\n                        parameters.push((name, value));\n                    }\n                }\n            }\n            if line.contains(\");\") {\n                break;\n            }\n        }\n    }\n\n    if module_name.is_empty() {\n        return Err(anyhow::anyhow!(\"Could not find module declaration in {}. Please ensure the file contains a valid Verilog or SystemVerilog module.\", module_path));\n    }\n\n    Ok((module_name, ports, parameters))\n}\n\nfn generate_testbench_header(module_name: &str) -> String {\n    format!(\"`timescale 1ns / 1ps\\n\\nmodule {}_tb;\\n\\n\", module_name)\n}\n\nfn declare_parameters(parameters: &[(String, String)]) -> String {\n    let mut declarations = String::new();\n    for (name, value) in parameters {\n        let mut line = format!(\"    parameter {} = {}\", name, value);\n        if !line.ends_with(')') && line.contains('(') {\n            line.push(')');\n        }\n        declarations.push_str(&line);\n        declarations.push_str(\";\\n\");\n    }\n    declarations.push_str(\"\\n\");\n    declarations\n}\n\nfn declare_wires_for_ports(ports: &[(String, Option<String>, String)], is_systemverilog: bool) -> String {\n    let mut declarations = String::new();\n    for (direction, bus_width, name) in ports {\n        let wire_type = if is_systemverilog { \"logic\" } else { \"reg\" };\n        let declaration = match bus_width {\n            Some(width) => format!(\"    {} {} {};\\n\", if direction == \"input\" { wire_type } else { \"wire\" }, width, name),\n            None => format!(\"    {} {};\\n\", if direction == \"input\" { wire_type } else { \"wire\" }, name),\n        };\n        declarations.push_str(&declaration);\n    }\n    declarations.push_str(\"\\n\");\n    declarations\n}\n\nfn instantiate_module(module_name: &str, ports: &[(String, Option<String>, String)], parameters: &[(String, String)]) -> String {\n    let mut instantiation = String::new();\n    if !parameters.is_empty() {\n        instantiation.push_str(&format!(\"    {} #(\\n\", module_name));\n        for (i, (name, _)) in parameters.iter().enumerate() {\n            instantiation.push_str(&format!(\"        .{}({}){}\\n\", name, name, if i < parameters.len() - 1 { \",\" } else { \"\" }));\n        }\n        instantiation.push_str(\"    ) uut (\\n\");\n    } else {\n        instantiation.push_str(&format!(\"    {} uut (\\n\", module_name));\n    }\n    for (i, (_, _, name)) in ports.iter().enumerate() {\n        instantiation.push_str(&format!(\"        .{}({}){}\\n\", name, name, if i < ports.len() - 1 { \",\" } else { \"\" }));\n    }\n    instantiation.push_str(\"    );\\n\\n\");\n    instantiation\n}\n\nfn generate_clock(ports: &[(String, Option<String>, String)]) -> String {\n    if let Some((_, _, clock_name)) = ports.iter().find(|(_, _, name)| name.to_lowercase().contains(\"clk\")) {\n        format!(\"    localparam CLOCK_PERIOD = 10;\\n    initial begin\\n        {} = 0;\\n        forever #(CLOCK_PERIOD/2) {} = ~{};\\n    end\\n\\n\", clock_name, clock_name, clock_name)\n    } else {\n        String::new()\n    }\n}\n\nfn generate_initial_block(ports: &[(String, Option<String>, String)], is_systemverilog: bool) -> String {\n    let mut initial_block = String::from(\"    initial begin\\n        $dumpfile(\\\"waveform.vcd\\\");\\n        $dumpvars(0, uut);\\n\\n\");\n    for (direction, bus_width, name) in ports {\n        if direction == \"input\" && !name.to_lowercase().contains(\"clk\") {\n            match bus_width {\n                Some(width) => {\n                    let width_value = width.trim_matches(|c| c == '[' || c == ']').split(':').next().unwrap_or(\"0\");\n                    if is_systemverilog {\n                        initial_block.push_str(&format!(\"        {} = '{{{} {{1'b0}}}};\\n\", name, width_value));\n                    } else {\n                        initial_block.push_str(&format!(\"        {} = {{'b0}};\\n\", name));\n                    }\n                },\n                None => initial_block.push_str(&format!(\"        {} = 1'b0;\\n\", name)),\n            }\n        }\n    }\n    initial_block.push_str(\"\\n        #100;\\n\\n        #1000;\\n        $finish;\\n    end\\n\\n\");\n    initial_block\n}\n\nfn generate_check_outputs_task(ports: &[(String, Option<String>, String)], is_systemverilog: bool) -> String {\n    let mut task = String::from(\"    task check_outputs;\\n\");\n    if is_systemverilog {\n        task.push_str(\"        input string test_case;\\n\");\n    } else {\n        task.push_str(\"        input [8*32-1:0] test_case;\\n\");\n    }\n    task.push_str(\"        begin\\n            $display(\\\"Checking outputs for %s\\\", test_case);\\n\");\n    for (direction, bus_width, name) in ports {\n        if direction == \"output\" {\n            let format_specifier = match bus_width {\n                Some(_) => \"%h\",\n                None => \"%b\",\n            };\n            task.push_str(&format!(\"            $display(\\\"  {} = {}\\\", {});\\n\", name, format_specifier, name));\n        }\n    }\n    task.push_str(\"        end\\n    endtask\\n\\n\");\n    task\n}\n\nfn remove_comments_from_file(file_path: &str) -> Result<()> {\n    let file = File::open(file_path)\n        .context(format!(\"Failed to open file: {}\", file_path))?;\n    let reader = BufReader::new(file);\n    let mut content = String::new();\n\n    for line in reader.lines() {\n        let line = line?;\n        let parts: Vec<&str> = line.split(\"//\").collect();\n        if !parts.is_empty() {\n            let code_part = parts[0].trim_end();\n            if !code_part.is_empty() {\n                content.push_str(code_part);\n                content.push('\\n');\n            }\n        }\n    }\n\n    fs::write(file_path, content)\n        .context(format!(\"Failed to write updated content to file: {}\", file_path))?;\n\n    Ok(())\n}\n\npub fn compile_verilog(verilog_files: &Vec<String>) -> Result<PathBuf> {\n    println!(\"Compiling Verilog files...\");\n\n    let first_file = &verilog_files[0];\n    let output_dir = Path::new(first_file).parent().unwrap();\n    let random_output_name = generate_random_output_name();\n    let output_path = output_dir.join(&random_output_name);\n    let command_status = run_iverilog_command(output_path.to_str().unwrap(), verilog_files)?;\n\n    if !command_status.success() {\n        return Err(anyhow::anyhow!(\"Failed to compile Verilog files. Please check your Verilog code for syntax errors.\"));\n    }\n\n    if !output_path.exists() {\n        return Err(anyhow::anyhow!(\"Output binary not found: {:?}. Compilation may have failed silently.\", output_path));\n    }\n    println!(\"Compiled output: {:?}\", output_path);\n    Ok(output_path)\n}\n\nfn generate_random_output_name() -> String {\n    std::iter::repeat_with(fastrand::alphanumeric)\n        .take(10)\n        .collect()\n}\n\nfn run_iverilog_command(output_name: &str, verilog_files: &[String]) -> Result<std::process::ExitStatus> {\n    let mut command = Command::new(\"iverilog\");\n    command.arg(\"-o\").arg(output_name);\n    for file in verilog_files {\n        command.arg(file);\n    }\n    command.status()\n        .context(\"Failed to execute Icarus Verilog compilation. Please ensure Icarus Verilog is installed and accessible.\")\n}\n\npub fn run_simulation(output_path: &PathBuf) -> Result<()> {\n    println!(\"Running simulation...\");\n\n    let current_dir = env::current_dir()\n        .context(\"Failed to get current directory. Please check your file system permissions.\")?;\n    \n    let binary_path: PathBuf = current_dir.join(output_path);\n\n    let status = Command::new(&binary_path)\n        .status()\n        .context(format!(\"Failed to execute simulation. Please ensure the binary at {:?} is executable.\", binary_path))?;\n\n    if !status.success() {\n        eprintln!(\"Warning: Simulation completed with non-zero exit status. This may indicate errors in your Verilog code.\");\n    } else {\n        println!(\"Simulation completed successfully.\");\n    }\n\n    if let Err(e) = fs::remove_file(&binary_path) {\n        eprintln!(\"Warning: Failed to remove temporary binary file: {}. You may want to delete it manually.\", e);\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "src/cmd/synth.rs",
    "content": "use anyhow::{Result, Context};\nuse std::path::PathBuf;\nuse std::process::Command;\nuse std::fs::File;\nuse std::io::Write;\n\nuse crate::cmd::{Execute, Synth};\n\nimpl Execute for Synth {\n    async fn execute(&self) -> Result<()> {\n        synthesize_design(\n            &self.top_module_path,\n            self.riscv,\n            self.core_path.as_ref(),\n            &self.board,\n            self.gen_yosys_script\n        )\n    }\n}\n\nfn synthesize_design(\n    top_module_path: &str,\n    riscv: bool,\n    core_path: Option<&String>,\n    board: &Option<String>,\n    gen_yosys_script: bool\n) -> Result<()> {\n    let top_module_path = PathBuf::from(top_module_path);\n    let (input_file, module_name, parent_dir, _) = extract_path_info(&top_module_path);\n    \n    let script_content = match board {\n        Some(board) if board.to_lowercase() == \"xilinx\" => {\n            let board_name = \"artix7\";\n            let output_file = format!(\"{}/{}_{}_{}_synth.v\", parent_dir, module_name, board_name, \"xilinx\");\n            generate_xilinx_script_content(&input_file, riscv, core_path.cloned(), &module_name, &output_file)?\n        },\n        None => {\n            let output_file = format!(\"{}/{}_synth.v\", parent_dir, module_name);\n            generate_yosys_script_content(&input_file, &module_name, &output_file)\n        },\n        Some(other) => {\n            return Err(anyhow::anyhow!(\"Unsupported board: {}\", other));\n        }\n    };\n\n    if gen_yosys_script {\n        let script_file = PathBuf::from(&parent_dir).join(format!(\"{}_synth_script.ys\", module_name));\n        write_script_to_file(&script_file, &script_content)?;\n        println!(\"Yosys script generated at: {:?}\", script_file);\n    }\n\n    run_yosys_with_script_content(&script_content)?;\n    println!(\"Synthesis completed successfully.\");\n    Ok(())\n}\n\nfn extract_path_info(top_module_path: &PathBuf) -> (String, String, String, String) {\n    let input_file = top_module_path.to_str().unwrap().to_string();\n    let top_module = top_module_path.file_stem().unwrap().to_str().unwrap().to_string();\n    let parent_dir = top_module_path.parent().unwrap().to_string_lossy().to_string();\n    let output_file = format!(\"{}/{}_synth.v\", parent_dir, top_module);\n    (input_file, top_module, parent_dir, output_file)\n}\n\nfn generate_yosys_script_content(input_file: &str, top_module: &str, output_file: &str) -> String {\n    format!(\n        r#\"\n# Read the Verilog file\nread_verilog {}\n\n# Synthesize the design\nsynth -top {}\n\n# Optimize the design\nopt\n\n# Write the synthesized design\nwrite_verilog {}\n        \"#,\n        input_file,\n        top_module,\n        output_file\n    )\n}\n\nfn generate_xilinx_script_content(top_module_path_str: &str, riscv: bool, core_path: Option<String>, module_name: &str, output_file: &str) -> Result<String> {\n    let mut script_content = format!(\n        r#\"\n# Read the SystemVerilog file\nread_verilog -sv {top_module_path_str}\n\"#\n    );\n\n    if riscv {\n        if let Some(core_path) = core_path {\n            script_content.push_str(&format!(\n                r#\"\n# Read the RISC-V core\nread_verilog -sv {core_path}\n\"#\n            ));\n        } else {\n            return Err(anyhow::anyhow!(\"RISC-V core path is required when riscv flag is set\"));\n        }\n    }\n\n    script_content.push_str(&format!(\n        r#\"\n# Synthesize for Xilinx 7 series (Artix-7)\nsynth_xilinx -top {module_name} -family xc7\n\n# Optimize the design\nopt\n\n# Map to Xilinx 7 series cells\nabc -lut 6\n\n# Clean up\nclean\n\n# Write the synthesized design to a Verilog file\nwrite_verilog {output_file}\n\nwrite_edif {output_file}.edif\n\n# Print statistics\nstat\n\"#\n    ));\n\n    Ok(script_content)\n}\n\nfn write_script_to_file(script_file: &PathBuf, script_content: &str) -> Result<()> {\n    let mut file = File::create(script_file)?;\n    file.write_all(script_content.as_bytes())?;\n    Ok(())\n}\n\nfn run_yosys_with_script_content(script_content: &str) -> Result<()> {\n    let output = Command::new(\"yosys\")\n        .arg(\"-p\")\n        .arg(script_content)\n        .output()\n        .context(\"Failed to execute Yosys\")?;\n\n    println!(\"Yosys output:\");\n    println!(\"{}\", String::from_utf8_lossy(&output.stdout));\n\n    if !output.status.success() {\n        let error_message = String::from_utf8_lossy(&output.stderr);\n        return Err(anyhow::anyhow!(\"Yosys synthesis failed: {}\", error_message));\n    }\n\n    Ok(())\n}"
  },
  {
    "path": "src/cmd/update.rs",
    "content": "use anyhow::Result;\n\nuse crate::cmd::{Execute, Update};\nuse crate::cmd::include::get_head_commit_hash;\nuse crate::toml::{get_repo_links, add_top_module, remove_top_module};\nuse imara_diff::intern::InternedInput;\nuse imara_diff::{diff, Algorithm, UnifiedDiffBuilder};\n\nimpl Execute for Update {\n    async fn execute(&self) -> Result<()> {\n        let module_path = &self.module_path;\n        println!(\"Updating module '{}'\", module_path);\n        update_module(module_path, self.commit.as_deref())\n    }\n}\n\nfn update_module(module_path: &str, commit: Option<&str>) -> Result<()> {\n    let repo_links = get_repo_links(module_path);\n    if repo_links.is_empty() {\n        return Err(anyhow::anyhow!(\"No repositories found for module '{}'\", module_path));\n    }\n\n    let chosen_repo = if repo_links.len() == 1 {\n        repo_links.into_iter().next().unwrap()\n    } else {\n        println!(\"Multiple repositories found for module '{}'. Please choose one:\", module_path);\n        for (index, link) in repo_links.iter().enumerate() {\n            println!(\"{}. {}\", index + 1, link);\n        }\n        let mut choice = String::new();\n        std::io::stdin().read_line(&mut choice)?;\n        let index: usize = choice.trim().parse()?;\n        repo_links.into_iter().nth(index - 1)\n            .ok_or_else(|| anyhow::anyhow!(\"Invalid choice\"))?\n    };\n\n    let head_commit_hash = get_head_commit_hash(&chosen_repo).unwrap();\n    let commit_hash = commit.unwrap_or(&head_commit_hash);\n\n    println!(\"Updating module '{}' to commit '{}'\", module_path, commit_hash);\n    let old_contents = std::fs::read_to_string(module_path)?;\n    remove_top_module(&chosen_repo, module_path)?;\n    add_top_module(&chosen_repo, module_path, commit_hash)?;\n    let new_contents = std::fs::read_to_string(module_path)?;\n    println!(\"Module '{}' updated to commit '{}'\", module_path, commit_hash);\n\n    display_diff(&old_contents, &new_contents);\n\n    Ok(())\n}\n\nfn display_diff(old_contents: &str, new_contents: &str) {\n    let input = InternedInput::new(old_contents, new_contents);\n    let diff_output = diff(\n        Algorithm::Histogram,\n        &input,\n        UnifiedDiffBuilder::new(&input)\n    );\n\n    println!(\"Diff:\\n{}\", diff_output);\n}"
  },
  {
    "path": "src/cmd/upgrade.rs",
    "content": "use anyhow::Result;\nuse std::process::Command;\n\nuse crate::cmd::{Execute, Upgrade};\nuse crate::config_man::set_version;\n\nimpl Execute for Upgrade {\n    async fn execute(&self) -> Result<()> {\n        println!(\"Upgrading VPM...\");\n        upgrade_vpm()?;\n        let version = get_latest_version()?;\n        if !version.is_empty() {\n            set_version(&version)?;\n        }\n\n        println!(\"VPM upgrade completed successfully.\");\n        Ok(())\n    }\n}\n\nfn upgrade_vpm() -> Result<()> {\n    if cfg!(unix) {\n        let output = Command::new(\"sh\")\n            .arg(\"-c\")\n            .arg(\"curl -sSfL https://raw.githubusercontent.com/getinstachip/vpm-internal/main/install.sh | sh\")\n            .output()?;\n\n        if !output.status.success() {\n            return Err(anyhow::anyhow!(\"Upgrade command failed\"));\n        }\n    } else if cfg!(windows) {\n        println!(\"To upgrade VPM on Windows, please follow these steps:\");\n        println!(\"1. Visit https://github.com/getinstachip/vpm/releases/latest\");\n        println!(\"2. Download the appropriate .exe file for your system\");\n        println!(\"3. Run the downloaded .exe file to complete the upgrade\");\n        return Ok(());\n    } else {\n        return Err(anyhow::anyhow!(\"Unsupported operating system\"));\n    }\n\n    Ok(())\n}\n\nfn get_latest_version() -> Result<String> {\n    let output = Command::new(\"git\")\n        .arg(\"describe \")\n        .arg(\"--tags\")\n        .arg(\"--abbrev=0\")\n        .output()?;\n    Ok(String::from_utf8(output.stdout)?)\n}"
  },
  {
    "path": "src/config_man.rs",
    "content": "use anyhow::Result;\nuse directories::ProjectDirs;\nuse rand::RngCore;\nuse reqwest::Client;\nuse serde_json::json;\nuse std::fs;\nuse std::path::PathBuf;\nuse toml_edit::{DocumentMut, Item, Value, Table};\nuse uuid::Uuid;\n\nuse ring::aead::{self, Aad, LessSafeKey, Nonce};\nuse base64::{Engine as _, engine::general_purpose};\nuse ring::aead::UnboundKey;\nuse sha2::{Digest, Sha256};\nuse sys_info;\n\nconst POSTHOG_API_KEY: Option<&str> = option_env!(\"POSTHOG_API_KEY\");\nconst DOCS_KEY: Option<&str> = option_env!(\"DOCS_KEY\");\n\npub async fn send_event(command: String) -> Result<()> {\n    if get_analytics()? {\n        let uuid = get_uuid()?;\n        let version = env!(\"CARGO_PKG_VERSION\").to_string();\n        let api_key = POSTHOG_API_KEY.expect(\"POSTHOG_API_KEY environment variable not set\").to_string();\n        \n        let client = Client::new();\n        let payload = json!({\n            \"api_key\": api_key,\n            \"event\": \"user_action\",\n            \"distinct_id\": uuid,\n            \"properties\": {\n                \"command\": command,\n                \"version\": version\n            }\n        });\n\n        let _response = client.post(\"https://us.i.posthog.com/capture/\")\n            .json(&payload)\n            .send()\n            .await?;\n\n        // if !response.status().is_success() {\n        //     eprintln!(\"Failed to send event to PostHog: {}\", response.status());\n        // }\n    }\n    Ok(())\n}\n\npub fn get_config_path() -> Option<PathBuf> {\n    ProjectDirs::from(\"com\", \"Instachip\", \"vpm\")\n        .map(|proj_dirs| proj_dirs.config_dir().to_path_buf())\n        .map(|mut path| {\n            path.push(\"config.toml\");\n            path\n        })\n}\n\npub fn create_config() -> Result<()> {\n    let config_path = get_config_path().unwrap();\n    if !config_path.exists() {\n        if let Some(parent) = config_path.parent() {\n            fs::create_dir_all(parent)?;\n        }\n        fs::File::create(&config_path)?;\n    }\n    fs::write(config_path.clone(), \"\").expect(\"Failed to create config.toml\");\n    let contents = fs::read_to_string(config_path.clone())?;\n    let mut config_doc = contents.parse::<DocumentMut>().expect(\"Failed to parse config.toml\");\n\n    config_doc.insert(\"user\", Item::Table(Table::new()));\n    let user_table = config_doc[\"user\"].as_table_mut().unwrap();\n    user_table.insert(\"uuid\", Item::Value(Value::from(create_uuid()?)));\n    user_table.insert(\"os\", Item::Value(Value::from(std::env::consts::OS)));\n    user_table.insert(\"arch\", Item::Value(Value::from(std::env::consts::ARCH)));\n\n    config_doc.insert(\"tool\", Item::Table(Table::new()));\n    let tool_table = config_doc[\"tool\"].as_table_mut().unwrap();\n    tool_table.insert(\"version\", Item::Value(Value::from(env!(\"CARGO_PKG_VERSION\"))));\n\n    config_doc.insert(\"options\", Item::Table(Table::new()));\n    let options_table = config_doc[\"options\"].as_table_mut().unwrap();\n    options_table.insert(\"analytics\", Item::Value(Value::from(true)));\n\n    config_doc.insert(\"metrics\", Item::Table(Table::new()));\n    let metrics_table = config_doc[\"metrics\"].as_table_mut().unwrap();\n    metrics_table.insert(\"docs_count\", Item::Value(Value::from(0)));\n    encrypt_docs_count(0)?;\n\n    fs::write(config_path, config_doc.to_string()).expect(\"Failed to write config.toml\");\n    Ok(())\n}\n\nfn create_uuid() -> Result<String> {\n    let uuid = Uuid::now_v7().to_string();\n    let os = sys_info::os_type().unwrap_or_default();\n    let release = sys_info::os_release().unwrap_or_default();\n    let arch = std::env::consts::ARCH.to_string();\n    let cpu_num = sys_info::cpu_num().unwrap_or_default().to_string();\n    let cpu_speed = sys_info::cpu_speed().unwrap_or_default().to_string();\n    let mem_total = sys_info::mem_info().unwrap_or(sys_info::MemInfo { total: 0, free: 0, buffers: 0, cached: 0, swap_total: 0, swap_free: 0, avail: 0 }).total.to_string();\n    let hostname = sys_info::hostname().unwrap_or_default();\n    let timezone = std::env::var(\"TZ\").unwrap_or_else(|_| \"Unknown\".to_string());\n\n    let mut hasher = Sha256::new();\n    hasher.update(uuid);\n    hasher.update(os);\n    hasher.update(release);\n    hasher.update(arch);\n    hasher.update(cpu_num);\n    hasher.update(cpu_speed);\n    hasher.update(mem_total);\n    hasher.update(hostname);\n    hasher.update(timezone);\n    let hash = hasher.finalize();\n    Ok(format!(\"{:x}\", hash))\n}\n\nfn get_uuid() -> Result<String> {\n    let config_path = get_config_path().unwrap();\n    if !config_path.exists() {\n        create_config()?;\n    }\n    let contents = fs::read_to_string(config_path)?;\n    let config = contents.parse::<DocumentMut>().expect(\"Failed to parse config.toml\");\n    Ok(config[\"user\"][\"uuid\"].as_str().unwrap().to_string())\n}\n\npub fn set_analytics(value: bool) -> Result<()> {\n    let config_path = get_config_path().unwrap();\n    if !config_path.exists() {\n        create_config()?;\n    }\n    let config = fs::read_to_string(config_path.clone())?;\n    let mut config_doc = config.parse::<DocumentMut>().expect(\"Failed to parse config.toml\");\n    config_doc[\"options\"][\"analytics\"] = Item::Value(Value::from(value));\n    fs::write(config_path, config_doc.to_string()).expect(\"Failed to write config.toml\");\n    Ok(())\n}\n\nfn get_analytics() -> Result<bool> {\n    let config_path = get_config_path().unwrap();\n    if !config_path.exists() {\n        create_config()?;\n    }\n    let config = fs::read_to_string(config_path.clone())?;\n    let config_doc = config.parse::<DocumentMut>().expect(\"Failed to parse config.toml\");\n    Ok(config_doc[\"options\"][\"analytics\"].as_bool().unwrap())\n}\n\npub fn set_version(version: &str) -> Result<()> {\n    let config_path = get_config_path().unwrap();\n    if !config_path.exists() {\n        create_config()?;\n    }\n    let config = fs::read_to_string(config_path.clone())?;\n    let mut config_doc = config.parse::<DocumentMut>().expect(\"Failed to parse config.toml\");\n    config_doc[\"tool\"][\"version\"] = Item::Value(Value::from(version));\n    fs::write(config_path, config_doc.to_string()).expect(\"Failed to write config.toml\");\n    Ok(())\n}   \n\npub fn decrypt_docs_count() -> Result<u8> {\n    let config_path = get_config_path().ok_or(anyhow::anyhow!(\"Failed to get config path\"))?;\n    if !config_path.exists() {\n        create_config()?;\n    }\n\n    let config = fs::read_to_string(config_path)?;\n    let config_doc = config.parse::<DocumentMut>().expect(\"Failed to parse config.toml\");\n    let encrypted_docs_base64 = config_doc[\"metrics\"][\"docs_count\"]\n        .as_str()\n        .ok_or_else(|| anyhow::anyhow!(\"docs_count not found in config\"))?;\n    let encrypted_docs = general_purpose::STANDARD\n        .decode(encrypted_docs_base64)\n        .map_err(|e| anyhow::anyhow!(\"Failed to decode docs count: {}\", e))?;\n    // Get the key from environment variable\n    let docs_key_str = DOCS_KEY.ok_or_else(|| anyhow::anyhow!(\"DOCS_KEY is not set\"))?;\n    let key_bytes = hex::decode(docs_key_str).map_err(|e| anyhow::anyhow!(\"Invalid DOCS_KEY format: {}\", e))?;\n\n    // Create an AEAD key\n    let unbound_key =\n        UnboundKey::new(&aead::AES_256_GCM, &key_bytes).map_err(|_| anyhow::anyhow!(\"Invalid key\"))?;\n    let key = LessSafeKey::new(unbound_key);\n\n    // Extract nonce and ciphertext\n    if encrypted_docs.len() < 12 {\n        return Err(anyhow::anyhow!(\"Ciphertext too short\"));\n    }\n    let (nonce_bytes, ciphertext_and_tag) = encrypted_docs.split_at(12);\n    let nonce = Nonce::try_assume_unique_for_key(nonce_bytes).map_err(|_| anyhow::anyhow!(\"Invalid nonce\"))?;\n\n    // Prepare mutable buffer for decryption\n    let mut in_out = ciphertext_and_tag.to_vec();\n\n    // Decrypt the data\n    key.open_in_place(nonce, Aad::empty(), &mut in_out)\n        .map_err(|_| anyhow::anyhow!(\"Decryption failed\"))?;\n\n    // Convert decrypted data to string\n    let decrypted_str =\n        std::str::from_utf8(&in_out).map_err(|_| anyhow::anyhow!(\"Invalid UTF-8 in decrypted data\"))?;\n    let docs_count: u8 = decrypted_str.parse().map_err(|_| anyhow::anyhow!(\"Failed to parse decrypted data\"))?;\n\n    Ok(docs_count)\n}\n\npub fn encrypt_docs_count(docs_count: u8) -> Result<()> {\n    // Convert the docs_count to a string and then to bytes\n    let docs_count_bytes = docs_count.to_string().into_bytes();\n    // Get the key from the environment variable\n    let docs_key_str = DOCS_KEY.ok_or_else(|| anyhow::anyhow!(\"DOCS_KEY is not set\"))?;\n    let key_bytes = hex::decode(docs_key_str).map_err(|e| anyhow::anyhow!(\"Invalid DOCS_KEY format: {}\", e))?;\n\n    // Create an AEAD key\n    let unbound_key =\n        UnboundKey::new(&aead::AES_256_GCM, &key_bytes).map_err(|_| anyhow::anyhow!(\"Invalid key\"))?;\n    let key = LessSafeKey::new(unbound_key);\n\n    // Generate a random nonce\n    let mut nonce_bytes = [0u8; 12];\n    rand::rngs::OsRng.fill_bytes(&mut nonce_bytes);\n    let nonce = Nonce::assume_unique_for_key(nonce_bytes);\n\n    // Prepare buffer for encryption (data + space for the tag)\n    let mut in_out = docs_count_bytes;\n    in_out.extend_from_slice(&[0u8; 16]);\n\n    // Encrypt the data\n    key.seal_in_place_append_tag(nonce, Aad::empty(), &mut in_out)\n        .map_err(|_| anyhow::anyhow!(\"Encryption failed\"))?;\n\n    // Prepend nonce to the ciphertext\n    let mut encrypted_data = nonce_bytes.to_vec();\n    encrypted_data.extend_from_slice(&in_out);\n\n    // Encode the encrypted data to base64\n    let encrypted_base64 = general_purpose::STANDARD.encode(encrypted_data);\n\n    let config_path = get_config_path().unwrap();\n    let config = fs::read_to_string(config_path.clone())?;\n    let mut config_doc = config.parse::<DocumentMut>().expect(\"Failed to parse config.toml\");\n    config_doc[\"metrics\"][\"docs_count\"] = Item::Value(Value::from(encrypted_base64));\n    fs::write(config_path, config_doc.to_string()).expect(\"Failed to write config.toml\");\n\n    Ok(())\n}"
  },
  {
    "path": "src/error.rs",
    "content": "use core::fmt::Display;\nuse std::fmt::{self, Formatter};\n\n/// Custom error type for early exit\n#[derive(Debug)]\npub struct SilentExit {\n    pub code: u8,\n}\n\nimpl Display for SilentExit {\n    fn fmt(&self, _: &mut Formatter<'_>) -> fmt::Result {\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/main.rs",
    "content": "mod cmd;\nmod error;\nmod toml;\nmod config_man;\n\nuse std::env;\nuse std::io::{self, Write};\nuse std::process::ExitCode;\nuse std::fs;\n\nuse clap::Parser;\n\nuse crate::cmd::{Cmd, Execute};\nuse crate::error::SilentExit;\nuse crate::config_man::{get_config_path, create_config};\n\n#[tokio::main]\npub async fn main() -> ExitCode {\n    // Forcibly disable backtraces.\n    env::remove_var(\"RUST_LIB_BACKTRACE\");\n    env::remove_var(\"RUST_BACKTRACE\");\n\n    let flag_file = get_config_path().unwrap().with_file_name(\".vpm_welcome_shown\");\n    if !flag_file.exists() {\n        create_config().unwrap();\n\n        println!(\"Welcome to vpm!\");\n        println!(\"We collect anonymous usage data to improve the tool.\");\n        println!(\"The following information will be collected:\");\n        println!(\" - The version of vpm you are using\");\n        println!(\" - Which commands you run and when (not including arguments, input, or output)\");\n        println!(\"No personal information will be collected.\");\n        println!(\"To opt-out, run `vpm config --analytics false`. You may change this at any time.\\n\");\n        println!(\"Rerun your command to accept and continue.\");\n\n        fs::write(flag_file, \"\").unwrap();\n        return ExitCode::SUCCESS;\n    }\n\n    match Cmd::parse().execute().await {\n        Ok(()) => ExitCode::SUCCESS,\n        Err(e) => match e.downcast::<SilentExit>() {\n            Ok(SilentExit { code }) => code.into(),\n            Err(e) => {\n                _ = writeln!(io::stderr(), \"vpm: {e:?}\");\n                ExitCode::FAILURE\n            }\n        },\n    }\n}\n"
  },
  {
    "path": "src/toml.rs",
    "content": "use serde::{Deserialize, Serialize};\nuse std::fs::{OpenOptions, read_to_string};\nuse std::io::Write;\nuse std::path::Path;\nuse std::collections::HashSet;\nuse anyhow::Result;\nuse toml_edit::{Array, DocumentMut, InlineTable, Item, Table, Value};\n\n\n#[derive(Serialize, Deserialize, Debug)]\nstruct Package {\n    name: String,\n    version: String,\n    authors: Vec<String>,\n    description: String,\n    license: String,\n}\n\n#[derive(Debug)]\nstruct VpmToml {\n    toml_doc: DocumentMut,\n}\n\nimpl Default for Package {\n    fn default() -> Self {\n        Package {\n            name: \"my-vpm-package\".to_string(),\n            version: \"0.1.0\".to_string(),\n            authors: vec![\"<author-name> <author-email>\".to_string()],\n            description: \"A vpm package\".to_string(),\n            license: \"LicenseRef-LICENSE\".to_string(),\n        }\n    }\n}\n\nimpl VpmToml {    \n    pub fn from(filepath: &str) -> Self {\n        if !Path::new(filepath).exists() {\n            let mut initial_doc = DocumentMut::new();\n            initial_doc[\"package\"] = Item::Table(Table::new());\n            initial_doc[\"package\"][\"name\"] = Item::Value(Value::from(Package::default().name));\n            initial_doc[\"package\"][\"version\"] = Item::Value(Value::from(Package::default().version));\n            initial_doc[\"package\"][\"authors\"] = Item::Value(Value::from(Array::from(Package::default().authors.iter().map(|s| Value::from(s.to_string())).collect())));\n            initial_doc[\"package\"][\"description\"] = Item::Value(Value::from(Package::default().description));\n            initial_doc[\"package\"][\"license\"] = Item::Value(Value::from(Package::default().license));\n\n            initial_doc[\"dependencies\"] = Item::Table(Table::new());\n\n            let mut file = OpenOptions::new()\n                .write(true)\n                .create(true)\n                .truncate(true)\n                .open(filepath)\n                .expect(\"Failed to create vpm.toml\");\n            file.write_all(initial_doc.to_string().as_bytes()).expect(\"Failed to write to vpm.toml\");\n        }\n\n        let toml_content = read_to_string(filepath).expect(\"Failed to read vpm.toml\");\n        Self {\n            toml_doc: toml_content.parse::<DocumentMut>().expect(\"Failed to parse vpm.toml\")\n        }\n    }\n\n    pub fn get_dependencies(&self) -> Option<&Table> {\n        self.toml_doc[\"dependencies\"].as_table()\n    }\n\n    pub fn add_dependency(&mut self, git: &str) {\n        self.toml_doc[\"dependencies\"][git] = Item::Value(Value::Array(Array::new()));\n    }\n\n    pub fn add_top_module(&mut self, repo_link: &str, module_name: &str, commit: &str) {\n        let array = self.toml_doc[\"dependencies\"][repo_link].as_array_mut().unwrap();\n        if !array.iter().any(|m| m.as_inline_table().unwrap().get(\"top_module\").unwrap().as_str().unwrap() == module_name) {\n            let new_entry = Value::InlineTable({\n                let mut table = InlineTable::new();\n                table.insert(\"top_module\".to_string(), Value::from(module_name));\n                table.insert(\"commit_hash\".to_string(), Value::from(commit.to_string()));\n                table\n            });\n            array.push(new_entry);\n        }\n    }\n\n    pub fn remove_dependency(&mut self, git: &str) {\n        if let Some(dependencies) = self.toml_doc[\"dependencies\"].as_table_mut() {\n            dependencies.remove(git);\n        }\n    }\n\n    pub fn remove_top_module(&mut self, repo_link: &str, module_name: &str) {\n    if let Some(dependencies) = self.toml_doc[\"dependencies\"].as_table_mut() {\n        if let Some(modules) = dependencies.get_mut(repo_link).and_then(|v| v.as_array_mut()) {\n            modules.retain(|m| {\n                if let Some(table) = m.as_inline_table() {\n                    if let Some(top_module) = table.get(\"top_module\").and_then(|v| v.as_str()) {\n                        return top_module != module_name;\n                    }\n                }\n                true\n            });\n\n            // If the array is empty after removal, remove the entire dependency\n            if modules.is_empty() {\n                dependencies.remove(repo_link);\n            }\n        }\n    }\n    }\n\n    pub fn write_to_file(&self, filepath: &str) -> Result<()> {\n        let toml_content = self.toml_doc.to_string();\n        let mut formatted_content = String::new();\n        for line in toml_content.lines() {\n            if !line.trim().contains(\"}, \") {\n                formatted_content.push_str(line);\n            } else {\n                let indent_level = line.chars().take_while(|&c| c != '{').count();\n                formatted_content.push_str(&line.replace(\"}, \", &format!(\"}},\\n{}\", \" \".repeat(indent_level))));\n            }\n            formatted_content.push('\\n');\n        }\n\n        let mut file = OpenOptions::new()\n            .write(true)\n            .create(true)\n            .truncate(true)\n            .open(filepath)\n            .expect(\"Failed to open vpm.toml\");\n        file.write_all(formatted_content.as_bytes()).expect(\"Failed to write to vpm.toml\");\n        Ok(())\n    }\n\n    pub fn get_repo_links(&self, module_name: &str) -> HashSet<String> {\n        let mut repo_links = HashSet::new();\n        if let Some(dependencies) = self.toml_doc[\"dependencies\"].as_table() {\n            for (repo_link, dependency) in dependencies.iter() {\n                if let Some(top_modules) = dependency.as_array() {\n                    if top_modules.iter().any(|m| m.as_inline_table().unwrap().get(\"top_module\").unwrap().as_str().unwrap() == module_name) {\n                        repo_links.insert(repo_link.to_string());\n                    }\n                }\n            }\n        }\n        repo_links\n    }\n}\n\npub fn add_dependency(git: &str) -> Result<()> {\n    let mut vpm_toml = VpmToml::from(\"vpm.toml\");\n    if !vpm_toml.get_dependencies().unwrap().contains_key(git) {\n        vpm_toml.add_dependency(git);\n        vpm_toml.write_to_file(\"vpm.toml\")?;\n    }\n    Ok(())\n}\n\npub fn add_top_module(repo_link: &str, module_path: &str, commit: &str) -> Result<()> {\n    let mut vpm_toml = VpmToml::from(\"vpm.toml\");\n    vpm_toml.add_top_module(repo_link, module_path, commit);\n    vpm_toml.write_to_file(\"vpm.toml\")?;\n    Ok(())\n}\n\nfn remove_dependency(git: &str) -> Result<()> {\n    let mut vpm_toml = VpmToml::from(\"vpm.toml\");\n    vpm_toml.remove_dependency(git);\n    vpm_toml.write_to_file(\"vpm.toml\")?;\n    Ok(())\n}\n\npub fn remove_top_module(repo_link: &str, module_name: &str) -> Result<()> {\n    let mut vpm_toml = VpmToml::from(\"vpm.toml\");\n    vpm_toml.remove_top_module(repo_link, module_name);\n    if let Some(dependencies) = vpm_toml.toml_doc[\"dependencies\"].as_table() {\n        if let Some(modules) = dependencies.get(repo_link).and_then(|v| v.as_array()) {\n            if modules.is_empty() {\n                remove_dependency(repo_link)?;\n            }\n        }\n    }\n    vpm_toml.write_to_file(\"vpm.toml\")?;\n    Ok(())\n}\n\npub fn get_repo_links(module_name: &str) -> HashSet<String> {\n    let vpm_toml = VpmToml::from(\"vpm.toml\");\n    vpm_toml.get_repo_links(module_name)\n}"
  }
]