├── .gitignore ├── .gitattributes ├── src ├── cli │ ├── commands │ │ ├── mod.rs │ │ ├── export.rs │ │ ├── pycharm.rs │ │ ├── channel.rs │ │ └── new.rs │ ├── mod.rs │ └── command.rs ├── sdk │ ├── game_client │ │ └── mod.rs │ ├── mod.rs │ ├── node │ │ ├── windows.rs │ │ ├── linux_or_macos.rs │ │ └── mod.rs │ ├── nvm │ │ ├── linux_or_mac_os │ │ │ ├── install.rs │ │ │ └── mod.rs │ │ ├── windows │ │ │ ├── install.rs │ │ │ └── mod.rs │ │ └── mod.rs │ ├── as3 │ │ ├── mod.rs │ │ └── install.rs │ ├── conda │ │ ├── environment.rs │ │ ├── install.rs │ │ └── mod.rs │ ├── npm │ │ └── mod.rs │ ├── flash_lib │ │ └── mod.rs │ ├── asconfigc │ │ └── mod.rs │ └── game_sources │ │ └── mod.rs ├── main.rs ├── utils │ ├── mod.rs │ ├── convert_pathbuf_to_string.rs │ ├── tmp_dir.rs │ ├── downloader.rs │ ├── command.rs │ ├── convert_to_absolute_path.rs │ ├── copy_directory.rs │ ├── pattern_validator.rs │ ├── extract_archive.rs │ ├── file_template.rs │ └── zip.rs ├── new │ ├── mod.rs │ ├── tests.rs │ └── template.rs ├── config │ ├── asconfig_json.rs │ ├── mod_conf.rs │ ├── settings.rs │ └── mod.rs └── builder │ ├── flash.rs │ ├── python.rs │ └── mod.rs ├── .rustfmt.toml ├── .github └── workflows │ ├── release.yml │ └── rust.yml ├── README.md ├── Cargo.toml ├── LICENSE └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.vscode 3 | /.idea 4 | .DS_Store 5 | /.cargo -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect Text files and perform LF normalization 2 | * Text=auto 3 | -------------------------------------------------------------------------------- /src/cli/commands/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod channel; 2 | pub mod export; 3 | pub mod new; 4 | pub mod pycharm; 5 | -------------------------------------------------------------------------------- /src/sdk/game_client/mod.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | pub struct GameClient { 4 | pub path: PathBuf, 5 | } 6 | 7 | impl From<&PathBuf> for GameClient { 8 | fn from(path: &PathBuf) -> Self { 9 | GameClient { path: path.clone() } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | fn_params_layout = "Compressed" 2 | max_width = 80 3 | edition = "2021" 4 | match_arm_leading_pipes = "Always" 5 | match_block_trailing_comma = true 6 | newline_style = "Unix" 7 | use_try_shorthand = true 8 | reorder_modules = true 9 | reorder_imports = true 10 | remove_nested_parens = true 11 | merge_derives = true 12 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use crate::config::Configs; 2 | 3 | mod builder; 4 | mod cli; 5 | mod config; 6 | mod new; 7 | mod sdk; 8 | mod utils; 9 | 10 | fn main() { 11 | Configs::load().expect("Unable to load wg-mod configs"); 12 | 13 | match cli::run() { 14 | | Err(err) => eprintln!("{}", err.to_string()), 15 | | _ => (), 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /src/sdk/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod as3; 2 | pub mod asconfigc; 3 | pub mod conda; 4 | pub mod flash_lib; 5 | pub mod game_client; 6 | pub mod game_sources; 7 | pub mod node; 8 | pub mod npm; 9 | pub mod nvm; 10 | 11 | type InstallResult = Result<(), String>; 12 | 13 | pub trait Installable { 14 | fn is_installed(&self) -> bool; 15 | fn install(&self) -> InstallResult; 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod command; 2 | pub mod convert_pathbuf_to_string; 3 | pub mod convert_to_absolute_path; 4 | pub mod copy_directory; 5 | pub mod downloader; 6 | pub mod extract_archive; 7 | pub mod file_template; 8 | pub mod pattern_validator; 9 | pub mod tmp_dir; 10 | pub mod zip; 11 | 12 | #[derive(Clone, Debug)] 13 | pub struct Env { 14 | pub key: String, 15 | pub value: String, 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | release: 5 | types: [ published ] 6 | 7 | env: 8 | CARGO_TERM_COLOR: always 9 | 10 | jobs: 11 | publish: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - name: Login to crates.io 18 | run: cargo login ${{ secrets.CRATES_IO_TOKEN }} 19 | 20 | - name: Publish the crate 21 | run: cargo publish -------------------------------------------------------------------------------- /src/new/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod template; 2 | mod tests; 3 | 4 | use crate::utils::file_template; 5 | use std::path::PathBuf; 6 | 7 | #[derive(thiserror::Error, Debug)] 8 | pub enum Error { 9 | #[error("Unable to create this template file")] 10 | FileTemplateError(#[from] file_template::Error), 11 | } 12 | 13 | #[derive(Debug, Clone, PartialEq)] 14 | pub struct NewArgs { 15 | pub name: String, 16 | pub directory: PathBuf, 17 | pub version: String, 18 | pub description: String, 19 | pub package_name: String, 20 | } 21 | -------------------------------------------------------------------------------- /src/utils/convert_pathbuf_to_string.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use std::result; 3 | 4 | #[derive(thiserror::Error, Debug)] 5 | pub enum Error { 6 | #[error("Conversion failed")] 7 | ConversionFailed, 8 | } 9 | 10 | type Result = result::Result; 11 | 12 | pub trait Stringify { 13 | fn to_string(&self) -> Result; 14 | } 15 | 16 | impl Stringify for PathBuf { 17 | fn to_string(&self) -> Result { 18 | Ok(self.to_str().ok_or(Error::ConversionFailed)?.to_string()) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/utils/tmp_dir.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::path::PathBuf; 3 | use tempfile::tempdir; 4 | 5 | #[derive(Debug, thiserror::Error)] 6 | pub enum TempDirError { 7 | #[error("Failed to create temporary directory")] 8 | TempDirError(#[from] io::Error), 9 | } 10 | 11 | pub fn prepare_tmp_directory( 12 | ) -> Result<(impl FnOnce() -> io::Result<()>, PathBuf), TempDirError> { 13 | let tmp_dir = tempdir()?; 14 | let path = tmp_dir.path(); 15 | let path_buf = path.to_path_buf(); 16 | 17 | let close_tmp_dir = move || tmp_dir.close(); 18 | 19 | Ok((close_tmp_dir, path_buf)) 20 | } 21 | -------------------------------------------------------------------------------- /src/utils/downloader.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::{io, result}; 3 | 4 | #[derive(thiserror::Error, Debug)] 5 | pub enum Error { 6 | #[error("An error occurred while downloading the file")] 7 | Fetch(#[from] reqwest::Error), 8 | 9 | #[error("An error occurred while saving the file")] 10 | Io(#[from] io::Error), 11 | } 12 | 13 | type Result = result::Result; 14 | 15 | pub fn download_file(url: &str, path: &str) -> Result<()> { 16 | let mut http_response = reqwest::blocking::get(url)?; 17 | let mut file = File::create(path)?; 18 | 19 | io::copy(&mut http_response, &mut file)?; 20 | Ok(()) 21 | } 22 | -------------------------------------------------------------------------------- /src/utils/command.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::Env; 2 | use std::ffi::OsStr; 3 | use std::process::{Command, Output}; 4 | use std::{io, result}; 5 | 6 | #[derive(thiserror::Error, Debug)] 7 | pub enum Error { 8 | #[error("Failed to execute command")] 9 | ExecutionError(#[from] io::Error), 10 | } 11 | 12 | type Result = result::Result; 13 | 14 | pub fn command>( 15 | command: S, args: Vec<&str>, env: Vec, 16 | ) -> Result { 17 | let mut command = Command::new(command); 18 | command.args(args); 19 | 20 | env.iter().for_each(|env| { 21 | command.env(&env.key, &env.value); 22 | }); 23 | 24 | let out = command.output().map_err(Error::ExecutionError)?; 25 | 26 | Ok(out) 27 | } 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WG modding tools 2 | 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 4 | 5 | Features: 6 | 7 | - Init mod project 8 | - Python builder 9 | 10 | Next features: 11 | 12 | - Mod builder AS3 13 | - WoT debbuger 14 | 15 | Follow me on my discord server [https://discord.gg/XGzqS5Sb9G](https://discord.gg/XGzqS5Sb9G) 16 | 17 | ## Installation 18 | 19 | You must have [Cargo / Rust](https://www.rust-lang.org/tools/install) on your machine. 20 | 21 | ```bash 22 | cargo install wg-mod 23 | ``` 24 | 25 | ## Usage 26 | 27 | Create a new mod directory 28 | 29 | ```bash 30 | wg-mod new 31 | 32 | ``` 33 | 34 | Build a .wotmod file 35 | ```bash 36 | wg-mod build # In mod directory 37 | ``` 38 | -------------------------------------------------------------------------------- /src/cli/mod.rs: -------------------------------------------------------------------------------- 1 | mod command; 2 | pub mod commands; 3 | 4 | use self::{ 5 | command::RunnableCommand, commands::channel::ChannelCommand, 6 | commands::export::ExportCommand, commands::new::NewCommand, 7 | commands::pycharm::PycharmCommand, 8 | }; 9 | 10 | pub fn run() -> Result<(), command::Error> { 11 | let matches = command::command().get_matches(); 12 | 13 | match matches.subcommand() { 14 | | Some(("new", args)) => NewCommand::run(args), 15 | | Some(("export", args)) => ExportCommand::run(args), 16 | | Some(("pycharm", args)) => PycharmCommand::run(args), 17 | | Some(("channel", args)) => ChannelCommand::run(args), 18 | | Some((_, _)) => Err(command::Error::CommandNotImplemented), 19 | | None => Err(command::Error::NoCommandProvided), 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/utils/convert_to_absolute_path.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use std::result; 3 | 4 | #[derive(thiserror::Error, Debug)] 5 | pub enum Error { 6 | #[error("Cannot get absolute path of {0}")] 7 | ConvertAbsolutePathError(PathBuf), 8 | } 9 | 10 | type Result = result::Result; 11 | 12 | pub fn convert_to_absolute_path(path: &PathBuf) -> Result { 13 | let absolute_path = path.canonicalize().map_err(|e| { 14 | eprintln!("{:?}", e); 15 | Error::ConvertAbsolutePathError(path.clone()) 16 | })?; 17 | 18 | let str_path = absolute_path 19 | .to_str() 20 | .ok_or(Error::ConvertAbsolutePathError(path.clone()))?; 21 | 22 | if cfg!(target_os = "windows") { 23 | let path_without_prefix = str_path.replace("\\\\?\\", ""); 24 | Ok(path_without_prefix) 25 | } else { 26 | Ok(str_path.to_string()) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/sdk/node/windows.rs: -------------------------------------------------------------------------------- 1 | use crate::sdk::node; 2 | use crate::sdk::node::Node; 3 | use crate::sdk::npm::NPM; 4 | use crate::utils::command::command; 5 | use std::path::PathBuf; 6 | use std::process::Output; 7 | 8 | pub struct WindowsNode { 9 | node_path: PathBuf, 10 | } 11 | 12 | impl From for WindowsNode { 13 | fn from(node_path: PathBuf) -> Self { 14 | Self { node_path } 15 | } 16 | } 17 | 18 | impl Node for WindowsNode { 19 | fn get_npm(&self) -> NPM { 20 | NPM::from(self.node_path.join("npm.cmd").to_path_buf()) 21 | } 22 | 23 | fn exec(&self, args: Vec<&str>) -> Result { 24 | let node_exec_path = self.node_path.join("node"); 25 | let executable = node_exec_path.as_os_str(); 26 | 27 | command(executable, args, vec![]).map_err(|e| { 28 | eprintln!("{}", e); 29 | node::Error::FailedExecution 30 | }) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wg-mod" 3 | version = "0.11.0" 4 | edition = "2021" 5 | authors = ["Gabriel Hamel "] 6 | repository = "https://github.com/gabrielhamel/wg-mod" 7 | homepage = "https://github.com/gabrielhamel/wg-mod" 8 | description = "A CLI to help people making WorldOfTanks mods" 9 | license = "MIT" 10 | readme = "README.md" 11 | categories = ["command-line-utilities"] 12 | keywords = ["cli", "wot", "WorldOfTanks", "modding"] 13 | 14 | [dependencies] 15 | clap = { version = "4.5.20" } 16 | home = "0.5.9" 17 | thiserror = "1.0.64" 18 | serde = "1.0.210" 19 | serde_derive = "1.0.210" 20 | inquire = "0.7.5" 21 | regex = "1.11.0" 22 | convert_case = "0.6.0" 23 | handlebars = "6.1.0" 24 | serde_json = "1.0.128" 25 | tempfile = "3.13.0" 26 | reqwest = { version = "0.12.8", features = ["stream", "blocking"] } 27 | fs_extra = "1.3.0" 28 | glob = "0.3.1" 29 | zip = "2.2.0" 30 | zip-extensions = "0.8.1" 31 | git2 = "0.19.0" 32 | -------------------------------------------------------------------------------- /src/sdk/node/linux_or_macos.rs: -------------------------------------------------------------------------------- 1 | use crate::sdk::node; 2 | use crate::sdk::node::Node; 3 | use crate::sdk::npm::NPM; 4 | use crate::utils::command::command; 5 | use std::path::PathBuf; 6 | use std::process::Output; 7 | 8 | pub struct LinuxOrMacNode { 9 | node_path: PathBuf, 10 | } 11 | 12 | impl From for LinuxOrMacNode { 13 | fn from(node_path: PathBuf) -> Self { 14 | Self { node_path } 15 | } 16 | } 17 | 18 | impl Node for LinuxOrMacNode { 19 | fn get_npm(&self) -> NPM { 20 | NPM::from(self.node_path.join("bin").join("npm")) 21 | } 22 | 23 | fn exec(&self, args: Vec<&str>) -> Result { 24 | let binaries_path = self.node_path.join("bin"); 25 | let node_exec_path = binaries_path.join("node"); 26 | 27 | let executable = node_exec_path.as_os_str(); 28 | 29 | command(executable, args, vec![]) 30 | .map_err(|_| node::Error::FailedExecution) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/utils/copy_directory.rs: -------------------------------------------------------------------------------- 1 | use fs_extra::dir::CopyOptions; 2 | use std::fs::read_dir; 3 | use std::path::PathBuf; 4 | use std::{io, result}; 5 | 6 | #[derive(thiserror::Error, Debug)] 7 | pub enum Error { 8 | #[error("Cannot copy directory {0} to {1}\n{2}")] 9 | CopyDirectoryError(PathBuf, PathBuf, fs_extra::error::Error), 10 | 11 | #[error("Cannot read the directory")] 12 | GetDirectoryContentError(#[from] io::Error), 13 | } 14 | 15 | type Result = result::Result; 16 | 17 | pub fn copy_directory(source: &PathBuf, destination: &PathBuf) -> Result<()> { 18 | let options = CopyOptions::new(); 19 | let content = read_dir(source)? 20 | .flatten() 21 | .map(|entry| entry.path()) 22 | .collect::>(); 23 | 24 | fs_extra::copy_items(&content, destination.as_path(), &options).map_err( 25 | |e| Error::CopyDirectoryError(source.clone(), destination.clone(), e), 26 | )?; 27 | 28 | Ok(()) 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Gabriel Hamel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/sdk/nvm/linux_or_mac_os/install.rs: -------------------------------------------------------------------------------- 1 | use crate::sdk::nvm; 2 | use crate::sdk::nvm::create_nvm_directory; 3 | use crate::utils::convert_pathbuf_to_string::Stringify; 4 | use crate::utils::downloader::download_file; 5 | use std::fs; 6 | use std::path::PathBuf; 7 | use std::process::Command; 8 | 9 | pub fn install_nvm_sdk(nvm_path: &PathBuf) -> nvm::Result<()> { 10 | create_nvm_directory(nvm_path) 11 | .map_err(|e| nvm::Error::InstallError(e.to_string()))?; 12 | let downloaded_file_path = nvm_path.join("install.sh"); 13 | let downloaded_file = downloaded_file_path.to_string()?; 14 | 15 | download_file( 16 | "https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh", 17 | &downloaded_file, 18 | ) 19 | .map_err(|e| nvm::Error::DownloadError(e.to_string()))?; 20 | 21 | let mut command = Command::new("bash"); 22 | command.arg(&downloaded_file).env("NVM_DIR", nvm_path); 23 | 24 | let _ = command 25 | .output() 26 | .map_err(|e| nvm::Error::InstallError(e.to_string()))?; 27 | 28 | fs::remove_file(&downloaded_file_path) 29 | .map_err(|e| nvm::Error::InstallError(e.to_string()))?; 30 | 31 | Ok(()) 32 | } 33 | -------------------------------------------------------------------------------- /src/config/asconfig_json.rs: -------------------------------------------------------------------------------- 1 | use serde_derive::{Deserialize, Serialize}; 2 | use std::io; 3 | use std::path::PathBuf; 4 | 5 | #[derive(Debug, Serialize, Deserialize)] 6 | pub struct AsconfigcJson { 7 | #[serde(rename = "config")] 8 | pub config: String, 9 | #[serde(rename = "compilerOptions")] 10 | pub compiler_option: CompilerOption, 11 | #[serde(rename = "mainClass")] 12 | pub main_class: String, 13 | } 14 | 15 | #[derive(Debug, Serialize, Deserialize)] 16 | pub struct CompilerOption { 17 | #[serde(rename = "output")] 18 | pub output: String, 19 | #[serde(rename = "source-path")] 20 | pub source_path: Vec, 21 | #[serde(rename = "external-library-path")] 22 | pub library_path: Vec, 23 | } 24 | 25 | impl AsconfigcJson { 26 | pub fn write_json_to_file( 27 | &self, file_path: &PathBuf, 28 | ) -> Result<(), io::Error> { 29 | let file = std::fs::File::create(file_path)?; 30 | 31 | serde_json::to_writer_pretty(file, self)?; 32 | Ok(()) 33 | } 34 | 35 | pub fn from_file(filename: &PathBuf) -> Result { 36 | let file = std::fs::File::open(filename)?; 37 | Ok(serde_json::from_reader(file)?) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/cli/command.rs: -------------------------------------------------------------------------------- 1 | use crate::cli::commands::channel::ChannelCommand; 2 | use crate::cli::commands::export::ExportCommand; 3 | use crate::cli::commands::new::NewCommand; 4 | use crate::cli::commands::pycharm::PycharmCommand; 5 | use clap::{ArgMatches, Command}; 6 | 7 | #[derive(thiserror::Error, Debug)] 8 | pub enum Error { 9 | #[error("This command isn't implemented")] 10 | CommandNotImplemented, 11 | 12 | #[error("No command provided, refer to the help section")] 13 | NoCommandProvided, 14 | 15 | #[error("Error occurred during the command execution\n{0}")] 16 | CommandExecutionError(String), 17 | } 18 | 19 | pub trait RunnableCommand { 20 | fn command() -> Command; 21 | 22 | fn run(args: &ArgMatches) -> Result<(), Error>; 23 | } 24 | 25 | pub fn command() -> Command { 26 | Command::new("wg-mod") 27 | .version(env!("CARGO_PKG_VERSION")) 28 | .author("Gabriel Hamel ") 29 | .about("Provides cli tools for Wargaming games modding") 30 | .subcommand_required(true) 31 | .subcommand(NewCommand::command()) 32 | .subcommand(ExportCommand::command()) 33 | .subcommand(PycharmCommand::command()) 34 | .subcommand(ChannelCommand::command()) 35 | } 36 | -------------------------------------------------------------------------------- /src/cli/commands/export.rs: -------------------------------------------------------------------------------- 1 | use crate::builder; 2 | use crate::builder::ModBuilder; 3 | use crate::cli::command; 4 | use crate::cli::command::RunnableCommand; 5 | use clap::{ArgMatches, Command}; 6 | use std::path::PathBuf; 7 | use std::result; 8 | 9 | #[derive(thiserror::Error, Debug)] 10 | pub enum Error { 11 | #[error("Failed to use build tools\n{0}")] 12 | ModBuilderError(#[from] builder::Error), 13 | } 14 | 15 | type Result = result::Result; 16 | 17 | pub struct ExportCommand; 18 | 19 | fn build() -> Result<()> { 20 | let mod_path = PathBuf::from("."); 21 | let mod_builder = ModBuilder::new(mod_path)?; 22 | mod_builder.build()?; 23 | 24 | Ok(()) 25 | } 26 | 27 | impl RunnableCommand for ExportCommand { 28 | fn command() -> Command { 29 | Command::new("export") 30 | .about("Assemble sources into a .wotmod") 31 | .long_about("Compile the local mod directory as a .wotmod file") 32 | } 33 | 34 | fn run(_: &ArgMatches) -> result::Result<(), command::Error> { 35 | match build() { 36 | | Ok(()) => Ok(()), 37 | | Err(e) => { 38 | Err(command::Error::CommandExecutionError(e.to_string())) 39 | }, 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/sdk/nvm/windows/install.rs: -------------------------------------------------------------------------------- 1 | use crate::new::template::template_nvm_config; 2 | use crate::sdk::nvm; 3 | use crate::sdk::nvm::create_nvm_directory; 4 | use crate::utils::convert_pathbuf_to_string::Stringify; 5 | use crate::utils::downloader::download_file; 6 | use crate::utils::zip; 7 | use std::fs; 8 | use std::path::PathBuf; 9 | 10 | pub fn install_nvm_windows(nvm_path: &PathBuf) -> nvm::Result<()> { 11 | create_nvm_directory(nvm_path) 12 | .map_err(|e| nvm::Error::InstallError(e.to_string()))?; 13 | let downloaded_file_path = nvm_path.join("nvm.zip"); 14 | let downloaded_file = downloaded_file_path 15 | .to_string() 16 | .map_err(|e| nvm::Error::InstallError(e.to_string()))?; 17 | 18 | // not in 1.1.12 because issue in it: https://github.com/coreybutler/nvm-windows/issues/1068 19 | download_file( 20 | "https://github.com/coreybutler/nvm-windows/releases/download/1.1.11/nvm-noinstall.zip", 21 | &downloaded_file, 22 | ) 23 | .map_err(|e| nvm::Error::DownloadError(e.to_string()))?; 24 | 25 | zip::extract(&downloaded_file_path, nvm_path) 26 | .map_err(|e| nvm::Error::InstallError(e.to_string()))?; 27 | 28 | fs::remove_file(&downloaded_file_path) 29 | .map_err(|e| nvm::Error::InstallError(e.to_string()))?; 30 | 31 | template_nvm_config(nvm_path) 32 | .map_err(|e| nvm::Error::InstallError(e.to_string()))?; 33 | 34 | Ok(()) 35 | } 36 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ 'main' ] 6 | 7 | pull_request: 8 | branches: [ 'main' ] 9 | 10 | env: 11 | CARGO_TERM_COLOR: always 12 | 13 | jobs: 14 | lint: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - name: Lint 21 | run: cargo fmt --check 22 | 23 | tests-windows: 24 | runs-on: windows-latest 25 | 26 | steps: 27 | - uses: actions/checkout@v4 28 | 29 | - name: Retrieve cache 30 | uses: actions/cache@v4 31 | with: 32 | path: target 33 | key: ${{ runner.os }}-${{ hashFiles('Cargo.lock') }} 34 | 35 | - name: Tests 36 | run: cargo test --verbose 37 | 38 | tests-linux: 39 | runs-on: ubuntu-latest 40 | 41 | steps: 42 | - uses: actions/checkout@v4 43 | 44 | - name: Retrieve cache 45 | uses: actions/cache@v4 46 | with: 47 | path: target 48 | key: ${{ runner.os }}-${{ hashFiles('Cargo.lock') }} 49 | 50 | - name: Tests 51 | run: cargo test --verbose 52 | 53 | tests-macos: 54 | runs-on: macos-latest 55 | 56 | steps: 57 | - uses: actions/checkout@v4 58 | 59 | - name: Retrieve cache 60 | uses: actions/cache@v4 61 | with: 62 | path: target 63 | key: ${{ runner.os }}-${{ hashFiles('Cargo.lock') }} 64 | 65 | - name: Tests 66 | run: cargo test --verbose 67 | -------------------------------------------------------------------------------- /src/sdk/node/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod linux_or_macos; 2 | pub mod windows; 3 | 4 | use crate::sdk::npm::NPM; 5 | use std::process::Output; 6 | use std::result; 7 | use std::string::FromUtf8Error; 8 | 9 | #[derive(thiserror::Error, Debug)] 10 | pub enum Error { 11 | #[error("Unable to decode output of the command")] 12 | DecodeOutputError(#[from] FromUtf8Error), 13 | 14 | #[error("Unable to run command")] 15 | FailedExecution, 16 | } 17 | 18 | type Result = result::Result; 19 | 20 | pub trait Node { 21 | fn get_npm(&self) -> NPM; 22 | 23 | fn exec(&self, args: Vec<&str>) -> Result; 24 | 25 | fn version(&self) -> Result { 26 | let out = self.exec(vec!["--version"])?; 27 | 28 | Ok(String::from_utf8(out.stdout)?.trim().to_string()) 29 | } 30 | } 31 | 32 | #[cfg(test)] 33 | pub mod tests { 34 | use crate::sdk::nvm::load_nvm; 35 | use regex::Regex; 36 | use tempfile::tempdir; 37 | 38 | #[test] 39 | fn install_node() { 40 | let tmp_dir = tempdir().unwrap(); 41 | let tmp_dir_path = tmp_dir.path().to_path_buf(); 42 | 43 | let nvm_path = tmp_dir_path.join("nvm"); 44 | 45 | let nvm = load_nvm(&nvm_path).unwrap(); 46 | let node = nvm.get_node().unwrap(); 47 | 48 | let version = node.version().unwrap(); 49 | 50 | let semantic_version_pattern = Regex::new("^v([0-9]+)\\.([0-9]+)\\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?(?:\\+[0-9A-Za-z-]+)?$").unwrap(); 51 | assert!(semantic_version_pattern.is_match(&version)); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/sdk/as3/mod.rs: -------------------------------------------------------------------------------- 1 | mod install; 2 | 3 | use crate::sdk::as3::install::install_flex_sdk; 4 | use crate::sdk::{InstallResult, Installable}; 5 | use std::path::PathBuf; 6 | use std::{fs, result}; 7 | 8 | #[derive(thiserror::Error, Debug)] 9 | pub enum Error { 10 | #[error("Wasn't able to install AS3 SDK")] 11 | InstallError(String), 12 | } 13 | 14 | type Result = result::Result; 15 | 16 | pub struct AS3 { 17 | as3_path: PathBuf, 18 | } 19 | 20 | impl From<&PathBuf> for AS3 { 21 | fn from(as3_path: &PathBuf) -> Self { 22 | Self { 23 | as3_path: as3_path.clone(), 24 | } 25 | } 26 | } 27 | 28 | impl AS3 { 29 | pub fn get_as3_path(&self) -> PathBuf { 30 | self.as3_path.clone() 31 | } 32 | } 33 | 34 | impl Installable for AS3 { 35 | fn is_installed(&self) -> bool { 36 | match fs::metadata(&self.as3_path) { 37 | | Ok(metadata) => metadata.is_dir(), 38 | | Err(_) => false, 39 | } 40 | } 41 | 42 | fn install(&self) -> InstallResult { 43 | if self.is_installed() { 44 | Err("AS3 SDK is already installed".into()) 45 | } else { 46 | install_flex_sdk(&self.as3_path).map_err(|error| error.to_string()) 47 | } 48 | } 49 | } 50 | 51 | pub fn load_as3(as3_path: &PathBuf) -> Result { 52 | let as3 = AS3::from(as3_path); 53 | 54 | if !as3.is_installed() { 55 | println!("Installing action script SDK..."); 56 | as3.install().map_err(|e| Error::InstallError(e))?; 57 | } 58 | 59 | Ok(as3) 60 | } 61 | -------------------------------------------------------------------------------- /src/utils/pattern_validator.rs: -------------------------------------------------------------------------------- 1 | use inquire::{ 2 | validator::{StringValidator, Validation}, 3 | CustomUserError, 4 | }; 5 | use regex::{Error, Regex}; 6 | use std::str::FromStr; 7 | 8 | #[derive(Clone)] 9 | pub struct PatternValidator { 10 | pattern: Regex, 11 | error_message: String, 12 | } 13 | 14 | impl PatternValidator { 15 | pub fn new(pattern: &str, error_message: &str) -> Result { 16 | Ok(PatternValidator { 17 | pattern: Regex::from_str(pattern)?, 18 | error_message: error_message.into(), 19 | }) 20 | } 21 | } 22 | 23 | impl StringValidator for PatternValidator { 24 | fn validate(&self, input: &str) -> Result { 25 | if self.pattern.is_match(input) { 26 | Ok(Validation::Valid) 27 | } else { 28 | Ok(Validation::Invalid(self.error_message.clone().into())) 29 | } 30 | } 31 | } 32 | 33 | #[cfg(test)] 34 | mod tests { 35 | use super::*; 36 | 37 | #[test] 38 | fn pattern_validator() { 39 | let validator = PatternValidator::new( 40 | r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$", 41 | "Must respect the semantic versioning", 42 | ) 43 | .unwrap(); 44 | 45 | assert_eq!(validator.validate("0.0.1").unwrap(), Validation::Valid); 46 | assert_eq!( 47 | validator.validate("Hello world").unwrap(), 48 | Validation::Invalid(inquire::validator::ErrorMessage::Custom( 49 | "Must respect the semantic versioning".to_owned() 50 | )) 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/builder/flash.rs: -------------------------------------------------------------------------------- 1 | use crate::config; 2 | use crate::config::Configs; 3 | use crate::sdk::asconfigc; 4 | use crate::sdk::asconfigc::ASConfigc; 5 | use crate::utils::copy_directory; 6 | use crate::utils::tmp_dir::TempDirError; 7 | use glob::{GlobError, PatternError}; 8 | use std::fs::create_dir_all; 9 | use std::io; 10 | use std::path::PathBuf; 11 | 12 | #[derive(Debug, thiserror::Error)] 13 | pub enum Error { 14 | #[error("Failed to get asconfigc: {0}")] 15 | AsConfigcError(#[from] config::Error), 16 | #[error("Failed to manage temporary directory: {0}")] 17 | TempDirError(#[from] TempDirError), 18 | #[error("Failed to write file and directory: {0}")] 19 | WriteError(#[from] io::Error), 20 | #[error("Failed to build: {0}")] 21 | BuildError(#[from] asconfigc::Error), 22 | #[error("Failed to copy directory: {0}")] 23 | CopyError(#[from] copy_directory::Error), 24 | #[error("Failed to unwrap glob: {0}")] 25 | GlobUnwrapError(#[from] GlobError), 26 | #[error("Failed to creta glob: {0}")] 27 | GlobCreationError(#[from] PatternError), 28 | } 29 | 30 | pub struct FlashBuilder { 31 | asconfigc: ASConfigc, 32 | } 33 | 34 | impl FlashBuilder { 35 | pub fn new() -> Result { 36 | let config = Configs::load().map_err(|e| Error::AsConfigcError(e))?; 37 | 38 | Ok(Self { 39 | asconfigc: config.asconfigc, 40 | }) 41 | } 42 | 43 | pub fn build( 44 | &self, source: &PathBuf, destination: &PathBuf, 45 | ) -> Result<(), Error> { 46 | self.asconfigc.build(source)?; 47 | 48 | create_dir_all(destination)?; 49 | 50 | Ok(()) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/cli/commands/pycharm.rs: -------------------------------------------------------------------------------- 1 | use crate::cli::command; 2 | use crate::cli::command::RunnableCommand; 3 | use crate::config; 4 | use crate::config::Configs; 5 | use crate::sdk::game_sources; 6 | use clap::{ArgMatches, Command}; 7 | use std::result; 8 | 9 | #[derive(thiserror::Error, Debug)] 10 | pub enum Error { 11 | #[error("Failed to load modding tools\n{0}")] 12 | ConfigsError(#[from] config::Error), 13 | 14 | #[error("Failed to use build tools\n{0}")] 15 | GameSourceError(#[from] game_sources::Error), 16 | } 17 | 18 | type Result = result::Result; 19 | 20 | pub struct PycharmCommand; 21 | 22 | fn pycharm() -> Result<()> { 23 | let config = Configs::load()?; 24 | let python_root_modules = config.game_sources.list_python_root_modules()?; 25 | 26 | println!("Resolve WoT imports: 27 | 1. Go in your PyCharm project settings 28 | 2. Open Python Interpreter tab 29 | 3. Click on your actual conda interpreter (must be wg-mod) and on the button 'Show all' 30 | 4. Select 'wg-mod' and click on the small directories icon 'Show Interpreter Paths' 31 | 5. Add these following paths by clicking the button '+' 32 | "); 33 | for module in python_root_modules { 34 | println!(" - {module}"); 35 | } 36 | 37 | Ok(()) 38 | } 39 | 40 | impl RunnableCommand for PycharmCommand { 41 | fn command() -> Command { 42 | Command::new("pycharm") 43 | .about("Help to configure your local PyCharm IDE") 44 | } 45 | 46 | fn run(_: &ArgMatches) -> result::Result<(), command::Error> { 47 | match pycharm() { 48 | | Ok(()) => Ok(()), 49 | | Err(e) => { 50 | Err(command::Error::CommandExecutionError(e.to_string())) 51 | }, 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/config/mod_conf.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::file_template; 2 | use crate::utils::file_template::write_template; 3 | use serde_derive::{Deserialize, Serialize}; 4 | use serde_json::json; 5 | use std::io; 6 | use std::path::PathBuf; 7 | 8 | #[derive(thiserror::Error, Debug)] 9 | pub enum Error { 10 | #[error("Unable to create this template file")] 11 | FileTemplateError(#[from] file_template::Error), 12 | } 13 | 14 | #[derive(Debug, Serialize, Deserialize)] 15 | pub struct ModConf { 16 | #[serde(rename = "id")] 17 | pub package_name: String, 18 | #[serde(rename = "version")] 19 | pub version: String, 20 | #[serde(rename = "name")] 21 | pub name: String, 22 | #[serde(rename = "description")] 23 | pub description: String, 24 | } 25 | 26 | impl ModConf { 27 | pub fn write_json_to_file( 28 | &self, file_path: &PathBuf, 29 | ) -> Result<(), io::Error> { 30 | let file = std::fs::File::create(file_path)?; 31 | 32 | serde_json::to_writer_pretty(file, self)?; 33 | Ok(()) 34 | } 35 | 36 | pub fn export_mod_meta( 37 | &self, filepath: &PathBuf, filename: &str, 38 | ) -> Result<(), Error> { 39 | write_template( 40 | &filepath, 41 | filename, 42 | " 43 | {{package_name}} 44 | {{version}} 45 | {{name}} 46 | {{description}} 47 | 48 | ", 49 | &json!({ 50 | "package_name": self.package_name, 51 | "version": self.version, 52 | "name": self.name, 53 | "description": self.description 54 | }), 55 | )?; 56 | 57 | Ok(()) 58 | } 59 | 60 | pub fn from_file(filename: &PathBuf) -> Result { 61 | let file = std::fs::File::open(filename)?; 62 | Ok(serde_json::from_reader(file)?) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/utils/extract_archive.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::{BufWriter, Write}; 3 | use std::path::PathBuf; 4 | use zip::result::ZipError; 5 | use zip::write::{ExtendedFileOptions, FileOptions}; 6 | use zip::{CompressionMethod, ZipArchive, ZipWriter}; 7 | 8 | #[derive(thiserror::Error, Debug)] 9 | pub enum Error { 10 | #[error("Archive does not exist: {0}")] 11 | ArchiveError(String), 12 | #[error("Failed to extract archive: {0}")] 13 | UnzipError(#[from] ZipError), 14 | } 15 | 16 | pub fn extract_archive( 17 | archive_path: &PathBuf, destination: &PathBuf, 18 | ) -> Result<(), Error> { 19 | let file = File::open(&archive_path) 20 | .map_err(|e| Error::ArchiveError(e.to_string()))?; 21 | 22 | let mut archive = ZipArchive::new(file)?; 23 | archive.extract(destination)?; 24 | 25 | Ok(()) 26 | } 27 | 28 | #[cfg(test)] 29 | mod tests { 30 | use super::*; 31 | use tempfile::tempdir; 32 | #[test] 33 | 34 | fn extract_archive_test() { 35 | let tmp_dir = tempdir().unwrap(); 36 | 37 | mock_archive( 38 | &tmp_dir.path().to_path_buf(), 39 | "build_test.zip".to_string(), 40 | ); 41 | 42 | extract_archive( 43 | &tmp_dir.path().to_path_buf().join("build_test.zip"), 44 | &tmp_dir.path().to_path_buf(), 45 | ) 46 | .unwrap(); 47 | 48 | assert!(tmp_dir.path().join("build_test.txt").exists()); 49 | tmp_dir.close().unwrap(); 50 | } 51 | } 52 | 53 | fn mock_archive(path: &PathBuf, archive_name: String) { 54 | let file = File::create(path.join(archive_name)).unwrap(); 55 | let writer = BufWriter::new(file); 56 | let mut zip = ZipWriter::new(writer); 57 | let options: FileOptions<'_, ExtendedFileOptions> = 58 | FileOptions::default().compression_method(CompressionMethod::Deflated); 59 | 60 | zip.start_file("build_test.txt", options).unwrap(); 61 | zip.write_all(b"Hello, world!").unwrap(); 62 | 63 | zip.finish().unwrap(); 64 | } 65 | -------------------------------------------------------------------------------- /src/cli/commands/channel.rs: -------------------------------------------------------------------------------- 1 | use crate::cli::command; 2 | use crate::cli::command::RunnableCommand; 3 | use crate::config; 4 | use crate::config::Configs; 5 | use crate::sdk::game_sources; 6 | use clap::{ArgMatches, Command}; 7 | use std::result; 8 | 9 | #[derive(thiserror::Error, Debug)] 10 | pub enum Error { 11 | #[error("Failed to load modding tools\n{0}")] 12 | ConfigsError(#[from] config::Error), 13 | 14 | #[error("Failed to use build tools\n{0}")] 15 | GameSourceError(#[from] game_sources::Error), 16 | } 17 | 18 | type Result = result::Result; 19 | 20 | pub struct ChannelCommand; 21 | 22 | fn channel() -> Result<()> { 23 | let config = Configs::load()?; 24 | let channel = config.game_sources.get_channel()?; 25 | 26 | println!("Current WoT channel: {}", channel); 27 | Ok(()) 28 | } 29 | 30 | fn switch_channel() -> Result<()> { 31 | let config = Configs::load()?; 32 | config.game_sources.prompt_channel()?; 33 | 34 | Ok(()) 35 | } 36 | 37 | impl RunnableCommand for ChannelCommand { 38 | fn command() -> Command { 39 | Command::new("channel") 40 | .about("Display / set current WoT region selected") 41 | .long_about("Display / set current WoT region selected") 42 | .subcommand_required(false) 43 | .subcommand( 44 | Command::new("switch") 45 | .about("Change WoT region") 46 | .long_about("Change WoT region currently selected"), 47 | ) 48 | } 49 | 50 | fn run(args: &ArgMatches) -> result::Result<(), command::Error> { 51 | if let Some(_) = args.subcommand_matches("switch") { 52 | return match switch_channel() { 53 | | Ok(()) => Ok(()), 54 | | Err(e) => { 55 | Err(command::Error::CommandExecutionError(e.to_string())) 56 | }, 57 | }; 58 | } 59 | 60 | match channel() { 61 | | Ok(()) => Ok(()), 62 | | Err(e) => { 63 | Err(command::Error::CommandExecutionError(e.to_string())) 64 | }, 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/sdk/conda/environment.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use std::process::{Command, Output}; 3 | use std::result; 4 | use std::str::Utf8Error; 5 | 6 | #[derive(thiserror::Error, Debug)] 7 | pub enum Error { 8 | #[error("Can't invoke command: {0}")] 9 | CommandInvocationError(PathBuf), 10 | 11 | #[error("Command error")] 12 | CommandError(Output), 13 | 14 | #[error("Cannot read the command output")] 15 | CommandOutputParsingError(#[from] Utf8Error), 16 | 17 | #[error("Unable to reads sources directory")] 18 | PathError, 19 | } 20 | 21 | type Result = result::Result; 22 | 23 | pub struct CondaEnvironment { 24 | environment_path: PathBuf, 25 | } 26 | 27 | impl From for CondaEnvironment { 28 | fn from(path: PathBuf) -> Self { 29 | Self { 30 | environment_path: path, 31 | } 32 | } 33 | } 34 | 35 | impl CondaEnvironment { 36 | pub fn compile_all(&self, directory: &PathBuf) -> Result<()> { 37 | let python_src = directory.to_str().ok_or(Error::PathError)?; 38 | 39 | self.python(vec!["-m", "compileall", python_src])?; 40 | 41 | Ok(()) 42 | } 43 | 44 | fn python(&self, args: Vec<&str>) -> Result<(String, String)> { 45 | self.command("python", args) 46 | } 47 | 48 | fn command( 49 | &self, executable_name: &str, args: Vec<&str>, 50 | ) -> Result<(String, String)> { 51 | let executable_path = self.get_executable_path(executable_name); 52 | let mut command = Command::new(&executable_path); 53 | 54 | let output = command 55 | .args(args) 56 | .output() 57 | .map_err(|_| Error::CommandInvocationError(executable_path))?; 58 | 59 | if !output.status.success() { 60 | return Err(Error::CommandError(output)); 61 | } 62 | 63 | let stdout = std::str::from_utf8(&output.stdout)?.to_string(); 64 | let stderr = std::str::from_utf8(&output.stderr)?.to_string(); 65 | 66 | Ok((stdout, stderr)) 67 | } 68 | 69 | fn get_executable_path(&self, name: &str) -> PathBuf { 70 | let mut conda_binaries_path = self.environment_path.clone(); 71 | 72 | let executable_name = if cfg!(target_os = "windows") { 73 | format!("{name}.exe") 74 | } else { 75 | conda_binaries_path = conda_binaries_path.join("bin"); 76 | name.to_string() 77 | }; 78 | 79 | conda_binaries_path.join(executable_name) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/utils/file_template.rs: -------------------------------------------------------------------------------- 1 | use crate::config; 2 | use crate::utils::{convert_pathbuf_to_string, convert_to_absolute_path}; 3 | use handlebars::Handlebars; 4 | use serde::Serialize; 5 | use std::{ 6 | fs::{self, File}, 7 | io, 8 | path::PathBuf, 9 | result, 10 | }; 11 | 12 | #[derive(thiserror::Error, Debug)] 13 | pub enum Error { 14 | #[error("Error occurred during file creation")] 15 | FileCreateError(io::Error, PathBuf), 16 | 17 | #[error("Unable to write in this file")] 18 | TemplateWriteError(#[from] handlebars::RenderError), 19 | 20 | #[error("Unable to create directory")] 21 | DirectoryCreateError(io::Error), 22 | 23 | #[error("Failed to init git repository")] 24 | GitInitError(#[from] git2::Error), 25 | 26 | #[error("Unable to display absolute mod path")] 27 | ConvertAbsolutePath(#[from] convert_to_absolute_path::Error), 28 | 29 | #[error("Unable to convert path to string")] 30 | ConvertPathToString(#[from] convert_pathbuf_to_string::Error), 31 | 32 | #[error("Failed to get wg home directory")] 33 | WgHomePath(#[from] config::Error), 34 | } 35 | 36 | type Result = result::Result; 37 | 38 | pub fn write_template( 39 | dir: &PathBuf, filename: &str, template: &str, data: &T, 40 | ) -> Result<()> 41 | where 42 | T: Serialize, 43 | { 44 | fs::create_dir_all(&dir).map_err(Error::DirectoryCreateError)?; 45 | 46 | let filepath = dir.join(filename); 47 | let file = File::create(&filepath) 48 | .map_err(|e| Error::FileCreateError(e, filepath))?; 49 | 50 | Handlebars::new() 51 | .render_template_to_write(template, data, file) 52 | .map_err(Error::TemplateWriteError) 53 | } 54 | 55 | #[cfg(test)] 56 | mod tests { 57 | use super::*; 58 | use crate::utils::file_template::write_template; 59 | 60 | #[test] 61 | fn file_template() { 62 | use serde_json::json; 63 | use std::io::Read; 64 | use tempfile::tempdir; 65 | 66 | let tmp_dir = tempdir().unwrap(); 67 | let filepath = tmp_dir.path().join("file.txt"); 68 | 69 | write_template( 70 | &tmp_dir.path().to_path_buf(), 71 | "file.txt", 72 | "{{one}} {{two}} !", 73 | &json!({ 74 | "one": "Hello", 75 | "two": "world" 76 | }), 77 | ) 78 | .unwrap(); 79 | 80 | let mut file = File::open(&filepath).unwrap(); 81 | let mut file_content = String::new(); 82 | let bytes_reads = file.read_to_string(&mut file_content).unwrap(); 83 | 84 | assert_eq!(bytes_reads, 13); 85 | assert_eq!(file_content, "Hello world !"); 86 | 87 | tmp_dir.close().unwrap(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/sdk/as3/install.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::{convert_to_absolute_path, downloader}; 2 | use std::fs::File; 3 | use std::path::PathBuf; 4 | use std::{fs, io, result}; 5 | use zip::result::ZipError; 6 | use zip::ZipArchive; 7 | 8 | #[derive(thiserror::Error, Debug)] 9 | pub enum Error { 10 | #[error("No ActionScript 3 SDK is available for this platform")] 11 | PlatformNotSupported, 12 | 13 | #[error("Unable to resolve download destination")] 14 | ConvertAbsolutePath(#[from] convert_to_absolute_path::Error), 15 | 16 | #[error("An error occurred during the SDK download")] 17 | SdkDownloadFailed(#[from] downloader::Error), 18 | 19 | #[error("Fail to resolve the following path: {0}")] 20 | ResolvePathFailed(PathBuf), 21 | 22 | #[error("Invalid file downloaded")] 23 | InvalidArchive(io::Error), 24 | 25 | #[error("Invalid Zip operation")] 26 | InvalidZipOperation(#[from] ZipError), 27 | 28 | #[error("Unable to delete archive")] 29 | DeleteArchiveFailed(io::Error), 30 | } 31 | 32 | type Result = result::Result; 33 | 34 | fn get_archive_url() -> Result { 35 | let os = match std::env::consts::OS { 36 | | "macos" => Ok("macos"), 37 | | "windows" => Ok("windows"), 38 | | _ => Err(Error::PlatformNotSupported), 39 | }?; 40 | 41 | Ok(format!( 42 | "https://wg-mod.s3.eu-west-3.amazonaws.com/apache-flex-{os}.zip" 43 | )) 44 | } 45 | 46 | fn get_sdk_archive_destination( 47 | install_destination: &PathBuf, script_name: &String, 48 | ) -> Result { 49 | let parent_path = install_destination 50 | .parent() 51 | .ok_or(Error::ResolvePathFailed(install_destination.clone()))?; 52 | 53 | let sdk_path = parent_path.join(PathBuf::from(&script_name)); 54 | 55 | Ok(sdk_path 56 | .to_str() 57 | .ok_or(Error::ResolvePathFailed(sdk_path.clone()))? 58 | .to_string()) 59 | } 60 | 61 | fn download_sdk_archive(destination: &PathBuf) -> Result { 62 | let archive_url = get_archive_url()?; 63 | 64 | let archive_name = String::from("as3-sdk.zip"); 65 | let archive_destination = 66 | get_sdk_archive_destination(destination, &archive_name)?; 67 | 68 | downloader::download_file(&archive_url, &archive_destination)?; 69 | 70 | Ok(PathBuf::from(archive_destination)) 71 | } 72 | 73 | pub fn install_flex_sdk(destination: &PathBuf) -> Result<()> { 74 | let archive_path = download_sdk_archive(destination)?; 75 | let file = File::open(&archive_path).map_err(Error::InvalidArchive)?; 76 | 77 | let mut archive = ZipArchive::new(file)?; 78 | archive.extract(destination)?; 79 | 80 | fs::remove_file(archive_path).map_err(Error::DeleteArchiveFailed)?; 81 | 82 | Ok(()) 83 | } 84 | -------------------------------------------------------------------------------- /src/sdk/nvm/windows/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::sdk::node::windows::WindowsNode; 2 | use crate::sdk::node::Node; 3 | use crate::sdk::nvm::windows::install::install_nvm_windows; 4 | use crate::sdk::nvm::NVM; 5 | use crate::sdk::{nvm, InstallResult, Installable}; 6 | use crate::utils::command::command; 7 | use crate::utils::convert_pathbuf_to_string::Stringify; 8 | use crate::utils::Env; 9 | use std::path::PathBuf; 10 | use std::process::Output; 11 | 12 | mod install; 13 | 14 | pub struct WindowsNVM { 15 | nvm_path: PathBuf, 16 | } 17 | 18 | impl WindowsNVM { 19 | fn get_executable_path(&self) -> PathBuf { 20 | self.nvm_path.join("nvm.exe") 21 | } 22 | } 23 | 24 | impl From<&PathBuf> for WindowsNVM { 25 | fn from(nvm_path: &PathBuf) -> Self { 26 | Self { 27 | nvm_path: nvm_path.clone(), 28 | } 29 | } 30 | } 31 | 32 | impl Installable for WindowsNVM { 33 | fn is_installed(&self) -> bool { 34 | self.nvm_path.exists() 35 | } 36 | 37 | fn install(&self) -> InstallResult { 38 | if self.is_installed() { 39 | Err("NVM already installed".into()) 40 | } else { 41 | install_nvm_windows(&self.nvm_path).map_err(|err| err.to_string()) 42 | } 43 | } 44 | } 45 | 46 | impl NVM for WindowsNVM { 47 | fn install_node(&self) -> nvm::Result<()> { 48 | println!("Installing Node via nvm..."); 49 | 50 | let args = vec!["install", "latest"]; 51 | self.exec(args) 52 | .map_err(|e| nvm::Error::InstallError(e.to_string()))?; 53 | 54 | self.nvm_use("latest")?; 55 | 56 | Ok(()) 57 | } 58 | 59 | fn exec(&self, args: Vec<&str>) -> nvm::Result { 60 | let executable = self.get_executable_path(); 61 | let executable_str = executable.as_os_str(); 62 | 63 | let env = vec![Env { 64 | key: "NVM_HOME".to_string(), 65 | value: self.nvm_path.to_string()?, 66 | }]; 67 | 68 | command(executable_str, args, env).map_err(|_| nvm::Error::ExecError) 69 | } 70 | 71 | fn get_node(&self) -> nvm::Result> { 72 | let mut version = self.current_node_version()?; 73 | let mut node_path = self.nvm_path.join(version); 74 | 75 | if !node_path.exists() { 76 | self.install_node()?; 77 | } 78 | 79 | version = self.current_node_version()?; 80 | node_path = self.nvm_path.join(version); 81 | 82 | Ok(Box::new(WindowsNode::from(node_path))) 83 | } 84 | 85 | fn current_node_version(&self) -> nvm::Result { 86 | let out = self.exec(vec!["list", "installed"])?; 87 | if !out.status.success() { 88 | return Err(nvm::Error::ExecError); 89 | } 90 | 91 | let stdout = String::from_utf8(out.stdout) 92 | .map_err(|_| nvm::Error::ExecCurrentError)? 93 | .trim() 94 | .to_string() 95 | .replace("* ", "") 96 | .replace(" (Currently using 64-bit executable)", ""); 97 | 98 | Ok(format!("v{}", stdout)) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/builder/python.rs: -------------------------------------------------------------------------------- 1 | use crate::config; 2 | use crate::config::Configs; 3 | use crate::sdk::conda; 4 | use crate::sdk::conda::environment::CondaEnvironment; 5 | use crate::utils::copy_directory; 6 | use crate::utils::copy_directory::copy_directory; 7 | use crate::utils::tmp_dir::TempDirError; 8 | use glob::glob; 9 | use std::fs::{create_dir_all, remove_file}; 10 | use std::path::PathBuf; 11 | use std::{io, result}; 12 | use tempfile::tempdir; 13 | 14 | #[derive(thiserror::Error, Debug)] 15 | pub enum Error { 16 | #[error("Conda environment error\n{0}")] 17 | CondaEnvironmentError(#[from] conda::environment::Error), 18 | 19 | #[error("Conda error\n{0}")] 20 | CondaError(#[from] conda::Error), 21 | 22 | #[error("Can't access to configs")] 23 | ConfigsError(#[from] config::Error), 24 | 25 | #[error("Unable to create a directory")] 26 | CreateDirectoryError(#[from] io::Error), 27 | 28 | #[error("Copy directory failed")] 29 | CopyDirectoryError(#[from] copy_directory::Error), 30 | 31 | #[error("Invalid path given")] 32 | PathError, 33 | 34 | #[error("File selection by pattern failed")] 35 | GlobError(#[from] glob::GlobError), 36 | 37 | #[error("Invalid pattern given")] 38 | PatternError(#[from] glob::PatternError), 39 | 40 | #[error("Can't copy or create files\n{0}")] 41 | WriteFilesError(io::Error), 42 | 43 | #[error("Tempory directory usage failed")] 44 | TempDirError(#[from] TempDirError), 45 | } 46 | 47 | type Result = result::Result; 48 | 49 | pub struct PythonBuilder { 50 | conda_environment: CondaEnvironment, 51 | } 52 | 53 | impl PythonBuilder { 54 | pub fn new() -> Result { 55 | let configs = Configs::load()?; 56 | 57 | Ok(Self { 58 | conda_environment: configs.conda_environment, 59 | }) 60 | } 61 | 62 | pub fn build(&self, source: &PathBuf, destination: &PathBuf) -> Result<()> { 63 | let (close_tmp_dir, tmp_dir_path) = self.prepare_tmp_directory()?; 64 | 65 | copy_directory(source, &tmp_dir_path)?; 66 | 67 | self.conda_environment.compile_all(&tmp_dir_path)?; 68 | self.delete_all_sources(&tmp_dir_path)?; 69 | 70 | create_dir_all(destination)?; 71 | copy_directory(&tmp_dir_path, destination)?; 72 | 73 | close_tmp_dir()?; 74 | 75 | Ok(()) 76 | } 77 | 78 | fn prepare_tmp_directory( 79 | &self, 80 | ) -> Result<(impl FnOnce() -> io::Result<()>, PathBuf)> { 81 | let tmp_dir = tempdir()?; 82 | let path = tmp_dir.path(); 83 | let path_buf = path.to_path_buf(); 84 | 85 | let close_tmp_dir = move || tmp_dir.close(); 86 | 87 | Ok((close_tmp_dir, path_buf)) 88 | } 89 | 90 | fn delete_all_sources(&self, directory: &PathBuf) -> Result<()> { 91 | let directory_path = directory.to_str().ok_or(Error::PathError)?; 92 | let glob_pattern = format!("{}/**/*.py", directory_path); 93 | 94 | let remaining_python_files = glob(&glob_pattern)?; 95 | 96 | for entry in remaining_python_files { 97 | let file = entry?; 98 | remove_file(file).map_err(Error::WriteFilesError)?; 99 | } 100 | 101 | Ok(()) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/sdk/nvm/linux_or_mac_os/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::new::template::create_nvm_executable; 2 | use crate::sdk::node::linux_or_macos::LinuxOrMacNode; 3 | use crate::sdk::node::Node; 4 | use crate::sdk::nvm::linux_or_mac_os::install::install_nvm_sdk; 5 | use crate::sdk::nvm::NVM; 6 | use crate::sdk::{nvm, InstallResult, Installable}; 7 | use crate::utils::command::command; 8 | use crate::utils::convert_pathbuf_to_string::Stringify; 9 | use crate::utils::Env; 10 | use std::path::PathBuf; 11 | use std::process::Output; 12 | 13 | mod install; 14 | 15 | pub struct LinuxOrMacOsNVM { 16 | nvm_path: PathBuf, 17 | } 18 | 19 | impl From<&PathBuf> for LinuxOrMacOsNVM { 20 | fn from(nvm_path: &PathBuf) -> Self { 21 | Self { 22 | nvm_path: nvm_path.clone(), 23 | } 24 | } 25 | } 26 | 27 | impl LinuxOrMacOsNVM { 28 | fn get_executable_name(&self) -> String { 29 | "wg-mod-nvm.sh".to_string() 30 | } 31 | 32 | fn get_executable_path(&self) -> PathBuf { 33 | self.nvm_path.join(self.get_executable_name()) 34 | } 35 | 36 | fn prepare(&self) -> nvm::Result<()> { 37 | install_nvm_sdk(&self.nvm_path)?; 38 | create_nvm_executable( 39 | &self.nvm_path, 40 | self.get_executable_name().as_str(), 41 | ) 42 | .map_err(|e| nvm::Error::InstallError(e.to_string()))?; 43 | 44 | Ok(()) 45 | } 46 | } 47 | 48 | impl Installable for LinuxOrMacOsNVM { 49 | fn is_installed(&self) -> bool { 50 | self.nvm_path.exists() 51 | } 52 | 53 | fn install(&self) -> InstallResult { 54 | if self.is_installed() { 55 | Err("NVM already installed".into()) 56 | } else { 57 | self.prepare().map_err(|err| err.to_string()) 58 | } 59 | } 60 | } 61 | 62 | impl NVM for LinuxOrMacOsNVM { 63 | fn install_node(&self) -> nvm::Result<()> { 64 | println!("Installing Node via nvm..."); 65 | 66 | self.exec(vec!["install", "node"])?; 67 | 68 | Ok(()) 69 | } 70 | 71 | fn current_node_version(&self) -> nvm::Result { 72 | let out = self.exec(vec!["current"])?; 73 | 74 | Ok(String::from_utf8(out.stdout) 75 | .map_err(|_| nvm::Error::ExecCurrentError)? 76 | .trim() 77 | .to_string()) 78 | } 79 | 80 | fn exec(&self, args: Vec<&str>) -> nvm::Result { 81 | let executable_path = self.get_executable_path(); 82 | let executable = &executable_path.to_string()?; 83 | 84 | let mut mutable_args = args.clone(); 85 | mutable_args.insert(0, executable); 86 | 87 | let env = vec![Env { 88 | key: "NVM_DIR".to_string(), 89 | value: self.nvm_path.to_string()?, 90 | }]; 91 | 92 | command("bash", mutable_args, env).map_err(|_| nvm::Error::ExecError) 93 | } 94 | 95 | fn get_node(&self) -> nvm::Result> { 96 | let node_path = self.nvm_path.join("versions").join("node"); 97 | 98 | if !node_path.exists() { 99 | self.install_node()?; 100 | } 101 | 102 | let current_version = self.current_node_version()?; 103 | let current_node_path = node_path.join(current_version); 104 | 105 | Ok(Box::new(LinuxOrMacNode::from(current_node_path))) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/sdk/npm/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::command::command; 2 | use crate::utils::convert_pathbuf_to_string::Stringify; 3 | use crate::utils::Env; 4 | use std::path::PathBuf; 5 | use std::process::Output; 6 | use std::result; 7 | 8 | #[derive(thiserror::Error, Debug)] 9 | pub enum Error { 10 | #[error("Execution failed")] 11 | FailedExecution, 12 | 13 | #[error("Get node bin directory")] 14 | GetBinDirectoryError, 15 | 16 | #[error("Unable to install package")] 17 | InstallPackageFailed(String), 18 | } 19 | 20 | type Result = result::Result; 21 | 22 | pub struct NPM { 23 | npm_bin: PathBuf, 24 | } 25 | 26 | impl From for NPM { 27 | fn from(npm_bin: PathBuf) -> Self { 28 | Self { npm_bin } 29 | } 30 | } 31 | 32 | impl NPM { 33 | fn exec(&self, args: Vec<&str>) -> Result { 34 | let executable = self.npm_bin.as_os_str(); 35 | 36 | let env = vec![ 37 | (Env { 38 | key: "PATH".to_string(), 39 | value: self 40 | .get_bin_directory()? 41 | .to_string() 42 | .map_err(|_| Error::GetBinDirectoryError)?, 43 | }), 44 | ]; 45 | 46 | command(executable, args, env).map_err(|_| Error::FailedExecution) 47 | } 48 | 49 | pub fn is_package_installed(&self, name: &str) -> Result { 50 | let result = self 51 | .exec(vec!["list", "-g", name]) 52 | .map_err(|e| Error::InstallPackageFailed(e.to_string()))?; 53 | 54 | Ok(result.status.success()) 55 | } 56 | 57 | pub fn get_bin_directory(&self) -> Result { 58 | self.npm_bin 59 | .parent() 60 | .ok_or(Error::GetBinDirectoryError) 61 | .and_then(|res| Ok(PathBuf::from(res))) 62 | } 63 | 64 | pub fn install_package(&self, name: &str) -> Result<()> { 65 | println!("Installing {}...", name); 66 | 67 | let result = self 68 | .exec(vec!["install", "-g", name]) 69 | .map_err(|e| Error::InstallPackageFailed(e.to_string()))?; 70 | 71 | if result.status.success() { 72 | return Ok(()); 73 | } 74 | 75 | let stdout = String::from_utf8_lossy(&result.stdout); 76 | let stderr = String::from_utf8_lossy(&result.stderr); 77 | 78 | Err(Error::InstallPackageFailed(format!( 79 | "{}\n{}", 80 | stdout, stderr 81 | ))) 82 | } 83 | 84 | pub fn version(&self) -> Result { 85 | let out = self.exec(vec!["--version"])?; 86 | 87 | Ok(String::from_utf8(out.stdout) 88 | .map_err(|_| Error::FailedExecution)? 89 | .trim() 90 | .to_string()) 91 | } 92 | } 93 | 94 | #[cfg(test)] 95 | pub mod tests { 96 | use crate::sdk::nvm::load_nvm; 97 | use regex::Regex; 98 | use tempfile::tempdir; 99 | 100 | #[test] 101 | fn install_npm() { 102 | let tmp_dir = tempdir().unwrap(); 103 | let tmp_dir_path = tmp_dir.path().to_path_buf(); 104 | let nvm_path = tmp_dir_path.join("nvm"); 105 | 106 | let nvm = load_nvm(&nvm_path).unwrap(); 107 | let node = nvm.get_node().unwrap(); 108 | let npm = node.get_npm(); 109 | 110 | let version = npm.version().unwrap(); 111 | 112 | let semantic_version_pattern = Regex::new("^([0-9]+)\\.([0-9]+)\\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?(?:\\+[0-9A-Za-z-]+)?$").unwrap(); 113 | assert!(semantic_version_pattern.is_match(&version)); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/cli/commands/new.rs: -------------------------------------------------------------------------------- 1 | use crate::cli::command; 2 | use crate::cli::command::RunnableCommand; 3 | use crate::new::{template::create_mod_files, NewArgs}; 4 | use crate::utils::pattern_validator::PatternValidator; 5 | use clap::{ArgMatches, Command}; 6 | use convert_case::{Case, Casing}; 7 | use inquire::min_length; 8 | use std::path::PathBuf; 9 | use std::result; 10 | 11 | #[derive(thiserror::Error, Debug)] 12 | pub enum Error { 13 | #[error("Invalid regex provided")] 14 | RegexBuildError(#[from] regex::Error), 15 | 16 | #[error("Error occurred during prompt")] 17 | PromptError(#[from] inquire::InquireError), 18 | } 19 | 20 | type Result = result::Result; 21 | 22 | fn prompt_version() -> Result { 23 | let validator = PatternValidator::new( 24 | r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$", 25 | "Your mod version must respect the semantic versioning", 26 | )?; 27 | 28 | let value = inquire::Text::new("Version:") 29 | .with_default("0.0.1") 30 | .with_validator(validator) 31 | .prompt()?; 32 | 33 | Ok(value) 34 | } 35 | 36 | fn prompt_name() -> Result { 37 | let value = inquire::Text::new("Mod name:") 38 | .with_placeholder("Better Matchmaking") 39 | .with_validator(min_length!(2, "Minimum of 2 characters required")) 40 | .prompt()?; 41 | 42 | Ok(value) 43 | } 44 | 45 | fn prompt_description() -> Result { 46 | let value = inquire::Text::new("Description:") 47 | .with_placeholder("My first mod ! Hello world") 48 | .with_initial_value("") 49 | .prompt()?; 50 | 51 | Ok(value) 52 | } 53 | 54 | fn prompt_package_name(name: &String) -> Result { 55 | let validator = PatternValidator::new( 56 | r"^([a-z]{1}[a-z-\d_]*\.)+[a-z][a-z-\d_]*$", 57 | "Your package name must be formated like this .., only lower case allowed", 58 | )?; 59 | 60 | let value = inquire::Text::new("Package name:") 61 | .with_default( 62 | format!( 63 | "com.example.{}", 64 | name.from_case(Case::Alternating).to_case(Case::Kebab) 65 | ) 66 | .as_str(), 67 | ) 68 | .with_validator(validator) 69 | .prompt()?; 70 | 71 | Ok(value) 72 | } 73 | 74 | fn collect_args() -> Result { 75 | let name = prompt_name()?; 76 | let version = prompt_version()?; 77 | let description = prompt_description()?; 78 | let package_name = prompt_package_name(&name)?; 79 | let directory = PathBuf::from("."); 80 | 81 | Ok(NewArgs { 82 | name, 83 | description, 84 | package_name, 85 | version, 86 | directory, 87 | }) 88 | } 89 | 90 | pub struct NewCommand; 91 | 92 | impl RunnableCommand for NewCommand { 93 | fn command() -> Command { 94 | Command::new("new") 95 | .about("Create a new mod project") 96 | .long_about("Create a directory with all default configs files and mod entrypoints") 97 | } 98 | 99 | fn run(_: &ArgMatches) -> result::Result<(), command::Error> { 100 | match collect_args() { 101 | | Ok(args) => match create_mod_files(args) { 102 | | Ok(()) => Ok(()), 103 | | Err(e) => { 104 | Err(command::Error::CommandExecutionError(e.to_string())) 105 | }, 106 | }, 107 | | Err(e) => { 108 | Err(command::Error::CommandExecutionError(e.to_string())) 109 | }, 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/sdk/conda/install.rs: -------------------------------------------------------------------------------- 1 | use crate::sdk::conda; 2 | use crate::utils::downloader::download_file; 3 | use std::path::PathBuf; 4 | use std::process::{Command, Output}; 5 | use std::{fs, result}; 6 | 7 | type Result = result::Result; 8 | 9 | pub fn install_conda(destination: &PathBuf) -> Result<()> { 10 | fs::create_dir_all(destination) 11 | .map_err(conda::Error::CreateCondaDirectory)?; 12 | 13 | let install_script_name = get_install_script_name(); 14 | let install_script_destination = 15 | get_script_destination(destination, &install_script_name)?; 16 | 17 | let url = 18 | format!("https://repo.anaconda.com/miniconda/{install_script_name}"); 19 | 20 | // FIXME get the hash and check if there is already one downloaded in local 21 | download_file(&url, install_script_destination.as_str())?; 22 | 23 | let install_destination = 24 | destination.to_str().ok_or(conda::Error::PathError)?; 25 | 26 | let install_result = if cfg!(target_os = "windows") { 27 | install_on_windows(&install_script_destination, install_destination)? 28 | } else { 29 | install_on_linux_and_macos( 30 | &install_script_destination, 31 | install_destination, 32 | )? 33 | }; 34 | 35 | if !install_result.status.success() { 36 | eprintln!("status: {}", install_result.status); 37 | eprintln!( 38 | "stdout: {}", 39 | String::from_utf8_lossy(&install_result.stdout) 40 | ); 41 | eprintln!( 42 | "stderr: {}", 43 | String::from_utf8_lossy(&install_result.stderr) 44 | ); 45 | return Err(conda::Error::NotInstalledError); 46 | } 47 | 48 | fs::remove_file(install_script_destination) 49 | .map_err(conda::Error::InstallError)?; 50 | 51 | // FIXME accept TOS 52 | // conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/main 53 | // conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/r 54 | 55 | Ok(()) 56 | } 57 | 58 | fn get_install_script_name() -> String { 59 | let enforce_x86_arch_on_macos = "x86_64"; 60 | 61 | let (os, arch, extension) = 62 | match (std::env::consts::OS, std::env::consts::ARCH) { 63 | | ("macos", "aarch64") => { 64 | ("MacOSX", enforce_x86_arch_on_macos, "sh") 65 | }, 66 | | ("macos", arch) => ("MacOSX", arch, "sh"), 67 | | ("windows", arch) => ("Windows", arch, "exe"), 68 | | ("linux", arch) => ("Linux", arch, "sh"), 69 | | (os, arch) => (os, arch, "sh"), 70 | }; 71 | 72 | format!("Miniconda3-latest-{os}-{arch}.{extension}") 73 | } 74 | 75 | fn get_script_destination( 76 | install_destination: &PathBuf, script_name: &String, 77 | ) -> Result { 78 | Ok(install_destination 79 | .parent() 80 | .ok_or(conda::Error::PathError)? 81 | .join(PathBuf::from(&script_name)) 82 | .to_str() 83 | .ok_or(conda::Error::PathError)? 84 | .to_string()) 85 | } 86 | 87 | fn install_on_windows( 88 | script_location: &String, conda_path: &str, 89 | ) -> Result { 90 | Ok(Command::new(script_location) 91 | .args(["/S", &format!("/D={conda_path}")]) 92 | .output() 93 | .map_err(conda::Error::InstallError)?) 94 | } 95 | 96 | fn install_on_linux_and_macos( 97 | script_location: &String, conda_path: &str, 98 | ) -> Result { 99 | Ok(Command::new("sh") 100 | .args([script_location, "-p", conda_path, "-b", "-u"]) 101 | .output() 102 | .map_err(conda::Error::InstallError)?) 103 | } 104 | -------------------------------------------------------------------------------- /src/sdk/nvm/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod linux_or_mac_os; 2 | pub mod windows; 3 | 4 | use crate::sdk::node::Node; 5 | use crate::sdk::nvm::linux_or_mac_os::LinuxOrMacOsNVM; 6 | use crate::sdk::nvm::windows::WindowsNVM; 7 | use crate::sdk::{node, Installable}; 8 | use crate::utils::convert_pathbuf_to_string; 9 | use std::fs::create_dir_all; 10 | use std::path::PathBuf; 11 | use std::process::Output; 12 | use std::result; 13 | use std::string::FromUtf8Error; 14 | 15 | #[derive(thiserror::Error, Debug)] 16 | pub enum Error { 17 | #[error("nvm install failed")] 18 | InstallError(String), 19 | #[error("Download nvm install binary failed")] 20 | DownloadError(String), 21 | #[error("node install failed")] 22 | InstallNodeError(#[from] node::Error), 23 | #[error("Failed to create NVM directory ")] 24 | CreateNVMDirectory, 25 | #[error("NVM directory already exists ")] 26 | DirExists, 27 | #[error("Failed to execute nvm command")] 28 | ExecError, 29 | #[error("Failed to execute nvm use command")] 30 | ExecUseError, 31 | #[error("Failed to execute nvm current command")] 32 | ExecCurrentError, 33 | #[error("Conversion failed")] 34 | ConversionError(#[from] convert_pathbuf_to_string::Error), 35 | #[error("Failed to convert utf8 to string")] 36 | Utf8Error(#[from] FromUtf8Error), 37 | } 38 | 39 | type Result = result::Result; 40 | 41 | pub trait NVM { 42 | fn install_node(&self) -> Result<()>; 43 | 44 | fn exec(&self, args: Vec<&str>) -> Result; 45 | 46 | fn get_node(&self) -> Result>; 47 | 48 | fn nvm_use(&self, version: &str) -> Result { 49 | self.exec(vec!["use", version]) 50 | .map_err(|_| Error::ExecUseError) 51 | } 52 | 53 | fn current_node_version(&self) -> Result; 54 | 55 | fn version(&self) -> Result { 56 | let out = self.exec(vec!["--version"])?; 57 | 58 | Ok(String::from_utf8(out.stdout) 59 | .map_err(|_| Error::ExecCurrentError)? 60 | .trim() 61 | .to_string()) 62 | } 63 | } 64 | 65 | fn create_nvm_directory(nvm_path: &PathBuf) -> Result<()> { 66 | if !nvm_path.exists() { 67 | create_dir_all(nvm_path).map_err(|_| Error::CreateNVMDirectory)?; 68 | } else { 69 | Err(Error::DirExists)? 70 | } 71 | 72 | Ok(()) 73 | } 74 | 75 | pub type BoxedNVM = Box; 76 | 77 | pub fn load_nvm(nvm_path: &PathBuf) -> Result { 78 | let nvm_installer: Box = if cfg!(target_os = "windows") { 79 | Box::new(WindowsNVM::from(nvm_path)) 80 | } else { 81 | Box::new(LinuxOrMacOsNVM::from(nvm_path)) 82 | }; 83 | 84 | if !nvm_installer.is_installed() { 85 | println!("Install nvm ..."); 86 | nvm_installer 87 | .install() 88 | .map_err(|e| Error::InstallError(e))?; 89 | } 90 | 91 | let nvm: BoxedNVM = if cfg!(target_os = "windows") { 92 | Box::new(WindowsNVM::from(nvm_path)) 93 | } else { 94 | Box::new(LinuxOrMacOsNVM::from(nvm_path)) 95 | }; 96 | 97 | Ok(nvm) 98 | } 99 | 100 | #[cfg(test)] 101 | mod tests { 102 | use super::*; 103 | use regex::Regex; 104 | use tempfile::tempdir; 105 | 106 | #[test] 107 | fn install_nvm() { 108 | let tmp_dir = tempdir().unwrap(); 109 | let tmp_dir_path = tmp_dir.path().to_path_buf(); 110 | let nvm_path = tmp_dir_path.join("nvm"); 111 | 112 | let nvm = load_nvm(&nvm_path).unwrap(); 113 | let version = nvm.version().unwrap(); 114 | 115 | let semantic_version_pattern = Regex::new("^([0-9]+)\\.([0-9]+)\\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?(?:\\+[0-9A-Za-z-]+)?$").unwrap(); 116 | assert!(semantic_version_pattern.is_match(&version)); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/new/tests.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use crate::config::asconfig_json::{AsconfigcJson, CompilerOption}; 4 | use crate::config::get_tool_home; 5 | use crate::utils::convert_pathbuf_to_string::Stringify; 6 | 7 | #[test] 8 | fn mod_files() { 9 | use crate::new::template::create_mod_files; 10 | use crate::new::template::template_nvm_config; 11 | use crate::new::NewArgs; 12 | use std::fs::read_to_string; 13 | use tempfile::tempdir; 14 | 15 | let tmp_dir = tempdir().unwrap(); 16 | 17 | let args = NewArgs { 18 | version: "1.0.2".to_owned(), 19 | description: "Best mod ever".to_owned(), 20 | name: "Better matchmaking".to_owned(), 21 | directory: tmp_dir.path().to_owned(), 22 | package_name: "fr.gabouchet.better-matchmaking".to_owned(), 23 | }; 24 | 25 | create_mod_files(args).unwrap(); 26 | 27 | let mod_path = tmp_dir.path().join("better-matchmaking"); 28 | 29 | assert_eq!(mod_path.exists(), true); 30 | 31 | let meta_content = read_to_string(mod_path.join("mod.json")).unwrap(); 32 | assert_eq!( 33 | meta_content, 34 | "{ 35 | \"id\": \"fr.gabouchet.better-matchmaking\", 36 | \"version\": \"1.0.2\", 37 | \"name\": \"Better matchmaking\", 38 | \"description\": \"Best mod ever\" 39 | }" 40 | ); 41 | 42 | let script_entrypoint_content = 43 | read_to_string(mod_path.join("scripts/mod_better_matchmaking.py")) 44 | .unwrap(); 45 | assert_eq!( 46 | script_entrypoint_content, 47 | "def init(): 48 | print(\"Hello world from Better matchmaking\") 49 | 50 | def fini(): 51 | print(\"Good bye world from Better matchmaking\") 52 | " 53 | ); 54 | 55 | let ui_entrypoint_content = read_to_string( 56 | mod_path.join("ui/src/fr/gabouchet/BetterMatchmaking.as"), 57 | ) 58 | .unwrap(); 59 | assert_eq!( 60 | ui_entrypoint_content, 61 | "package fr.gabouchet { 62 | import net.wg.infrastructure.base.AbstractView; 63 | 64 | class BetterMatchmaking extends AbstractView { 65 | 66 | } 67 | } 68 | " 69 | ); 70 | 71 | let ui_config_content = 72 | read_to_string(mod_path.join("ui/asconfig.json")).unwrap(); 73 | let wg_home = get_tool_home().unwrap(); 74 | let flash_lib_home = wg_home.join("flash_lib"); 75 | let lib_content = vec![ 76 | "base_app-1.0-SNAPSHOT.swc", 77 | "battle.swc", 78 | "common-1.0-SNAPSHOT.swc", 79 | "common_i18n_library-1.0-SNAPSHOT.swc", 80 | "gui_base-1.0-SNAPSHOT.swc", 81 | "gui_battle-1.0-SNAPSHOT.swc", 82 | "gui_lobby-1.0-SNAPSHOT.swc", 83 | "lobby.swc", 84 | ]; 85 | let lib_content_path = lib_content 86 | .iter() 87 | .map(|filename| flash_lib_home.join(filename).to_string().unwrap()) 88 | .collect::>(); 89 | let json_ui_config = AsconfigcJson { 90 | config: "flex".to_string(), 91 | compiler_option: CompilerOption { 92 | output: "".to_string(), 93 | source_path: vec!["src".to_string()], 94 | library_path: lib_content_path, 95 | }, 96 | main_class: "fr.gabouchet.BetterMatchmaking".to_string(), 97 | }; 98 | let attended = serde_json::to_string_pretty(&json_ui_config).unwrap(); 99 | assert_eq!(ui_config_content, attended); 100 | 101 | template_nvm_config(&mod_path).unwrap(); 102 | let nvm_config_content = 103 | read_to_string(mod_path.join("settings.txt")).unwrap(); 104 | assert_eq!( 105 | nvm_config_content, 106 | "root: ".to_owned() 107 | + mod_path.to_str().unwrap() 108 | + "\n 109 | path: " + mod_path.to_str().unwrap() 110 | + "\\nodejs\n 111 | arch: 64\n 112 | proxy: none\n" 113 | ); 114 | 115 | tmp_dir.close().unwrap(); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/config/settings.rs: -------------------------------------------------------------------------------- 1 | use crate::config; 2 | use crate::config::get_tool_home; 3 | use crate::config::settings::Error::LoadError; 4 | use crate::utils::convert_pathbuf_to_string::Stringify; 5 | use serde_derive::{Deserialize, Serialize}; 6 | use std::env; 7 | use std::fs::File; 8 | use std::path::PathBuf; 9 | use thiserror::Error; 10 | 11 | #[derive(Error, Debug)] 12 | pub enum Error { 13 | #[error("File interaction failed : {0}")] 14 | FileError(#[from] std::io::Error), 15 | #[error("Failed to read json : {0}")] 16 | ParsingError(String), 17 | #[error("Failed to load settings : {0}")] 18 | LoadError(String), 19 | } 20 | 21 | #[derive(Debug, Serialize, Deserialize, Clone)] 22 | pub struct Settings { 23 | #[serde(skip)] 24 | pub settings_file_path: PathBuf, 25 | #[serde(rename = "game_client_path")] 26 | pub game_client_path: Option, 27 | } 28 | 29 | impl Settings { 30 | pub fn create_default_settings(settings_file_path: PathBuf) -> Self { 31 | Self { 32 | settings_file_path, 33 | game_client_path: None, 34 | } 35 | } 36 | 37 | fn prompt_game_client_path(&self) -> Result { 38 | let default_game_client_path = if cfg!(target_os = "windows") { 39 | PathBuf::from("C:\\Games\\World_of_Tanks_EU") 40 | } else { 41 | let user = env::var("USER")?; 42 | PathBuf::from(format!( 43 | "/Users/{user}/Documents/Wargaming.net Games/World_of_Tanks_EU" 44 | )) 45 | }; 46 | let default_game_client_str = default_game_client_path.to_string()?; 47 | 48 | let value = inquire::Text::new("WoT client path:") 49 | .with_default(default_game_client_str.as_str()) 50 | .prompt() 51 | .map_err(config::Error::PromptError)?; 52 | 53 | Ok(value) 54 | } 55 | 56 | pub fn verify_game_client_path_validity(&mut self) { 57 | let is_path_valid = 58 | if let Some(game_client_path) = &self.game_client_path { 59 | game_client_path.exists() 60 | } else { 61 | false 62 | }; 63 | 64 | if !is_path_valid { 65 | let new_path = self.prompt_game_client_path(); 66 | let is_path_valid = match &new_path { 67 | | Ok(path) => { 68 | let path_buf = PathBuf::from(path); 69 | if path_buf.exists() { 70 | self.game_client_path = Some(path_buf); 71 | true 72 | } else { 73 | self.game_client_path = None; 74 | false 75 | } 76 | }, 77 | | Err(_) => { 78 | self.game_client_path = None; 79 | false 80 | }, 81 | }; 82 | 83 | if !is_path_valid { 84 | println!(); 85 | println!("--- Pay attention ---"); 86 | println!("Game client path doesn't exist: {:?}", new_path); 87 | println!("You will not be able to build"); 88 | println!("to set it, rerun wg-mod command"); 89 | println!("---------------------"); 90 | println!(); 91 | } 92 | } 93 | } 94 | 95 | pub fn write_to_json_file(&self) -> Result<(), Error> { 96 | let file = File::create(&self.settings_file_path)?; 97 | 98 | serde_json::to_writer_pretty(file, self) 99 | .map_err(|e| Error::ParsingError(e.to_string()))?; 100 | 101 | Ok(()) 102 | } 103 | 104 | pub fn from_json_file(filename: &PathBuf) -> Result { 105 | let file = File::open(filename)?; 106 | let mut settings: Settings = serde_json::from_reader(file) 107 | .map_err(|e| Error::ParsingError(e.to_string()))?; 108 | settings.settings_file_path = filename.clone(); 109 | Ok(settings) 110 | } 111 | } 112 | 113 | pub fn load_settings() -> Result { 114 | let wg_mod_home = get_tool_home().map_err(|e| LoadError(e.to_string()))?; 115 | let settings = Settings::from_json_file(&wg_mod_home.join("settings.json")) 116 | .map_err(|e| LoadError(e.to_string()))?; 117 | 118 | Ok(settings) 119 | } 120 | -------------------------------------------------------------------------------- /src/sdk/flash_lib/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::config::settings::load_settings; 2 | use crate::utils::copy_directory::copy_directory; 3 | use crate::utils::extract_archive; 4 | use crate::utils::extract_archive::extract_archive; 5 | use regex::Regex; 6 | use std::fs::{create_dir_all, read_dir, remove_dir_all}; 7 | use std::path::PathBuf; 8 | use tempfile::tempdir; 9 | 10 | #[derive(thiserror::Error, Debug)] 11 | pub(crate) enum Error { 12 | #[error("Failed to extract archive: {0}")] 13 | ExtractError(#[from] extract_archive::Error), 14 | #[error("Failed to build lib: {0}")] 15 | BuildError(String), 16 | #[error("Value is null: {0}")] 17 | NullError(String), 18 | #[error("Convertion failed: {0}")] 19 | PatternError(#[from] regex::Error), 20 | } 21 | 22 | pub struct GameFlashLib { 23 | game_flash_lib: PathBuf, 24 | } 25 | 26 | impl From for GameFlashLib { 27 | fn from(value: PathBuf) -> Self { 28 | Self { 29 | game_flash_lib: value, 30 | } 31 | } 32 | } 33 | 34 | impl GameFlashLib { 35 | fn get_flash_archive_path( 36 | &self, game_client_path: &PathBuf, file_identifier: String, 37 | ) -> PathBuf { 38 | game_client_path 39 | .join(format!("res/packages/gui-part{}.pkg", file_identifier)) 40 | } 41 | 42 | fn is_present(&self) -> bool { 43 | self.game_flash_lib.exists() 44 | } 45 | 46 | fn extract(&self) -> Result<(), Error> { 47 | println!("Building game flash lib..."); 48 | let tmp_dir = 49 | tempdir().map_err(|e| Error::BuildError(e.to_string()))?; 50 | 51 | let settings = 52 | load_settings().map_err(|e| Error::BuildError(e.to_string()))?; 53 | let game_client_path = settings 54 | .game_client_path 55 | .ok_or(Error::NullError("game_client_path".to_string()))?; 56 | 57 | let package_path = game_client_path.join("res/packages/"); 58 | let archive_list = read_dir(package_path) 59 | .map_err(|e| Error::BuildError(e.to_string()))?; 60 | 61 | let pattern = Regex::new(r"^.*gui-part[0-9].pkg?")?; 62 | for archive in archive_list.flatten() { 63 | let dir_item_path = archive.path(); 64 | let str = dir_item_path.to_str().ok_or(Error::BuildError( 65 | "failed to convert path to string".to_string(), 66 | ))?; 67 | if pattern.is_match(str) { 68 | extract_archive(&dir_item_path, &tmp_dir.path().to_path_buf()) 69 | .map_err(|e| Error::BuildError(e.to_string()))?; 70 | } 71 | } 72 | 73 | let inside_archive_path = 74 | tmp_dir.path().to_path_buf().join("gui/flash/swc/"); 75 | copy_directory(&inside_archive_path, &self.game_flash_lib) 76 | .map_err(|e| Error::BuildError(e.to_string()))?; 77 | 78 | tmp_dir.close().ok(); 79 | Ok(()) 80 | } 81 | } 82 | 83 | pub fn extract_flash_client_lib( 84 | wg_mod_home: &PathBuf, 85 | ) -> Result { 86 | let game_flash_lib_path = wg_mod_home.join("flash_lib"); 87 | let game_flash_lib = GameFlashLib::from(game_flash_lib_path); 88 | 89 | if game_flash_lib.game_flash_lib.exists() { 90 | remove_dir_all(&game_flash_lib.game_flash_lib) 91 | .map_err(|e| Error::BuildError(e.to_string()))?; 92 | } 93 | 94 | create_dir_all(&game_flash_lib.game_flash_lib) 95 | .map_err(|e| Error::BuildError(e.to_string()))?; 96 | 97 | game_flash_lib 98 | .extract() 99 | .map_err(|e| Error::BuildError(e.to_string()))?; 100 | 101 | Ok(game_flash_lib) 102 | } 103 | 104 | #[cfg(test)] 105 | mod tests { 106 | use super::*; 107 | 108 | #[test] 109 | fn get_flash_archive_path() { 110 | let tmp_dir = tempdir().unwrap(); 111 | 112 | let game_client_lib_path = 113 | GameFlashLib::from(tmp_dir.path().to_path_buf()); 114 | 115 | let flash_file_path = game_client_lib_path.get_flash_archive_path( 116 | &tmp_dir.path().to_path_buf(), 117 | "1".to_string(), 118 | ); 119 | 120 | assert_eq!( 121 | flash_file_path, 122 | tmp_dir 123 | .path() 124 | .to_path_buf() 125 | .join("res/packages/gui-part1.pkg") 126 | ); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/utils/zip.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io; 3 | use std::io::{Read, Write}; 4 | use std::path::Component; 5 | use std::path::PathBuf; 6 | use zip::result::ZipResult; 7 | use zip::write::SimpleFileOptions; 8 | use zip::{CompressionMethod, ZipWriter}; 9 | use zip_extensions::zip_extract; 10 | 11 | #[derive(Debug, thiserror::Error)] 12 | pub enum Error { 13 | #[error("Unable to create an zip archive: {0}")] 14 | ArchiveError(String), 15 | 16 | #[error("Unable to extract the archive: {0}")] 17 | ExtractError(String), 18 | } 19 | 20 | fn make_relative_path(root: &PathBuf, current: &PathBuf) -> PathBuf { 21 | let mut result = PathBuf::new(); 22 | let root_components = root.components().collect::>(); 23 | let current_components = current.components().collect::>(); 24 | for i in 0..current_components.len() { 25 | let current_path_component: Component = current_components[i]; 26 | if i < root_components.len() { 27 | let other: Component = root_components[i]; 28 | if other != current_path_component { 29 | break; 30 | } 31 | } else { 32 | result.push(current_path_component) 33 | } 34 | } 35 | result 36 | } 37 | 38 | fn path_as_string(path: &std::path::Path) -> String { 39 | let mut path_str = String::new(); 40 | for component in path.components() { 41 | if let Component::Normal(os_str) = component { 42 | if !path_str.is_empty() { 43 | path_str.push('/'); 44 | } 45 | path_str.push_str(&*os_str.to_string_lossy()); 46 | } 47 | } 48 | path_str 49 | } 50 | 51 | trait ZipWriterExtensions { 52 | fn create_from_directory_with_options( 53 | self, directory: &PathBuf, 54 | ) -> ZipResult<()>; 55 | } 56 | 57 | impl ZipWriterExtensions for ZipWriter { 58 | fn create_from_directory_with_options( 59 | mut self, directory: &PathBuf, 60 | ) -> ZipResult<()> { 61 | let file_options = SimpleFileOptions::default() 62 | .compression_method(CompressionMethod::Stored); 63 | 64 | let mut paths_queue: Vec = vec![]; 65 | paths_queue.push(directory.clone()); 66 | 67 | let mut buffer = Vec::new(); 68 | 69 | while let Some(next) = paths_queue.pop() { 70 | let directory_entry_iterator = std::fs::read_dir(next)?; 71 | 72 | for entry in directory_entry_iterator { 73 | let entry_path = entry?.path(); 74 | let entry_metadata = std::fs::metadata(entry_path.clone())?; 75 | if entry_metadata.is_file() { 76 | let mut f = File::open(&entry_path)?; 77 | f.read_to_end(&mut buffer)?; 78 | let relative_path = 79 | make_relative_path(&directory, &entry_path); 80 | self.start_file( 81 | path_as_string(&relative_path), 82 | file_options, 83 | )?; 84 | self.write_all(buffer.as_ref())?; 85 | buffer.clear(); 86 | } else if entry_metadata.is_dir() { 87 | let relative_path = 88 | make_relative_path(&directory, &entry_path); 89 | self.add_directory( 90 | path_as_string(&relative_path), 91 | file_options, 92 | )?; 93 | paths_queue.push(entry_path.clone()); 94 | } 95 | } 96 | } 97 | 98 | self.finish()?; 99 | Ok(()) 100 | } 101 | } 102 | 103 | pub fn archive_directory( 104 | archive_file: &PathBuf, directory: &PathBuf, 105 | ) -> Result<(), Error> { 106 | let file = File::create(archive_file) 107 | .map_err(|error| Error::ArchiveError(error.to_string()))?; 108 | let zip_writer = ZipWriter::new(file); 109 | 110 | zip_writer 111 | .create_from_directory_with_options(directory) 112 | .map_err(|error| Error::ArchiveError(error.to_string())) 113 | } 114 | 115 | pub fn extract( 116 | archive_file: &PathBuf, directory: &PathBuf, 117 | ) -> Result<(), Error> { 118 | zip_extract(archive_file, directory) 119 | .map_err(|error| Error::ExtractError(error.to_string())) 120 | } 121 | -------------------------------------------------------------------------------- /src/sdk/asconfigc/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::config::Configs; 2 | use crate::sdk::npm::NPM; 3 | use crate::sdk::nvm::BoxedNVM; 4 | use crate::sdk::{npm, nvm, InstallResult, Installable}; 5 | use crate::utils::command::{self, command}; 6 | use std::path::PathBuf; 7 | use std::string::FromUtf8Error; 8 | use std::{process, result}; 9 | 10 | #[derive(thiserror::Error, Debug)] 11 | pub enum Error { 12 | #[error("Execution error")] 13 | ExecutionError(#[from] command::Error), 14 | 15 | #[error("Bad asconfigc exit status")] 16 | BadExitStatus(process::Output), 17 | 18 | #[error("NVM error")] 19 | NVMError(#[from] nvm::Error), 20 | 21 | #[error("NPM error")] 22 | NPMError(#[from] npm::Error), 23 | 24 | #[error("Install error")] 25 | InstallError(String), 26 | 27 | #[error("Unable to decode output of the command")] 28 | DecodeOutputError(#[from] FromUtf8Error), 29 | 30 | #[error("Failed to build flash")] 31 | BuildError(String), 32 | 33 | #[error("Failed to convert")] 34 | ConvertionError(String), 35 | } 36 | 37 | type Result = result::Result; 38 | 39 | pub struct ASConfigc { 40 | npm: NPM, 41 | } 42 | 43 | impl Installable for ASConfigc { 44 | fn is_installed(&self) -> bool { 45 | match self.npm.is_package_installed("asconfigc") { 46 | | Ok(res) => res, 47 | | Err(e) => { 48 | eprintln!("{}", e); 49 | false 50 | }, 51 | } 52 | } 53 | 54 | fn install(&self) -> InstallResult { 55 | self.npm 56 | .install_package("asconfigc") 57 | .map_err(|e| e.to_string())?; 58 | 59 | Ok(()) 60 | } 61 | } 62 | 63 | impl From for ASConfigc { 64 | fn from(npm: NPM) -> Self { 65 | ASConfigc { npm } 66 | } 67 | } 68 | 69 | impl ASConfigc { 70 | fn exec(&self, args: Vec<&str>) -> Result { 71 | let bin_dir = self.npm.get_bin_directory()?; 72 | let mut args_override = args.clone(); 73 | let exec_path: PathBuf; 74 | 75 | if cfg!(target_os = "windows") { 76 | exec_path = bin_dir.join("npx.cmd"); 77 | args_override.insert(0, "asconfigc"); 78 | } else { 79 | exec_path = bin_dir.join("asconfigc"); 80 | }; 81 | 82 | let executable = exec_path.as_os_str(); 83 | let output = command(executable, args_override, vec![]) 84 | .map_err(Error::ExecutionError)?; 85 | 86 | if !output.status.success() { 87 | return Err(Error::BadExitStatus(output)); 88 | } 89 | 90 | Ok(output) 91 | } 92 | 93 | pub fn build(&self, input_path: &PathBuf) -> Result<()> { 94 | let config = 95 | Configs::load().map_err(|e| Error::BuildError(e.to_string()))?; 96 | let as3_sdk_path = config.as3.get_as3_path(); 97 | let as3_sdk_path_string = as3_sdk_path.to_str().ok_or( 98 | Error::ConvertionError("as3_sdk_path to string".to_string()), 99 | )?; 100 | 101 | let input_path_string = input_path.to_str().ok_or( 102 | Error::ConvertionError("input_path to string".to_string()), 103 | )?; 104 | 105 | let _ = self.exec(vec![ 106 | "--sdk", 107 | as3_sdk_path_string, 108 | "-p", 109 | input_path_string, 110 | ])?; 111 | 112 | Ok(()) 113 | } 114 | 115 | pub fn version(&self) -> Result { 116 | let out = self.exec(vec!["--version"])?; 117 | let version = String::from_utf8(out.stdout)?.trim().to_string(); 118 | 119 | Ok(version) 120 | } 121 | } 122 | 123 | pub fn load_asconfigc(nvm: BoxedNVM) -> Result { 124 | let node = nvm.get_node()?; 125 | let npm = node.get_npm(); 126 | let asconfigc = ASConfigc::from(npm); 127 | 128 | if !asconfigc.is_installed() { 129 | asconfigc.install().map_err(|e| Error::InstallError(e))?; 130 | } 131 | 132 | Ok(asconfigc) 133 | } 134 | 135 | #[cfg(test)] 136 | mod tests { 137 | use super::*; 138 | use crate::sdk::nvm; 139 | use regex::Regex; 140 | use tempfile::tempdir; 141 | 142 | #[test] 143 | fn install_asconfigc() { 144 | let tmp_dir = tempdir().unwrap(); 145 | let tmp_dir_path = tmp_dir.path().to_path_buf(); 146 | let nvm_path = tmp_dir_path.join("nvm"); 147 | let nvm = nvm::load_nvm(&nvm_path).unwrap(); 148 | 149 | let asconfigc = load_asconfigc(nvm).unwrap(); 150 | let version = asconfigc.version().unwrap(); 151 | 152 | let semantic_version_pattern = Regex::new("^Version: ([0-9]+)\\.([0-9]+)\\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?(?:\\+[0-9A-Za-z-]+)?$").unwrap(); 153 | assert!(semantic_version_pattern.is_match(&version)); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/sdk/conda/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod environment; 2 | mod install; 3 | 4 | use crate::sdk::conda::environment::CondaEnvironment; 5 | use crate::sdk::conda::install::install_conda; 6 | use crate::sdk::{InstallResult, Installable}; 7 | use crate::utils::downloader; 8 | use std::{ 9 | fs, 10 | path::PathBuf, 11 | process::{Command, Output}, 12 | result, 13 | str::Utf8Error, 14 | }; 15 | 16 | #[derive(thiserror::Error, Debug)] 17 | pub enum Error { 18 | #[error("Cannot download provided url")] 19 | DownloadError(#[from] downloader::Error), 20 | 21 | #[error("Cannot create the conda directory")] 22 | CreateCondaDirectory(std::io::Error), 23 | 24 | #[error("Cannot create a download directory")] 25 | PathError, 26 | 27 | #[error("Conda isn't installed")] 28 | NotInstalledError, 29 | 30 | #[error("Conda install error")] 31 | InstallError(std::io::Error), 32 | 33 | #[error("Can't invoke command")] 34 | CommandInvocationError(std::io::Error), 35 | 36 | #[error("Command error")] 37 | CommandError(Output), 38 | 39 | #[error("Cannot read the command output")] 40 | CommandOutputParsingError(#[from] Utf8Error), 41 | } 42 | 43 | type Result = result::Result; 44 | 45 | pub struct Conda { 46 | conda_path: PathBuf, 47 | } 48 | 49 | impl From<&PathBuf> for Conda { 50 | fn from(conda_path: &PathBuf) -> Self { 51 | Self { 52 | conda_path: conda_path.clone(), 53 | } 54 | } 55 | } 56 | 57 | impl Conda { 58 | fn get_executable_path(&self) -> PathBuf { 59 | if cfg!(target_os = "windows") { 60 | self.conda_path.join("condabin").join("conda.bat") 61 | } else { 62 | self.conda_path.join("bin").join("conda") 63 | } 64 | } 65 | 66 | fn command(&self, args: Vec<&str>) -> Result<(String, String)> { 67 | if !self.is_installed() { 68 | return Err(Error::NotInstalledError); 69 | } 70 | 71 | let executable_path = self.get_executable_path(); 72 | let mut command = Command::new(executable_path); 73 | 74 | let output = command 75 | .args(args) 76 | .output() 77 | .map_err(Error::CommandInvocationError)?; 78 | 79 | if !output.status.success() { 80 | return Err(Error::CommandError(output)); 81 | } 82 | 83 | let stdout = std::str::from_utf8(&output.stdout)?.to_string(); 84 | let stderr = std::str::from_utf8(&output.stderr)?.to_string(); 85 | Ok((stdout, stderr)) 86 | } 87 | 88 | pub fn create_environment( 89 | &self, name: &str, python_version: &str, 90 | ) -> Result<()> { 91 | self.command(vec![ 92 | "create", 93 | "-p", 94 | self.conda_path 95 | .join("envs") 96 | .join(name) 97 | .to_str() 98 | .ok_or(Error::PathError)?, 99 | &format!("python={}", python_version), 100 | ])?; 101 | 102 | Ok(()) 103 | } 104 | 105 | pub fn get_environment(&self, name: &str) -> CondaEnvironment { 106 | let conda_envs_path = self.conda_path.join("envs"); 107 | let environment_path = conda_envs_path.join(name); 108 | 109 | CondaEnvironment::from(environment_path) 110 | } 111 | 112 | pub fn has_environment(&self, name: &str) -> bool { 113 | let conda_envs_path = self.conda_path.join("envs"); 114 | let environment_path = conda_envs_path.join(name); 115 | 116 | environment_path.exists() 117 | } 118 | 119 | pub fn version(&self) -> Result { 120 | let (out, _) = self.command(vec!["--version"])?; 121 | 122 | Ok(out.trim().to_string()) 123 | } 124 | } 125 | 126 | impl Installable for Conda { 127 | fn is_installed(&self) -> bool { 128 | match fs::metadata(self.get_executable_path()) { 129 | | Ok(metadata) => metadata.is_file(), 130 | | Err(_) => false, 131 | } 132 | } 133 | 134 | fn install(&self) -> InstallResult { 135 | if self.is_installed() { 136 | Err("Conda is already installed".into()) 137 | } else { 138 | install_conda(&self.conda_path).map_err(|error| error.to_string()) 139 | } 140 | } 141 | } 142 | 143 | pub fn load_conda(conda_path: &PathBuf) -> Result { 144 | let conda = Conda::from(conda_path); 145 | 146 | if !conda.is_installed() { 147 | println!("Installing conda..."); 148 | conda.install().expect("failed conda installation"); 149 | } 150 | 151 | Ok(conda) 152 | } 153 | 154 | #[cfg(test)] 155 | mod tests { 156 | use super::*; 157 | use regex::Regex; 158 | use tempfile::tempdir; 159 | 160 | #[test] 161 | fn install_conda() { 162 | if !cfg!(target_os = "windows") { 163 | let tmp_dir = tempdir().unwrap(); 164 | let tmp_dir_path = tmp_dir.path().to_path_buf(); 165 | let conda_path = tmp_dir_path.join("conda"); 166 | 167 | let conda = load_conda(&conda_path).unwrap(); 168 | let version = conda.version().unwrap(); 169 | 170 | let semantic_version_pattern = Regex::new("^conda ([0-9]+)\\.([0-9]+)\\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?(?:\\+[0-9A-Za-z-]+)?$").unwrap(); 171 | assert!(semantic_version_pattern.is_match(&version)); 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/config/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod asconfig_json; 2 | pub mod mod_conf; 3 | pub mod settings; 4 | 5 | use crate::config::settings::Settings; 6 | use crate::sdk::as3::AS3; 7 | use crate::sdk::asconfigc::ASConfigc; 8 | use crate::sdk::conda::environment::CondaEnvironment; 9 | use crate::sdk::conda::Conda; 10 | use crate::sdk::game_client::GameClient; 11 | use crate::sdk::game_sources::GameSources; 12 | use crate::sdk::nvm::BoxedNVM; 13 | use crate::sdk::{ 14 | as3, asconfigc, conda, flash_lib, game_sources, nvm, Installable, 15 | }; 16 | use crate::utils; 17 | use inquire::InquireError; 18 | use std::env::VarError; 19 | use std::path::PathBuf; 20 | use std::result; 21 | 22 | #[derive(thiserror::Error, Debug)] 23 | pub enum Error { 24 | #[error("Unable to find the user home directory")] 25 | UserHomeError, 26 | 27 | #[error("Terminal prompt error: {0}")] 28 | PromptError(#[from] InquireError), 29 | 30 | #[error("Unable to load game sources: {0}")] 31 | GameSourcesError(#[from] game_sources::Error), 32 | 33 | #[error("Unable to build client flash lib: {0}")] 34 | GameClientLibError(#[from] flash_lib::Error), 35 | 36 | #[error("Unable to load Conda: {0}")] 37 | CondaError(#[from] conda::Error), 38 | 39 | #[error("Unable to load AS3: {0}")] 40 | AS3Error(#[from] as3::Error), 41 | 42 | #[error("Unable to load settings: {0}")] 43 | SettingsError(#[from] settings::Error), 44 | 45 | #[error("NVM error")] 46 | NVMError(#[from] nvm::Error), 47 | 48 | #[error("ASConfigc loading error")] 49 | ASConfigcError(#[from] asconfigc::Error), 50 | 51 | #[error("Failed to compose string: {0}")] 52 | StringError(#[from] std::fmt::Error), 53 | 54 | #[error("Failed convertion: {0}")] 55 | ConvertionError(#[from] utils::convert_pathbuf_to_string::Error), 56 | 57 | #[error("Unable to read current user name")] 58 | Environment(#[from] VarError), 59 | } 60 | 61 | type Result = result::Result; 62 | 63 | pub struct Configs { 64 | pub wg_mod_home: PathBuf, 65 | pub game_sources: GameSources, 66 | pub game_client: Option, 67 | pub conda_environment: CondaEnvironment, 68 | pub as3: AS3, 69 | pub asconfigc: ASConfigc, 70 | pub settings: Settings, 71 | } 72 | 73 | pub fn get_tool_home() -> Result { 74 | let user_path: PathBuf = home::home_dir().ok_or(Error::UserHomeError)?; 75 | let wg_tool_path = user_path.join(".wg-mod"); 76 | Ok(wg_tool_path) 77 | } 78 | 79 | impl Configs { 80 | pub fn load() -> Result { 81 | let wg_mod_home = get_tool_home()?; 82 | let game_sources = load_game_sources(&wg_mod_home)?; 83 | let conda_environment = load_conda_environment(&wg_mod_home)?; 84 | let as3 = load_as3(&wg_mod_home)?; 85 | let settings = load_settings(&wg_mod_home)?; 86 | let asconfigc = load_asconfigc(&wg_mod_home)?; 87 | let game_client = load_game_client(&settings); 88 | 89 | Ok(Configs { 90 | game_sources, 91 | game_client, 92 | wg_mod_home, 93 | conda_environment, 94 | as3, 95 | asconfigc, 96 | settings, 97 | }) 98 | } 99 | } 100 | 101 | fn load_asconfigc(wg_mod_home: &PathBuf) -> Result { 102 | let nvm = load_nvm(&wg_mod_home)?; 103 | let asconfigc = asconfigc::load_asconfigc(nvm)?; 104 | 105 | Ok(asconfigc) 106 | } 107 | 108 | fn load_game_sources(wg_mod_home: &PathBuf) -> Result { 109 | let game_sources_path = wg_mod_home.join("wot-src"); 110 | let game_sources = GameSources::load(&game_sources_path)?; 111 | 112 | Ok(game_sources) 113 | } 114 | 115 | fn load_conda(wg_mod_home: &PathBuf) -> Result { 116 | let conda_path = wg_mod_home.join("conda"); 117 | let conda = conda::load_conda(&conda_path)?; 118 | 119 | Ok(conda) 120 | } 121 | 122 | fn load_as3(wg_mod_home: &PathBuf) -> Result { 123 | let as3_path = wg_mod_home.join("as3"); 124 | let as3 = as3::load_as3(&as3_path)?; 125 | 126 | Ok(as3) 127 | } 128 | 129 | fn load_nvm(wg_mod_home: &PathBuf) -> Result { 130 | let nvm_path = wg_mod_home.join("nvm"); 131 | let nvm = nvm::load_nvm(&nvm_path)?; 132 | 133 | Ok(nvm) 134 | } 135 | 136 | fn load_conda_environment(wg_mod_home: &PathBuf) -> Result { 137 | let conda = load_conda(wg_mod_home)?; 138 | 139 | if !conda.has_environment("wg-mod") { 140 | println!("Create conda env..."); 141 | conda.create_environment("wg-mod", "2")?; 142 | } 143 | 144 | Ok(conda.get_environment("wg-mod")) 145 | } 146 | 147 | fn load_game_client(settings: &Settings) -> Option { 148 | if let Some(game_client_path) = &settings.game_client_path { 149 | Some(GameClient::from(game_client_path)) 150 | } else { 151 | None 152 | } 153 | } 154 | 155 | fn load_settings(wg_mod_home: &PathBuf) -> Result { 156 | let settings_file_path = wg_mod_home.join("settings.json"); 157 | let mut settings: Settings; 158 | 159 | if !settings_file_path.exists() { 160 | settings = 161 | Settings::create_default_settings(settings_file_path.clone()); 162 | } else { 163 | settings = Settings::from_json_file(&settings_file_path)?; 164 | } 165 | 166 | settings.verify_game_client_path_validity(); 167 | settings.write_to_json_file()?; 168 | 169 | Ok(settings) 170 | } 171 | 172 | fn get_conda(wg_mod_home: &PathBuf) -> Result { 173 | let conda_path = wg_mod_home.join("conda"); 174 | let conda = Conda::from(&conda_path); 175 | 176 | if !conda.is_installed() { 177 | println!("Installing conda..."); 178 | conda.install().expect("failed conda installation"); 179 | } 180 | 181 | Ok(conda) 182 | } 183 | -------------------------------------------------------------------------------- /src/new/template.rs: -------------------------------------------------------------------------------- 1 | use super::NewArgs; 2 | use crate::config::asconfig_json::{AsconfigcJson, CompilerOption}; 3 | use crate::config::get_tool_home; 4 | use crate::config::mod_conf::ModConf; 5 | use crate::utils::convert_pathbuf_to_string::Stringify; 6 | use crate::utils::convert_to_absolute_path::convert_to_absolute_path; 7 | use crate::utils::file_template; 8 | use crate::utils::file_template::write_template; 9 | use convert_case::{Case, Casing}; 10 | use serde_json::json; 11 | use std::path::PathBuf; 12 | use std::{fs, result}; 13 | 14 | type Result = result::Result; 15 | 16 | fn template_mod_conf(args: &NewArgs, parent_dir: &PathBuf) -> Result<()> { 17 | fs::create_dir_all(&parent_dir) 18 | .map_err(file_template::Error::DirectoryCreateError)?; 19 | 20 | let meta = ModConf { 21 | package_name: args.package_name.clone(), 22 | version: args.version.clone(), 23 | name: args.name.clone(), 24 | description: args.description.clone(), 25 | }; 26 | let file_path = &parent_dir.join("mod.json"); 27 | 28 | meta.write_json_to_file(file_path).map_err(|e| { 29 | file_template::Error::FileCreateError(e, file_path.clone()) 30 | })?; 31 | 32 | Ok(()) 33 | } 34 | 35 | fn template_script_entrypoint( 36 | args: &NewArgs, parent_dir: &PathBuf, 37 | ) -> Result<()> { 38 | write_template( 39 | &parent_dir, 40 | &format!("mod_{}.py", args.name.to_case(Case::Snake)), 41 | "def init(): 42 | print(\"Hello world from {{name}}\") 43 | 44 | def fini(): 45 | print(\"Good bye world from {{name}}\") 46 | ", 47 | &json!({ 48 | "name": args.name 49 | }), 50 | )?; 51 | 52 | Ok(()) 53 | } 54 | 55 | fn template_git_ignore(parent_dir: &PathBuf) -> Result<()> { 56 | write_template( 57 | &parent_dir, 58 | ".gitignore", 59 | "/.idea 60 | /.vscode 61 | /target 62 | .DS_Store 63 | ", 64 | &json!({}), 65 | )?; 66 | 67 | Ok(()) 68 | } 69 | 70 | fn template_ui_entrypoint(args: &NewArgs, parent_dir: &PathBuf) -> Result<()> { 71 | let tokens = args.package_name.split(".").collect::>(); 72 | let package_name_without_suffix = tokens[..tokens.len() - 1].join("."); 73 | 74 | write_template( 75 | parent_dir, 76 | &format!("{}.as", args.name.to_case(Case::Pascal)), 77 | "package {{package_name}} { 78 | import net.wg.infrastructure.base.AbstractView; 79 | 80 | class {{class_name}} extends AbstractView { 81 | 82 | } 83 | } 84 | ", 85 | &json!({ 86 | "class_name": args.name.to_case(Case::Pascal), 87 | "package_name": package_name_without_suffix 88 | }), 89 | ) 90 | } 91 | 92 | fn template_ui_config(args: &NewArgs, parent_dir: &PathBuf) -> Result<()> { 93 | fs::create_dir_all(parent_dir) 94 | .map_err(file_template::Error::DirectoryCreateError)?; 95 | 96 | let wg_home = get_tool_home()?; 97 | let flash_lib_home = wg_home.join("flash_lib"); 98 | let mut lib = vec![]; 99 | // todo -> change this to libhome listfile when the flash lib extract will be done before "new" command 100 | let lib_content = vec![ 101 | "base_app-1.0-SNAPSHOT.swc", 102 | "battle.swc", 103 | "common-1.0-SNAPSHOT.swc", 104 | "common_i18n_library-1.0-SNAPSHOT.swc", 105 | "gui_base-1.0-SNAPSHOT.swc", 106 | "gui_battle-1.0-SNAPSHOT.swc", 107 | "gui_lobby-1.0-SNAPSHOT.swc", 108 | "lobby.swc", 109 | ]; 110 | for string in lib_content { 111 | lib.push(flash_lib_home.join(string).to_string()?); 112 | } 113 | 114 | let tokens = args.package_name.split(".").collect::>(); 115 | let package_name_without_suffix = &tokens[..tokens.len() - 1]; 116 | let mut main_class_name = package_name_without_suffix.join("."); 117 | main_class_name.push_str("."); 118 | main_class_name.push_str(args.name.clone().to_case(Case::Pascal).as_str()); 119 | 120 | let ui_config = AsconfigcJson { 121 | config: "flex".to_string(), 122 | compiler_option: CompilerOption { 123 | output: "".to_string(), 124 | source_path: vec!["src".to_string()], 125 | library_path: lib, 126 | }, 127 | main_class: main_class_name, 128 | }; 129 | 130 | let filename = parent_dir.join("asconfig.json"); 131 | 132 | Ok(ui_config 133 | .write_json_to_file(&filename) 134 | .map_err(|e| file_template::Error::FileCreateError(e, filename))?) 135 | } 136 | 137 | fn init_git_repository(directory: &PathBuf) -> Result<()> { 138 | template_git_ignore(directory)?; 139 | 140 | git2::Repository::init(directory)?; 141 | 142 | Ok(()) 143 | } 144 | 145 | pub fn create_mod_files(args: NewArgs) -> Result<()> { 146 | let kebab_name = 147 | args.name.from_case(Case::Alternating).to_case(Case::Kebab); 148 | 149 | let root_path = args.directory.join(&kebab_name); 150 | 151 | template_mod_conf(&args, &root_path)?; 152 | 153 | let scripts_entrypoint_path = &root_path.join("scripts"); 154 | template_script_entrypoint(&args, &scripts_entrypoint_path)?; 155 | 156 | let ui_path = &root_path.join("ui"); 157 | let mut ui_sources_path = ui_path.join("src"); 158 | let tokens = args.package_name.split(".").collect::>(); 159 | for token in tokens[..tokens.len() - 1].iter() { 160 | ui_sources_path = ui_sources_path.join(token); 161 | } 162 | 163 | template_ui_entrypoint(&args, &ui_sources_path)?; 164 | template_ui_config(&args, &ui_path)?; 165 | 166 | init_git_repository(&root_path)?; 167 | 168 | let absolute_mod_path = convert_to_absolute_path(&root_path)?; 169 | println!("Success! Created {kebab_name} at {absolute_mod_path}"); 170 | 171 | Ok(()) 172 | } 173 | 174 | pub fn template_nvm_config(parent_dir: &PathBuf) -> Result<()> { 175 | write_template( 176 | parent_dir, 177 | "settings.txt", 178 | "root: {{nvm_dir}}\n 179 | path: {{nvm_dir}}\\nodejs\n 180 | arch: 64\n 181 | proxy: none\n", 182 | &json!({ 183 | "nvm_dir": parent_dir 184 | }), 185 | ) 186 | } 187 | 188 | pub fn create_nvm_executable(parent_dir: &PathBuf, name: &str) -> Result<()> { 189 | write_template( 190 | parent_dir, 191 | name, 192 | "[ -s \"$NVM_DIR/nvm.sh\" ] && \\. \"$NVM_DIR/nvm.sh\" # This loads nvm 193 | [ -s \"$NVM_DIR/bash_completion\" ] && \\. \"$NVM_DIR/bash_completion\" # This loads nvm bash_completion 194 | nvm $@", 195 | &json!({})) 196 | } 197 | -------------------------------------------------------------------------------- /src/sdk/game_sources/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::convert_to_absolute_path::convert_to_absolute_path; 2 | use fs_extra::dir::get_dir_content; 3 | use git2::{ 4 | Branch, BranchType, FetchOptions, Remote, RemoteCallbacks, Repository, 5 | }; 6 | use inquire::Select; 7 | use std::fs::create_dir_all; 8 | use std::io::Write; 9 | use std::path::{PathBuf, MAIN_SEPARATOR}; 10 | use std::{io, result}; 11 | 12 | #[derive(thiserror::Error, Debug)] 13 | pub enum Error { 14 | #[error("Unable to create directory\n{0}")] 15 | CreateDirectoryError(io::Error), 16 | 17 | #[error("Git error occurred")] 18 | GitError(#[from] git2::Error), 19 | 20 | #[error("Unable to read branch name")] 21 | GitBranchError, 22 | 23 | #[error("An error occurred during user prompting")] 24 | CliPromptError(#[from] inquire::InquireError), 25 | 26 | #[error("Invalid path")] 27 | PathError, 28 | 29 | #[error("Unable to walk in the directory")] 30 | FilesystemError(#[from] fs_extra::error::Error), 31 | } 32 | 33 | type Result = result::Result; 34 | 35 | pub struct GameSources { 36 | repository: Repository, 37 | } 38 | 39 | fn fetch(remote: &mut Remote) -> Result<()> { 40 | let mut cb = RemoteCallbacks::new(); 41 | cb.transfer_progress(|stats| { 42 | let download_progress = 100_f32 * stats.received_objects() as f32 43 | / stats.total_objects() as f32; 44 | let unzip_progress = 100_f32 * stats.indexed_deltas() as f32 45 | / stats.total_deltas() as f32; 46 | 47 | if stats.received_objects() != stats.total_objects() { 48 | print!("Fetching WoT sources ... {:.0}%\r", download_progress); 49 | } else { 50 | print!("Unpacking WoT sources ... {:.0}%\r", unzip_progress); 51 | } 52 | io::stdout().flush().ok(); 53 | true 54 | }); 55 | 56 | let mut fetch_options = FetchOptions::default(); 57 | fetch_options.remote_callbacks(cb); 58 | remote.fetch( 59 | &["+refs/heads/*:refs/remotes/origin/*"], 60 | Some(&mut fetch_options), 61 | None, 62 | )?; 63 | 64 | Ok(()) 65 | } 66 | 67 | fn get_repository( 68 | path: &PathBuf, need_to_be_initialized: bool, 69 | ) -> Result { 70 | let repository = if need_to_be_initialized { 71 | create_dir_all(&path).map_err(Error::CreateDirectoryError)?; 72 | Repository::init(&path)? 73 | } else { 74 | Repository::open(&path)? 75 | }; 76 | 77 | Ok(repository) 78 | } 79 | 80 | fn get_default_remote( 81 | repository: &Repository, need_to_be_initialized: bool, 82 | ) -> Result { 83 | let wot_src_remote_url = "https://github.com/IzeBerg/wot-src.git"; 84 | let remote = if need_to_be_initialized { 85 | repository.remote("origin", wot_src_remote_url)? 86 | } else { 87 | repository.find_remote("origin")? 88 | }; 89 | 90 | Ok(remote) 91 | } 92 | 93 | impl GameSources { 94 | pub fn load(path: &PathBuf) -> Result { 95 | let already_exists = path.exists(); 96 | 97 | let repository = get_repository(&path, !already_exists)?; 98 | let mut remote = get_default_remote(&repository, !already_exists)?; 99 | 100 | fetch(&mut remote)?; 101 | 102 | let game_sources = GameSources { 103 | repository: get_repository(&path, false)?, 104 | }; 105 | 106 | if !already_exists { 107 | game_sources.prompt_channel()?; 108 | } 109 | 110 | Ok(game_sources) 111 | } 112 | 113 | fn list_channels(&self) -> Result> { 114 | let branches_options = Some(BranchType::Remote); 115 | let it = self.repository.branches(branches_options)?; 116 | let mut branches: Vec = vec![]; 117 | 118 | let branches_result: Vec< 119 | result::Result<(Branch, BranchType), git2::Error>, 120 | > = it.collect(); 121 | 122 | for branch_result in branches_result { 123 | let (branch, _) = branch_result?; 124 | let branch_name = 125 | branch.name()?.ok_or(Error::GitBranchError)?.to_string(); 126 | let short_branch_name = branch_name.replace("origin/", ""); 127 | branches.push(short_branch_name); 128 | } 129 | 130 | Ok(branches) 131 | } 132 | 133 | pub fn prompt_channel(&self) -> Result<()> { 134 | let channels_available = self.list_channels()?; 135 | let channel_selected = Select::new( 136 | "Select a World of Tanks development channel:", 137 | channels_available, 138 | ) 139 | .prompt()?; 140 | 141 | self.switch_channel(&channel_selected)?; 142 | 143 | Ok(()) 144 | } 145 | 146 | fn switch_channel(&self, channel_name: &str) -> Result<()> { 147 | let branch_name = format!("origin/{channel_name}"); 148 | let (object, reference) = self.repository.revparse_ext(&branch_name)?; 149 | 150 | self.repository.checkout_tree(&object, None)?; 151 | match reference { 152 | | Some(reference) => self 153 | .repository 154 | .set_head(reference.name().ok_or(Error::GitBranchError)?), 155 | | None => self.repository.set_head_detached(object.id()), 156 | }?; 157 | 158 | Ok(()) 159 | } 160 | 161 | pub fn get_channel(&self) -> Result { 162 | let current_commit = self.repository.head()?.peel_to_commit()?; 163 | let references = self.repository.references()?; 164 | 165 | for reference_pack in references { 166 | if let Ok(reference) = reference_pack { 167 | if let Some(target) = reference.target() { 168 | if target == current_commit.id() 169 | && reference.name().is_some() 170 | { 171 | return Ok(reference 172 | .name() 173 | .ok_or(Error::GitBranchError)? 174 | .to_string() 175 | .replace("refs/heads/", "") 176 | .replace("refs/remotes/origin/", "")); 177 | } 178 | } 179 | } 180 | } 181 | 182 | Err(Error::GitBranchError) 183 | } 184 | 185 | fn list_directory_paths(&self, path: &PathBuf) -> Result> { 186 | let directory_content = get_dir_content(path)?; 187 | let folders = directory_content.directories; 188 | 189 | let cleaned_path = folders 190 | .iter() 191 | .map(|p| { 192 | convert_to_absolute_path(&PathBuf::from(p)) 193 | .map_err(|_| Error::PathError) 194 | }) 195 | .collect::>>()?; 196 | 197 | Ok(cleaned_path) 198 | } 199 | 200 | fn is_game_python_root_module(&self, folder: &String) -> bool { 201 | folder.ends_with(&format!("scripts{}common", MAIN_SEPARATOR)) 202 | || folder.ends_with(&format!("scripts{}client", MAIN_SEPARATOR)) 203 | || folder 204 | .ends_with(&format!("scripts{}client_common", MAIN_SEPARATOR)) 205 | } 206 | 207 | pub fn list_python_root_modules(&self) -> Result> { 208 | let sources_path = 209 | self.repository.path().parent().ok_or(Error::PathError)?; 210 | let python_sources_path = sources_path.join("sources/res"); 211 | let sub_paths = self.list_directory_paths(&python_sources_path)?; 212 | 213 | Ok(sub_paths 214 | .iter() 215 | .filter_map(|folder| { 216 | if self.is_game_python_root_module(folder) { 217 | Some(folder.clone()) 218 | } else { 219 | None 220 | } 221 | }) 222 | .collect()) 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /src/builder/mod.rs: -------------------------------------------------------------------------------- 1 | mod flash; 2 | mod python; 3 | 4 | use crate::builder::flash::FlashBuilder; 5 | use crate::builder::python::PythonBuilder; 6 | use crate::config; 7 | use crate::config::asconfig_json::AsconfigcJson; 8 | use crate::config::mod_conf::ModConf; 9 | use crate::config::{get_tool_home, mod_conf}; 10 | use crate::sdk::flash_lib; 11 | use crate::sdk::flash_lib::extract_flash_client_lib; 12 | use crate::utils::convert_pathbuf_to_string::Stringify; 13 | use crate::utils::convert_to_absolute_path::convert_to_absolute_path; 14 | use crate::utils::zip; 15 | use crate::utils::{convert_pathbuf_to_string, convert_to_absolute_path}; 16 | use convert_case::{Case, Casing}; 17 | use inquire::InquireError; 18 | use std::path::PathBuf; 19 | use std::{fs, io, result}; 20 | 21 | #[derive(thiserror::Error, Debug)] 22 | pub enum Error { 23 | #[error("Failed to use the python mod builder\n{0}")] 24 | PythonBuilderError(#[from] python::Error), 25 | 26 | #[error("Failed to use the flash mod builder\n{0}")] 27 | FlashBuilderError(#[from] flash::Error), 28 | 29 | #[error("Copy directory failed\n{0}")] 30 | CopyDirectoryError(#[from] fs_extra::error::Error), 31 | 32 | #[error("Glob error\n{0}")] 33 | GlobError(#[from] glob::GlobError), 34 | 35 | #[error("The path \"{0}\" isn't a mod folder")] 36 | BadModFolderError(PathBuf), 37 | 38 | #[error("Path error: {0}")] 39 | PathError(String), 40 | 41 | #[error("Unable to write mod archive\n{0}")] 42 | ZipWriteError(#[from] zip::Error), 43 | 44 | #[error("Unable to get the absolute path of the archive: {0}")] 45 | ConvertAbsolutePathError(#[from] convert_to_absolute_path::Error), 46 | 47 | #[error("Unable to get the absolute path of the archive: {0}")] 48 | ConvertPathToStringError(#[from] convert_pathbuf_to_string::Error), 49 | 50 | #[error("Manage config file: {0}")] 51 | ConfigFileError(#[from] io::Error), 52 | 53 | #[error("Unable to write mod config: {0}")] 54 | ModConfigFileError(#[from] mod_conf::Error), 55 | 56 | #[error("Failed to get config: {0}")] 57 | ConfigError(#[from] config::Error), 58 | 59 | #[error("Failed prompt main class_name: {0}")] 60 | PromptError(#[from] InquireError), 61 | 62 | #[error("Failed build game client flash lib: {0}")] 63 | BuildFlashLibError(#[from] flash_lib::Error), 64 | } 65 | 66 | type Result = result::Result; 67 | 68 | pub struct ModBuilder { 69 | python_builder: PythonBuilder, 70 | flash_builder: FlashBuilder, 71 | mod_path: PathBuf, 72 | target_path: PathBuf, 73 | build_path: PathBuf, 74 | } 75 | 76 | impl ModBuilder { 77 | pub fn new(mod_path: PathBuf) -> Result { 78 | let python_builder = PythonBuilder::new()?; 79 | let flash_builder = FlashBuilder::new()?; 80 | let target_path = mod_path.join("target"); 81 | let build_path = target_path.join("build"); 82 | 83 | Ok(Self { 84 | python_builder, 85 | flash_builder, 86 | mod_path, 87 | target_path, 88 | build_path, 89 | }) 90 | } 91 | 92 | fn clean_target_directory(&self) -> Result<()> { 93 | let _ = fs::remove_dir_all(&self.target_path); 94 | 95 | Ok(()) 96 | } 97 | 98 | fn build_python_src(&self) -> Result<()> { 99 | let python_sources = self.mod_path.join("scripts"); 100 | let python_build_destination = 101 | self.build_path.join("res/scripts/client/gui/mods"); 102 | 103 | self.python_builder 104 | .build(&python_sources, &python_build_destination)?; 105 | 106 | Ok(()) 107 | } 108 | 109 | fn copy_meta_file(&self) -> Result<()> { 110 | let meta_path = self.mod_path.join("mod.json"); 111 | let mod_conf = ModConf::from_file(&meta_path)?; 112 | mod_conf.export_mod_meta(&self.build_path, "meta.xml")?; 113 | 114 | Ok(()) 115 | } 116 | fn build_flash_src(&self) -> Result<()> { 117 | let flash_sources = self.mod_path.join("ui"); 118 | let flash_build_destination = self.build_path.join("res/gui/flash"); 119 | 120 | if !flash_sources.exists() { 121 | Err(Error::PathError(format!( 122 | "Flash source does not exist: {:?}", 123 | flash_sources 124 | )))? 125 | } 126 | 127 | let wg_home = get_tool_home()?; 128 | extract_flash_client_lib(&wg_home)?; 129 | 130 | self.update_asconfigc_json(&flash_sources)?; 131 | 132 | self.flash_builder 133 | .build(&flash_sources, &flash_build_destination)?; 134 | 135 | Ok(()) 136 | } 137 | 138 | fn update_asconfigc_json(&self, flash_sources: &PathBuf) -> Result<()> { 139 | let asconfigc_json_path = flash_sources.join("asconfig.json"); 140 | let mut asconfigc = AsconfigcJson::from_file(&asconfigc_json_path)?; 141 | 142 | let main_class_string_path = asconfigc.main_class.replace(".", "/"); 143 | 144 | let prompt_class_string = 145 | self.prompt_main_class(main_class_string_path.as_str())?; 146 | 147 | let prompt_class_with_extention = if prompt_class_string.contains(".as") 148 | { 149 | &prompt_class_string 150 | } else { 151 | &format!("{}.as", &prompt_class_string) 152 | }; 153 | let prompt_class_globale_path = self 154 | .mod_path 155 | .join("ui") 156 | .join("src") 157 | .join(&prompt_class_with_extention); 158 | if !prompt_class_globale_path.exists() { 159 | let error_message = format!("Prompt class path given ({:?}) does not exist (File not found)", prompt_class_globale_path.to_string()); 160 | Err(Error::PathError(error_message))? 161 | } 162 | 163 | let prompt_class_without_extention = 164 | if prompt_class_string.contains(".as") { 165 | &prompt_class_string.replace(".as", "") 166 | } else { 167 | &prompt_class_string 168 | }; 169 | let prompt_class = prompt_class_without_extention.replace("/", "."); 170 | if !prompt_class.eq(asconfigc.main_class.as_str()) { 171 | asconfigc.main_class = prompt_class; 172 | } 173 | 174 | let meta_path = self.mod_path.join("mod.json"); 175 | let mod_conf = ModConf::from_file(&meta_path)?; 176 | let output = format!("{}.swf", mod_conf.name.to_case(Case::Snake)); 177 | asconfigc.compiler_option.output = PathBuf::from("..") 178 | .join(&self.build_path) 179 | .join("res") 180 | .join("gui") 181 | .join("flash") 182 | .join(output) 183 | .to_string()?; 184 | asconfigc.write_json_to_file(&asconfigc_json_path)?; 185 | 186 | Ok(()) 187 | } 188 | 189 | fn prompt_main_class(&self, default: &str) -> Result { 190 | let value = 191 | inquire::Text::new("What's the main ui class path (from ui/src)") 192 | .with_default(default) 193 | .with_placeholder(default) 194 | .prompt()?; 195 | 196 | Ok(value) 197 | } 198 | 199 | fn make_archive(&self) -> Result { 200 | let archive_file = self.target_path.join("result.wotmod"); 201 | zip::archive_directory(&archive_file, &self.build_path)?; 202 | 203 | Ok(archive_file) 204 | } 205 | 206 | pub fn build(&self) -> Result<()> { 207 | self.throw_if_isn_t_mod_folder()?; 208 | 209 | self.clean_target_directory()?; 210 | 211 | self.build_python_src()?; 212 | self.copy_meta_file()?; 213 | 214 | self.build_flash_src()?; 215 | 216 | let archive_path = self.make_archive()?; 217 | let absolute_build_path = convert_to_absolute_path(&archive_path)?; 218 | println!("Build finished: {}", absolute_build_path); 219 | 220 | Ok(()) 221 | } 222 | 223 | fn throw_if_isn_t_mod_folder(&self) -> Result<()> { 224 | let meta_path = self.mod_path.join("mod.json"); 225 | 226 | if meta_path.exists() == false { 227 | let absolute_mod_folder_path = 228 | self.mod_path.canonicalize().map_err(|_| { 229 | Error::PathError( 230 | "failed to put in canonical form".to_string(), 231 | ) 232 | })?; 233 | 234 | return Err(Error::BadModFolderError(absolute_mod_folder_path)); 235 | }; 236 | 237 | Ok(()) 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "adler2" 7 | version = "2.0.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" 10 | 11 | [[package]] 12 | name = "aes" 13 | version = "0.8.4" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" 16 | dependencies = [ 17 | "cfg-if", 18 | "cipher", 19 | "cpufeatures", 20 | ] 21 | 22 | [[package]] 23 | name = "aho-corasick" 24 | version = "1.1.4" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" 27 | dependencies = [ 28 | "memchr", 29 | ] 30 | 31 | [[package]] 32 | name = "anstream" 33 | version = "0.6.21" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" 36 | dependencies = [ 37 | "anstyle", 38 | "anstyle-parse", 39 | "anstyle-query", 40 | "anstyle-wincon", 41 | "colorchoice", 42 | "is_terminal_polyfill", 43 | "utf8parse", 44 | ] 45 | 46 | [[package]] 47 | name = "anstyle" 48 | version = "1.0.13" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" 51 | 52 | [[package]] 53 | name = "anstyle-parse" 54 | version = "0.2.7" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" 57 | dependencies = [ 58 | "utf8parse", 59 | ] 60 | 61 | [[package]] 62 | name = "anstyle-query" 63 | version = "1.1.5" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" 66 | dependencies = [ 67 | "windows-sys 0.61.2", 68 | ] 69 | 70 | [[package]] 71 | name = "anstyle-wincon" 72 | version = "3.0.11" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" 75 | dependencies = [ 76 | "anstyle", 77 | "once_cell_polyfill", 78 | "windows-sys 0.61.2", 79 | ] 80 | 81 | [[package]] 82 | name = "arbitrary" 83 | version = "1.4.2" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" 86 | dependencies = [ 87 | "derive_arbitrary", 88 | ] 89 | 90 | [[package]] 91 | name = "atomic-waker" 92 | version = "1.1.2" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 95 | 96 | [[package]] 97 | name = "base64" 98 | version = "0.22.1" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 101 | 102 | [[package]] 103 | name = "bitflags" 104 | version = "1.3.2" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 107 | 108 | [[package]] 109 | name = "bitflags" 110 | version = "2.10.0" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" 113 | 114 | [[package]] 115 | name = "block-buffer" 116 | version = "0.10.4" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 119 | dependencies = [ 120 | "generic-array", 121 | ] 122 | 123 | [[package]] 124 | name = "bumpalo" 125 | version = "3.19.0" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" 128 | 129 | [[package]] 130 | name = "byteorder" 131 | version = "1.5.0" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 134 | 135 | [[package]] 136 | name = "bytes" 137 | version = "1.11.0" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" 140 | 141 | [[package]] 142 | name = "bzip2" 143 | version = "0.5.2" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" 146 | dependencies = [ 147 | "bzip2-sys", 148 | ] 149 | 150 | [[package]] 151 | name = "bzip2-sys" 152 | version = "0.1.13+1.0.8" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" 155 | dependencies = [ 156 | "cc", 157 | "pkg-config", 158 | ] 159 | 160 | [[package]] 161 | name = "cc" 162 | version = "1.2.46" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36" 165 | dependencies = [ 166 | "find-msvc-tools", 167 | "jobserver", 168 | "libc", 169 | "shlex", 170 | ] 171 | 172 | [[package]] 173 | name = "cfg-if" 174 | version = "1.0.4" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 177 | 178 | [[package]] 179 | name = "cipher" 180 | version = "0.4.4" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" 183 | dependencies = [ 184 | "crypto-common", 185 | "inout", 186 | ] 187 | 188 | [[package]] 189 | name = "clap" 190 | version = "4.5.51" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" 193 | dependencies = [ 194 | "clap_builder", 195 | ] 196 | 197 | [[package]] 198 | name = "clap_builder" 199 | version = "4.5.51" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" 202 | dependencies = [ 203 | "anstream", 204 | "anstyle", 205 | "clap_lex", 206 | "strsim", 207 | ] 208 | 209 | [[package]] 210 | name = "clap_lex" 211 | version = "0.7.6" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" 214 | 215 | [[package]] 216 | name = "colorchoice" 217 | version = "1.0.4" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" 220 | 221 | [[package]] 222 | name = "constant_time_eq" 223 | version = "0.3.1" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" 226 | 227 | [[package]] 228 | name = "convert_case" 229 | version = "0.6.0" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" 232 | dependencies = [ 233 | "unicode-segmentation", 234 | ] 235 | 236 | [[package]] 237 | name = "core-foundation" 238 | version = "0.9.4" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 241 | dependencies = [ 242 | "core-foundation-sys", 243 | "libc", 244 | ] 245 | 246 | [[package]] 247 | name = "core-foundation-sys" 248 | version = "0.8.7" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 251 | 252 | [[package]] 253 | name = "cpufeatures" 254 | version = "0.2.17" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 257 | dependencies = [ 258 | "libc", 259 | ] 260 | 261 | [[package]] 262 | name = "crc" 263 | version = "3.3.0" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" 266 | dependencies = [ 267 | "crc-catalog", 268 | ] 269 | 270 | [[package]] 271 | name = "crc-catalog" 272 | version = "2.4.0" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" 275 | 276 | [[package]] 277 | name = "crc32fast" 278 | version = "1.5.0" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" 281 | dependencies = [ 282 | "cfg-if", 283 | ] 284 | 285 | [[package]] 286 | name = "crossbeam-utils" 287 | version = "0.8.21" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 290 | 291 | [[package]] 292 | name = "crossterm" 293 | version = "0.25.0" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" 296 | dependencies = [ 297 | "bitflags 1.3.2", 298 | "crossterm_winapi", 299 | "libc", 300 | "mio 0.8.11", 301 | "parking_lot", 302 | "signal-hook", 303 | "signal-hook-mio", 304 | "winapi", 305 | ] 306 | 307 | [[package]] 308 | name = "crossterm_winapi" 309 | version = "0.9.1" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" 312 | dependencies = [ 313 | "winapi", 314 | ] 315 | 316 | [[package]] 317 | name = "crypto-common" 318 | version = "0.1.7" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" 321 | dependencies = [ 322 | "generic-array", 323 | "typenum", 324 | ] 325 | 326 | [[package]] 327 | name = "darling" 328 | version = "0.20.11" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" 331 | dependencies = [ 332 | "darling_core", 333 | "darling_macro", 334 | ] 335 | 336 | [[package]] 337 | name = "darling_core" 338 | version = "0.20.11" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" 341 | dependencies = [ 342 | "fnv", 343 | "ident_case", 344 | "proc-macro2", 345 | "quote", 346 | "strsim", 347 | "syn", 348 | ] 349 | 350 | [[package]] 351 | name = "darling_macro" 352 | version = "0.20.11" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" 355 | dependencies = [ 356 | "darling_core", 357 | "quote", 358 | "syn", 359 | ] 360 | 361 | [[package]] 362 | name = "deflate64" 363 | version = "0.1.10" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "26bf8fc351c5ed29b5c2f0cbbac1b209b74f60ecd62e675a998df72c49af5204" 366 | 367 | [[package]] 368 | name = "deranged" 369 | version = "0.5.5" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" 372 | dependencies = [ 373 | "powerfmt", 374 | ] 375 | 376 | [[package]] 377 | name = "derive_arbitrary" 378 | version = "1.4.2" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" 381 | dependencies = [ 382 | "proc-macro2", 383 | "quote", 384 | "syn", 385 | ] 386 | 387 | [[package]] 388 | name = "derive_builder" 389 | version = "0.20.2" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" 392 | dependencies = [ 393 | "derive_builder_macro", 394 | ] 395 | 396 | [[package]] 397 | name = "derive_builder_core" 398 | version = "0.20.2" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" 401 | dependencies = [ 402 | "darling", 403 | "proc-macro2", 404 | "quote", 405 | "syn", 406 | ] 407 | 408 | [[package]] 409 | name = "derive_builder_macro" 410 | version = "0.20.2" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" 413 | dependencies = [ 414 | "derive_builder_core", 415 | "syn", 416 | ] 417 | 418 | [[package]] 419 | name = "digest" 420 | version = "0.10.7" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 423 | dependencies = [ 424 | "block-buffer", 425 | "crypto-common", 426 | "subtle", 427 | ] 428 | 429 | [[package]] 430 | name = "displaydoc" 431 | version = "0.2.5" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 434 | dependencies = [ 435 | "proc-macro2", 436 | "quote", 437 | "syn", 438 | ] 439 | 440 | [[package]] 441 | name = "dyn-clone" 442 | version = "1.0.20" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" 445 | 446 | [[package]] 447 | name = "encoding_rs" 448 | version = "0.8.35" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 451 | dependencies = [ 452 | "cfg-if", 453 | ] 454 | 455 | [[package]] 456 | name = "equivalent" 457 | version = "1.0.2" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 460 | 461 | [[package]] 462 | name = "errno" 463 | version = "0.3.14" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" 466 | dependencies = [ 467 | "libc", 468 | "windows-sys 0.61.2", 469 | ] 470 | 471 | [[package]] 472 | name = "fastrand" 473 | version = "2.3.0" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 476 | 477 | [[package]] 478 | name = "find-msvc-tools" 479 | version = "0.1.5" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" 482 | 483 | [[package]] 484 | name = "flate2" 485 | version = "1.1.5" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" 488 | dependencies = [ 489 | "crc32fast", 490 | "libz-rs-sys", 491 | "miniz_oxide", 492 | ] 493 | 494 | [[package]] 495 | name = "fnv" 496 | version = "1.0.7" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 499 | 500 | [[package]] 501 | name = "foreign-types" 502 | version = "0.3.2" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 505 | dependencies = [ 506 | "foreign-types-shared", 507 | ] 508 | 509 | [[package]] 510 | name = "foreign-types-shared" 511 | version = "0.1.1" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 514 | 515 | [[package]] 516 | name = "form_urlencoded" 517 | version = "1.2.2" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" 520 | dependencies = [ 521 | "percent-encoding", 522 | ] 523 | 524 | [[package]] 525 | name = "fs_extra" 526 | version = "1.3.0" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" 529 | 530 | [[package]] 531 | name = "futures-channel" 532 | version = "0.3.31" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 535 | dependencies = [ 536 | "futures-core", 537 | "futures-sink", 538 | ] 539 | 540 | [[package]] 541 | name = "futures-core" 542 | version = "0.3.31" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 545 | 546 | [[package]] 547 | name = "futures-io" 548 | version = "0.3.31" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 551 | 552 | [[package]] 553 | name = "futures-macro" 554 | version = "0.3.31" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 557 | dependencies = [ 558 | "proc-macro2", 559 | "quote", 560 | "syn", 561 | ] 562 | 563 | [[package]] 564 | name = "futures-sink" 565 | version = "0.3.31" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 568 | 569 | [[package]] 570 | name = "futures-task" 571 | version = "0.3.31" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 574 | 575 | [[package]] 576 | name = "futures-util" 577 | version = "0.3.31" 578 | source = "registry+https://github.com/rust-lang/crates.io-index" 579 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 580 | dependencies = [ 581 | "futures-core", 582 | "futures-io", 583 | "futures-macro", 584 | "futures-sink", 585 | "futures-task", 586 | "memchr", 587 | "pin-project-lite", 588 | "pin-utils", 589 | "slab", 590 | ] 591 | 592 | [[package]] 593 | name = "fuzzy-matcher" 594 | version = "0.3.7" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" 597 | dependencies = [ 598 | "thread_local", 599 | ] 600 | 601 | [[package]] 602 | name = "fxhash" 603 | version = "0.2.1" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" 606 | dependencies = [ 607 | "byteorder", 608 | ] 609 | 610 | [[package]] 611 | name = "generic-array" 612 | version = "0.14.7" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 615 | dependencies = [ 616 | "typenum", 617 | "version_check", 618 | ] 619 | 620 | [[package]] 621 | name = "getrandom" 622 | version = "0.2.16" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" 625 | dependencies = [ 626 | "cfg-if", 627 | "libc", 628 | "wasi", 629 | ] 630 | 631 | [[package]] 632 | name = "getrandom" 633 | version = "0.3.4" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" 636 | dependencies = [ 637 | "cfg-if", 638 | "js-sys", 639 | "libc", 640 | "r-efi", 641 | "wasip2", 642 | "wasm-bindgen", 643 | ] 644 | 645 | [[package]] 646 | name = "git2" 647 | version = "0.19.0" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724" 650 | dependencies = [ 651 | "bitflags 2.10.0", 652 | "libc", 653 | "libgit2-sys", 654 | "log", 655 | "openssl-probe", 656 | "openssl-sys", 657 | "url", 658 | ] 659 | 660 | [[package]] 661 | name = "glob" 662 | version = "0.3.3" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" 665 | 666 | [[package]] 667 | name = "h2" 668 | version = "0.4.12" 669 | source = "registry+https://github.com/rust-lang/crates.io-index" 670 | checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" 671 | dependencies = [ 672 | "atomic-waker", 673 | "bytes", 674 | "fnv", 675 | "futures-core", 676 | "futures-sink", 677 | "http", 678 | "indexmap", 679 | "slab", 680 | "tokio", 681 | "tokio-util", 682 | "tracing", 683 | ] 684 | 685 | [[package]] 686 | name = "handlebars" 687 | version = "6.3.2" 688 | source = "registry+https://github.com/rust-lang/crates.io-index" 689 | checksum = "759e2d5aea3287cb1190c8ec394f42866cb5bf74fcbf213f354e3c856ea26098" 690 | dependencies = [ 691 | "derive_builder", 692 | "log", 693 | "num-order", 694 | "pest", 695 | "pest_derive", 696 | "serde", 697 | "serde_json", 698 | "thiserror 2.0.17", 699 | ] 700 | 701 | [[package]] 702 | name = "hashbrown" 703 | version = "0.16.0" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" 706 | 707 | [[package]] 708 | name = "hmac" 709 | version = "0.12.1" 710 | source = "registry+https://github.com/rust-lang/crates.io-index" 711 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 712 | dependencies = [ 713 | "digest", 714 | ] 715 | 716 | [[package]] 717 | name = "home" 718 | version = "0.5.12" 719 | source = "registry+https://github.com/rust-lang/crates.io-index" 720 | checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" 721 | dependencies = [ 722 | "windows-sys 0.61.2", 723 | ] 724 | 725 | [[package]] 726 | name = "http" 727 | version = "1.3.1" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" 730 | dependencies = [ 731 | "bytes", 732 | "fnv", 733 | "itoa", 734 | ] 735 | 736 | [[package]] 737 | name = "http-body" 738 | version = "1.0.1" 739 | source = "registry+https://github.com/rust-lang/crates.io-index" 740 | checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 741 | dependencies = [ 742 | "bytes", 743 | "http", 744 | ] 745 | 746 | [[package]] 747 | name = "http-body-util" 748 | version = "0.1.3" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" 751 | dependencies = [ 752 | "bytes", 753 | "futures-core", 754 | "http", 755 | "http-body", 756 | "pin-project-lite", 757 | ] 758 | 759 | [[package]] 760 | name = "httparse" 761 | version = "1.10.1" 762 | source = "registry+https://github.com/rust-lang/crates.io-index" 763 | checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 764 | 765 | [[package]] 766 | name = "hyper" 767 | version = "1.8.1" 768 | source = "registry+https://github.com/rust-lang/crates.io-index" 769 | checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" 770 | dependencies = [ 771 | "atomic-waker", 772 | "bytes", 773 | "futures-channel", 774 | "futures-core", 775 | "h2", 776 | "http", 777 | "http-body", 778 | "httparse", 779 | "itoa", 780 | "pin-project-lite", 781 | "pin-utils", 782 | "smallvec", 783 | "tokio", 784 | "want", 785 | ] 786 | 787 | [[package]] 788 | name = "hyper-rustls" 789 | version = "0.27.7" 790 | source = "registry+https://github.com/rust-lang/crates.io-index" 791 | checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" 792 | dependencies = [ 793 | "http", 794 | "hyper", 795 | "hyper-util", 796 | "rustls", 797 | "rustls-pki-types", 798 | "tokio", 799 | "tokio-rustls", 800 | "tower-service", 801 | ] 802 | 803 | [[package]] 804 | name = "hyper-tls" 805 | version = "0.6.0" 806 | source = "registry+https://github.com/rust-lang/crates.io-index" 807 | checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" 808 | dependencies = [ 809 | "bytes", 810 | "http-body-util", 811 | "hyper", 812 | "hyper-util", 813 | "native-tls", 814 | "tokio", 815 | "tokio-native-tls", 816 | "tower-service", 817 | ] 818 | 819 | [[package]] 820 | name = "hyper-util" 821 | version = "0.1.18" 822 | source = "registry+https://github.com/rust-lang/crates.io-index" 823 | checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56" 824 | dependencies = [ 825 | "base64", 826 | "bytes", 827 | "futures-channel", 828 | "futures-core", 829 | "futures-util", 830 | "http", 831 | "http-body", 832 | "hyper", 833 | "ipnet", 834 | "libc", 835 | "percent-encoding", 836 | "pin-project-lite", 837 | "socket2", 838 | "system-configuration", 839 | "tokio", 840 | "tower-service", 841 | "tracing", 842 | "windows-registry", 843 | ] 844 | 845 | [[package]] 846 | name = "icu_collections" 847 | version = "2.1.1" 848 | source = "registry+https://github.com/rust-lang/crates.io-index" 849 | checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" 850 | dependencies = [ 851 | "displaydoc", 852 | "potential_utf", 853 | "yoke", 854 | "zerofrom", 855 | "zerovec", 856 | ] 857 | 858 | [[package]] 859 | name = "icu_locale_core" 860 | version = "2.1.1" 861 | source = "registry+https://github.com/rust-lang/crates.io-index" 862 | checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" 863 | dependencies = [ 864 | "displaydoc", 865 | "litemap", 866 | "tinystr", 867 | "writeable", 868 | "zerovec", 869 | ] 870 | 871 | [[package]] 872 | name = "icu_normalizer" 873 | version = "2.1.1" 874 | source = "registry+https://github.com/rust-lang/crates.io-index" 875 | checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" 876 | dependencies = [ 877 | "icu_collections", 878 | "icu_normalizer_data", 879 | "icu_properties", 880 | "icu_provider", 881 | "smallvec", 882 | "zerovec", 883 | ] 884 | 885 | [[package]] 886 | name = "icu_normalizer_data" 887 | version = "2.1.1" 888 | source = "registry+https://github.com/rust-lang/crates.io-index" 889 | checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" 890 | 891 | [[package]] 892 | name = "icu_properties" 893 | version = "2.1.1" 894 | source = "registry+https://github.com/rust-lang/crates.io-index" 895 | checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" 896 | dependencies = [ 897 | "icu_collections", 898 | "icu_locale_core", 899 | "icu_properties_data", 900 | "icu_provider", 901 | "zerotrie", 902 | "zerovec", 903 | ] 904 | 905 | [[package]] 906 | name = "icu_properties_data" 907 | version = "2.1.1" 908 | source = "registry+https://github.com/rust-lang/crates.io-index" 909 | checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" 910 | 911 | [[package]] 912 | name = "icu_provider" 913 | version = "2.1.1" 914 | source = "registry+https://github.com/rust-lang/crates.io-index" 915 | checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" 916 | dependencies = [ 917 | "displaydoc", 918 | "icu_locale_core", 919 | "writeable", 920 | "yoke", 921 | "zerofrom", 922 | "zerotrie", 923 | "zerovec", 924 | ] 925 | 926 | [[package]] 927 | name = "ident_case" 928 | version = "1.0.1" 929 | source = "registry+https://github.com/rust-lang/crates.io-index" 930 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 931 | 932 | [[package]] 933 | name = "idna" 934 | version = "1.1.0" 935 | source = "registry+https://github.com/rust-lang/crates.io-index" 936 | checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" 937 | dependencies = [ 938 | "idna_adapter", 939 | "smallvec", 940 | "utf8_iter", 941 | ] 942 | 943 | [[package]] 944 | name = "idna_adapter" 945 | version = "1.2.1" 946 | source = "registry+https://github.com/rust-lang/crates.io-index" 947 | checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" 948 | dependencies = [ 949 | "icu_normalizer", 950 | "icu_properties", 951 | ] 952 | 953 | [[package]] 954 | name = "indexmap" 955 | version = "2.12.0" 956 | source = "registry+https://github.com/rust-lang/crates.io-index" 957 | checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" 958 | dependencies = [ 959 | "equivalent", 960 | "hashbrown", 961 | ] 962 | 963 | [[package]] 964 | name = "inout" 965 | version = "0.1.4" 966 | source = "registry+https://github.com/rust-lang/crates.io-index" 967 | checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" 968 | dependencies = [ 969 | "generic-array", 970 | ] 971 | 972 | [[package]] 973 | name = "inquire" 974 | version = "0.7.5" 975 | source = "registry+https://github.com/rust-lang/crates.io-index" 976 | checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a" 977 | dependencies = [ 978 | "bitflags 2.10.0", 979 | "crossterm", 980 | "dyn-clone", 981 | "fuzzy-matcher", 982 | "fxhash", 983 | "newline-converter", 984 | "once_cell", 985 | "unicode-segmentation", 986 | "unicode-width", 987 | ] 988 | 989 | [[package]] 990 | name = "ipnet" 991 | version = "2.11.0" 992 | source = "registry+https://github.com/rust-lang/crates.io-index" 993 | checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" 994 | 995 | [[package]] 996 | name = "iri-string" 997 | version = "0.7.9" 998 | source = "registry+https://github.com/rust-lang/crates.io-index" 999 | checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" 1000 | dependencies = [ 1001 | "memchr", 1002 | "serde", 1003 | ] 1004 | 1005 | [[package]] 1006 | name = "is_terminal_polyfill" 1007 | version = "1.70.2" 1008 | source = "registry+https://github.com/rust-lang/crates.io-index" 1009 | checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" 1010 | 1011 | [[package]] 1012 | name = "itoa" 1013 | version = "1.0.15" 1014 | source = "registry+https://github.com/rust-lang/crates.io-index" 1015 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 1016 | 1017 | [[package]] 1018 | name = "jobserver" 1019 | version = "0.1.34" 1020 | source = "registry+https://github.com/rust-lang/crates.io-index" 1021 | checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" 1022 | dependencies = [ 1023 | "getrandom 0.3.4", 1024 | "libc", 1025 | ] 1026 | 1027 | [[package]] 1028 | name = "js-sys" 1029 | version = "0.3.82" 1030 | source = "registry+https://github.com/rust-lang/crates.io-index" 1031 | checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" 1032 | dependencies = [ 1033 | "once_cell", 1034 | "wasm-bindgen", 1035 | ] 1036 | 1037 | [[package]] 1038 | name = "libc" 1039 | version = "0.2.177" 1040 | source = "registry+https://github.com/rust-lang/crates.io-index" 1041 | checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" 1042 | 1043 | [[package]] 1044 | name = "libgit2-sys" 1045 | version = "0.17.0+1.8.1" 1046 | source = "registry+https://github.com/rust-lang/crates.io-index" 1047 | checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224" 1048 | dependencies = [ 1049 | "cc", 1050 | "libc", 1051 | "libssh2-sys", 1052 | "libz-sys", 1053 | "openssl-sys", 1054 | "pkg-config", 1055 | ] 1056 | 1057 | [[package]] 1058 | name = "libssh2-sys" 1059 | version = "0.3.1" 1060 | source = "registry+https://github.com/rust-lang/crates.io-index" 1061 | checksum = "220e4f05ad4a218192533b300327f5150e809b54c4ec83b5a1d91833601811b9" 1062 | dependencies = [ 1063 | "cc", 1064 | "libc", 1065 | "libz-sys", 1066 | "openssl-sys", 1067 | "pkg-config", 1068 | "vcpkg", 1069 | ] 1070 | 1071 | [[package]] 1072 | name = "libz-rs-sys" 1073 | version = "0.5.2" 1074 | source = "registry+https://github.com/rust-lang/crates.io-index" 1075 | checksum = "840db8cf39d9ec4dd794376f38acc40d0fc65eec2a8f484f7fd375b84602becd" 1076 | dependencies = [ 1077 | "zlib-rs", 1078 | ] 1079 | 1080 | [[package]] 1081 | name = "libz-sys" 1082 | version = "1.1.23" 1083 | source = "registry+https://github.com/rust-lang/crates.io-index" 1084 | checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7" 1085 | dependencies = [ 1086 | "cc", 1087 | "libc", 1088 | "pkg-config", 1089 | "vcpkg", 1090 | ] 1091 | 1092 | [[package]] 1093 | name = "linux-raw-sys" 1094 | version = "0.11.0" 1095 | source = "registry+https://github.com/rust-lang/crates.io-index" 1096 | checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" 1097 | 1098 | [[package]] 1099 | name = "litemap" 1100 | version = "0.8.1" 1101 | source = "registry+https://github.com/rust-lang/crates.io-index" 1102 | checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" 1103 | 1104 | [[package]] 1105 | name = "lock_api" 1106 | version = "0.4.14" 1107 | source = "registry+https://github.com/rust-lang/crates.io-index" 1108 | checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" 1109 | dependencies = [ 1110 | "scopeguard", 1111 | ] 1112 | 1113 | [[package]] 1114 | name = "log" 1115 | version = "0.4.28" 1116 | source = "registry+https://github.com/rust-lang/crates.io-index" 1117 | checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" 1118 | 1119 | [[package]] 1120 | name = "lzma-rs" 1121 | version = "0.3.0" 1122 | source = "registry+https://github.com/rust-lang/crates.io-index" 1123 | checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" 1124 | dependencies = [ 1125 | "byteorder", 1126 | "crc", 1127 | ] 1128 | 1129 | [[package]] 1130 | name = "lzma-sys" 1131 | version = "0.1.20" 1132 | source = "registry+https://github.com/rust-lang/crates.io-index" 1133 | checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" 1134 | dependencies = [ 1135 | "cc", 1136 | "libc", 1137 | "pkg-config", 1138 | ] 1139 | 1140 | [[package]] 1141 | name = "memchr" 1142 | version = "2.7.6" 1143 | source = "registry+https://github.com/rust-lang/crates.io-index" 1144 | checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" 1145 | 1146 | [[package]] 1147 | name = "mime" 1148 | version = "0.3.17" 1149 | source = "registry+https://github.com/rust-lang/crates.io-index" 1150 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 1151 | 1152 | [[package]] 1153 | name = "miniz_oxide" 1154 | version = "0.8.9" 1155 | source = "registry+https://github.com/rust-lang/crates.io-index" 1156 | checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" 1157 | dependencies = [ 1158 | "adler2", 1159 | "simd-adler32", 1160 | ] 1161 | 1162 | [[package]] 1163 | name = "mio" 1164 | version = "0.8.11" 1165 | source = "registry+https://github.com/rust-lang/crates.io-index" 1166 | checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" 1167 | dependencies = [ 1168 | "libc", 1169 | "log", 1170 | "wasi", 1171 | "windows-sys 0.48.0", 1172 | ] 1173 | 1174 | [[package]] 1175 | name = "mio" 1176 | version = "1.1.0" 1177 | source = "registry+https://github.com/rust-lang/crates.io-index" 1178 | checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" 1179 | dependencies = [ 1180 | "libc", 1181 | "wasi", 1182 | "windows-sys 0.61.2", 1183 | ] 1184 | 1185 | [[package]] 1186 | name = "native-tls" 1187 | version = "0.2.14" 1188 | source = "registry+https://github.com/rust-lang/crates.io-index" 1189 | checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" 1190 | dependencies = [ 1191 | "libc", 1192 | "log", 1193 | "openssl", 1194 | "openssl-probe", 1195 | "openssl-sys", 1196 | "schannel", 1197 | "security-framework", 1198 | "security-framework-sys", 1199 | "tempfile", 1200 | ] 1201 | 1202 | [[package]] 1203 | name = "newline-converter" 1204 | version = "0.3.0" 1205 | source = "registry+https://github.com/rust-lang/crates.io-index" 1206 | checksum = "47b6b097ecb1cbfed438542d16e84fd7ad9b0c76c8a65b7f9039212a3d14dc7f" 1207 | dependencies = [ 1208 | "unicode-segmentation", 1209 | ] 1210 | 1211 | [[package]] 1212 | name = "num-conv" 1213 | version = "0.1.0" 1214 | source = "registry+https://github.com/rust-lang/crates.io-index" 1215 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 1216 | 1217 | [[package]] 1218 | name = "num-modular" 1219 | version = "0.6.1" 1220 | source = "registry+https://github.com/rust-lang/crates.io-index" 1221 | checksum = "17bb261bf36fa7d83f4c294f834e91256769097b3cb505d44831e0a179ac647f" 1222 | 1223 | [[package]] 1224 | name = "num-order" 1225 | version = "1.2.0" 1226 | source = "registry+https://github.com/rust-lang/crates.io-index" 1227 | checksum = "537b596b97c40fcf8056d153049eb22f481c17ebce72a513ec9286e4986d1bb6" 1228 | dependencies = [ 1229 | "num-modular", 1230 | ] 1231 | 1232 | [[package]] 1233 | name = "once_cell" 1234 | version = "1.21.3" 1235 | source = "registry+https://github.com/rust-lang/crates.io-index" 1236 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 1237 | 1238 | [[package]] 1239 | name = "once_cell_polyfill" 1240 | version = "1.70.2" 1241 | source = "registry+https://github.com/rust-lang/crates.io-index" 1242 | checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" 1243 | 1244 | [[package]] 1245 | name = "openssl" 1246 | version = "0.10.75" 1247 | source = "registry+https://github.com/rust-lang/crates.io-index" 1248 | checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" 1249 | dependencies = [ 1250 | "bitflags 2.10.0", 1251 | "cfg-if", 1252 | "foreign-types", 1253 | "libc", 1254 | "once_cell", 1255 | "openssl-macros", 1256 | "openssl-sys", 1257 | ] 1258 | 1259 | [[package]] 1260 | name = "openssl-macros" 1261 | version = "0.1.1" 1262 | source = "registry+https://github.com/rust-lang/crates.io-index" 1263 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 1264 | dependencies = [ 1265 | "proc-macro2", 1266 | "quote", 1267 | "syn", 1268 | ] 1269 | 1270 | [[package]] 1271 | name = "openssl-probe" 1272 | version = "0.1.6" 1273 | source = "registry+https://github.com/rust-lang/crates.io-index" 1274 | checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" 1275 | 1276 | [[package]] 1277 | name = "openssl-sys" 1278 | version = "0.9.111" 1279 | source = "registry+https://github.com/rust-lang/crates.io-index" 1280 | checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" 1281 | dependencies = [ 1282 | "cc", 1283 | "libc", 1284 | "pkg-config", 1285 | "vcpkg", 1286 | ] 1287 | 1288 | [[package]] 1289 | name = "parking_lot" 1290 | version = "0.12.5" 1291 | source = "registry+https://github.com/rust-lang/crates.io-index" 1292 | checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" 1293 | dependencies = [ 1294 | "lock_api", 1295 | "parking_lot_core", 1296 | ] 1297 | 1298 | [[package]] 1299 | name = "parking_lot_core" 1300 | version = "0.9.12" 1301 | source = "registry+https://github.com/rust-lang/crates.io-index" 1302 | checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" 1303 | dependencies = [ 1304 | "cfg-if", 1305 | "libc", 1306 | "redox_syscall", 1307 | "smallvec", 1308 | "windows-link", 1309 | ] 1310 | 1311 | [[package]] 1312 | name = "pbkdf2" 1313 | version = "0.12.2" 1314 | source = "registry+https://github.com/rust-lang/crates.io-index" 1315 | checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" 1316 | dependencies = [ 1317 | "digest", 1318 | "hmac", 1319 | ] 1320 | 1321 | [[package]] 1322 | name = "percent-encoding" 1323 | version = "2.3.2" 1324 | source = "registry+https://github.com/rust-lang/crates.io-index" 1325 | checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" 1326 | 1327 | [[package]] 1328 | name = "pest" 1329 | version = "2.8.3" 1330 | source = "registry+https://github.com/rust-lang/crates.io-index" 1331 | checksum = "989e7521a040efde50c3ab6bbadafbe15ab6dc042686926be59ac35d74607df4" 1332 | dependencies = [ 1333 | "memchr", 1334 | "ucd-trie", 1335 | ] 1336 | 1337 | [[package]] 1338 | name = "pest_derive" 1339 | version = "2.8.3" 1340 | source = "registry+https://github.com/rust-lang/crates.io-index" 1341 | checksum = "187da9a3030dbafabbbfb20cb323b976dc7b7ce91fcd84f2f74d6e31d378e2de" 1342 | dependencies = [ 1343 | "pest", 1344 | "pest_generator", 1345 | ] 1346 | 1347 | [[package]] 1348 | name = "pest_generator" 1349 | version = "2.8.3" 1350 | source = "registry+https://github.com/rust-lang/crates.io-index" 1351 | checksum = "49b401d98f5757ebe97a26085998d6c0eecec4995cad6ab7fc30ffdf4b052843" 1352 | dependencies = [ 1353 | "pest", 1354 | "pest_meta", 1355 | "proc-macro2", 1356 | "quote", 1357 | "syn", 1358 | ] 1359 | 1360 | [[package]] 1361 | name = "pest_meta" 1362 | version = "2.8.3" 1363 | source = "registry+https://github.com/rust-lang/crates.io-index" 1364 | checksum = "72f27a2cfee9f9039c4d86faa5af122a0ac3851441a34865b8a043b46be0065a" 1365 | dependencies = [ 1366 | "pest", 1367 | "sha2", 1368 | ] 1369 | 1370 | [[package]] 1371 | name = "pin-project-lite" 1372 | version = "0.2.16" 1373 | source = "registry+https://github.com/rust-lang/crates.io-index" 1374 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 1375 | 1376 | [[package]] 1377 | name = "pin-utils" 1378 | version = "0.1.0" 1379 | source = "registry+https://github.com/rust-lang/crates.io-index" 1380 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1381 | 1382 | [[package]] 1383 | name = "pkg-config" 1384 | version = "0.3.32" 1385 | source = "registry+https://github.com/rust-lang/crates.io-index" 1386 | checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 1387 | 1388 | [[package]] 1389 | name = "potential_utf" 1390 | version = "0.1.4" 1391 | source = "registry+https://github.com/rust-lang/crates.io-index" 1392 | checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" 1393 | dependencies = [ 1394 | "zerovec", 1395 | ] 1396 | 1397 | [[package]] 1398 | name = "powerfmt" 1399 | version = "0.2.0" 1400 | source = "registry+https://github.com/rust-lang/crates.io-index" 1401 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 1402 | 1403 | [[package]] 1404 | name = "proc-macro2" 1405 | version = "1.0.103" 1406 | source = "registry+https://github.com/rust-lang/crates.io-index" 1407 | checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" 1408 | dependencies = [ 1409 | "unicode-ident", 1410 | ] 1411 | 1412 | [[package]] 1413 | name = "quote" 1414 | version = "1.0.42" 1415 | source = "registry+https://github.com/rust-lang/crates.io-index" 1416 | checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" 1417 | dependencies = [ 1418 | "proc-macro2", 1419 | ] 1420 | 1421 | [[package]] 1422 | name = "r-efi" 1423 | version = "5.3.0" 1424 | source = "registry+https://github.com/rust-lang/crates.io-index" 1425 | checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" 1426 | 1427 | [[package]] 1428 | name = "redox_syscall" 1429 | version = "0.5.18" 1430 | source = "registry+https://github.com/rust-lang/crates.io-index" 1431 | checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" 1432 | dependencies = [ 1433 | "bitflags 2.10.0", 1434 | ] 1435 | 1436 | [[package]] 1437 | name = "regex" 1438 | version = "1.12.2" 1439 | source = "registry+https://github.com/rust-lang/crates.io-index" 1440 | checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" 1441 | dependencies = [ 1442 | "aho-corasick", 1443 | "memchr", 1444 | "regex-automata", 1445 | "regex-syntax", 1446 | ] 1447 | 1448 | [[package]] 1449 | name = "regex-automata" 1450 | version = "0.4.13" 1451 | source = "registry+https://github.com/rust-lang/crates.io-index" 1452 | checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" 1453 | dependencies = [ 1454 | "aho-corasick", 1455 | "memchr", 1456 | "regex-syntax", 1457 | ] 1458 | 1459 | [[package]] 1460 | name = "regex-syntax" 1461 | version = "0.8.8" 1462 | source = "registry+https://github.com/rust-lang/crates.io-index" 1463 | checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" 1464 | 1465 | [[package]] 1466 | name = "reqwest" 1467 | version = "0.12.24" 1468 | source = "registry+https://github.com/rust-lang/crates.io-index" 1469 | checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" 1470 | dependencies = [ 1471 | "base64", 1472 | "bytes", 1473 | "encoding_rs", 1474 | "futures-channel", 1475 | "futures-core", 1476 | "futures-util", 1477 | "h2", 1478 | "http", 1479 | "http-body", 1480 | "http-body-util", 1481 | "hyper", 1482 | "hyper-rustls", 1483 | "hyper-tls", 1484 | "hyper-util", 1485 | "js-sys", 1486 | "log", 1487 | "mime", 1488 | "native-tls", 1489 | "percent-encoding", 1490 | "pin-project-lite", 1491 | "rustls-pki-types", 1492 | "serde", 1493 | "serde_json", 1494 | "serde_urlencoded", 1495 | "sync_wrapper", 1496 | "tokio", 1497 | "tokio-native-tls", 1498 | "tokio-util", 1499 | "tower", 1500 | "tower-http", 1501 | "tower-service", 1502 | "url", 1503 | "wasm-bindgen", 1504 | "wasm-bindgen-futures", 1505 | "wasm-streams", 1506 | "web-sys", 1507 | ] 1508 | 1509 | [[package]] 1510 | name = "ring" 1511 | version = "0.17.14" 1512 | source = "registry+https://github.com/rust-lang/crates.io-index" 1513 | checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" 1514 | dependencies = [ 1515 | "cc", 1516 | "cfg-if", 1517 | "getrandom 0.2.16", 1518 | "libc", 1519 | "untrusted", 1520 | "windows-sys 0.52.0", 1521 | ] 1522 | 1523 | [[package]] 1524 | name = "rustix" 1525 | version = "1.1.2" 1526 | source = "registry+https://github.com/rust-lang/crates.io-index" 1527 | checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" 1528 | dependencies = [ 1529 | "bitflags 2.10.0", 1530 | "errno", 1531 | "libc", 1532 | "linux-raw-sys", 1533 | "windows-sys 0.61.2", 1534 | ] 1535 | 1536 | [[package]] 1537 | name = "rustls" 1538 | version = "0.23.35" 1539 | source = "registry+https://github.com/rust-lang/crates.io-index" 1540 | checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" 1541 | dependencies = [ 1542 | "once_cell", 1543 | "rustls-pki-types", 1544 | "rustls-webpki", 1545 | "subtle", 1546 | "zeroize", 1547 | ] 1548 | 1549 | [[package]] 1550 | name = "rustls-pki-types" 1551 | version = "1.13.0" 1552 | source = "registry+https://github.com/rust-lang/crates.io-index" 1553 | checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" 1554 | dependencies = [ 1555 | "zeroize", 1556 | ] 1557 | 1558 | [[package]] 1559 | name = "rustls-webpki" 1560 | version = "0.103.8" 1561 | source = "registry+https://github.com/rust-lang/crates.io-index" 1562 | checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" 1563 | dependencies = [ 1564 | "ring", 1565 | "rustls-pki-types", 1566 | "untrusted", 1567 | ] 1568 | 1569 | [[package]] 1570 | name = "rustversion" 1571 | version = "1.0.22" 1572 | source = "registry+https://github.com/rust-lang/crates.io-index" 1573 | checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" 1574 | 1575 | [[package]] 1576 | name = "ryu" 1577 | version = "1.0.20" 1578 | source = "registry+https://github.com/rust-lang/crates.io-index" 1579 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 1580 | 1581 | [[package]] 1582 | name = "schannel" 1583 | version = "0.1.28" 1584 | source = "registry+https://github.com/rust-lang/crates.io-index" 1585 | checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" 1586 | dependencies = [ 1587 | "windows-sys 0.61.2", 1588 | ] 1589 | 1590 | [[package]] 1591 | name = "scopeguard" 1592 | version = "1.2.0" 1593 | source = "registry+https://github.com/rust-lang/crates.io-index" 1594 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1595 | 1596 | [[package]] 1597 | name = "security-framework" 1598 | version = "2.11.1" 1599 | source = "registry+https://github.com/rust-lang/crates.io-index" 1600 | checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 1601 | dependencies = [ 1602 | "bitflags 2.10.0", 1603 | "core-foundation", 1604 | "core-foundation-sys", 1605 | "libc", 1606 | "security-framework-sys", 1607 | ] 1608 | 1609 | [[package]] 1610 | name = "security-framework-sys" 1611 | version = "2.15.0" 1612 | source = "registry+https://github.com/rust-lang/crates.io-index" 1613 | checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" 1614 | dependencies = [ 1615 | "core-foundation-sys", 1616 | "libc", 1617 | ] 1618 | 1619 | [[package]] 1620 | name = "serde" 1621 | version = "1.0.228" 1622 | source = "registry+https://github.com/rust-lang/crates.io-index" 1623 | checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" 1624 | dependencies = [ 1625 | "serde_core", 1626 | "serde_derive", 1627 | ] 1628 | 1629 | [[package]] 1630 | name = "serde_core" 1631 | version = "1.0.228" 1632 | source = "registry+https://github.com/rust-lang/crates.io-index" 1633 | checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 1634 | dependencies = [ 1635 | "serde_derive", 1636 | ] 1637 | 1638 | [[package]] 1639 | name = "serde_derive" 1640 | version = "1.0.228" 1641 | source = "registry+https://github.com/rust-lang/crates.io-index" 1642 | checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 1643 | dependencies = [ 1644 | "proc-macro2", 1645 | "quote", 1646 | "syn", 1647 | ] 1648 | 1649 | [[package]] 1650 | name = "serde_json" 1651 | version = "1.0.145" 1652 | source = "registry+https://github.com/rust-lang/crates.io-index" 1653 | checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" 1654 | dependencies = [ 1655 | "itoa", 1656 | "memchr", 1657 | "ryu", 1658 | "serde", 1659 | "serde_core", 1660 | ] 1661 | 1662 | [[package]] 1663 | name = "serde_urlencoded" 1664 | version = "0.7.1" 1665 | source = "registry+https://github.com/rust-lang/crates.io-index" 1666 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1667 | dependencies = [ 1668 | "form_urlencoded", 1669 | "itoa", 1670 | "ryu", 1671 | "serde", 1672 | ] 1673 | 1674 | [[package]] 1675 | name = "sha1" 1676 | version = "0.10.6" 1677 | source = "registry+https://github.com/rust-lang/crates.io-index" 1678 | checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 1679 | dependencies = [ 1680 | "cfg-if", 1681 | "cpufeatures", 1682 | "digest", 1683 | ] 1684 | 1685 | [[package]] 1686 | name = "sha2" 1687 | version = "0.10.9" 1688 | source = "registry+https://github.com/rust-lang/crates.io-index" 1689 | checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" 1690 | dependencies = [ 1691 | "cfg-if", 1692 | "cpufeatures", 1693 | "digest", 1694 | ] 1695 | 1696 | [[package]] 1697 | name = "shlex" 1698 | version = "1.3.0" 1699 | source = "registry+https://github.com/rust-lang/crates.io-index" 1700 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1701 | 1702 | [[package]] 1703 | name = "signal-hook" 1704 | version = "0.3.18" 1705 | source = "registry+https://github.com/rust-lang/crates.io-index" 1706 | checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" 1707 | dependencies = [ 1708 | "libc", 1709 | "signal-hook-registry", 1710 | ] 1711 | 1712 | [[package]] 1713 | name = "signal-hook-mio" 1714 | version = "0.2.5" 1715 | source = "registry+https://github.com/rust-lang/crates.io-index" 1716 | checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" 1717 | dependencies = [ 1718 | "libc", 1719 | "mio 0.8.11", 1720 | "signal-hook", 1721 | ] 1722 | 1723 | [[package]] 1724 | name = "signal-hook-registry" 1725 | version = "1.4.6" 1726 | source = "registry+https://github.com/rust-lang/crates.io-index" 1727 | checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" 1728 | dependencies = [ 1729 | "libc", 1730 | ] 1731 | 1732 | [[package]] 1733 | name = "simd-adler32" 1734 | version = "0.3.7" 1735 | source = "registry+https://github.com/rust-lang/crates.io-index" 1736 | checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" 1737 | 1738 | [[package]] 1739 | name = "slab" 1740 | version = "0.4.11" 1741 | source = "registry+https://github.com/rust-lang/crates.io-index" 1742 | checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" 1743 | 1744 | [[package]] 1745 | name = "smallvec" 1746 | version = "1.15.1" 1747 | source = "registry+https://github.com/rust-lang/crates.io-index" 1748 | checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 1749 | 1750 | [[package]] 1751 | name = "socket2" 1752 | version = "0.6.1" 1753 | source = "registry+https://github.com/rust-lang/crates.io-index" 1754 | checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" 1755 | dependencies = [ 1756 | "libc", 1757 | "windows-sys 0.60.2", 1758 | ] 1759 | 1760 | [[package]] 1761 | name = "stable_deref_trait" 1762 | version = "1.2.1" 1763 | source = "registry+https://github.com/rust-lang/crates.io-index" 1764 | checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" 1765 | 1766 | [[package]] 1767 | name = "strsim" 1768 | version = "0.11.1" 1769 | source = "registry+https://github.com/rust-lang/crates.io-index" 1770 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1771 | 1772 | [[package]] 1773 | name = "subtle" 1774 | version = "2.6.1" 1775 | source = "registry+https://github.com/rust-lang/crates.io-index" 1776 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 1777 | 1778 | [[package]] 1779 | name = "syn" 1780 | version = "2.0.110" 1781 | source = "registry+https://github.com/rust-lang/crates.io-index" 1782 | checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" 1783 | dependencies = [ 1784 | "proc-macro2", 1785 | "quote", 1786 | "unicode-ident", 1787 | ] 1788 | 1789 | [[package]] 1790 | name = "sync_wrapper" 1791 | version = "1.0.2" 1792 | source = "registry+https://github.com/rust-lang/crates.io-index" 1793 | checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 1794 | dependencies = [ 1795 | "futures-core", 1796 | ] 1797 | 1798 | [[package]] 1799 | name = "synstructure" 1800 | version = "0.13.2" 1801 | source = "registry+https://github.com/rust-lang/crates.io-index" 1802 | checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" 1803 | dependencies = [ 1804 | "proc-macro2", 1805 | "quote", 1806 | "syn", 1807 | ] 1808 | 1809 | [[package]] 1810 | name = "system-configuration" 1811 | version = "0.6.1" 1812 | source = "registry+https://github.com/rust-lang/crates.io-index" 1813 | checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" 1814 | dependencies = [ 1815 | "bitflags 2.10.0", 1816 | "core-foundation", 1817 | "system-configuration-sys", 1818 | ] 1819 | 1820 | [[package]] 1821 | name = "system-configuration-sys" 1822 | version = "0.6.0" 1823 | source = "registry+https://github.com/rust-lang/crates.io-index" 1824 | checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" 1825 | dependencies = [ 1826 | "core-foundation-sys", 1827 | "libc", 1828 | ] 1829 | 1830 | [[package]] 1831 | name = "tempfile" 1832 | version = "3.23.0" 1833 | source = "registry+https://github.com/rust-lang/crates.io-index" 1834 | checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" 1835 | dependencies = [ 1836 | "fastrand", 1837 | "getrandom 0.3.4", 1838 | "once_cell", 1839 | "rustix", 1840 | "windows-sys 0.61.2", 1841 | ] 1842 | 1843 | [[package]] 1844 | name = "thiserror" 1845 | version = "1.0.69" 1846 | source = "registry+https://github.com/rust-lang/crates.io-index" 1847 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 1848 | dependencies = [ 1849 | "thiserror-impl 1.0.69", 1850 | ] 1851 | 1852 | [[package]] 1853 | name = "thiserror" 1854 | version = "2.0.17" 1855 | source = "registry+https://github.com/rust-lang/crates.io-index" 1856 | checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" 1857 | dependencies = [ 1858 | "thiserror-impl 2.0.17", 1859 | ] 1860 | 1861 | [[package]] 1862 | name = "thiserror-impl" 1863 | version = "1.0.69" 1864 | source = "registry+https://github.com/rust-lang/crates.io-index" 1865 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 1866 | dependencies = [ 1867 | "proc-macro2", 1868 | "quote", 1869 | "syn", 1870 | ] 1871 | 1872 | [[package]] 1873 | name = "thiserror-impl" 1874 | version = "2.0.17" 1875 | source = "registry+https://github.com/rust-lang/crates.io-index" 1876 | checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" 1877 | dependencies = [ 1878 | "proc-macro2", 1879 | "quote", 1880 | "syn", 1881 | ] 1882 | 1883 | [[package]] 1884 | name = "thread_local" 1885 | version = "1.1.9" 1886 | source = "registry+https://github.com/rust-lang/crates.io-index" 1887 | checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" 1888 | dependencies = [ 1889 | "cfg-if", 1890 | ] 1891 | 1892 | [[package]] 1893 | name = "time" 1894 | version = "0.3.44" 1895 | source = "registry+https://github.com/rust-lang/crates.io-index" 1896 | checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" 1897 | dependencies = [ 1898 | "deranged", 1899 | "num-conv", 1900 | "powerfmt", 1901 | "serde", 1902 | "time-core", 1903 | ] 1904 | 1905 | [[package]] 1906 | name = "time-core" 1907 | version = "0.1.6" 1908 | source = "registry+https://github.com/rust-lang/crates.io-index" 1909 | checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" 1910 | 1911 | [[package]] 1912 | name = "tinystr" 1913 | version = "0.8.2" 1914 | source = "registry+https://github.com/rust-lang/crates.io-index" 1915 | checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" 1916 | dependencies = [ 1917 | "displaydoc", 1918 | "zerovec", 1919 | ] 1920 | 1921 | [[package]] 1922 | name = "tokio" 1923 | version = "1.48.0" 1924 | source = "registry+https://github.com/rust-lang/crates.io-index" 1925 | checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" 1926 | dependencies = [ 1927 | "bytes", 1928 | "libc", 1929 | "mio 1.1.0", 1930 | "pin-project-lite", 1931 | "socket2", 1932 | "windows-sys 0.61.2", 1933 | ] 1934 | 1935 | [[package]] 1936 | name = "tokio-native-tls" 1937 | version = "0.3.1" 1938 | source = "registry+https://github.com/rust-lang/crates.io-index" 1939 | checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 1940 | dependencies = [ 1941 | "native-tls", 1942 | "tokio", 1943 | ] 1944 | 1945 | [[package]] 1946 | name = "tokio-rustls" 1947 | version = "0.26.4" 1948 | source = "registry+https://github.com/rust-lang/crates.io-index" 1949 | checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" 1950 | dependencies = [ 1951 | "rustls", 1952 | "tokio", 1953 | ] 1954 | 1955 | [[package]] 1956 | name = "tokio-util" 1957 | version = "0.7.17" 1958 | source = "registry+https://github.com/rust-lang/crates.io-index" 1959 | checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" 1960 | dependencies = [ 1961 | "bytes", 1962 | "futures-core", 1963 | "futures-sink", 1964 | "pin-project-lite", 1965 | "tokio", 1966 | ] 1967 | 1968 | [[package]] 1969 | name = "tower" 1970 | version = "0.5.2" 1971 | source = "registry+https://github.com/rust-lang/crates.io-index" 1972 | checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" 1973 | dependencies = [ 1974 | "futures-core", 1975 | "futures-util", 1976 | "pin-project-lite", 1977 | "sync_wrapper", 1978 | "tokio", 1979 | "tower-layer", 1980 | "tower-service", 1981 | ] 1982 | 1983 | [[package]] 1984 | name = "tower-http" 1985 | version = "0.6.6" 1986 | source = "registry+https://github.com/rust-lang/crates.io-index" 1987 | checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" 1988 | dependencies = [ 1989 | "bitflags 2.10.0", 1990 | "bytes", 1991 | "futures-util", 1992 | "http", 1993 | "http-body", 1994 | "iri-string", 1995 | "pin-project-lite", 1996 | "tower", 1997 | "tower-layer", 1998 | "tower-service", 1999 | ] 2000 | 2001 | [[package]] 2002 | name = "tower-layer" 2003 | version = "0.3.3" 2004 | source = "registry+https://github.com/rust-lang/crates.io-index" 2005 | checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 2006 | 2007 | [[package]] 2008 | name = "tower-service" 2009 | version = "0.3.3" 2010 | source = "registry+https://github.com/rust-lang/crates.io-index" 2011 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 2012 | 2013 | [[package]] 2014 | name = "tracing" 2015 | version = "0.1.41" 2016 | source = "registry+https://github.com/rust-lang/crates.io-index" 2017 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 2018 | dependencies = [ 2019 | "pin-project-lite", 2020 | "tracing-core", 2021 | ] 2022 | 2023 | [[package]] 2024 | name = "tracing-core" 2025 | version = "0.1.34" 2026 | source = "registry+https://github.com/rust-lang/crates.io-index" 2027 | checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" 2028 | dependencies = [ 2029 | "once_cell", 2030 | ] 2031 | 2032 | [[package]] 2033 | name = "try-lock" 2034 | version = "0.2.5" 2035 | source = "registry+https://github.com/rust-lang/crates.io-index" 2036 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 2037 | 2038 | [[package]] 2039 | name = "typenum" 2040 | version = "1.19.0" 2041 | source = "registry+https://github.com/rust-lang/crates.io-index" 2042 | checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" 2043 | 2044 | [[package]] 2045 | name = "ucd-trie" 2046 | version = "0.1.7" 2047 | source = "registry+https://github.com/rust-lang/crates.io-index" 2048 | checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" 2049 | 2050 | [[package]] 2051 | name = "unicode-ident" 2052 | version = "1.0.22" 2053 | source = "registry+https://github.com/rust-lang/crates.io-index" 2054 | checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" 2055 | 2056 | [[package]] 2057 | name = "unicode-segmentation" 2058 | version = "1.12.0" 2059 | source = "registry+https://github.com/rust-lang/crates.io-index" 2060 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 2061 | 2062 | [[package]] 2063 | name = "unicode-width" 2064 | version = "0.1.14" 2065 | source = "registry+https://github.com/rust-lang/crates.io-index" 2066 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 2067 | 2068 | [[package]] 2069 | name = "untrusted" 2070 | version = "0.9.0" 2071 | source = "registry+https://github.com/rust-lang/crates.io-index" 2072 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 2073 | 2074 | [[package]] 2075 | name = "url" 2076 | version = "2.5.7" 2077 | source = "registry+https://github.com/rust-lang/crates.io-index" 2078 | checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" 2079 | dependencies = [ 2080 | "form_urlencoded", 2081 | "idna", 2082 | "percent-encoding", 2083 | "serde", 2084 | ] 2085 | 2086 | [[package]] 2087 | name = "utf8_iter" 2088 | version = "1.0.4" 2089 | source = "registry+https://github.com/rust-lang/crates.io-index" 2090 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 2091 | 2092 | [[package]] 2093 | name = "utf8parse" 2094 | version = "0.2.2" 2095 | source = "registry+https://github.com/rust-lang/crates.io-index" 2096 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 2097 | 2098 | [[package]] 2099 | name = "vcpkg" 2100 | version = "0.2.15" 2101 | source = "registry+https://github.com/rust-lang/crates.io-index" 2102 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 2103 | 2104 | [[package]] 2105 | name = "version_check" 2106 | version = "0.9.5" 2107 | source = "registry+https://github.com/rust-lang/crates.io-index" 2108 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 2109 | 2110 | [[package]] 2111 | name = "want" 2112 | version = "0.3.1" 2113 | source = "registry+https://github.com/rust-lang/crates.io-index" 2114 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 2115 | dependencies = [ 2116 | "try-lock", 2117 | ] 2118 | 2119 | [[package]] 2120 | name = "wasi" 2121 | version = "0.11.1+wasi-snapshot-preview1" 2122 | source = "registry+https://github.com/rust-lang/crates.io-index" 2123 | checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 2124 | 2125 | [[package]] 2126 | name = "wasip2" 2127 | version = "1.0.1+wasi-0.2.4" 2128 | source = "registry+https://github.com/rust-lang/crates.io-index" 2129 | checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" 2130 | dependencies = [ 2131 | "wit-bindgen", 2132 | ] 2133 | 2134 | [[package]] 2135 | name = "wasm-bindgen" 2136 | version = "0.2.105" 2137 | source = "registry+https://github.com/rust-lang/crates.io-index" 2138 | checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" 2139 | dependencies = [ 2140 | "cfg-if", 2141 | "once_cell", 2142 | "rustversion", 2143 | "wasm-bindgen-macro", 2144 | "wasm-bindgen-shared", 2145 | ] 2146 | 2147 | [[package]] 2148 | name = "wasm-bindgen-futures" 2149 | version = "0.4.55" 2150 | source = "registry+https://github.com/rust-lang/crates.io-index" 2151 | checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" 2152 | dependencies = [ 2153 | "cfg-if", 2154 | "js-sys", 2155 | "once_cell", 2156 | "wasm-bindgen", 2157 | "web-sys", 2158 | ] 2159 | 2160 | [[package]] 2161 | name = "wasm-bindgen-macro" 2162 | version = "0.2.105" 2163 | source = "registry+https://github.com/rust-lang/crates.io-index" 2164 | checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" 2165 | dependencies = [ 2166 | "quote", 2167 | "wasm-bindgen-macro-support", 2168 | ] 2169 | 2170 | [[package]] 2171 | name = "wasm-bindgen-macro-support" 2172 | version = "0.2.105" 2173 | source = "registry+https://github.com/rust-lang/crates.io-index" 2174 | checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" 2175 | dependencies = [ 2176 | "bumpalo", 2177 | "proc-macro2", 2178 | "quote", 2179 | "syn", 2180 | "wasm-bindgen-shared", 2181 | ] 2182 | 2183 | [[package]] 2184 | name = "wasm-bindgen-shared" 2185 | version = "0.2.105" 2186 | source = "registry+https://github.com/rust-lang/crates.io-index" 2187 | checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" 2188 | dependencies = [ 2189 | "unicode-ident", 2190 | ] 2191 | 2192 | [[package]] 2193 | name = "wasm-streams" 2194 | version = "0.4.2" 2195 | source = "registry+https://github.com/rust-lang/crates.io-index" 2196 | checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" 2197 | dependencies = [ 2198 | "futures-util", 2199 | "js-sys", 2200 | "wasm-bindgen", 2201 | "wasm-bindgen-futures", 2202 | "web-sys", 2203 | ] 2204 | 2205 | [[package]] 2206 | name = "web-sys" 2207 | version = "0.3.82" 2208 | source = "registry+https://github.com/rust-lang/crates.io-index" 2209 | checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" 2210 | dependencies = [ 2211 | "js-sys", 2212 | "wasm-bindgen", 2213 | ] 2214 | 2215 | [[package]] 2216 | name = "wg-mod" 2217 | version = "0.11.0" 2218 | dependencies = [ 2219 | "clap", 2220 | "convert_case", 2221 | "fs_extra", 2222 | "git2", 2223 | "glob", 2224 | "handlebars", 2225 | "home", 2226 | "inquire", 2227 | "regex", 2228 | "reqwest", 2229 | "serde", 2230 | "serde_derive", 2231 | "serde_json", 2232 | "tempfile", 2233 | "thiserror 1.0.69", 2234 | "zip 2.4.2", 2235 | "zip-extensions", 2236 | ] 2237 | 2238 | [[package]] 2239 | name = "winapi" 2240 | version = "0.3.9" 2241 | source = "registry+https://github.com/rust-lang/crates.io-index" 2242 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 2243 | dependencies = [ 2244 | "winapi-i686-pc-windows-gnu", 2245 | "winapi-x86_64-pc-windows-gnu", 2246 | ] 2247 | 2248 | [[package]] 2249 | name = "winapi-i686-pc-windows-gnu" 2250 | version = "0.4.0" 2251 | source = "registry+https://github.com/rust-lang/crates.io-index" 2252 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2253 | 2254 | [[package]] 2255 | name = "winapi-x86_64-pc-windows-gnu" 2256 | version = "0.4.0" 2257 | source = "registry+https://github.com/rust-lang/crates.io-index" 2258 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2259 | 2260 | [[package]] 2261 | name = "windows-link" 2262 | version = "0.2.1" 2263 | source = "registry+https://github.com/rust-lang/crates.io-index" 2264 | checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 2265 | 2266 | [[package]] 2267 | name = "windows-registry" 2268 | version = "0.6.1" 2269 | source = "registry+https://github.com/rust-lang/crates.io-index" 2270 | checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" 2271 | dependencies = [ 2272 | "windows-link", 2273 | "windows-result", 2274 | "windows-strings", 2275 | ] 2276 | 2277 | [[package]] 2278 | name = "windows-result" 2279 | version = "0.4.1" 2280 | source = "registry+https://github.com/rust-lang/crates.io-index" 2281 | checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" 2282 | dependencies = [ 2283 | "windows-link", 2284 | ] 2285 | 2286 | [[package]] 2287 | name = "windows-strings" 2288 | version = "0.5.1" 2289 | source = "registry+https://github.com/rust-lang/crates.io-index" 2290 | checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" 2291 | dependencies = [ 2292 | "windows-link", 2293 | ] 2294 | 2295 | [[package]] 2296 | name = "windows-sys" 2297 | version = "0.48.0" 2298 | source = "registry+https://github.com/rust-lang/crates.io-index" 2299 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 2300 | dependencies = [ 2301 | "windows-targets 0.48.5", 2302 | ] 2303 | 2304 | [[package]] 2305 | name = "windows-sys" 2306 | version = "0.52.0" 2307 | source = "registry+https://github.com/rust-lang/crates.io-index" 2308 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 2309 | dependencies = [ 2310 | "windows-targets 0.52.6", 2311 | ] 2312 | 2313 | [[package]] 2314 | name = "windows-sys" 2315 | version = "0.60.2" 2316 | source = "registry+https://github.com/rust-lang/crates.io-index" 2317 | checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" 2318 | dependencies = [ 2319 | "windows-targets 0.53.5", 2320 | ] 2321 | 2322 | [[package]] 2323 | name = "windows-sys" 2324 | version = "0.61.2" 2325 | source = "registry+https://github.com/rust-lang/crates.io-index" 2326 | checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" 2327 | dependencies = [ 2328 | "windows-link", 2329 | ] 2330 | 2331 | [[package]] 2332 | name = "windows-targets" 2333 | version = "0.48.5" 2334 | source = "registry+https://github.com/rust-lang/crates.io-index" 2335 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 2336 | dependencies = [ 2337 | "windows_aarch64_gnullvm 0.48.5", 2338 | "windows_aarch64_msvc 0.48.5", 2339 | "windows_i686_gnu 0.48.5", 2340 | "windows_i686_msvc 0.48.5", 2341 | "windows_x86_64_gnu 0.48.5", 2342 | "windows_x86_64_gnullvm 0.48.5", 2343 | "windows_x86_64_msvc 0.48.5", 2344 | ] 2345 | 2346 | [[package]] 2347 | name = "windows-targets" 2348 | version = "0.52.6" 2349 | source = "registry+https://github.com/rust-lang/crates.io-index" 2350 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 2351 | dependencies = [ 2352 | "windows_aarch64_gnullvm 0.52.6", 2353 | "windows_aarch64_msvc 0.52.6", 2354 | "windows_i686_gnu 0.52.6", 2355 | "windows_i686_gnullvm 0.52.6", 2356 | "windows_i686_msvc 0.52.6", 2357 | "windows_x86_64_gnu 0.52.6", 2358 | "windows_x86_64_gnullvm 0.52.6", 2359 | "windows_x86_64_msvc 0.52.6", 2360 | ] 2361 | 2362 | [[package]] 2363 | name = "windows-targets" 2364 | version = "0.53.5" 2365 | source = "registry+https://github.com/rust-lang/crates.io-index" 2366 | checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" 2367 | dependencies = [ 2368 | "windows-link", 2369 | "windows_aarch64_gnullvm 0.53.1", 2370 | "windows_aarch64_msvc 0.53.1", 2371 | "windows_i686_gnu 0.53.1", 2372 | "windows_i686_gnullvm 0.53.1", 2373 | "windows_i686_msvc 0.53.1", 2374 | "windows_x86_64_gnu 0.53.1", 2375 | "windows_x86_64_gnullvm 0.53.1", 2376 | "windows_x86_64_msvc 0.53.1", 2377 | ] 2378 | 2379 | [[package]] 2380 | name = "windows_aarch64_gnullvm" 2381 | version = "0.48.5" 2382 | source = "registry+https://github.com/rust-lang/crates.io-index" 2383 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 2384 | 2385 | [[package]] 2386 | name = "windows_aarch64_gnullvm" 2387 | version = "0.52.6" 2388 | source = "registry+https://github.com/rust-lang/crates.io-index" 2389 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 2390 | 2391 | [[package]] 2392 | name = "windows_aarch64_gnullvm" 2393 | version = "0.53.1" 2394 | source = "registry+https://github.com/rust-lang/crates.io-index" 2395 | checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" 2396 | 2397 | [[package]] 2398 | name = "windows_aarch64_msvc" 2399 | version = "0.48.5" 2400 | source = "registry+https://github.com/rust-lang/crates.io-index" 2401 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 2402 | 2403 | [[package]] 2404 | name = "windows_aarch64_msvc" 2405 | version = "0.52.6" 2406 | source = "registry+https://github.com/rust-lang/crates.io-index" 2407 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2408 | 2409 | [[package]] 2410 | name = "windows_aarch64_msvc" 2411 | version = "0.53.1" 2412 | source = "registry+https://github.com/rust-lang/crates.io-index" 2413 | checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" 2414 | 2415 | [[package]] 2416 | name = "windows_i686_gnu" 2417 | version = "0.48.5" 2418 | source = "registry+https://github.com/rust-lang/crates.io-index" 2419 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 2420 | 2421 | [[package]] 2422 | name = "windows_i686_gnu" 2423 | version = "0.52.6" 2424 | source = "registry+https://github.com/rust-lang/crates.io-index" 2425 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2426 | 2427 | [[package]] 2428 | name = "windows_i686_gnu" 2429 | version = "0.53.1" 2430 | source = "registry+https://github.com/rust-lang/crates.io-index" 2431 | checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" 2432 | 2433 | [[package]] 2434 | name = "windows_i686_gnullvm" 2435 | version = "0.52.6" 2436 | source = "registry+https://github.com/rust-lang/crates.io-index" 2437 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2438 | 2439 | [[package]] 2440 | name = "windows_i686_gnullvm" 2441 | version = "0.53.1" 2442 | source = "registry+https://github.com/rust-lang/crates.io-index" 2443 | checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" 2444 | 2445 | [[package]] 2446 | name = "windows_i686_msvc" 2447 | version = "0.48.5" 2448 | source = "registry+https://github.com/rust-lang/crates.io-index" 2449 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 2450 | 2451 | [[package]] 2452 | name = "windows_i686_msvc" 2453 | version = "0.52.6" 2454 | source = "registry+https://github.com/rust-lang/crates.io-index" 2455 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2456 | 2457 | [[package]] 2458 | name = "windows_i686_msvc" 2459 | version = "0.53.1" 2460 | source = "registry+https://github.com/rust-lang/crates.io-index" 2461 | checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" 2462 | 2463 | [[package]] 2464 | name = "windows_x86_64_gnu" 2465 | version = "0.48.5" 2466 | source = "registry+https://github.com/rust-lang/crates.io-index" 2467 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 2468 | 2469 | [[package]] 2470 | name = "windows_x86_64_gnu" 2471 | version = "0.52.6" 2472 | source = "registry+https://github.com/rust-lang/crates.io-index" 2473 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2474 | 2475 | [[package]] 2476 | name = "windows_x86_64_gnu" 2477 | version = "0.53.1" 2478 | source = "registry+https://github.com/rust-lang/crates.io-index" 2479 | checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" 2480 | 2481 | [[package]] 2482 | name = "windows_x86_64_gnullvm" 2483 | version = "0.48.5" 2484 | source = "registry+https://github.com/rust-lang/crates.io-index" 2485 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 2486 | 2487 | [[package]] 2488 | name = "windows_x86_64_gnullvm" 2489 | version = "0.52.6" 2490 | source = "registry+https://github.com/rust-lang/crates.io-index" 2491 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2492 | 2493 | [[package]] 2494 | name = "windows_x86_64_gnullvm" 2495 | version = "0.53.1" 2496 | source = "registry+https://github.com/rust-lang/crates.io-index" 2497 | checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" 2498 | 2499 | [[package]] 2500 | name = "windows_x86_64_msvc" 2501 | version = "0.48.5" 2502 | source = "registry+https://github.com/rust-lang/crates.io-index" 2503 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 2504 | 2505 | [[package]] 2506 | name = "windows_x86_64_msvc" 2507 | version = "0.52.6" 2508 | source = "registry+https://github.com/rust-lang/crates.io-index" 2509 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2510 | 2511 | [[package]] 2512 | name = "windows_x86_64_msvc" 2513 | version = "0.53.1" 2514 | source = "registry+https://github.com/rust-lang/crates.io-index" 2515 | checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" 2516 | 2517 | [[package]] 2518 | name = "wit-bindgen" 2519 | version = "0.46.0" 2520 | source = "registry+https://github.com/rust-lang/crates.io-index" 2521 | checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" 2522 | 2523 | [[package]] 2524 | name = "writeable" 2525 | version = "0.6.2" 2526 | source = "registry+https://github.com/rust-lang/crates.io-index" 2527 | checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" 2528 | 2529 | [[package]] 2530 | name = "xz2" 2531 | version = "0.1.7" 2532 | source = "registry+https://github.com/rust-lang/crates.io-index" 2533 | checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" 2534 | dependencies = [ 2535 | "lzma-sys", 2536 | ] 2537 | 2538 | [[package]] 2539 | name = "yoke" 2540 | version = "0.8.1" 2541 | source = "registry+https://github.com/rust-lang/crates.io-index" 2542 | checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" 2543 | dependencies = [ 2544 | "stable_deref_trait", 2545 | "yoke-derive", 2546 | "zerofrom", 2547 | ] 2548 | 2549 | [[package]] 2550 | name = "yoke-derive" 2551 | version = "0.8.1" 2552 | source = "registry+https://github.com/rust-lang/crates.io-index" 2553 | checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" 2554 | dependencies = [ 2555 | "proc-macro2", 2556 | "quote", 2557 | "syn", 2558 | "synstructure", 2559 | ] 2560 | 2561 | [[package]] 2562 | name = "zerofrom" 2563 | version = "0.1.6" 2564 | source = "registry+https://github.com/rust-lang/crates.io-index" 2565 | checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" 2566 | dependencies = [ 2567 | "zerofrom-derive", 2568 | ] 2569 | 2570 | [[package]] 2571 | name = "zerofrom-derive" 2572 | version = "0.1.6" 2573 | source = "registry+https://github.com/rust-lang/crates.io-index" 2574 | checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" 2575 | dependencies = [ 2576 | "proc-macro2", 2577 | "quote", 2578 | "syn", 2579 | "synstructure", 2580 | ] 2581 | 2582 | [[package]] 2583 | name = "zeroize" 2584 | version = "1.8.2" 2585 | source = "registry+https://github.com/rust-lang/crates.io-index" 2586 | checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" 2587 | dependencies = [ 2588 | "zeroize_derive", 2589 | ] 2590 | 2591 | [[package]] 2592 | name = "zeroize_derive" 2593 | version = "1.4.2" 2594 | source = "registry+https://github.com/rust-lang/crates.io-index" 2595 | checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" 2596 | dependencies = [ 2597 | "proc-macro2", 2598 | "quote", 2599 | "syn", 2600 | ] 2601 | 2602 | [[package]] 2603 | name = "zerotrie" 2604 | version = "0.2.3" 2605 | source = "registry+https://github.com/rust-lang/crates.io-index" 2606 | checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" 2607 | dependencies = [ 2608 | "displaydoc", 2609 | "yoke", 2610 | "zerofrom", 2611 | ] 2612 | 2613 | [[package]] 2614 | name = "zerovec" 2615 | version = "0.11.5" 2616 | source = "registry+https://github.com/rust-lang/crates.io-index" 2617 | checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" 2618 | dependencies = [ 2619 | "yoke", 2620 | "zerofrom", 2621 | "zerovec-derive", 2622 | ] 2623 | 2624 | [[package]] 2625 | name = "zerovec-derive" 2626 | version = "0.11.2" 2627 | source = "registry+https://github.com/rust-lang/crates.io-index" 2628 | checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" 2629 | dependencies = [ 2630 | "proc-macro2", 2631 | "quote", 2632 | "syn", 2633 | ] 2634 | 2635 | [[package]] 2636 | name = "zip" 2637 | version = "2.4.2" 2638 | source = "registry+https://github.com/rust-lang/crates.io-index" 2639 | checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" 2640 | dependencies = [ 2641 | "aes", 2642 | "arbitrary", 2643 | "bzip2", 2644 | "constant_time_eq", 2645 | "crc32fast", 2646 | "crossbeam-utils", 2647 | "deflate64", 2648 | "displaydoc", 2649 | "flate2", 2650 | "getrandom 0.3.4", 2651 | "hmac", 2652 | "indexmap", 2653 | "lzma-rs", 2654 | "memchr", 2655 | "pbkdf2", 2656 | "sha1", 2657 | "thiserror 2.0.17", 2658 | "time", 2659 | "xz2", 2660 | "zeroize", 2661 | "zopfli", 2662 | "zstd", 2663 | ] 2664 | 2665 | [[package]] 2666 | name = "zip" 2667 | version = "3.0.0" 2668 | source = "registry+https://github.com/rust-lang/crates.io-index" 2669 | checksum = "12598812502ed0105f607f941c386f43d441e00148fce9dec3ca5ffb0bde9308" 2670 | dependencies = [ 2671 | "aes", 2672 | "arbitrary", 2673 | "constant_time_eq", 2674 | "crc32fast", 2675 | "flate2", 2676 | "getrandom 0.3.4", 2677 | "hmac", 2678 | "indexmap", 2679 | "lzma-rs", 2680 | "memchr", 2681 | "pbkdf2", 2682 | "sha1", 2683 | "xz2", 2684 | "zeroize", 2685 | "zopfli", 2686 | ] 2687 | 2688 | [[package]] 2689 | name = "zip-extensions" 2690 | version = "0.8.3" 2691 | source = "registry+https://github.com/rust-lang/crates.io-index" 2692 | checksum = "9f105becb0a5da773e655775dd05fee454ca1475bcc980ec9d940a02f42cee40" 2693 | dependencies = [ 2694 | "zip 3.0.0", 2695 | ] 2696 | 2697 | [[package]] 2698 | name = "zlib-rs" 2699 | version = "0.5.2" 2700 | source = "registry+https://github.com/rust-lang/crates.io-index" 2701 | checksum = "2f06ae92f42f5e5c42443fd094f245eb656abf56dd7cce9b8b263236565e00f2" 2702 | 2703 | [[package]] 2704 | name = "zopfli" 2705 | version = "0.8.3" 2706 | source = "registry+https://github.com/rust-lang/crates.io-index" 2707 | checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" 2708 | dependencies = [ 2709 | "bumpalo", 2710 | "crc32fast", 2711 | "log", 2712 | "simd-adler32", 2713 | ] 2714 | 2715 | [[package]] 2716 | name = "zstd" 2717 | version = "0.13.3" 2718 | source = "registry+https://github.com/rust-lang/crates.io-index" 2719 | checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" 2720 | dependencies = [ 2721 | "zstd-safe", 2722 | ] 2723 | 2724 | [[package]] 2725 | name = "zstd-safe" 2726 | version = "7.2.4" 2727 | source = "registry+https://github.com/rust-lang/crates.io-index" 2728 | checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" 2729 | dependencies = [ 2730 | "zstd-sys", 2731 | ] 2732 | 2733 | [[package]] 2734 | name = "zstd-sys" 2735 | version = "2.0.16+zstd.1.5.7" 2736 | source = "registry+https://github.com/rust-lang/crates.io-index" 2737 | checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" 2738 | dependencies = [ 2739 | "cc", 2740 | "pkg-config", 2741 | ] 2742 | --------------------------------------------------------------------------------