├── choco-help ├── .gitignore ├── man ├── cmd.txt ├── reinstall.txt ├── config.txt ├── remove.txt ├── run.txt ├── version.txt ├── flags.txt ├── new.txt ├── action.txt ├── ssh.txt ├── sync.txt ├── ask_for.txt ├── path.txt ├── sandbox.txt ├── export.txt └── env.txt ├── PKGBUILD ├── Makefile ├── chocolate_in ├── help.py ├── template.py ├── log.py ├── path.py ├── config.py ├── help.cpp ├── sandbox.cpp ├── tests │ └── main.py ├── project_manager.py ├── sftp.py └── main.py ├── README.md ├── contribute.md ├── DOCSfa.md └── DOCS.md /choco-help: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrChocolate/chocolate/HEAD/choco-help -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .venv 2 | __pycache__ 3 | log 4 | chocolate.json 5 | .vscode 6 | choco-sandbox 7 | choco-help\ 8 | .ccls-cache -------------------------------------------------------------------------------- /man/cmd.txt: -------------------------------------------------------------------------------- 1 | [bold][magenta]## chocolate cmd [/magenta][/bold] 2 | -> This function runs command on ssh server. 3 | 4 | -------------------------------------------------------------------------------- /man/reinstall.txt: -------------------------------------------------------------------------------- 1 | [bold][magenta]## chocolate reinstall[/magenta][/bold] 2 | -> This function is used to reinstall all dependencies. -------------------------------------------------------------------------------- /man/config.txt: -------------------------------------------------------------------------------- 1 | [bold][magenta]## chocolate config[/magenta][/bold] 2 | -> This function displays the current project configuration. 3 | - Use `chocolate config` to view the current configuration details. 4 | -------------------------------------------------------------------------------- /man/remove.txt: -------------------------------------------------------------------------------- 1 | [bold][magenta]## chocolate remove[/magenta][/bold] 2 | -> This function removes the project configuration file. 3 | - Use `chocolate remove` to delete the configuration file (`.chocolate`). 4 | -------------------------------------------------------------------------------- /man/run.txt: -------------------------------------------------------------------------------- 1 | [bold][magenta]## chocolate run[/magenta][/bold] 2 | -> This function runs the app specified inside .chocolate.run.main. 3 | - Use `-r` to reinstall dependencies before running the project. 4 | 5 | -------------------------------------------------------------------------------- /man/version.txt: -------------------------------------------------------------------------------- 1 | [bold][magenta]## chocolate version[/magenta][/bold] 2 | -> This function displays the current version of the Chocolate Project Manager. 3 | - Use `chocolate version` to check the version of Chocolate Project Manager. 4 | -------------------------------------------------------------------------------- /man/flags.txt: -------------------------------------------------------------------------------- 1 | [bold][magenta]## chocolate flags[/magenta][/bold] 2 | -> This function is used to set flags for running .chocolate.run.startfile. 3 | - Use `flags flag1 flag2 flag3 flag4 ...` to add or overwrite flags. 4 | - Use `flags ""` to remove all flags. 5 | -------------------------------------------------------------------------------- /man/new.txt: -------------------------------------------------------------------------------- 1 | [bold][magenta]## chocolate new [/magenta][/bold] 2 | -> This function is used to create a new project. 3 | - Use `` to specify the project name. 4 | - Use `
` to specify the main file path. 5 | -------------------------------------------------------------------------------- /man/action.txt: -------------------------------------------------------------------------------- 1 | [bold][magenta]## chocolate action[/magenta][/bold] 2 | -> This function is used for handling custom actions defined in the project. 3 | - Use `add ` to add a new custom action. 4 | - Use `remove ` to remove a custom action. 5 | - Use `` to execute. 6 | -------------------------------------------------------------------------------- /man/ssh.txt: -------------------------------------------------------------------------------- 1 | [bold][magenta]## chocolate ssh[/magenta][/bold] 2 | -> This function adds server details to config. 3 | - Use `chocolate config` to view the current configuration details and change the ssh server details for connection. 4 | - Or use chocolate ssh alternativly. -------------------------------------------------------------------------------- /man/sync.txt: -------------------------------------------------------------------------------- 1 | [bold][magenta]## chocolate sync[/magenta][/bold] 2 | -> This function syncs all codes into your ssh server. 3 | - Use `chocolate config` to view the current configuration details and change the ssh server details for connection. 4 | - Or use chocolate ssh alternativly. -------------------------------------------------------------------------------- /man/ask_for.txt: -------------------------------------------------------------------------------- 1 | [bold][magenta]## chocolate ask_for[/magenta][/bold] 2 | -> This function manages the environment variables to be asked for during startup. 3 | - Use `add ...` to add environment variables to the 'ask for' list. 4 | - Use `remove ...` to remove environment variables from the 'ask for' list. 5 | -------------------------------------------------------------------------------- /man/path.txt: -------------------------------------------------------------------------------- 1 | [bold][magenta]## chocolate path[/magenta][/bold] 2 | -> This function is used to include or exclude a path from the project. 3 | - Use `include ` to include a path inside a project. 4 | - Use `exclude ` to exclude a path inside a project. 5 | 6 | [bold]example[/bold] 7 | chocolate path include assets/ 8 | chocolate path exclude assets/db.db 9 | -------------------------------------------------------------------------------- /man/sandbox.txt: -------------------------------------------------------------------------------- 1 | [bold][magenta]## chocolate sandbox[/magenta][/bold] 2 | -> This function runs the project in a sandbox mode. 3 | - Use `` to set the memory limit (use -1 for unlimited). 4 | - Use `` to set the CPU time limit (use -1 for unlimited). 5 | - Use `` to set the CPU frequency (use -1 for unlimited). 6 | -------------------------------------------------------------------------------- /PKGBUILD: -------------------------------------------------------------------------------- 1 | pkgname=chocolatePM 2 | pkgver=3.0.2 3 | pkgrel=1 4 | pkgdesc="Chocolate Project Manager" 5 | arch=('x86_64') 6 | url="https://github.com/frchocolate/chocolate" 7 | license=('MIT') 8 | depends=('python') 9 | source=("git+https://github.com/frchocolate/chocolate.git") 10 | md5sums=('SKIP') 11 | 12 | package() { 13 | cd "$srcdir/chocolate" 14 | make all 15 | } -------------------------------------------------------------------------------- /man/export.txt: -------------------------------------------------------------------------------- 1 | [bold][magenta]## chocolate export[/magenta][/bold] 2 | -> This function is used to export the project into a zip file. 3 | - Use `-o ` to specify the output file. 4 | - Use `-a` to export the entire folder (see the attachment). 5 | - Use `-w` to avoid exporting the .chocolate file (not recommended). 6 | 7 | [bold]Attachment:[/bold] By default, the export function only exports the files and folders that are in the path. Run `chocolate path -h` to get help with adding or removing paths from the project. 8 | -------------------------------------------------------------------------------- /man/env.txt: -------------------------------------------------------------------------------- 1 | [bold][magenta]## chocolate env[/magenta][/bold] 2 | -> This function is used for declaring, removing, or getting environment variables. 3 | - Use `remove key1 key2` to remove specified keys. 4 | - Use `list` to list all environment variables. 5 | - Use `key1=value1 key2=value2 ...` to set values. 6 | - Use `private key1 key2 ...` to toggle a key's privacy. 7 | 8 | [underline]Private keys won't be exported with the export command.[/underline] 9 | 10 | [bold]You can't name your environment variable 'list', 'remove', or 'private'.[/bold] 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | APP_DIR = chocolate_in 2 | PYTHON_FILE = main.py 3 | RUNNER_SCRIPT = chocolate 4 | VENV_DIR = .venv 5 | SAFE_DIR = /opt/chocolate 6 | INSTALL_DIR = /usr/local/bin 7 | 8 | all: sandbox helper create-venv install-deps copy-app create-runner move-runner 9 | 10 | create-venv: 11 | @python3 -m venv $(VENV_DIR) 12 | 13 | install-deps: 14 | @$(VENV_DIR)/bin/pip install --upgrade pip 15 | @$(VENV_DIR)/bin/pip install rich pytest paramiko 16 | 17 | copy-app: 18 | @sudo rm -rf $(SAFE_DIR) 19 | @sudo mkdir -p $(SAFE_DIR) 20 | @sudo cp -r $(APP_DIR) $(SAFE_DIR) 21 | @sudo cp -r $(VENV_DIR) $(SAFE_DIR) 22 | @sudo cp choco-sandbox $(INSTALL_DIR) 23 | @sudo cp choco-help $(INSTALL_DIR) 24 | @sudo mkdir -p /usr/share/doc/chocolate 25 | @sudo cp -r man/* /usr/share/doc/chocolate 26 | 27 | 28 | create-runner: 29 | @echo "#!/bin/bash" > $(RUNNER_SCRIPT) 30 | @echo "$(SAFE_DIR)/$(VENV_DIR)/bin/python3 $(SAFE_DIR)/$(APP_DIR)/$(PYTHON_FILE) \"\$$@\"" >> $(RUNNER_SCRIPT) 31 | @chmod +x $(RUNNER_SCRIPT) 32 | 33 | move-runner: 34 | @sudo mv $(RUNNER_SCRIPT) $(INSTALL_DIR)/$(RUNNER_SCRIPT) 35 | 36 | sandbox: 37 | g++ -o choco-sandbox chocolate_in/sandbox.cpp -lseccomp -D_GNU_SOURCE 38 | 39 | helper: 40 | g++ -o choco-help chocolate_in/help.cpp 41 | 42 | 43 | clean: 44 | @rm -f $(RUNNER_SCRIPT) 45 | @rm -rf $(VENV_DIR) 46 | @sudo rm -rf $(SAFE_DIR) 47 | -------------------------------------------------------------------------------- /chocolate_in/help.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | from rich import print 3 | from rich.panel import Panel 4 | from rich.markdown import Markdown 5 | from rich import box 6 | 7 | short_help = dict( 8 | run="Runs the app specified in .chocolate.run.main with various options for logging and testing.", 9 | env="Manages environment variables, including adding, removing, and setting privacy.", 10 | new="Creates a new project with a specified name and main file path.", 11 | reinstall="Reinstalls all dependencies for the project.", 12 | flags="Sets or clears flags for running the project with custom parameters.", 13 | export="Exports the project as a zip file with options to include/exclude certain files.", 14 | path="Includes or excludes specific paths in the project.", 15 | action="Handles custom actions for the project, allowing addition, removal, and execution.", 16 | sandbox="Runs the project in a controlled environment with resource limits.", 17 | ask_for="Manages environment variables that should be asked for during startup.", 18 | remove="Deletes the project configuration file (.chocolate).", 19 | config="Displays the current configuration of the project.", 20 | version="Shows the current version of the Chocolate Project Manager.", 21 | ) 22 | 23 | 24 | def ensure_help(args): 25 | if args.help: 26 | subprocess.run(["choco-help", args.action]) 27 | quit() 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

5 | logo 6 |

7 | 8 | --- 9 |

10 | Chocolate Project Manager (chocolate) is a command-line tool for managing projects with virtual environments, dependencies, and environment variables. 11 |

12 |

🔥🔥 Now with ssh auto deployment support!

13 | 14 | 15 | 16 | ## Features 17 | - Create new projects 18 | - Run projects with virtual environments 19 | - Add and reinstall dependencies 20 | - Manage environment variables and runtime flags 21 | 22 | > [Read the docs](DOCS.md) [🇺🇸] 23 | 24 | > [Read the docs](DOCSfa.md) [🇮🇷] 25 | 26 | > [Contribution Guidelines](contribute.md) [🧑‍⚕️] 27 | 28 | ## Installation 29 | 30 | ### Install compile dependencies 31 | > libseccomp-dev 32 | > build-essential 33 | ### Installtion for arch 34 | ```sh 35 | yay -S chocolate 36 | ``` 37 | 38 | 39 | ### Installtion chocolate with makefile 40 | 41 | ```sh 42 | git clone https://github.com/frchocolate/chocolate 43 | cd chocolate 44 | make all 45 | ``` 46 | 47 | ## Usage 48 | Run the `chocolate version` command to get started. 49 | 50 | 51 | 52 | ## License 53 | This project is licensed under the MIT License. 54 | 55 | ## Author 56 | Chocolateisfr 57 | -------------------------------------------------------------------------------- /contribute.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | > Thank you for your interest in contributing to our project! We welcome contributions from everyone. Please follow the guidelines below to ensure a smooth process. 4 | 5 | ## How to Contribute ❓ 6 | 7 | 1. Fork the Repository 8 | - Start by forking the repository to your own GitHub account. 9 | 2. Create a New Branch 10 | - Create a new branch for your feature or bug fix. Use a descriptive name for the branch: 11 | ```bash 12 | git checkout -b feature/your-feature-name 13 | ``` 14 | 3. Make Your Changes 15 | 16 | - Make the necessary changes in your branch. Ensure that your code follows the project's coding standards. 17 | 18 | 4. Write Tests 19 | 20 | - If applicable, write tests for your changes to ensure that they work as expected. 21 | 5. Run the Tests 22 | 23 | 5. Commit Your Changes 24 | 25 | - Commit your changes with a clear and concise commit message: 26 | ```bash 27 | git commit -m "Add feature: your feature description" 28 | ``` 29 | 6. Push to Your Fork 30 | 31 | - Push your changes to your forked repository: 32 | ```bash 33 | git push origin feature/your-feature-name 34 | ``` 35 | 7. Create a Pull Request 36 | 37 | > Go to the original repository and create a pull request from your branch. Provide a detailed description of your changes and why they should be merged. 38 | 39 | > If you encounter any issues, please report them using the Issues section of the repository. Be sure to provide as much detail as possible. 40 | Thank You! 41 | 42 | > We appreciate your contributions and support in making this project better! -------------------------------------------------------------------------------- /chocolate_in/template.py: -------------------------------------------------------------------------------- 1 | executer = """ 2 | #!/bin/bash 3 | 4 | echo -e ' ▄▀▀ █▄█ ▄▀▄ ▄▀▀ ▄▀▄ █ ▄▀▄ ▀█▀ ██▀ 5 | ▀▄▄ █ █ ▀▄▀ ▀▄▄ ▀▄▀ █▄▄ █▀█ █ █▄▄' 6 | 7 | echo '------------------------------------------' 8 | 9 | set -e # Exit on error 10 | 11 | # 1) Check if python is installed 12 | if ! python3 --version >/dev/null 2>&1; then 13 | echo "❌ Python3 is not installed. Install it and try again." 14 | exit 1 15 | fi 16 | 17 | # 2) Create virtual environment 18 | echo "🔧 Creating virtual environment..." 19 | python3 -m venv venv 20 | 21 | # 3) Activate it and install requirements 22 | . venv/bin/activate 23 | 24 | if [ -f requirements.txt ]; then 25 | echo "📦 Installing dependencies from requirements.txt..." 26 | venv/bin/pip install --upgrade pip 27 | venv/bin/pip install -r requirements.txt 28 | else 29 | echo "⚠️ requirements.txt not found, skipping pip install." 30 | fi 31 | 32 | # 4) Load env variables from .env 33 | if [ -f .env ]; then 34 | echo "🌱 Loading environment variables from .env..." 35 | while IFS='=' read -r key value; do 36 | if [[ "$key" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]; then 37 | export "$key=$value" 38 | fi 39 | done < .env 40 | fi 41 | 42 | # Load flags from .flags 43 | FLAGS="" 44 | if [ -f .flags ]; then 45 | echo "🎯 Loading flags from .flags..." 46 | FLAGS=$(cat .flags) 47 | fi 48 | 49 | # Run main.py 50 | echo "🚀 Running main.py..." 51 | venv/bin/python {} $FLAGS 52 | """ 53 | 54 | 55 | hashfind = """ 56 | #!/bin/bash 57 | find . -type f ! -path "./venv/*" ! -path "./logs/*" | while read -r file; do 58 | hash=$(sha256sum "$file" | awk '{print $1}') 59 | relpath="${file#./}" # remove leading ./ for clean output 60 | echo "$relpath=$hash" 61 | done 62 | """ 63 | -------------------------------------------------------------------------------- /chocolate_in/log.py: -------------------------------------------------------------------------------- 1 | from rich.logging import RichHandler 2 | from rich.console import Console 3 | import logging 4 | from logging.handlers import TimedRotatingFileHandler 5 | import os 6 | import getpass 7 | import datetime 8 | from rich import print 9 | 10 | # Create a logs directory if it doesn't exist 11 | if not os.path.exists("log"): 12 | os.makedirs("log") 13 | 14 | # Fetch the username of the device 15 | username = getpass.getuser() 16 | 17 | # Configure logging 18 | 19 | 20 | class CallbackHandler(logging.Handler): 21 | def __init__(self, callback): 22 | super().__init__() 23 | self.callback = callback 24 | 25 | def emit(self, record): 26 | log_entry = self.format(record) 27 | self.callback(log_entry) 28 | 29 | 30 | def setup_logging(level=logging.INFO, print_callback=None): 31 | # Create a custom logger 32 | log_file = datetime.datetime.now().strftime("log/%y-%m-%d.log") 33 | logger = logging.getLogger(__name__) 34 | logger.setLevel(level) 35 | 36 | # Create a TimedRotatingFileHandler 37 | file_handler = TimedRotatingFileHandler( 38 | log_file, when="midnight", interval=1, backupCount=7 39 | ) 40 | file_handler.setLevel(level) 41 | 42 | # Create a formatter that includes the username for the file 43 | file_formatter = logging.Formatter(f"%(asctime)s %(levelname)s - %(message)s") 44 | file_handler.setFormatter(file_formatter) 45 | 46 | # Add the file handler to the logger 47 | logger.addHandler(file_handler) 48 | 49 | # Register the CallbackHandler if a print callback is provided 50 | if print_callback: 51 | callback_handler = CallbackHandler(print_callback) 52 | # Using RichHandler for enhanced terminal output 53 | rich_handler = RichHandler() 54 | logger.addHandler(rich_handler) 55 | 56 | return logger 57 | 58 | 59 | # Custom print function using Rich 60 | 61 | 62 | def custom_print_format(log_entry): 63 | console = Console() 64 | console.log(log_entry) # Using rich to print the log entry 65 | -------------------------------------------------------------------------------- /chocolate_in/path.py: -------------------------------------------------------------------------------- 1 | import os 2 | from os.path import isfile 3 | from typing import Union 4 | import json 5 | import mimetypes 6 | 7 | 8 | def is_binary(filename): 9 | mime_type, _ = mimetypes.guess_type(filename) 10 | return mime_type is None or mime_type.startswith("application/") 11 | 12 | 13 | class Path: 14 | def __init__(self, base: Union[None, str] = None) -> None: 15 | self.base = os.getcwd() if base is None else base 16 | 17 | def __setattr__(self, name: str, value: Union[str, bytes, dict]) -> None: 18 | if name == "base": # Prevent recursion 19 | super().__setattr__(name, value) 20 | return 21 | 22 | path = os.path.join(self.base, name) 23 | 24 | if isinstance(value, bytes): 25 | mode = "wb" 26 | elif isinstance(value, str): 27 | mode = "wt" 28 | elif isinstance(value, dict): 29 | mode = "wt" 30 | value = json.dumps(value, indent=2) 31 | else: 32 | raise ValueError(f"Unsupported type: {type(value)}") 33 | 34 | with open(path, mode) as fp: 35 | fp.write(value) 36 | 37 | def __setitem__(self, name: str, value: Union[str, bytes, dict]) -> None: 38 | self.__setattr__(name, value) 39 | 40 | def __getattr__(self, attr): 41 | path = os.path.join(self.base, attr) 42 | return Path(path) 43 | 44 | def __getitem__(self, item): 45 | return self.__getattr__(item) 46 | 47 | def __repr__(self) -> str: 48 | return f"" 49 | 50 | def __str__(self) -> str: 51 | return self.base 52 | 53 | def __neg__(self): 54 | if isfile(self.base): 55 | return json.load(open(self.base, "rb")) 56 | raise FileNotFoundError(f"File not found: {self.base}") 57 | 58 | def __invert__(self): 59 | if isfile(self.base): 60 | mode = "rb" if is_binary(self.base) else "rt" 61 | with open(self.base, mode) as fp: 62 | if self.base.endswith(".json"): 63 | return json.load(fp) 64 | return fp.read() 65 | raise FileNotFoundError(f"File not found: {self.base}") 66 | 67 | def __iter__(self): 68 | yield from os.listdir(self.base) 69 | -------------------------------------------------------------------------------- /chocolate_in/config.py: -------------------------------------------------------------------------------- 1 | from os.path import isfile 2 | from typing import Any 3 | from path import Path 4 | import json 5 | import os 6 | from rich import print 7 | import zipfile 8 | from log import setup_logging 9 | 10 | 11 | log = setup_logging() 12 | 13 | def normalize_path(path): 14 | """Normalize the path to a standard format.""" 15 | return path.rstrip("/") 16 | 17 | 18 | def create_zip(name, include, exclude): 19 | with zipfile.ZipFile(name, "w", zipfile.ZIP_DEFLATED) as zipf: 20 | for path in include: 21 | if not os.path.exists(path): 22 | print(f"Warning: {path} does not exist.") 23 | continue 24 | for root, dirs, files in os.walk(path): 25 | if any(excl in root for excl in exclude): 26 | dirs[:] = [] 27 | continue 28 | for file in files: 29 | file_path = os.path.join(root, file) 30 | zipf.write( 31 | file_path, 32 | os.path.relpath(file_path, os.path.commonpath(include)), 33 | ) 34 | 35 | 36 | def ensure_folder(path): 37 | if not os.path.exists(path): 38 | os.makedirs(path, exist_ok=True) 39 | elif os.path.isfile(path): 40 | raise (FileExistsError(f"Tried to make the folder ({path}) but file exists.")) 41 | 42 | 43 | def ensure_length(pkgs, mini, maxi): 44 | l = len(pkgs) 45 | if l < mini: 46 | print(f"[red]This function requires at least {mini} arguments.") 47 | quit(1) 48 | elif l > maxi: 49 | print(f"[red]This function requires at most {maxi} arguments.") 50 | quit(1) 51 | 52 | 53 | 54 | class JsonConfig: 55 | def __init__(self, name: str) -> None: 56 | self.name = name 57 | self.config = ~Path(name) 58 | if not isinstance(self.config, dict): 59 | self.config = json.loads(self.config) 60 | 61 | def __getitem__(self, items): 62 | if not isinstance(items, tuple): 63 | items = (items,) 64 | value = self.config 65 | for i in items: 66 | value = value[i] 67 | return value 68 | 69 | def commit(self): 70 | Path()[self.name] = self.config 71 | 72 | def __setitem__(self, key, value): 73 | log.info(f'CONFIG: Set {key} to {value}') 74 | self.config[key] = value 75 | self.commit() 76 | -------------------------------------------------------------------------------- /chocolate_in/help.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | // ANSI Escape Sequences for terminal styling 8 | const std::string RESET = "\033[0m"; 9 | const std::string BOLD = "\033[1m"; 10 | const std::string RED = "\033[31m"; 11 | const std::string YELLOW = "\033[33m"; 12 | const std::string BLUE = "\033[34m"; 13 | const std::string MAGENTA = "\033[35m"; 14 | const std::string UNDERLINE = "\033[4m"; 15 | 16 | // Function to replace markers with ANSI escape sequences 17 | std::string applyStyles(const std::string &line) { 18 | std::string result = line; 19 | 20 | // Replace [red] with red color 21 | result = std::regex_replace(result, std::regex(R"(\[red\])"), RED); 22 | result = std::regex_replace(result, std::regex(R"(\[/red\])"), RESET); 23 | result = 24 | std::regex_replace(result, std::regex(R"(\[underline\])"), UNDERLINE); 25 | result = std::regex_replace(result, std::regex(R"(\[/underline\])"), RESET); 26 | 27 | // Replace [bold] with bold text 28 | result = std::regex_replace(result, std::regex(R"(\[bold\])"), BOLD); 29 | result = std::regex_replace(result, std::regex(R"(\[/bold\])"), RESET); 30 | 31 | // Replace [yellow] with yellow color 32 | result = std::regex_replace(result, std::regex(R"(\[yellow\])"), YELLOW); 33 | result = std::regex_replace(result, std::regex(R"(\[/yellow\])"), RESET); 34 | 35 | // Replace [blue] with blue color 36 | result = std::regex_replace(result, std::regex(R"(\[blue\])"), BLUE); 37 | result = std::regex_replace(result, std::regex(R"(\[/blue\])"), RESET); 38 | 39 | // Replace [magenta] with magenta color 40 | result = std::regex_replace(result, std::regex(R"(\[magenta\])"), MAGENTA); 41 | result = std::regex_replace(result, std::regex(R"(\[/magenta\])"), RESET); 42 | 43 | // Replace text inside backticks with blue color 44 | result = std::regex_replace(result, std::regex(R"(`([^`]+)`)"), 45 | BLUE + "`$1`" + RESET); 46 | 47 | // Replace lines starting with - with  48 | result = std::regex_replace(result, std::regex(R"(^\s*- )"), " "); 49 | 50 | return result; 51 | } 52 | 53 | // Function to display the help content 54 | void displayHelp(const std::string &action) { 55 | std::ifstream file("/usr/share/doc/chocolate/" + action + ".txt"); 56 | 57 | if (!file.is_open()) { 58 | std::cerr << "Help file for '" << action << "' not found.\n"; 59 | return; 60 | } 61 | 62 | std::string line; 63 | while (std::getline(file, line)) { 64 | std::cout << applyStyles(line) << "\n"; 65 | } 66 | 67 | file.close(); 68 | } 69 | 70 | int main(int argc, char *argv[]) { 71 | if (argc != 2) { 72 | std::cerr << "Usage: " << argv[0] << " \n"; 73 | return 1; 74 | } 75 | 76 | std::string command = argv[1]; 77 | displayHelp(command); 78 | 79 | return 0; 80 | } 81 | -------------------------------------------------------------------------------- /DOCSfa.md: -------------------------------------------------------------------------------- 1 | # 🍫 مستندات مدیر پروژه Chocolate 2 | 3 | Chocolate یک مدیر پروژه سبک است که برای مدیریت محیط‌های مجازی، کنترل وابستگی‌ها و خودکارسازی وظایف پروژه طراحی شده است. 4 | 5 | ## 🎯 **استفاده** 6 | 7 | 8 | 9 | ```bash 10 | chocolate [گزینه‌ها] [آرگومان‌ها] 11 | ``` 12 | 13 | ## 📂 **ایجاد یک پروژه جدید** 14 | 15 | 16 | 17 | ```bash 18 | chocolate new
19 | ``` 20 | 21 | ## 🚀 **اجرا کردن پروژه** 22 | 23 | 24 | 25 | ```bash 26 | chocolate run 27 | ``` 28 | 29 | - فایل اصلی پروژه را درون محیط مجازی اجرا می‌کند. 30 | - به‌طور خودکار متغیرهای محیطی و پرچم‌ها را بارگذاری می‌کند. 31 | 32 | ### ✅ مثال: 33 | 34 | 35 | 36 | ```bash 37 | chocolate run 38 | ``` 39 | 40 | ### با پرچم `--reinstall` (اجبار به نصب مجدد وابستگی‌ها): 41 | 42 | 43 | 44 | ```bash 45 | chocolate run --reinstall 46 | ``` 47 | 48 | ## **💻 یکسان سازی با سرور** 49 | 50 | ```bash 51 | chocolate sync 52 | ``` 53 | 54 | ### تغییر مشخصات سرور: 55 | 56 | ```bash 57 | chocolate ssh 58 | ``` 59 | 60 | 61 | ## 📦 **مدیریت بسته‌ها** 62 | 63 | ### نصب بسته‌ها: 64 | 65 | 66 | 67 | ```bash 68 | chocolate add ... 69 | ``` 70 | 71 | - بسته‌ها را نصب و به پیکربندی `.chocolate` اضافه می‌کند. 72 | 73 | ### ✅ مثال: 74 | 75 | 76 | 77 | ```bash 78 | chocolate add rich requests flask 79 | ``` 80 | 81 | ### نصب مجدد تمام بسته‌ها: 82 | 83 | 84 | 85 | ```bash 86 | chocolate reinstall 87 | ``` 88 | 89 | ## 🌐 **مدیریت متغیرهای محیطی** 90 | 91 | ### فهرست تمام متغیرها: 92 | 93 | 94 | 95 | ```bash 96 | chocolate env list 97 | ``` 98 | 99 | ### اضافه کردن متغیرهای محیطی: 100 | 101 | 102 | 103 | ``` 104 | chocolate env VAR1=value VAR2=value 105 | ``` 106 | 107 | ### حذف متغیرها: 108 | 109 | 110 | 111 | ```bash 112 | chocolate env remove VAR1 VAR2 113 | ``` 114 | 115 | ### خصوصی/عمومی کردن متغیر: 116 | متغیر های خصوصی با دستور اکسپورت استخراج نمیشوند. 117 | 118 | 119 | ```bash 120 | chocolate env private VAR1 VAR2 121 | ``` 122 | 123 | ## 🏁 **مدیریت پرچم‌ها** 124 | 125 | 126 | 127 | ```bash 128 | chocolate flags 129 | ``` 130 | 131 | ### ✅ مثال: 132 | 133 | 134 | 135 | ```bash 136 | chocolate flags --debug --fast 137 | ``` 138 | 139 | ## 🛠️ **عملیات سفارشی** 140 | 141 | ### اضافه کردن یک عملیات جدید: 142 | 143 | 144 | ```bash 145 | chocolate action add 146 | ``` 147 | 148 | - Use `-i ` to insert multiple line bash. 149 | 150 | ### حذف یک عملیات: 151 | 152 | 153 | 154 | ```bash 155 | chocolate action remove <نام_عملیات> 156 | ``` 157 | 158 | ### اجرای یک عملیات: 159 | 160 | 161 | 162 | ```bash 163 | chocolate action <نام_عملیات> 164 | ``` 165 | 166 | ## 🛤️ **مدیریت مسیرها** 167 | 168 | ### استثنا کردن مسیرها: 169 | 170 | 171 | 172 | ```bash 173 | chocolate path exclude <مسیر1> <مسیر2> 174 | ``` 175 | 176 | ### شامل کردن مجدد مسیرها: 177 | 178 | 179 | 180 | ```bash 181 | chocolate path include <مسیر1> <مسیر2> 182 | ``` 183 | 184 | ### فهرست مسیرهای استثنا شده: 185 | 186 | 187 | 188 | ```bash 189 | chocolate path list 190 | ``` 191 | 192 | ## 📤 **اکسپورت پروژه** 193 | 194 | bash 195 | 196 | ``` 197 | chocolate export -o 198 | ``` 199 | 200 | ## 📝 **کمک** 201 | 202 | 203 | 204 | ```bash 205 | chocolate help 206 | ``` 207 | 208 | 209 | ## مشاهده فایل کانفیگ 210 | ```bash 211 | chocolate config 212 | ``` 213 | 214 | ## استفاده از sandbox 215 | با استفاده از سندباکس میتونید مموری/سی پی یو تایم/سی پی یو فریکوئنسی اپ اتون رو محدود کنید. 216 | این کامند رو برای اطلاعات بیشتر بزنید. 217 | ‍‍```bash 218 | chocolate sandbox 219 | ``` 220 | 221 | ## 🔥 **مثال‌ها** 222 | 223 | 224 | 225 | ```bash 226 | # ایجاد یک پروژه 227 | chocolate new -n my_project -m main.py 228 | 229 | # نصب بسته‌ها 230 | chocolate add requests rich flask 231 | 232 | # اجرای پروژه 233 | chocolate run 234 | 235 | # اضافه کردن متغیر محیطی 236 | chocolate env API_KEY=123456789 237 | 238 | # نصب مجدد تمام بسته‌ها 239 | chocolate reinstall 240 | 241 | # صادرات پروژه به صورت zip 242 | chocolate export -o my_project.zip 243 | ``` 244 | 245 | 246 | 247 | ## ✅ **نتیجه‌گیری** 248 | 249 | مدیر پروژه Chocolate برای مدیریت تمام مراحل از راه‌اندازی پروژه تا مدیریت وابستگی‌ها و خودکارسازی سفارشی طراحی شده است. -------------------------------------------------------------------------------- /DOCS.md: -------------------------------------------------------------------------------- 1 | # 🍫 Chocolate Project Manager Documentation 2 | 3 | Chocolate is a lightweight project manager designed for managing virtual environments, handling dependencies, and automating project tasks. 4 | 5 | 6 | 7 | ## 🎯 **Usage** 8 | 9 | ```bash 10 | chocolate [options] [arguments] 11 | ``` 12 | 13 | 14 | 15 | ## 📂 **Creating a New Project** 16 | 17 | ```bash 18 | chocolate new 19 | ``` 20 | 21 | 22 | 23 | ## 🚀 **Running the Project** 24 | 25 | ```bash 26 | chocolate run 27 | ``` 28 | 29 | - Runs the project's main file inside the virtual environment. 30 | - Automatically loads environment variables and flags. 31 | 32 | ### ✅ Example: 33 | 34 | ```bash 35 | chocolate run 36 | ``` 37 | 38 | ### With `--reinstall` flag (force reinstall dependencies): 39 | 40 | ```bash 41 | chocolate run --reinstall 42 | ``` 43 | 44 | ## 💻 **Sync with ssh server** 45 | 46 | ### Sync files: 47 | 48 | ```bash 49 | chocolate sync 50 | ``` 51 | 52 | ### Change SSH creds: 53 | 54 | ```bash 55 | chocolate ssh 56 | ``` 57 | 58 | 59 | ## 📦 **Managing Packages** 60 | 61 | ### Install Packages: 62 | 63 | ```bash 64 | chocolate add ... 65 | ``` 66 | 67 | - Installs and adds packages to the `.chocolate` configuration. 68 | 69 | ### ✅ Example: 70 | 71 | ```bash 72 | chocolate add rich requests flask 73 | ``` 74 | 75 | ### Reinstall All Packages: 76 | 77 | ```bash 78 | chocolate reinstall 79 | ``` 80 | 81 | 82 | 83 | ## 🌐 **Managing Environment Variables** 84 | 85 | ### List All Variables: 86 | 87 | ```bash 88 | chocolate env list 89 | ``` 90 | 91 | ### Add Environment Variables: 92 | 93 | ```bash 94 | chocolate env VAR1=value VAR2=value 95 | ``` 96 | 97 | ### Remove Variables: 98 | 99 | ```bash 100 | chocolate env remove VAR1 VAR2 101 | ``` 102 | 103 | ### Make Variable Private/Public: 104 | 105 | ```bash 106 | chocolate env private VAR1 VAR2 107 | ``` 108 | 109 | 110 | 111 | ## 🏁 **Managing Flags** 112 | 113 | ```bash 114 | chocolate flags 115 | ``` 116 | 117 | ### ✅ Example: 118 | 119 | ```bash 120 | chocolate flags --debug --fast 121 | ``` 122 | 123 | 124 | 125 | ## 🛠️ **Custom Actions** 126 | 127 | ### Add a New Action: 128 | 129 | ```bash 130 | chocolate action add 131 | ``` 132 | 133 | - Use -i to add multiple line actions. 134 | 135 | ### Remove an Action: 136 | 137 | ```bash 138 | chocolate action remove 139 | ``` 140 | 141 | ### Run an Action: 142 | 143 | ```bash 144 | chocolate action 145 | ``` 146 | 147 | 148 | 149 | ## 🛤️ **Handling Paths** 150 | 151 | ### Exclude Paths: 152 | 153 | ```bash 154 | chocolate path exclude 155 | ``` 156 | 157 | ### Include Paths Back: 158 | 159 | ```bash 160 | chocolate path include 161 | ``` 162 | 163 | ### List Excluded Paths: 164 | 165 | ```bash 166 | chocolate path list 167 | ``` 168 | 169 | 170 | 171 | ## 📤 **Export the Project** 172 | 173 | ```bash 174 | chocolate export -o 175 | ``` 176 | 177 | 178 | 179 | ## 📝 **Help** 180 | 181 | ```bash 182 | chocolate help 183 | ``` 184 | ## Printing the config file 185 | ```bash 186 | chocolate config 187 | ``` 188 | ## Using sandbox 189 | You can use `chocolate sandbox` to limit the memory usage/ cpu time/ cpu freq of the project. 190 | > Note that manual error handling for these in the code is required, For example you should handle the memoryError for high memory usage 191 | ```bash 192 | chocolate sandbox 193 | ``` 194 | You can use `-1` as unlimited. 195 | 196 | 197 | ## 🔥 **Examples** 198 | 199 | ```bash 200 | # Create a project 201 | chocolate new -n my_project -m main.py 202 | 203 | # Install packages 204 | chocolate add requests rich flask 205 | 206 | # Run the project 207 | chocolate run 208 | 209 | # Add environment variable 210 | chocolate env API_KEY=123456789 211 | 212 | # Reinstall all packages 213 | chocolate reinstall 214 | 215 | # Export the project as a zip 216 | chocolate export -o my_project.zip 217 | ``` 218 | 219 | 220 | ## ✅ **Conclusion** 221 | 222 | Chocolate Project Manager is designed to handle everything from project initialization to dependency management and custom automation. 223 | -------------------------------------------------------------------------------- /chocolate_in/sandbox.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define STACK_SIZE (1024 * 1024) 13 | 14 | // Colors 15 | #define RESET "\033[0m" 16 | #define RED "\033[31m" 17 | #define GREEN "\033[32m" 18 | #define YELLOW "\033[33m" 19 | #define BLUE "\033[34m" 20 | #define MAGENTA "\033[35m" 21 | 22 | void set_resource_limits(int max_memory, int max_cpu) { 23 | if (max_memory != -1) { 24 | struct rlimit mem_limit; 25 | mem_limit.rlim_cur = mem_limit.rlim_max = 26 | max_memory * 1024 * 1024; // In bytes 27 | setrlimit(RLIMIT_AS, &mem_limit); 28 | std::cout << GREEN << "Memory limit set to " << max_memory << "MB" << RESET 29 | << std::endl; 30 | } 31 | 32 | if (max_cpu != -1) { 33 | struct rlimit cpu_limit; 34 | cpu_limit.rlim_cur = cpu_limit.rlim_max = max_cpu; 35 | setrlimit(RLIMIT_CPU, &cpu_limit); 36 | std::cout << GREEN << "CPU time limit set to " << max_cpu << " seconds" 37 | << RESET << std::endl; 38 | } 39 | } 40 | 41 | void set_cpu_freq(int freq) { 42 | if (freq == -1) 43 | return; 44 | 45 | std::ofstream cpu_min( 46 | "/sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq"); // check for cpu 47 | // min freq 48 | std::ofstream cpu_max( 49 | "/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq"); // check for cpu 50 | // max freq 51 | 52 | if (cpu_min.is_open() && cpu_max.is_open()) { 53 | cpu_min << freq * 1000; // Convert to kHz 54 | cpu_max << freq * 1000; 55 | std::cout << BLUE << "CPU frequency set to " << freq << " MHz" << RESET 56 | << std::endl; 57 | } else { 58 | std::cerr << RED << "Failed to set CPU frequency" << RESET << std::endl; 59 | exit(1); 60 | } 61 | } 62 | 63 | int child(void *arg) { 64 | char **args = static_cast(arg); 65 | std::string command = args[0]; 66 | int max_memory = std::stoi(args[1]); 67 | int max_cpu = std::stoi(args[2]); 68 | int cpu_freq = std::stoi(args[3]); 69 | 70 | // Set limits 71 | std::cout << YELLOW << "Setting resource limits..." << RESET << std::endl; 72 | set_resource_limits(max_memory, max_cpu); 73 | set_cpu_freq(cpu_freq); 74 | 75 | // Mount filesystem for process isolation 76 | std::cout << YELLOW << "Mounting filesystem for process isolation..." << RESET 77 | << std::endl; 78 | mount("none", "/", NULL, MS_REC | MS_PRIVATE, NULL); 79 | 80 | // Run command 81 | std::cout << MAGENTA << "Executing command: " << command << RESET 82 | << std::endl; 83 | if (system(command.c_str()) < 0) { 84 | std::cerr << RED << "Failed to execute command" << RESET << std::endl; 85 | exit(1); 86 | } 87 | 88 | return 0; 89 | } 90 | 91 | int main(int argc, char *argv[]) { 92 | if (argc < 5) { 93 | std::cerr << RED 94 | << "Usage: ./sandbox " 95 | "" 96 | << RESET << std::endl; 97 | return 1; 98 | } 99 | 100 | // Allocate stack 101 | std::cout << YELLOW << "Allocating stack for child process..." << RESET 102 | << std::endl; 103 | char *stack = static_cast(malloc(STACK_SIZE)); 104 | if (!stack) { 105 | std::cerr << RED << "Memory allocation failed" << RESET << std::endl; 106 | return 1; 107 | } 108 | 109 | // Clone process 110 | std::cout << YELLOW << "Cloning process..." << RESET << std::endl; 111 | pid_t pid = clone(child, stack + STACK_SIZE, 112 | CLONE_NEWPID | CLONE_NEWNS | SIGCHLD, argv + 1); 113 | 114 | if (pid < 0) { 115 | std::cerr << RED << "Clone failed" << RESET << std::endl; 116 | free(stack); 117 | return 1; 118 | } 119 | 120 | int status; 121 | waitpid(pid, &status, 0); 122 | 123 | if (WIFEXITED(status)) { 124 | std::cout << GREEN 125 | << "Child process exited with status: " << WEXITSTATUS(status) 126 | << RESET << "\n"; 127 | } else if (WIFSIGNALED(status)) { 128 | std::cout << RED 129 | << "Child process was killed by signal: " << WTERMSIG(status) 130 | << RESET << "\n"; 131 | } 132 | 133 | free(stack); 134 | return 0; 135 | } 136 | -------------------------------------------------------------------------------- /chocolate_in/tests/main.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from unittest.mock import patch, MagicMock 3 | from .main import ( 4 | convert_dict_to_table, 5 | ensure_env_variables, 6 | get_project_config, 7 | handle_new_project, 8 | run, 9 | handle_package_installation, 10 | handle_reinstall, 11 | handle_env_action, 12 | handle_flags, 13 | custom_action, 14 | handle_path, 15 | handle_remove, 16 | handle_config, 17 | export, 18 | ) 19 | 20 | 21 | @pytest.fixture 22 | def mock_project_config(): 23 | return { 24 | "startupEnv": ["VAR1", "VAR2"], 25 | "environmentVariables": {}, 26 | "flagsString": "", 27 | "mainFile": "main.py", 28 | "requirements": [], 29 | "exclude": [], 30 | } 31 | 32 | 33 | def test_convert_dict_to_table(): 34 | data = {"key1": "value1", "key2": "value2"} 35 | table = convert_dict_to_table(data) 36 | assert table.row_count == 2 37 | assert table.columns[0].header == "key1" 38 | assert table.columns[1].header == "value1" 39 | 40 | 41 | @patch("your_script.log") 42 | def test_ensure_env_variables(mock_log, mock_project_config): 43 | with patch("your_script.get_project_config", return_value=mock_project_config): 44 | with patch("builtins.input", side_effect=["value1", "value2"]): 45 | ensure_env_variables() 46 | assert mock_project_config["environmentVariables"] == { 47 | "VAR1": "value1", 48 | "VAR2": "value2", 49 | } 50 | 51 | 52 | @patch("your_script.log") 53 | @patch("your_script.prj.get_config", return_value=None) 54 | def test_get_project_config(mock_get_config, mock_log): 55 | with pytest.raises(SystemExit): 56 | get_project_config() 57 | 58 | 59 | @patch("your_script.prj.setup_project") 60 | @patch("your_script.log") 61 | def test_handle_new_project(mock_log, mock_setup_project): 62 | class Args: 63 | pkgs = ["my_project", "main.py"] 64 | 65 | handle_new_project(Args()) 66 | mock_setup_project.assert_called_once_with("my_project", "main.py") 67 | 68 | 69 | @patch("your_script.log") 70 | @patch("your_script.get_project_config") 71 | def test_run(mock_get_project_config, mock_log): 72 | mock_get_project_config.return_value = { 73 | "environmentVariables": {}, 74 | "mainFile": "main.py", 75 | "flagsString": "", 76 | } 77 | with patch("your_script.ensure_env_variables"), patch( 78 | "your_script.prj.VenvManager" 79 | ), patch("your_script.console.print"): 80 | run(Args(reinstall=False)) 81 | 82 | 83 | @patch("your_script.log") 84 | @patch("your_script.prj.VenvManager") 85 | def test_handle_package_installation(mock_venv, mock_log): 86 | mock_venv.return_value.install = MagicMock() 87 | packages = ["package1", "package2"] 88 | handle_package_installation(packages) 89 | assert mock_venv.return_value.install.call_count == 2 90 | 91 | 92 | @patch("your_script.log") 93 | @patch("your_script.get_project_config") 94 | def test_handle_reinstall(mock_get_project_config, mock_log): 95 | mock_get_project_config.return_value = {"requirements": ["package1", "package2"]} 96 | with patch("your_script.prj.VenvManager"): 97 | handle_reinstall() 98 | 99 | 100 | @patch("your_script.log") 101 | def test_handle_env_action(mock_log): 102 | class Args: 103 | pkgs = ["list"] 104 | 105 | with patch("your_script.get_project_config", return_value=mock_project_config): 106 | handle_env_action(Args()) 107 | assert mock_log.info.call_count > 0 108 | 109 | 110 | @patch("your_script.log") 111 | def test_handle_flags(mock_log): 112 | class Args: 113 | pkgs = ["--debug", "--verbose"] 114 | 115 | mock_config = {"flagsString": ""} 116 | with patch( 117 | "your_script.get_project_config", return_value=MagicMock(config=mock_config) 118 | ): 119 | handle_flags(Args()) 120 | assert mock_config["flagsString"] == " ".join(Args.pkgs) 121 | 122 | 123 | @patch("your_script.log") 124 | def test_custom_action(mock_log): 125 | class Args: 126 | pkgs = ["add", "action_name", "command"] 127 | 128 | mock_project = MagicMock(config={"actions": {}}) 129 | with patch("your_script.get_project_config", return_value=mock_project): 130 | custom_action(Args()) 131 | assert "action_name" in mock_project.config["actions"] 132 | 133 | 134 | @patch("your_script.log") 135 | def test_handle_remove(mock_log): 136 | with patch("os.remove") as mock_remove: 137 | handle_remove(MagicMock()) 138 | mock_remove.assert_called_once_with("CONFIG") 139 | 140 | 141 | @patch("your_script.log") 142 | def test_handle_config(mock_log): 143 | mock_project = MagicMock(config={"key": "value"}) 144 | with patch("your_script.get_project_config", return_value=mock_project): 145 | handle_config(MagicMock()) 146 | assert mock_log.info.call_count > 0 147 | 148 | 149 | @patch("your_script.log") 150 | def test_export(mock_log): 151 | class Args: 152 | output = "output.zip" 153 | 154 | mock_project = MagicMock(config={"name": "project_name", "exclude": []}) 155 | with patch("your_script.get_project_config", return_value=mock_project): 156 | with patch("your_script.create_zip"): 157 | export(Args()) 158 | assert mock_log.info.call_count > 0 159 | -------------------------------------------------------------------------------- /chocolate_in/project_manager.py: -------------------------------------------------------------------------------- 1 | import time 2 | from config import JsonConfig 3 | from path import Path 4 | import ast 5 | import os 6 | import venv 7 | import sys 8 | import subprocess 9 | 10 | CONFIG = "chocolate.json" 11 | p = Path() 12 | 13 | 14 | def get_config(): 15 | """Receiving config from the path""" 16 | if CONFIG in p: 17 | return JsonConfig(CONFIG) 18 | return False 19 | 20 | 21 | def setup_project(name, start): 22 | """Setting up the project""" 23 | p[CONFIG] = { 24 | "info": {"fork": "native", "createdUnix": round(time.time()), "name": name}, 25 | "requirements": list(), 26 | "startupEnv": list(), 27 | "privateEnv": list(), 28 | "exclude": list(), 29 | "mainFile": start, 30 | "flagsString": "", 31 | "environmentVariables": dict(), 32 | "actionsScript": dict(), 33 | "sshHost": None, 34 | "sshUsername": None, 35 | "sshPassword": None, 36 | "sshPort": None, 37 | } 38 | if not os.path.exists(start): 39 | with open(start, "+wt", encoding="utf-8") as fp: 40 | fp.write('print("Hello, Chocolate!")') 41 | 42 | 43 | class VenvManager: 44 | def __init__(self, venv_dir="venv"): 45 | self.venv_dir = venv_dir 46 | self.venv_python = ( 47 | os.path.join(venv_dir, "bin", "python") 48 | if os.name != "nt" 49 | else os.path.join(venv_dir, "Scripts", "python.exe") 50 | ) 51 | 52 | # Create the virtual environment if it doesn't exist 53 | if not os.path.exists(venv_dir): 54 | print(f"Creating virtual environment at {venv_dir}...") 55 | venv.create(venv_dir, with_pip=True) 56 | 57 | def install(self, package_name): 58 | process = subprocess.Popen( 59 | [self.venv_python, "-u", "-m", "pip", "install", package_name], 60 | stdout=subprocess.PIPE, 61 | stderr=subprocess.STDOUT, 62 | text=True, 63 | bufsize=1, # Line buffered 64 | ) 65 | if process.stdout: 66 | for line in iter(process.stdout.readline, ""): 67 | yield line.strip() 68 | elif process.stderr: 69 | for line in iter(process.stderr.readline, ""): 70 | yield line.strip() 71 | process.wait() 72 | if process.returncode != 0: 73 | raise subprocess.CalledProcessError(process.returncode, process.args) 74 | 75 | def run(self, file_name, flags="", env={}): 76 | """Run a script inside the virtual environment with optional flags.""" 77 | command = [self.venv_python, file_name] + flags.split() 78 | return subprocess.run(command, check=True, env=env).returncode 79 | 80 | def run_sandbox(self, file_name, flags="", env={}, memory=-1, cpu_time=-1, freq=-1): 81 | command = [self.venv_python, file_name] + flags.split() 82 | command = " ".join(command) 83 | command = ["sudo", "choco-sandbox", command, memory, cpu_time, freq] 84 | return subprocess.run(command, check=True, env=env).returncode 85 | 86 | 87 | def find_python_files(directory): 88 | """Recursively find all Python files in a directory.""" 89 | python_files = [] 90 | for root, dirs, files in os.walk(directory): 91 | for file in files: 92 | if file.endswith(".py"): 93 | python_files.append(os.path.join(root, file)) 94 | return python_files 95 | 96 | 97 | def extract_imports(file_path): 98 | """Extract imports from a Python file.""" 99 | with open(file_path, "r", encoding="utf-8") as file: 100 | try: 101 | tree = ast.parse(file.read()) 102 | except SyntaxError as e: 103 | print(f"Syntax error in {file_path}: {e}") 104 | return [] 105 | 106 | imports = [] 107 | for node in ast.walk(tree): 108 | if isinstance(node, ast.Import): 109 | for alias in node.names: 110 | imports.append(alias.name) 111 | elif isinstance(node, ast.ImportFrom): 112 | imports.append(node.module) 113 | 114 | return imports 115 | 116 | 117 | def collect_imports(directory): 118 | """Collect all unique imports from Python files in a directory.""" 119 | python_files = find_python_files(directory) 120 | all_imports = set() 121 | 122 | for file_path in python_files: 123 | imports = extract_imports(file_path) 124 | all_imports.update(imports) 125 | 126 | return all_imports 127 | 128 | 129 | def get_builtin_libraries(): 130 | """Return a set of built-in libraries.""" 131 | return set(sys.builtin_module_names) 132 | 133 | 134 | def export_non_builtin_imports(imports, output_file): 135 | """Export only non-builtin imports to a file, filtering out None values.""" 136 | # Get the set of built-in libraries 137 | builtin_libraries = get_builtin_libraries() 138 | 139 | # Filter out built-in libraries and None values from the imports 140 | non_builtin_imports = { 141 | imp for imp in imports if imp not in builtin_libraries and imp is not None 142 | } 143 | 144 | # Write the remaining non-builtin imports to the file 145 | with open(output_file, "w", encoding="utf-8") as file: 146 | for imp in sorted(non_builtin_imports): 147 | file.write(f"{imp}\n") 148 | 149 | 150 | def exporter(directory, output_file): 151 | imports = collect_imports(directory) 152 | export_non_builtin_imports(imports, output_file) 153 | print(f"Exported non-builtin imports to {output_file}") 154 | -------------------------------------------------------------------------------- /chocolate_in/sftp.py: -------------------------------------------------------------------------------- 1 | import os 2 | import paramiko 3 | from log import setup_logging 4 | from project_manager import get_config 5 | import hashlib 6 | 7 | logging = setup_logging() 8 | 9 | 10 | def get_file_hash(path): 11 | """Return the SHA-256 hash of the file at the given path.""" 12 | sha256 = hashlib.sha256() 13 | try: 14 | logging.debug(f"Opening file to compute hash: {path}") 15 | with open(path, "rb") as f: 16 | for chunk in iter(lambda: f.read(8192), b""): 17 | sha256.update(chunk) 18 | hash_value = sha256.hexdigest() 19 | logging.debug(f"Computed SHA-256 for {path}: {hash_value}") 20 | return hash_value 21 | except FileNotFoundError: 22 | logging.error(f"File not found for hashing: {path}") 23 | return None 24 | except Exception as e: 25 | logging.error(f"Error hashing file {path}: {e}") 26 | return None 27 | 28 | 29 | def ensure_config(): 30 | logging.info("Fetching project configuration.") 31 | if not (a := get_config()): 32 | logging.critical("No chocolate project found in this route.") 33 | quit(1) 34 | logging.info("Project configuration fetched successfully.") 35 | return a 36 | 37 | 38 | class Sftp: 39 | def __init__(self, ip, username, password, port) -> None: 40 | logging.info(f"Initializing SFTP connection to {ip}:{port} as {username}") 41 | self.ssh = paramiko.SSHClient() 42 | self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 43 | try: 44 | self.ssh.connect(ip, username=username, password=password, port=port) 45 | logging.info("SSH connection established.") 46 | self.sftp = self.ssh.open_sftp() 47 | logging.info("SFTP session opened.") 48 | except Exception as e: 49 | logging.critical(f"Failed to connect via SSH: {e}") 50 | raise 51 | 52 | def upload(self, file, dest): 53 | logging.info(f"Preparing to upload {file} to {dest}") 54 | try: 55 | self._mkdir_remote(os.path.dirname(dest)) 56 | self.sftp.put(file, dest) 57 | logging.info(f"Uploaded {file} to {dest}") 58 | except Exception as e: 59 | logging.error(f"Upload failed for {file} to {dest}: {e}") 60 | 61 | def run(self, name): 62 | cmd = f"cd ~/{name} && bash run.sh" 63 | logging.info(f"Executing remote command: {cmd}") 64 | try: 65 | channel = self.ssh.get_transport() 66 | if not channel: 67 | logging.error("SSH transport is not available.") 68 | return 69 | channel = channel.open_session() 70 | channel.exec_command(cmd) 71 | 72 | while True: 73 | if channel.recv_ready(): 74 | output = channel.recv(1024).decode() 75 | for line in output.splitlines(): 76 | yield line 77 | 78 | if channel.recv_stderr_ready(): 79 | error_output = channel.recv_stderr(1024).decode() 80 | for line in error_output.splitlines(): 81 | yield line 82 | 83 | if channel.exit_status_ready(): 84 | break 85 | logging.info("Remote script execution completed.") 86 | except Exception as e: 87 | logging.error(f"Failed to execute remote command: {e}") 88 | 89 | def get_hashes(self, name): 90 | cmd = f"cd ~/{name} && bash hash.sh" 91 | logging.info(f"Fetching hashes from remote with command: {cmd}") 92 | try: 93 | stdin, stdout, stderr = self.ssh.exec_command(cmd) 94 | stdout = stdout.read().decode().split("\n") 95 | err = stderr.read().decode() 96 | if err: 97 | logging.warning(f"Remote stderr: {err}") 98 | 99 | res = {} 100 | for i in stdout: 101 | i = i.split("=", 1) 102 | if len(i) == 2: 103 | logging.debug(f"Received hash: {i[0]} = {i[1]}") 104 | res[i[0]] = i[1] 105 | logging.info(f"Fetched {len(res)} file hashes from server.") 106 | return res 107 | except Exception as e: 108 | logging.error(f"Error fetching remote hashes: {e}") 109 | return {} 110 | 111 | def exec(self, cmd): 112 | logging.info(f"Executing remote command: {cmd}") 113 | try: 114 | channel = self.ssh.get_transport() 115 | if not channel: 116 | logging.error("SSH transport is not available.") 117 | return 118 | channel = channel.open_session() 119 | channel.exec_command(cmd) 120 | 121 | while True: 122 | if channel.recv_ready(): 123 | output = channel.recv(1024).decode() 124 | for line in output.splitlines(): 125 | yield line 126 | 127 | if channel.recv_stderr_ready(): 128 | error_output = channel.recv_stderr(1024).decode() 129 | for line in error_output.splitlines(): 130 | yield line 131 | 132 | if channel.exit_status_ready(): 133 | break 134 | logging.info("Remote script execution completed.") 135 | except Exception as e: 136 | logging.error(f"Failed to execute remote command: {e}") 137 | 138 | 139 | def _mkdir_remote(self, path): 140 | path = path.replace("\\", "/") 141 | cmd = f'mkdir -p "{path}"' 142 | logging.debug(f"Creating remote directory: {path}") 143 | try: 144 | stdin, stdout, stderr = self.ssh.exec_command(cmd) 145 | err = stderr.read().decode() 146 | if err: 147 | logging.warning(f"Remote mkdir stderr: {err}") 148 | except Exception as e: 149 | logging.error(f"Failed to mkdir {path}: {e}") 150 | 151 | def sync(self, dest_folder, not_sync=[]): 152 | logging.info(f"Syncing current directory to {dest_folder}") 153 | logging.info("Getting hashes from server.") 154 | hashes = self.get_hashes(dest_folder) 155 | 156 | for root, dirs, files in os.walk("."): 157 | logging.debug(f"Walking through directory: {root}") 158 | dirs[:] = [d for d in dirs if d not in ("log", "venv", *not_sync)] 159 | for filename in files: 160 | local_path = os.path.join(root, filename) 161 | rel_path = os.path.relpath(local_path, ".") 162 | remote_path = os.path.join(dest_folder, rel_path).replace("\\", "/") 163 | local_hash = get_file_hash(local_path) 164 | remote_hash = hashes.get(rel_path) 165 | 166 | if remote_hash and local_hash == remote_hash: 167 | logging.info(f"{rel_path} is not changed. skipping...") 168 | continue 169 | 170 | logging.info(f"{rel_path} changed or new. uploading...") 171 | try: 172 | self._mkdir_remote(os.path.dirname(remote_path)) 173 | self.sftp.put(local_path, remote_path) 174 | logging.info(f"Uploaded {rel_path} -> {remote_path}") 175 | except Exception as e: 176 | logging.error(f"Failed to upload {rel_path}: {e}") 177 | logging.info("Sync completed.") 178 | 179 | def close(self): 180 | logging.info("Closing SFTP and SSH connections.") 181 | try: 182 | self.sftp.close() 183 | self.ssh.close() 184 | logging.info("Connections closed.") 185 | except Exception as e: 186 | logging.warning(f"Error during closing connections: {e}") 187 | -------------------------------------------------------------------------------- /chocolate_in/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import argparse 3 | import sys 4 | from paramiko import sftp 5 | from rich.console import Console 6 | from rich.table import Table 7 | from rich.markdown import Markdown 8 | from rich.live import Live 9 | from rich.panel import Panel 10 | from rich.pretty import Pretty 11 | from log import setup_logging, custom_print_format 12 | from path import Path 13 | from config import create_zip 14 | from help import ensure_help, short_help 15 | import project_manager as prj 16 | from project_manager import CONFIG 17 | from sftp import Sftp 18 | from template import executer, hashfind 19 | 20 | console = Console() 21 | path = Path() 22 | log = setup_logging(print_callback=custom_print_format) 23 | 24 | 25 | def convert_dict_to_table(data): 26 | """ 27 | Convert a dictionary to a Rich table. 28 | 29 | Args: 30 | data (dict): The dictionary to convert. 31 | 32 | Returns: 33 | Table: A Rich Table object representing the dictionary. 34 | """ 35 | table = Table(show_header=False) 36 | for key, value in data.items(): 37 | table.add_row(str(key), str(value)) 38 | return table 39 | 40 | 41 | def ensure_env_variables(): 42 | """ 43 | Ensure that all required environment variables are set. 44 | 45 | Prompts the user for input if any variables are missing from the project configuration. 46 | """ 47 | log.info("Starting to ensure environment variables.") 48 | project = get_project_config() 49 | for key in project["startupEnv"]: 50 | if key not in project.config["environmentVariables"]: 51 | log.info("Requesting input for missing environment variable: %s", key) 52 | result = input().strip() 53 | project.config["environmentVariables"][key] = result 54 | project.commit() 55 | log.info("Environment variables have been ensured.") 56 | 57 | 58 | def get_project_config(): 59 | """ 60 | Fetch the current project configuration. 61 | 62 | Returns: 63 | dict: The project configuration. 64 | 65 | Raises: 66 | SystemExit: If no project is found. 67 | """ 68 | log.info("Fetching project configuration.") 69 | project = prj.get_config() 70 | if not project: 71 | log.critical("No project found. Please create a project first.") 72 | quit(1) 73 | log.info("Project configuration fetched successfully.") 74 | return project 75 | 76 | 77 | def handle_new_project(args): 78 | """ 79 | Handle the creation of a new project. 80 | 81 | Args: 82 | args (argparse.Namespace): The command line arguments. 83 | """ 84 | log.info("Attempting to create a new project.") 85 | if prj.get_config(): 86 | log.critical("Project already exists.") 87 | log.critical( 88 | "You can't create a new project because .chocolate already exists." 89 | ) 90 | quit(1) 91 | if len(args.pkgs) != 2: 92 | log.critical("Missing required arguments: 'main' or 'name'.") 93 | quit(1) 94 | 95 | log.info( 96 | "Creating a new project with name: %s and main file: %s.", 97 | args.pkgs[0], 98 | args.pkgs[1], 99 | ) 100 | prj.setup_project(args.pkgs[0], args.pkgs[1]) 101 | log.info("New project established successfully.") 102 | log.info("Project created successfully. Use `chocolate run` to start.") 103 | 104 | 105 | def run(args): 106 | """ 107 | Execute the main run process of the project. 108 | 109 | Args: 110 | args (argparse.Namespace): The command line arguments. 111 | """ 112 | log.info("Starting the run process.") 113 | ensure_env_variables() 114 | project = get_project_config() 115 | log.info("Running the startfile.") 116 | if args.reinstall: 117 | log.info("Reinstall flag detected, proceeding with reinstallation.") 118 | handle_reinstall() 119 | env = project["environmentVariables"] 120 | flags = project["flagsString"] 121 | venv = prj.VenvManager() 122 | try: 123 | log.info("Running the project startfile: %s.", project["mainFile"]) 124 | console.print(Markdown("---")) 125 | venv.run(project["mainFile"], flags, env) 126 | console.print(Markdown("---")) 127 | log.info("Process finished.") 128 | 129 | except Exception as e: 130 | log.critical("Project execution failed. Error: %s", e) 131 | 132 | 133 | def handle_package_installation(packages): 134 | """ 135 | Handle the installation of packages. 136 | 137 | Args: 138 | packages (list): List of packages to install. 139 | """ 140 | log.info("Starting the package installation process for packages: %s.", packages) 141 | project = get_project_config() 142 | venv = prj.VenvManager() 143 | res = Panel("", title="Output") 144 | with Live(res, refresh_per_second=4) as live: 145 | val = "" 146 | for pkg in packages: 147 | try: 148 | for i in venv.install(pkg): 149 | val += i + "\n" 150 | res.renderable = val 151 | except Exception as err: 152 | log.error("Problem while installing package %s: %s", pkg, err) 153 | else: 154 | log.info("Package %s installed successfully.", pkg) 155 | if pkg not in project.config["requirements"]: 156 | log.info("New package added: %s.", pkg) 157 | project.config["requirements"].append(pkg) 158 | 159 | path[CONFIG] = project.config 160 | log.info("All packages installed successfully.") 161 | 162 | 163 | def handle_env_action(args): 164 | """ 165 | Handle actions related to environment variables. 166 | 167 | Args: 168 | args (argparse.Namespace): The command line arguments. 169 | """ 170 | log.info("Handling environment action: %s.", args.pkgs[0]) 171 | try: 172 | project = get_project_config() 173 | raw_project = project.config 174 | if args.pkgs[0] == "list": 175 | log.info("Listing all environment variables.") 176 | log.info(convert_dict_to_table(project["environmentVariables"])) 177 | elif args.pkgs[0] == "remove": 178 | log.info("Removing environment keys.") 179 | for key in args.pkgs[1:]: 180 | log.info("Removing key from environment: %s.", key) 181 | raw_project["environmentVariables"].pop(key, None) 182 | path[CONFIG] = raw_project 183 | elif args.pkgs[0] == "private": 184 | for key in args.pkgs[1:]: 185 | if key in raw_project["privateEnv"]: 186 | log.info("Making key public: %s.", key) 187 | raw_project["private_env"].remove(key) 188 | else: 189 | log.info("Making key private: %s.", key) 190 | raw_project["privateEnv"].append(key) 191 | path[CONFIG] = raw_project 192 | else: 193 | log.info("Adding environment variables.") 194 | for env_var in args.pkgs: 195 | key, value = env_var.split("=", 1) 196 | if key in ["list", "remove"]: 197 | log.critical("Invalid environment key name: %s.", key) 198 | quit(1) 199 | raw_project["environmentVariables"][key] = value 200 | log.info("Added/edited environment variable: %s.", key) 201 | path[CONFIG] = raw_project 202 | log.info("All environment actions have been completed.") 203 | except Exception as e: 204 | log.critical("Error handling environment action: %s", e) 205 | 206 | 207 | def handle_flags(args): 208 | """ 209 | Handle updates to project flags. 210 | 211 | Args: 212 | args (argparse.Namespace): The command line arguments. 213 | """ 214 | log.info("Handling flags update with values: %s.", args.pkgs) 215 | try: 216 | raw_project = get_project_config().config 217 | raw_project["flagsString"] = " ".join(args.pkgs) 218 | log.info("Flags have been updated.") 219 | path[CONFIG] = raw_project 220 | except Exception as e: 221 | log.critical("Error handling flags update: %s", e) 222 | 223 | 224 | def custom_action(args): 225 | """ 226 | Handle custom actions defined in the project. 227 | 228 | Args: 229 | args (argparse.Namespace): The command line arguments. 230 | """ 231 | log.info("Processing custom action: %s.", args.pkgs[0]) 232 | try: 233 | project = get_project_config() 234 | if args.pkgs[0] == "add": 235 | log.info("Adding a custom action.") 236 | if args.input: 237 | args.pkgs.append(open(args.input).read()) 238 | project.config["actionsScript"][args.pkgs[1]] = args.pkgs[2] 239 | path[CONFIG] = project.config 240 | elif args.pkgs[0] == "remove": 241 | log.info("Removing a custom action.") 242 | project.config["actionsScript"].pop(args.pkgs[1]) 243 | path[CONFIG] = project.config 244 | else: 245 | log.info("Executing custom actions: %s.", args.pkgs) 246 | for i in args.pkgs: 247 | i = project["actionsScript"][i].split("\n") 248 | for j in i: 249 | os.system(j) 250 | except Exception as e: 251 | log.critical("Error executing custom action: %s", e) 252 | 253 | 254 | def handle_path(args): 255 | """ 256 | Handle actions related to project paths. 257 | 258 | Args: 259 | args (argparse.Namespace): The command line arguments. 260 | """ 261 | try: 262 | log.info("Handling path action: %s.", args.pkgs[0]) 263 | project = get_project_config() 264 | if args.pkgs[0] == "exclude": 265 | for path in args.pkgs[1:]: 266 | log.info("Excluding path: %s.", path) 267 | if path not in project["exclude"]: 268 | project["exclude"].append(path) 269 | project.commit() 270 | elif args.pkgs[0] == "include": 271 | for path in args.pkgs[1:]: 272 | log.info("Including path: %s.", path) 273 | project["exclude"].remove(path) 274 | project.commit() 275 | elif args.pkgs[0] == "list": 276 | print("Excludes:") 277 | print(", ".join(project["exclude"])) 278 | except Exception as e: 279 | log.critical("Error handling path action: %s", e) 280 | 281 | 282 | def handle_ask(args): 283 | """for for_ask in project["startupEnv"]: 284 | if for_ask not in env: 285 | print(f"[green]Enter the {for_ask} value: ") 286 | res = input() 287 | project["environmentVariables", for_ask] = res 288 | project.commit() 289 | 290 | Manage the list of environment variables to ask for. 291 | 292 | Args: 293 | args (argparse.Namespace): The command line arguments. 294 | """ 295 | vars = args.pkgs 296 | project = get_project_config() 297 | for i in vars: 298 | if i in project["startupEnv"]: 299 | project["startupEnv"].remove(i) 300 | log.info("%s removed from ask_for list.", i) 301 | else: 302 | project["startupEnv"].append(i) 303 | log.info("%s added to ask_for list.", i) 304 | project.commit() 305 | 306 | 307 | def handle_remove(args): 308 | """ 309 | Remove the project configuration file. 310 | 311 | Args: 312 | args (argparse.Namespace): The command line arguments. 313 | """ 314 | os.remove(CONFIG) 315 | 316 | 317 | def handle_config(args): 318 | """ 319 | Display the current project configuration. 320 | 321 | Args: 322 | args (argparse.Namespace): The command line arguments. 323 | """ 324 | console.print(Pretty(get_project_config().config)) 325 | 326 | 327 | def handle_version(args): 328 | console.print("[blue]Chocolate [/blue](4.0.2-beta)") 329 | 330 | 331 | def export(args): 332 | """ 333 | Export the project to a specified location or a default location. 334 | 335 | Args: 336 | args (argparse.Namespace): The command line arguments.y 337 | """ 338 | log.info( 339 | "Exporting project to %s.", args.output if args.output else "default location" 340 | ) 341 | try: 342 | project = get_project_config() 343 | name = args.output if args.output else project["info"]["name"] + ".zip" 344 | make_executer() 345 | create_zip(name, ".", project["exclude"]) 346 | log.info("Export completed successfully.") 347 | except Exception as e: 348 | log.critical("Error during export: %s", e) 349 | 350 | 351 | def handle_reinstall(*_): 352 | project = get_project_config() 353 | handle_package_installation(project["requirements"]) 354 | 355 | 356 | def handle_sandbox(args): 357 | """Running scripts in sandbox mode""" 358 | log.info("Starting the run process as sandbox mode.") 359 | ensure_env_variables() 360 | project = get_project_config() 361 | log.info("Running the startfile.") 362 | if args.reinstall: 363 | log.info("Reinstall flag detected, proceeding with reinstallation.") 364 | handle_reinstall() 365 | env = project["environmentVariables"] 366 | flags = project["flagsString"] 367 | venv = prj.VenvManager() 368 | if len(args.pkgs) != 3: 369 | log.critical( 370 | "Usage `chocolate sandbox `, Use -1 as unlimited." 371 | ) 372 | quit(1) 373 | try: 374 | log.info("Running the project startfile: %s.", project["mainFile"]) 375 | console.print(Markdown("---")) 376 | venv.run_sandbox( 377 | project["mainFile"], flags, env, args.pkgs[0], args.pkgs[1], args.pkgs[2] 378 | ) 379 | console.print(Markdown("---")) 380 | log.info("Process finished.") 381 | 382 | except Exception as e: 383 | log.critical("Project execution failed. Error: %s", e) 384 | 385 | def handle_ssh(args): 386 | project = get_project_config() 387 | vals = args.pkgs 388 | if len(vals) != 4: 389 | log.critical('Wrong usage. use chocolate ssh --help') 390 | 391 | project['sshHost'] = vals[0] 392 | project['sshPort'] = int(vals[1]) 393 | project['sshUsername'] = vals[2] 394 | project['sshPassword'] = vals[3] 395 | log.info('Done.') 396 | 397 | 398 | def handle_sync(args): 399 | project = get_project_config() 400 | if ( 401 | not (ip := project["sshHost"]) 402 | or not (username := project["sshUsername"]) 403 | or not (password := project["sshPassword"]) 404 | or not (port := project["sshPort"]) 405 | ): 406 | log.critical("SSH server details are incomplete.") 407 | quit(1) 408 | make_executer() 409 | client = Sftp(ip, username, password, port) 410 | client.sync(project["info", "name"], project['exclude']) 411 | log.info("Running...") 412 | 413 | res = Panel("", title="Ssh Output") 414 | with Live(res, refresh_per_second=4) as live: 415 | txt = "" 416 | for i in client.run(project["info"]["name"]): 417 | txt += i + "\n" 418 | res.renderable = txt 419 | 420 | 421 | def make_executer(): 422 | project = get_project_config() 423 | deps = "\n".join(project["requirements"]) 424 | env = "\n".join([f"{i}={j}" for i, j in project["environmentVariables"].items()]) 425 | flags = project["flagsString"] 426 | with open(".env", "+w") as fp: 427 | fp.write(env) 428 | with open(".flags", "+w") as fp: 429 | fp.write(flags) 430 | with open("requirements.txt", "+w") as fp: 431 | fp.write(deps) 432 | with open("run.sh", "+w") as fp: 433 | fp.write(executer.format(project["mainFile"])) 434 | with open("hash.sh", "+w") as fp: 435 | fp.write(hashfind) 436 | log.info("chocolate-free project is ready.") 437 | 438 | 439 | def handle_cmd(args): 440 | project = get_project_config() 441 | if ( 442 | not (ip := project["sshHost"]) 443 | or not (username := project["sshUsername"]) 444 | or not (password := project["sshPassword"]) 445 | or not (port := project["sshPort"]) 446 | ): 447 | log.critical("SSH server details are incomplete.") 448 | quit(1) 449 | 450 | client = Sftp(ip, username, password, port) 451 | 452 | res = Panel("", title="Ssh Output") 453 | with Live(res, refresh_per_second=4) as live: 454 | txt = "" 455 | for i in client.exec(' '.join(sys.argv[2:])): 456 | txt += i + "\n" 457 | res.renderable = txt 458 | 459 | def main(): 460 | """ 461 | Main entry point for the Chocolate Project Manager. 462 | 463 | Parses command line arguments and executes the specified action. 464 | """ 465 | 466 | parser = argparse.ArgumentParser( 467 | description="Chocolate Project Manager.", add_help=False 468 | ) 469 | parser.add_argument("action", type=str, help="Action") 470 | parser.add_argument("-o", "--output", type=str, help="Output file path.") 471 | parser.add_argument("-h", "--help", action="store_true") 472 | parser.add_argument("-r", "--reinstall", action="store_true") 473 | parser.add_argument("-i", "--input", required=False) 474 | parser.add_argument( 475 | "pkgs", nargs="*", help="Raw input after 'add' action", default=[] 476 | ) 477 | 478 | args = parser.parse_args() 479 | 480 | actions = { 481 | "new": handle_new_project, 482 | "add": lambda args: handle_package_installation(args.pkgs), 483 | "reinstall": handle_reinstall, 484 | "env": handle_env_action, 485 | "flags": handle_flags, 486 | "run": run, 487 | "export": export, 488 | "action": custom_action, 489 | "path": handle_path, 490 | "ask_for": handle_ask, 491 | "remove": handle_remove, 492 | "config": handle_config, 493 | "version": handle_version, 494 | "sandbox": handle_sandbox, 495 | "sync": handle_sync, 496 | "ssh": handle_ssh, 497 | "cmd": handle_cmd, 498 | "help": lambda x: console.print(convert_dict_to_table(short_help)), 499 | } 500 | 501 | if args.action in actions or args.action == "help": 502 | ensure_help(args) 503 | actions[args.action](args) 504 | else: 505 | log.error("Unknown action requested.") 506 | 507 | 508 | if __name__ == "__main__": 509 | main() 510 | --------------------------------------------------------------------------------