From 7b295e4a06dc760ab446d69d2793eee9ccc66cdf Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sun, 1 Sep 2024 12:56:15 +0700
Subject: [PATCH 01/29] Delete .github directory
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
.github/FUNDING.yml | 15 --------
.github/workflows/python-package.yml | 55 ----------------------------
2 files changed, 70 deletions(-)
delete mode 100644 .github/FUNDING.yml
delete mode 100644 .github/workflows/python-package.yml
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
deleted file mode 100644
index 42bf91f..0000000
--- a/.github/FUNDING.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-# These are supported funding model platforms
-
-github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
-patreon: # Replace with a single Patreon username
-open_collective: # Replace with a single Open Collective username
-ko_fi: # Replace with a single Ko-fi username
-tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
-community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
-liberapay: # Replace with a single Liberapay username
-issuehunt: # Replace with a single IssueHunt username
-lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
-polar: # Replace with a single Polar username
-buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
-thanks_dev: # Replace with a single thanks.dev username
-custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
deleted file mode 100644
index 005a246..0000000
--- a/.github/workflows/python-package.yml
+++ /dev/null
@@ -1,55 +0,0 @@
-name: Python Package - SuperNano
-
-on:
- push:
- branches:
- - main
- pull_request:
- branches:
- - main
-
-jobs:
- build:
-
- runs-on: ubuntu-latest
-
- strategy:
- matrix:
- python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11']
-
- steps:
- - name: Checkout repository
- uses: actions/checkout@v3
-
- - name: Set up Python
- uses: actions/setup-python@v4
- with:
- python-version: ${{ matrix.python-version }} # Sesuaikan dengan versi Python yang digunakan oleh SuperNano
-
- - name: Install dependencies
- run: |
- python -m pip install --upgrade pip
- pip install -r requirements.txt
-
- - name: Lint with flake8
- run: |
- pip install flake8
- # Lint seluruh direktori kecuali di bawah direktori migrations
- flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
- flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
-
- - name: Run tests
- run: |
- # Jalankan pengujian
- pytest
-
- - name: Build package
- run: |
- python setup.py sdist bdist_wheel
-
- - name: Upload Python Package to PyPI (Test)
- if: github.ref == 'refs/heads/main' && github.event_name == 'push'
- uses: pypa/gh-action-pypi-publish@release/v1
- with:
- password: ${{ secrets.TEST_PYPI_API_TOKEN }}
- repository-url: https://test.pypi.org/legacy/
From f4acd6e19dc896117a4c0265094c89a0da57438c Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sun, 1 Sep 2024 12:56:38 +0700
Subject: [PATCH 02/29] Delete cache directory
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
cache/file_browser.log | 1 -
1 file changed, 1 deletion(-)
delete mode 100644 cache/file_browser.log
diff --git a/cache/file_browser.log b/cache/file_browser.log
deleted file mode 100644
index 8b13789..0000000
--- a/cache/file_browser.log
+++ /dev/null
@@ -1 +0,0 @@
-
From 78867bf9c70938c12d1edf95ff3e5516c4579b53 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sun, 1 Sep 2024 12:56:58 +0700
Subject: [PATCH 03/29] Delete libs directory
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
libs/ParserNodeModule.js | 118 ------
libs/ParserPHP.js | 72 ----
libs/capture_network.py | 36 --
libs/checkedit.py | 433 --------------------
libs/cmd_filter.py | 725 ---------------------------------
libs/commandcheck.py | 456 ---------------------
libs/errrorHandler.py | 62 ---
libs/filemanager.py | 840 ---------------------------------------
libs/filterror.py | 682 -------------------------------
libs/hellpper.py | 264 ------------
libs/helperegex.py | 535 -------------------------
libs/https.py | 112 ------
libs/randoms.py | 134 -------
libs/readme.txt | 1 -
libs/system_manajemen.py | 84 ----
libs/timeout.py | 47 ---
libs/titlecommand.py | 25 --
17 files changed, 4626 deletions(-)
delete mode 100644 libs/ParserNodeModule.js
delete mode 100644 libs/ParserPHP.js
delete mode 100644 libs/capture_network.py
delete mode 100644 libs/checkedit.py
delete mode 100644 libs/cmd_filter.py
delete mode 100644 libs/commandcheck.py
delete mode 100644 libs/errrorHandler.py
delete mode 100644 libs/filemanager.py
delete mode 100644 libs/filterror.py
delete mode 100644 libs/hellpper.py
delete mode 100644 libs/helperegex.py
delete mode 100644 libs/https.py
delete mode 100644 libs/randoms.py
delete mode 100644 libs/readme.txt
delete mode 100644 libs/system_manajemen.py
delete mode 100644 libs/timeout.py
delete mode 100644 libs/titlecommand.py
diff --git a/libs/ParserNodeModule.js b/libs/ParserNodeModule.js
deleted file mode 100644
index 9caa65e..0000000
--- a/libs/ParserNodeModule.js
+++ /dev/null
@@ -1,118 +0,0 @@
-const acorn = require('acorn');
-const fs = require('fs');
-
-function analyzeFile(filePath) {
- const code = fs.readFileSync(filePath, 'utf-8');
- const syntax = acorn.parse(code, { ecmaVersion: 2020, sourceType: 'module' });
-
-
- const result = {
- "module": "",
- "classes": [],
- "functions": [],
- "variables": [],
- }
-
- // Recursive function to traverse AST nodes
- function walk(node) {
- function extractParameters(params) {
- return params.map(param => param?.name || param?.params || '');
- }
- if (node.type === 'ClassDeclaration' && node.id) {
- const classInfo = {
- name: node.id.name,
- variables: [],
- functions: []
- };
-
- // Collect variables and methods within the class body
- node.body.body.forEach(member => {
- if (member.type === 'MethodDefinition' && member.key) {
- if (member.key.name) {
- const methodName = member.key.name;
- const methodType = member.static ? 'static' : 'instance';
- var parameters
- if (member.value.params){
- parameters = extractParameters(member.value.params)
- }else{
- parameters = [""]
- }
- classInfo.functions.push({ name: `${methodName}`, params: parameters, type: methodType });
- }
- } else if (member.type === 'ClassProperty' && member.key) {
- if (member.key.name) {
- classInfo.variables.push(member.key.name);
- }
- }
- });
-
- result.classes.push(classInfo);
- } else if (node.type === 'FunctionDeclaration' && node.id) {
- if (node.id?.name!=undefined){
- var parameters
- if (node.params){
- parameters = extractParameters(node.params);
- }
- result.functions.push({
- name: `${node.id.name}`,
- params: parameters
- });
- }
- }else if (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') {
- if (node.id?.name!=undefined){
- var parameters
- if (node.params){
- parameters = extractParameters(node.params);
- }else{
- parameters = ""
- }
- result.functions.push({
- name: `${node.id.name}`,
- params: parameters
- });
- }
- }else if (node.type === 'MethodDefinition' && node.key) {
- if (node.id?.name!=undefined) {
- var parameters
- if (node.params){
- parameters = extractParameters(node.params);
- }else{
- parameters = ""
- }
- result.functions.push({
- name: `${node.id.name}`,
- params: parameters
- });
- }
- }else if (node.type === 'VariableDeclarator' && node.id) {
- if (node.id?.name!=undefined){
- result.variables.push(node.id.name);
- }
- }
-
- // Traverse child nodes
- for (let key in node) {
- if (node[key] && typeof node[key] === 'object') {
- walk(node[key]);
- }
- }
- }
- // Start walking from the top-level nodes
- walk(syntax);
- //const functionMap = new Map();
- //functions.forEach(func => {
- //if (functionMap.has(func.name)) {
- // func.type = 'class method';
- //} else {
- // func.type = 'function';
- //}
- //});
- return result;//JSON.stringify({"classes":classes, "functions":functions, "variables":variables });
-}
-
-
-
-// Contoh penggunaan
-const filePath = process.argv[2];
-const inspectionResult = analyzeFile(filePath);
-console.log(JSON.stringify(inspectionResult, null, 2));
\ No newline at end of file
diff --git a/libs/ParserPHP.js b/libs/ParserPHP.js
deleted file mode 100644
index 793c41b..0000000
--- a/libs/ParserPHP.js
+++ /dev/null
@@ -1,72 +0,0 @@
-const fs = require('fs');
-const parser = require('php-parser');
-
-function inspectPHP(filePath) {
- const code = fs.readFileSync(filePath, 'utf-8');
- const phpParser = new parser.Engine({
- parser: {
- extractDoc: true,
- suppressErrors: true,
- },
- });
-
- const ast = phpParser.parseCode(code);
-
- const result = {
- "module": "",
- "classes": [],
- "functions": [],
- "variables": [],
- };
-
- function getFunctionParams(params) {
- return params.map(param => param.name ? param.name.name : '');
- }
-
- function traverse(node) {
- if (node.kind === 'class') {
- const classInfo = {
- name: node.name.name,
- functions: [],
- variables: [],
- };
-
- node.body.forEach(child => {
- if (child.kind === 'method') {
- const functions = {
- name: child.name.name,
- params: getFunctionParams(child.arguments),
- };
- classInfo.functions.push(functions);
- } else if (child.kind === 'property') {
- child.properties.forEach(prop => {
- classInfo.variables.push(prop.name.name);
- });
- }
- });
-
- result.classes.push(classInfo);
- } else if (node.kind === 'function') {
- const functionInfo = {
- name: node.name.name,
- params: getFunctionParams(node.arguments),
- };
- result.functions.push(functionInfo);
- } else if (node.kind === 'variable') {
- result.variables.push(node.name.name);
- }
-
- if (node.children) {
- node.children.forEach(traverse);
- }
- }
-
- ast.children.forEach(traverse);
-
- return result;
-}
-
-// Path ke file PHP
-const filePath = process.argv[2];
-const inspectionResult = inspectPHP(filePath);
-console.log(JSON.stringify(inspectionResult, null, 2));
\ No newline at end of file
diff --git a/libs/capture_network.py b/libs/capture_network.py
deleted file mode 100644
index 0d4b679..0000000
--- a/libs/capture_network.py
+++ /dev/null
@@ -1,36 +0,0 @@
-import json
-from selenium import webdriver
-from selenium.webdriver.support.ui import WebDriverWait
-from chromedriver_py import binary_path
-# Initialize Chrome WebDriver with performance logging enabled
-svc = webdriver.ChromeService(executable_path=binary_path)
-chrome_options = webdriver.ChromeOptions()
-chrome_options.add_argument('--remote-debugging-port=9222') # Enable DevTools Protocol
-chrome_options.add_argument("--incognito")
-chrome_options.add_argument('--enable-logging')
-chrome_options.add_argument("--ignore-certificate-errors")
-chrome_options.add_argument("--disable-web-security")
-chrome_options.add_argument("--allow-running-insecure-content")
-driver = webdriver.Chrome(service=svc, options=chrome_options)
-
-
-# Navigate to the target website
-driver.get("https://www.linkvideo.download/")
-WebDriverWait(driver, 30).until(
- lambda d: d.execute_script('return document.readyState') == 'complete'
-)
-test = driver.execute_async_script(
- """
- var performance = window.performance || window.mozPerformance || window.msPerformance || window.webkitPerformance || { };
- var callback = arguments[arguments.length - 1];
- if (performance) {
- if (typeof(performance.getEntries)==='function'){
- performance = performance.getEntriesByType('resource').map(entry => entry.name);
- };
- callback(performance);
- } else { callback(null);}"""
- )
-#response = driver.execute_async_script("""var performance = window.performance || window.mozPerformance || window.msPerformance || window.webkitPerformance || { }; return performance;""")
-print(test)
-# Close the WebDriver
-driver.quit()
\ No newline at end of file
diff --git a/libs/checkedit.py b/libs/checkedit.py
deleted file mode 100644
index 3422301..0000000
--- a/libs/checkedit.py
+++ /dev/null
@@ -1,433 +0,0 @@
-import py_cui
-import os, sys, time, argparse, logging
-from typing import List, Tuple, Any
-from datetime import datetime
-from string import ascii_letters, punctuation
-try:
- from .titlecommand import get_console_title, set_console_title
- from .cmd_filter import shorten_path, validate_folder
- from .errrorHandler import complex_handle_errors
- from .system_manajemen import set_low_priority, SafeProcessExecutor
- from .timeout import timeout_v2
- from .filemanager import StreamFile
-except:
- try:
- from titlecommand import get_console_title, set_console_title
- from cmd_filter import shorten_path, validate_folder
- from errrorHandler import complex_handle_errors
- from system_manajemen import set_low_priority, SafeProcessExecutor
- from timeout import timeout_v2
- from filemanager import StreamFile
- except:
- from libs.titlecommand import get_console_title, set_console_title
- from libs.cmd_filter import shorten_path, validate_folder
- from libs.errrorHandler import complex_handle_errors
- from libs.system_manajemen import set_low_priority, SafeProcessExecutor
- from libs.filemanager import StreamFile
- from libs.timeout import timeout_v2
-
-set_low_priority(os.getpid())
-#########mendapatkan process terbaik tanpa membebani ram dan cpu
-
-__version__ = "1.5.3"
-
-logging.basicConfig(
- level=logging.DEBUG, format="%(asctime)s - %(levelname)s - %(message)s"
-)
-
-
-def setTitle(title: str):
- process = title
- Getitles = get_console_title()
- if os.path.isdir(process) or os.path.isfile(process):
- length = int(process.__len__() / 2)
- if length < 28:
- x = process.__len__()
- nexts = int(50 - x) - (x / 2)
- if nexts < 28:
- length = int((28 - nexts) + nexts)
- else:
- length = nexts
- elif length > 50:
- length = 28
- process = shorten_path(process, length)
-
- if Getitles.startswith("Win-SuperNano"):
- output = str("Win-SuperNano {titles}".format(titles=process))
- else:
- output = title
- set_console_title(output)
-
-class SuperNano:
- def __init__(self, root: py_cui.PyCUI, path):
- self.root = root
- self.path = path
- # Confirmation message
- self.confirmation_popup = None
- self.prev_dir_stack: List[str] = []
-
- # Set Title Window Console
- setTitle("Win-SuperNano")
-
- # Determine if path is a file or directory
- if os.path.isfile(self.path):
- self.dir = os.path.dirname(self.path)
- self.file_to_open = os.path.basename(self.path)
- else:
- self.dir = self.path
- self.file_to_open = None
-
- # Initialize undo stack
- self.undo_stack: List[Tuple[str, int, Any]] = []
-
- # Key bindings
- self.root.add_key_command(py_cui.keys.KEY_CTRL_S, self.save_opened_file)
- self.root.add_key_command(py_cui.keys.KEY_CTRL_D, self.delete_selected_file)
- self.root.add_key_command(py_cui.keys.KEY_CTRL_Z, self.undo_last_edit)
-
- # File selection menu
- self.file_menu = self.root.add_scroll_menu(
- "Directory Files", 0, 0, row_span=5, column_span=2
- )
- self.file_menu.add_key_command(py_cui.keys.KEY_ENTER, self.open_file_dir)
- self.file_menu.add_key_command(
- py_cui.keys.KEY_DELETE, self.delete_selected_file
- )
- self.file_menu.add_text_color_rule(
- "
",
- py_cui.GREEN_ON_BLACK,
- "startswith",
- match_type="region",
- region=[5, 1000],
- )
- self.file_menu.set_color(py_cui.WHITE_ON_BLACK)
-
- # Search box
- self.search_box = self.root.add_text_box("Search", 5, 0, column_span=2)
- self.search_box.add_key_command(py_cui.keys.KEY_ENTER, self.search_files)
- self.search_box.set_color(py_cui.WHITE_ON_BLACK)
- self.search_box.set_focus_text('Press Enter')
- #self.search_box. ('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")[]')
-
- # Delete button
- self.delete_button = self.root.add_button(
- "Delete Selected File",
- 6,
- 0,
- column_span=2,
- command=self.delete_selected_file,
- )
- self.delete_button.set_color(py_cui.WHITE_ON_BLACK)
- self.delete_button.set_focus_text('Press Enter')
-
- # Directory text box
- self.current_dir_box = self.root.add_text_box(
- "Current Directory", 7, 0, column_span=2
- )
- self.current_dir_box.set_text(self.dir)
- self.current_dir_box.set_color(py_cui.WHITE_ON_BLACK)
- self.current_dir_box.add_key_command(
- py_cui.keys.KEY_ENTER, self.open_new_directory
- )
-
- # Text box for adding new files
- self.new_file_textbox = self.root.add_text_block(
- "Add New File", 8, 0, column_span=2
- )
- self.new_file_textbox.add_key_command(py_cui.keys.KEY_ENTER, self.add_new_file)
- self.new_file_textbox.set_color(py_cui.WHITE_ON_BLACK)
- self.new_file_textbox.set_focus_text('Press Enter')
-
- # Main text block for editing text
- self.edit_text_block = self.root.add_text_block(
- "Open file", 0, 2, row_span=8, column_span=6
- )
- self.edit_text_block.set_focus_text('Save - Ctrl + S')
- self.edit_text_block.set_color(py_cui.WHITE_ON_BLACK)
-
- # Footer Text
- self.footer = self.root.add_label(
- "Ctrl+S : Save file Ctrl+D : Delete File Ctr+Z : Undo Edit",
- 8,
- 2,
- column_span=6,
- )
- self.footer.set_color(py_cui.WHITE_ON_BLACK)
-
- # Open initial directory
- self.open_new_directory()
-
- # If initial path was a file, open it
- if self.file_to_open:
- self.open_file_dir(self.file_to_open)
-
- @complex_handle_errors()
- def save_current_state(self):
- """
- Save the current state of the text block for undo functionality.
- """
- current_text = self.edit_text_block.get()
- if not self.undo_stack or self.undo_stack[-1] != current_text:
- self.undo_stack.append(current_text)
-
- @complex_handle_errors()
- def undo_last_edit(self):
- """
- Undo the last edit by reverting to the previous state.
- """
- if self.undo_stack:
- self.undo_stack.pop() # Remove current state
- if self.undo_stack:
- last_state = self.undo_stack[-1] # Get the previous state
- self.edit_text_block.set_text(last_state)
-
- @complex_handle_errors()
- def open_new_directory(self):
- """
- Open and list the contents of a new directory.
- """
- target = self.current_dir_box.get()
- if not target:
- target = "."
- elif not os.path.exists(target):
- self.root.show_error_popup(
- "Does not exist", f"ERROR - {target} path does not exist"
- )
- return
- elif not os.path.isdir(target):
- self.root.show_error_popup(
- "Not a Dir", f"ERROR - {target} is not a directory"
- )
- return
-
- # Update previous directory stack and set the new directory
- if self.dir != target:
- self.prev_dir_stack.append(self.dir)
- self.dir = target
-
- target = os.path.abspath(target)
- self.current_dir_box.set_text(target)
-
- files = (
- [" .."] if self.prev_dir_stack else []
- ) # Add ".." only if we have a previous directory
- dir_contents = os.listdir(self.dir)
- for elem in dir_contents:
- if os.path.isfile(os.path.join(self.dir, elem)):
- if validate_folder(path=os.path.join(self.dir, elem)):
- files.append(elem)
- else:
- if validate_folder(path=os.path.join(self.dir, elem)):
- files.append(" " + elem)
-
- self.file_menu.clear()
- self.file_menu.add_item_list(files)
-
- @complex_handle_errors()
- def add_new_file(self):
- """
- Add a new file to the file menu.
- """
- new_file_name = self.new_file_textbox.get().strip()
- if not new_file_name:
- self.root.show_error_popup(
- "Invalid File Name", "Please enter a valid file name."
- )
- return
-
- self.file_menu.add_item(new_file_name)
- self.file_menu.selected_item = len(self.file_menu.get_item_list()) - 1
- self.new_file_textbox.set_selected(False)
- self.root.set_selected_widget(self.edit_text_block.get_id())
- self.edit_text_block.set_title(new_file_name)
- self.edit_text_block.clear()
- self.new_file_textbox.clear()
- self.undo_stack.clear() # Clear undo stack when a new file is added
-
- @complex_handle_errors()
- def open_file_dir(self, filename=None):
- """
- Open a file or directory.
- """
- filename = filename or self.file_menu.get()
- if filename.startswith(""):
- if filename == " ..":
- # Navigate back to the previous directory
- if self.prev_dir_stack:
- self.dir = self.prev_dir_stack.pop()
- self.current_dir_box.set_text(self.dir)
- self.open_new_directory()
- else:
- # Open the selected directory
- new_dir = os.path.join(self.dir, filename[6:])
- self.current_dir_box.set_text(new_dir)
- self.open_new_directory()
- else:
- if validate_folder(path=os.path.join(self.dir, filename)):
- try:
- with open(os.path.join(self.dir, filename), "r+") as fp:
- text = fp.read()
- self.edit_text_block.set_text(text)
- self.edit_text_block.set_title(filename)
- self.undo_stack.clear() # Clear undo stack when a new file is opened
- self.undo_stack.append(
- text
- ) # Add the initial state of the file to the undo stack
- # Set Title Window Console
- setTitle(os.path.join(self.dir, filename))
- except Exception as e:
- logging.error(f"Failed to open file {filename}: {e}")
- self.root.show_warning_popup(
- "Not a text file",
- "The selected file could not be opened - not a text file",
- )
- else:
- self.root.show_warning_popup(
- "Not a text file",
- "The selected file could not be opened - not a text file",
- )
-
- @complex_handle_errors()
- def save_opened_file(self):
- """
- Save the currently opened file.
- """
- if self.edit_text_block.get_title() != "Open file":
- self.save_current_state() # Save the current state before saving
- try:
- if validate_folder(
- path=os.path.join(self.dir, self.edit_text_block.get_title())
- ):
- with open(
- os.path.join(self.dir, self.edit_text_block.get_title()), "w+"
- ) as fp:
- fp.write(self.edit_text_block.get())
- self.root.show_message_popup(
- "Saved",
- f"Your file has been saved as {self.edit_text_block.get_title()}",
- )
- except Exception as e:
- logging.error(
- f"Failed to save file {self.edit_text_block.get_title()}: {e}"
- )
- self.root.show_error_popup("Save Error", "Failed to save the file.")
- else:
- self.root.show_error_popup(
- "No File Opened", "Please open a file before saving it."
- )
-
- @complex_handle_errors()
- def delete_selected_file(self):
- """
- Delete the currently selected file.
- """
- if self.edit_text_block.get_title() != "Open file":
- if validate_folder(
- path=os.path.join(self.dir, self.edit_text_block.get_title())
- ):
- self.root.show_yes_no_popup(
- "Are you sure you want to delete this file?",
- command=self.confirm_delete,
- )
- else:
- self.root.show_error_popup(
- "No File Opened", "Please open a file before deleting it."
- )
-
- @complex_handle_errors()
- def confirm_delete(self, confirmed: bool):
- """
- Confirm the deletion of the file based on user response.
- """
- if confirmed:
- try:
- os.remove(os.path.join(self.dir, self.edit_text_block.get_title()))
- self.edit_text_block.clear()
- self.edit_text_block.set_title("Open file")
- self.file_menu.remove_selected_item()
- self.root.show_message_popup(
- "Deleted", "The file has been successfully deleted."
- )
- except OSError as e:
- logging.error(
- f"Failed to delete file {self.edit_text_block.get_title()}: {e}"
- )
- self.root.show_error_popup(
- "OS Error", "Operation could not be completed due to an OS error."
- )
- else:
- self.root.show_message_popup(
- "Delete Cancelled", "File deletion was cancelled."
- )
-
- @complex_handle_errors()
- def search_files(self):
- """
- Search for files in the directory that match the search query.
- """
- query = self.search_box.get().strip().lower()
- if not query:
- self.root.show_error_popup(
- "Invalid Query", "Please enter a valid search query."
- )
- return
-
- matched_files = []
- for item in self.file_menu.get_item_list():
- if query in item.lower() and validate_folder(
- path=os.path.join(self.dir, item.lower())
- ):
- matched_files.append(item)
-
- if not matched_files:
- self.root.show_error_popup(
- "No Matches", "No files or directories match your search query."
- )
- else:
- self.file_menu.clear()
- self.file_menu.add_item_list(matched_files)
-
-
-@complex_handle_errors()
-def parse_args():
- """
- Parse command line arguments.
- """
- parser = argparse.ArgumentParser(
- description="An extension on nano for editing directories in CLI."
- )
- parser.add_argument("path", help="Target file or directory to edit.")
- args = vars(parser.parse_args())
- path = args.get("path", ".").strip()
- if os.path.exists(path):
- if validate_folder(path=path):
- pass
- else:
- logging.error(f"ERROR - {path} path cannot access")
- exit()
- else:
- logging.error(f"ERROR - {path} path does not exist")
- exit()
-
- return path
-
-
-def main():
- path = parse_args()
- # Initialize the PyCUI object, and set the title
- root = py_cui.PyCUI(9, 8)
- root.set_title(
- f"Win-SuperNano v{__version__} CopyRight: LcfherShell@{datetime.now().year}"
- )
- # Create the wrapper instance object.
- SuperNano(root, path)
- # Start the CUI
- root.start()
-
-if __name__ == "__main__":
- safe_executor = SafeProcessExecutor(max_workers=2)
- # Collect argument information
- safe_executor.submit(main)
- time.sleep(timeout_v2())
- safe_executor.shutdown(wait=True)
-
\ No newline at end of file
diff --git a/libs/cmd_filter.py b/libs/cmd_filter.py
deleted file mode 100644
index 10176bd..0000000
--- a/libs/cmd_filter.py
+++ /dev/null
@@ -1,725 +0,0 @@
-import json, subprocess, os, sys, threading, time, psutil, platform, random
-
-from typing import Iterable, Container, Protocol, List, Tuple
-
-from dataclasses import dataclass
-
-from functools import wraps, lru_cache
-try:
- from .helperegex import split, findpositions, re as regex
- from .randoms import random, randomDigits
-
-except:
- try:
- from helperegex import split, findpositions, re as regex
- from randoms import random, randomDigits
- except:
- from libs.helperegex import split, findpositions, re as regex
- from libs.randoms import random, randomDigits
-
-
-try:
- rangeX = range
-except:
- rangeX = xrange
- range = xrange
-
-
-
-@dataclass(order=True)
-class ResultContainer:
- results = (
- []
- ) # Mutable - anything inside this list will be accesable anywher in your program
-
-
-def save_result(cls):
- def decorator(func):
- def wrapper(args, *kwargs):
- # get result from the function
-
- func_result = func(args, *kwargs)
-
- # Pass the result into mutable list in our ResultContainer class
-
- cls.results.append(func_result)
-
- # Return result from the function
-
- return func_result
-
- return wrapper
-
- return decorator
-
-
-@save_result(ResultContainer)
-def func(a, b):
- return a * b
-
-@lru_cache(maxsize=128)
-def remwithre(text, there=regex.compile(regex.escape("=") + ".*")):
- return there.sub("", text)
-
-@lru_cache(maxsize=128)
-def compiltesV2(cmd: str) -> List[str]:
- def find_indices_of_substring(substring, string):
- return [m.start() for m in regex.finditer(regex.escape(substring), string)]
-
- if "#" in cmd:
- command_part, comment_part = cmd.split("#", 1)
- comment_part = "#" + comment_part
- else:
- command_part = cmd
- comment_part = ""
-
- # Handle quotes
- single_quote_count = find_indices_of_substring("'", command_part).__len__()
- double_quote_count = find_indices_of_substring('"', command_part).__len__()
-
- if single_quote_count % 2 == 0 and single_quote_count > 0:
- record = findpositions(r"'(.*?)'", command_part)
- keysplit = "'"
- elif double_quote_count % 2 == 0 and double_quote_count > 0:
- record = findpositions(r"\"(.*?)\"", command_part)
- keysplit = '"'
- else:
- return
-
- recordlist = [substr for substr in record]
-
- for idx, substr in enumerate(recordlist):
- recordlist[idx] = list(substr)
- placeholder = f"__PLACEHOLDER_{idx}__"
- command_part = command_part.replace(recordlist[idx][0][0], placeholder)
-
- # Preserve & inside quotes and comments
- segments: List[Tuple[str]] = []
- in_single_quote = in_double_quote = False
-
- i:int = 0
- while i < len(command_part):
- char = command_part[i]
- if char == "'":
- in_single_quote = not in_single_quote
- elif char == '"':
- in_double_quote = not in_double_quote
-
- if char == "&" and not (in_single_quote or in_double_quote):
- if i + 1 < len(command_part) and command_part[i + 1] == "&":
- segments.append("&&")
- i += 1
- else:
- segments.append("&")
- else:
- start = i
- while i < len(command_part) and (
- char != "&"
- or (in_single_quote or in_double_quote)
- or (i + 1 < len(command_part) and command_part[i + 1] != "&")
- ):
- i += 1
- if i < len(command_part):
- char = command_part[i]
- segments.append(command_part[start:i])
- continue
- i += 1
-
- # Reconstruct commands based on segments
- commands: List[Tuple[str]] = []
- outputs:List[Tuple[str]] = []
- command:str = ""
- for segment in segments:
- if segment == "&&":
- commands.append(command.strip())
- command = ""
- else:
- command += segment
-
- if command:
- commands.append(command.strip())
-
- for command in commands:
- # Restore quoted substrings from placeholders
- for i, substr in enumerate(recordlist):
- placeholder = f"__PLACEHOLDER_{i}__"
- command = command.replace(placeholder, recordlist[i][0][0])
- outputs.append(command)
- return outputs
-
-@lru_cache(maxsize=128)
-def compiltesV3(cmd: str) -> List[str]:
- def find_indices_of_substring(substring, string):
- return [m.start() for m in regex.finditer(regex.escape(substring), string)]
-
- def find_positions(pattern, string):
- return [(m.start(), m.group(0)) for m in regex.finditer(pattern, string)]
-
- # Split the command and comments
- if "#" in cmd:
- command_part, comment_part = cmd.split("#", 1)
- comment_part = "#" + comment_part
- else:
- command_part = cmd
- comment_part = ""
-
- # Handle quotes
- single_quote_count = len(find_indices_of_substring("'", command_part))
- double_quote_count = len(find_indices_of_substring('"', command_part))
-
- if single_quote_count % 2 == 0 and single_quote_count > 0:
- quoted_strings = find_positions(r"'(.*?)'", command_part)
- quote_char = "'"
- elif double_quote_count % 2 == 0 and double_quote_count > 0:
- quoted_strings = find_positions(r'"(.*?)"', command_part)
- quote_char = '"'
- else:
- return []
-
- # Replace quoted substrings with placeholders
- for i, (pos, substr) in enumerate(quoted_strings):
- placeholder = f"__PLACEHOLDER_{i}__"
- command_part = (
- command_part[:pos] + placeholder + command_part[pos + len(substr) :]
- )
-
- # Preserve & inside quotes and comments
- segments: List[Tuple[str]] = []
- i:int = 0
- while i < len(command_part):
- char = command_part[i]
- if char == quote_char:
- # Skip quoted parts
- i += 1
- while i < len(command_part) and command_part[i] != quote_char:
- i += 1
- i += 1
- elif char == "&":
- if i + 1 < len(command_part) and command_part[i + 1] == "&":
- segments.append("&&")
- i += 1
- else:
- segments.append("&")
- else:
- start = i
- while i < len(command_part) and (
- command_part[i] != "&"
- or (i + 1 < len(command_part) and command_part[i + 1] != "&")
- ):
- i += 1
- segments.append(command_part[start:i])
- continue
- i += 1
-
- # Reconstruct commands based on segments
- commands: List[Tuple[str]] = []
- command:str = ""
- for segment in segments:
- if segment == "&&":
- commands.append(command.strip())
- command = ""
- else:
- command += segment
-
- if command:
- commands.append(command.strip())
-
- # Restore quoted substrings from placeholders
- outputs: List[Tuple[str]] = []
- for command in commands:
- for i, (pos, substr) in enumerate(quoted_strings):
- placeholder = f"__PLACEHOLDER_{i}__"
- command = command.replace(placeholder, substr)
- outputs.append(command)
-
- return outputs
-
-@lru_cache(maxsize=128)
-def compiltes(string: str) -> List[str]:
- if string == "":
- return []
-
- _output: List[str] = []
-
- xout = findpositions(r"'(.*?)?'|\"(.*?)?\"", string)
- lenght = len(xout)
- cv = 0
- checkpoint: Tuple[str] = ()
- checkpoint2: List[Tuple[str]] = []
- place = "<%%"
- newstring = string
- keyplace = ""
-
- maxpend: List[int] = []
- digit: str = "1"
- xct = regex.finditer(r"<%%(.*?)?>", string)
- for xc in xct:
- if xc:
- if xc.group(0).count(" ") == 0:
- maxdigit = xc.group(1)
- maxpend.append(int(maxdigit))
-
- if maxpend:
- _s_s = len(str(max(maxpend))) + 1
- digit = str(_s_s)
-
- score_record: List[str] = []
- for y in xout:
- for x in y:
- if "&&" in x[0]:
- keyplace = "&&"
- elif "&" in x[0]:
- keyplace = "&"
-
- if lenght != 0:
- score = randomDigits(int(digit))
- while score in score_record:
- score = randomDigits(int(digit))
- score_record.append(score)
-
- newword = "".join([place, str(score), ">"])
- newpad = regex.sub(r"&&|&", newword, x[0])
- if x[0].count("&") == 0:
- cv += 1
- else:
- if lenght == len(xout) - cv:
- pass
- else:
- minus = len(newword) + len(keyplace)
- checkpoint2.append((
- x[1][0] + minus - len(keyplace),
- x[1][-1] + minus + 1,
- ))
-
- string = string.replace(x[0], newpad, 1)
- checkpoint = checkpoint + ((x[0], newword, score, keyplace),)
- lenght -= 1
-
- # Handle splitting based on characters following '&'
- temp_output = []
- i = 0
- while i < len(string):
- if string[i] == "&":
- if i + 1 < len(string) and (string[i + 1] == " " or string[i + 1] == "&"):
- # Split and handle based on next character
- if i > 0:
- temp_output.append(string[:i].strip())
- string = string[i + 1:].lstrip()
- i = 0
- else:
- i += 1
- else:
- i += 1
-
- if string:
- temp_output.append(string.strip())
- # Process checkpoints to replace placeholders
- for x in temp_output:
- for y in checkpoint:
- if y[1] in x:
- oldstring = regex.search(r"'(.*?)?'", x) or regex.search(r'"(.*?)?"', x)
- if oldstring:
- x = x.replace(oldstring.group(0), y[0], 1)
- else:
- x = x.replace(y[1], y[3])
- _output.append(x.strip())
-
- return _output
-
-
-@lru_cache(maxsize=128)
-def functionclean(functions, powershell=True):
- # fff = regex.search(r'function(.*)\(', functions, regex.MULTILINE)
-
- fff = regex.search(r"function(.*)\{", functions, regex.DOTALL or regex.MULTILINE)
-
- if fff:
- remove_scp = regex.sub(r"\s+", "", fff.group(1), regex.UNICODE)
-
- _functioname = regex.match(r"(.\S+)?\(", remove_scp).group(1)
-
- if _functioname.count("(") != 0 and _functioname.endswith("}") == False:
- _functioname = regex.match("(.*?)\(", _functioname).group(1)
-
- remove_scp = regex.match("(.*?){", remove_scp).group(1)
-
- # print(fff.group(1), "\nFunName:",_functioname, "\nRem:", remove_scp, "\n")
-
- _argsx = regex.match(r"{names}\((.*)\)".format(names=_functioname), remove_scp)
-
- _argsx = _argsx.group(1)
-
- if powershell == True:
- # print(_argsx)
-
- # print(regex.findall(r"(\[.\S+\])", "([string]$helllo=$fo, [int]$dddd)".format(args=_argsx)))
-
- stringsl = regex.sub(r"(,)", r"\1 ", "({args})".format(args=_argsx))
-
- for scleans in regex.findall(r"(\[.\S+\])", stringsl):
- _argsx = _argsx.replace(scleans, "", 1)
-
- _argsx = _argsx.replace("$", "")
-
- _spaces = ""
-
- if _argsx.count('"') != 0 and _argsx.count('"') % 2 == 0:
- _spaces = '"'
-
- elif _argsx.count("'") != 0 and _argsx.count("'") % 2 == 0:
- _spaces = "'"
-
- else:
- reps = regex.sub(r"(=)", r"\1'", _argsx)
-
- if reps:
- xappend: List[Tuple[str]] = []
-
- for xsplit in reps.split(","):
- if xsplit.count("'") != 0:
- xappend.append(str(xsplit[: xsplit.__len__()] + "'"))
-
- else:
- xappend.append(str(xsplit + "=None"))
-
- _argsx = ",".join(xappend)
-
- if _spaces:
- clos = regex.findall(r"{args}(.*){args}".format(args=_spaces), _argsx)
-
- xxs: List[Tuple[str]] = []
-
- if clos.__len__() != 0:
- for xin in clos:
- _argsx = _argsx.replace(xin, "", 1)
-
- splitz = _argsx.split(",")
-
- for xin in splitz:
- if xin.find("=") != -1:
- xxs.append(xin)
-
- elif xin.find("=") == -1:
- xxs.append(xin + "=''")
-
- splitz = None
-
- _argsx = ", ".join(xxs)
-
- xxs = None
-
- functionmake = """def {functioname}({arguments}):pass""".format(
- functioname=_functioname, arguments=_argsx
- )
-
- exec(
- functionmake
- + "\nf_code = {functioname}.__code__".format(functioname=_functioname)
- )
-
- get_arguments = eval(
- """f_code.co_varnames[:f_code.co_argcount + f_code.co_kwonlyargcount]"""
- )
-
- """if _argsx.count("=") !=0:
-
- xcs = []
-
- for a in _argsx.split(",", _argsx.count("=")):
-
- if a.find("=") != -1:
-
- xcs.append(remwithre( _argsx))"""
-
- return dict((x, y) for x, y in [(_functioname, list(get_arguments))])
-
- # except:
-
- # return _functioname, []
-
-@lru_cache(maxsize=128)
-def classclean(classname, powershell=True):
- classreg = regex.search(r"class(.*?)\{", classname, regex.DOTALL or regex.MULTILINE)
-
- if classreg:
- _argsx = ""
-
- remove_scp = regex.sub(r"\s+", "", classreg.group(1), regex.UNICODE)
-
- try:
- _classsname = regex.match(r"(.\S+)?\(", remove_scp).group(1)
-
- except:
- _classsname = regex.match(r"(.\S+)?", remove_scp).group(1)
-
- if powershell == True:
- argsx_ = classreg.group(1)
-
- _argsx = regex.match(
- r"{names}+\((.*)\)".format(names=_classsname), remove_scp
- )
-
- if _argsx:
- stringsl = regex.sub(
- r"(,)", r"\1 ", "({args})".format(args=_argsx.group(1))
- )
-
- _argsx = _argsx.group(1)
-
- else:
- pass
-
- for scleans in regex.findall(r"(\[.\S+\])", stringsl):
- _argsx = _argsx.replace(scleans, "", 1)
-
- _argsx = _argsx.replace("$", "")
-
- # print( regex.search(r"{(.*)}" , classname, regex.DOTALL).group(1))
-
- # print( r"{xxgro}(.*){endsx}".format(xxgro= classreg.group(0), endsx = "\}") )
-
- if _argsx.__len__() == 0:
- argsx_ = regex.search(r"{(.*)}", classname, regex.DOTALL)
-
- if argsx_:
- if powershell == True:
- splits = argsx_.group(1).split("\n")
-
- sout = ""
-
- for sout in splits:
- init_function = regex.match(
- r"(.*){names}+\((.*)\)?".format(names=_classsname),
- sout.strip(),
- )
-
- # print("after:", init_function)
-
- if sout.strip().startswith("[") and init_function:
- if init_function.group(1):
- sout = sout.replace(init_function.group(1), "", 1)
-
- if sout.endswith("{"):
- sout = sout[:-1].strip()
-
- break
-
- elif sout.strip().startswith(_classsname) and init_function:
- # if init_function.group(1):
-
- if init_function.group(0):
- sout = init_function.group(0).strip()[:-1]
-
- elif sout and sout.endswith("{"):
- sout = sout.strip()[:-1]
-
- break
-
- if sout:
- s_argsx = regex.match(
- r"{names}+\((.*)\)".format(names=_classsname), sout
- )
-
- cleansub = regex.sub(
- r"(,)", r"\1 ", "({args})".format(args=s_argsx.group(1))
- )
-
- for scleans in regex.findall(r"(\[.\S+\])", cleansub):
- sout = sout.replace(scleans, "")
-
- sout = regex.search(r"\((.*?)?\)", sout.replace("$", ""))
-
- if sout:
- _argsx = sout.group(1).strip()
-
- if _argsx.count("="):
- xx = "function xx({args})".format(args=_argsx)
- cleans = functionclean(xx + "{ pass}", True)
- _argsx = ",".join(cleans["xx"])
-
- functionmake = """def {_classsname}({arguments}):pass""".format(
- _classsname=_classsname, arguments=_argsx
- )
-
- exec(
- functionmake
- + "\nf_code = {_classsname}.__code__".format(_classsname=_classsname)
- )
-
- get_arguments = eval(
- """f_code.co_varnames[:f_code.co_argcount + f_code.co_kwonlyargcount]"""
- )
-
- return dict((x, y) for x, y in [(_classsname, list(get_arguments))])
-
-
-# functions = """function max_x2([string]$helllo='$fo', [int]$dddd){ pass }"""
-
-# print(functionclean(functions=functions, powershell=True))
-
-def map_function_arguments(class_str):
- # Regex untuk menemukan fungsi beserta argumennya
- func_pattern = regex.compile(r'[\[.*?\]]?(\w+)\((.*?)\)')
- matches = func_pattern.findall(class_str)
-
- func_args_map = {}
- for match in matches:
- func_name, args_str = match
- # Memisahkan argumen
- args = [arg.strip() for arg in args_str.split(',')]
- func_args_map[func_name] = args
-
- return func_args_map
-#print(map_function_arguments(classname))
-
-def validate_folder(path: str) -> bool:
- """
- Validates whether the given path is within the directory of the script.
-
- Parameters:
- - path (str): The folder path to validate.
-
- Returns:
- - bool: True if the path is valid and within the script directory, False otherwise.
- """
- # Absolute path of the script's directory
- script_dir = os.path.abspath(os.path.dirname(__file__)).replace("\\", "/")
- # Convert to lowercase and normalize paths
- script_dir = script_dir.lower()
- path = path.lower().replace("\\", "/")
-
- n = str(script_dir).split("/")
- Syscript = str("/".join(n[: n.__len__() - 1])).strip()
- # Check if path starts with the script directory path
- if path.startswith(Syscript) or path.startswith(script_dir):
- return False
- return True
-
-def shorten_path(path: str, max_length: int) -> str:
- """
- Shorten a given path to a specified maximum length, inserting '...'
- to represent omitted sections of the path.
-
- Parameters:
- - path (str): The full path to shorten.
- - max_length (int): The maximum length of the shortened path.
-
- Returns:
- - str: The shortened path.
- """
- if len(path) <= max_length:
- return path
-
- # Replace backslashes with forward slashes for consistency
- parts = path.replace("\\", "/").split("/")
-
- # Handle Windows drive letter
- if ":" in parts[0]:
- drive = parts.pop(0) + "/"
- else:
- drive = ""
-
- # If the path is too long, we need to shorten it
- result = drive + parts[0] + "/.../" + parts[-1]
-
- # Remove parts from the middle until the length is acceptable
- while len(result) > max_length and len(parts) > 2:
- parts.pop(1)
- result = drive + parts[0] + "/.../" + parts[-1]
-
- # If it's still too long, truncate the last part
- if len(result) > max_length:
- part_length = max_length - len(drive) - 6 # 6 for "/.../"
- if part_length > 0:
- truncated_last_part = parts[-1][:part_length] + "..."
- result = drive + parts[0] + "/.../" + truncated_last_part
- else:
- result = drive + "..."
-
- return result
-
-def maxsize():
- usage = round(psutil.disk_usage(get_system_partitions()[0]).percent, 2)
- try:
- MAX_INT = sys.maxsize
- except:
- MAX_INT = sys.maxint
- return round(MAX_INT / usage)
-
-
-def get_system_partitions():
- platformname = platform.system().lower()
- if platformname == "windows":
- partitions = (
- os.popen("wmic logicaldisk get name")
- .read()
- .strip()
- .replace("\n", "")
- .split("\n")[1:]
- )
- elif platformname == "linux" or platformname == "unix" or platformname == "darwin":
- partitions = (
- os.popen("df -h | grep \"^/dev/\" | awk '{print $1}'")
- .read()
- .strip()
- .replace("\n", "")
- .split("\n")
- )
- else:
- partitions: List[Tuple[str]] = []
- if partitions:
- for i in rangeX(0, partitions.__len__() - 1):
- try:
- if partitions[i].__len__() > 0:
- pass
- else:
- del partitions[i]
- except:
- pass
- return partitions
-
-
-def safe_load_json(json_string):
- try:
- # Memeriksa ukuran JSON
- if (
- json_string.__len__() > maxsize()
- ): # Tetapkan batasan ukuran sesuai kebutuhan
- raise ValueError("JSON data too large")
-
- # Mengurai JSON
- data = json.loads(json_string)
-
- # Validasi struktur JSON
- if not isinstance(data, dict): # Contoh validasi sederhana
- raise ValueError("Invalid JSON structure: expected a dictionary")
-
- return data
- except json.JSONDecodeError as e:
- pass
- except ValueError as e:
- pass
- except Exception as e:
- pass
-
- return None
-
-
-def filter_json(data, keys):
- """
- Memfilter data JSON berdasarkan kunci yang diberikan.
-
- :param data: Data JSON yang akan difilter (dalam bentuk dict atau list).
- :param keys: Daftar kunci yang ingin diambil.
- :return: JSON yang sudah difilter sebagai string.
- """
- if isinstance(data, dict):
- return {k: data.get(k) for k in keys if k in data}
-
- elif isinstance(data, list):
- return [{k: item.get(k) for k in keys if k in item} for item in data]
-
-#https://download.microsoft.com/download/1/6/5/165255E7-1014-4D0A-B094-B6A430A6BFFC/vcredist_x64.exe
-#https://download.microsoft.com/download/1/6/5/165255E7-1014-4D0A-B094-B6A430A6BFFC/vcredist_x86.exe
-#https://download.microsoft.com/download/0/6/4/064F84EA-D1DB-4EAA-9A5C-CC2F0FF6A638/vc_redist.x64.exe
-#https://download.microsoft.com/download/0/6/4/064F84EA-D1DB-4EAA-9A5C-CC2F0FF6A638/vc_redist.x86.exe
\ No newline at end of file
diff --git a/libs/commandcheck.py b/libs/commandcheck.py
deleted file mode 100644
index e3eb15a..0000000
--- a/libs/commandcheck.py
+++ /dev/null
@@ -1,456 +0,0 @@
-try:
- from .helperegex import fullmacth
-except:
- from helperegex import fullmacth
-
-from os import getppid, getenv
-import os, subprocess, threading, time, psutil, ctypes
-from functools import lru_cache
-from ctypes import wintypes
-try:
- from GPUtil import getGPUs
-except:
- def getGPUs():
- # Contoh fungsi untuk mendapatkan penggunaan GPU
- # Implementasi nyata bergantung pada library dan hardware yang digunakan
- # Untuk sekarang kita kembalikan None, artinya GPU tidak digunakan
- return None
-
-def yime():
- # Get CPU usage percentage
- cpu_usage = psutil.cpu_percent()
-
- # Get GPU usage percentage if available
- gpu_usage = getGPUs()
-
- if gpu_usage is not None:
- usage = gpu_usage
- else:
- usage = cpu_usage
-
- # Get RAM usage percentage
- ram_usage = psutil.virtual_memory().percent
-
- # Determine the divisor based on usage level
- if usage > 50:
- cpu_usage_divided = usage / 20 # Higher divisor for higher usage
- else:
- cpu_usage_divided = usage / 10 # Lower divisor for lower usage
-
- if ram_usage > 50:
- ram_usage_divided = ram_usage / 20 # Higher divisor for higher usage
- else:
- ram_usage_divided = ram_usage / 10 # Lower divisor for lower usage
-
- # Adjust for very low usage values
- if cpu_usage_divided < 0.1:
- cpu_usage_divided *= 2
- if ram_usage_divided < 0.1:
- ram_usage_divided *= 2
-
- # Calculate average usage and return
- data = [round(cpu_usage_divided, 1), round(ram_usage_divided, 1)]
- outputdata = round(sum(data) / len(data), 1) / 2
- if outputdata>0.9:
- pass
- else:
- outputdata = 1
- return outputdata
-
-class metablocks(type):
- def __setattr__(self, name, value):
- raise ValueError(name)
-
-
-class detectcommand(metaclass=metablocks):
- def __init__(self):
- try:
- #from psutil import Process
-
- #self.shell = Process(getppid()).name().lower()
- x, self.shell = get_console_process_name()
- except:
- process = subprocess.Popen(
- ["tasklist", "/fi", f"PID eq {getppid()}", "/fo", "csv", "/nh"],
- stdout=subprocess.PIPE,
- )
- stdout, _ = process.communicate()
- self.shell = stdout.decode().strip().split(",")[0].strip('"').lower()
-
- def __dir__(self):
- pass
-
- def __repr__(self) -> str:
- return self.shell
-
- @property
- def isPowershell(self):
- isPowershell = False
- if fullmacth(
- r"pswh|pswh.*|pswh.exe|pswh.*.exe|powershell.exe|powershell.*.exe",
- self.shell,
- ):
- isPowershell = True
- return isPowershell
-
- @property
- def iscmd(self):
- iscmd = False
- if fullmacth(r"cmd|cmd.*|cmd.exe|cmd.*.exe", self.shell):
- iscmd = True
- return iscmd
-
- @property
- def ispy(self):
- ispy = False
- if fullmacth(
- r"py.*|py.exe|py.*.exe|python.*|python.*.exe|python.exe", self.shell
- ):
- ispy = True
- return ispy
-
- @property
- def isbash(self):
- checkbash = (
- self.shell.count("bash")
- or self.shell.count("sh")
- or self.shell.count("dash")
- or self.shell.count("ash")
- )
- return checkbash != 0
-
- def commands(self):
- parent_process = getenv("PROCESSOR_IDENTIFIER", "")
-
- @lru_cache(maxsize=10)
- def auto(self):
- check = ["isPowershell", "iscmd", "ispy", "isbash"]
- xx_output = {}
-
- for values in check:
- xcmd = eval("self.{cmd}".format(cmd=values))
- if xcmd == True:
- xx_output["type_command"] = values
- break
- return xx_output
-
-
-def stream_output(pipe, name):
- for line in iter(pipe.readline, ""):
- print(f"{line.strip()}")
- pipe.close()
-
-@lru_cache(maxsize=10)
-def stream_command_output(command: str):
- process = subprocess.Popen(
- command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, text=True
- )
-
- stdout_thread = threading.Thread(target=stream_output, args=(process.stdout, ""))
- stderr_thread = threading.Thread(target=stream_output, args=(process.stderr, ""))
-
- stdout_thread.start()
- stderr_thread.start()
-
- stdout_thread.join()
- stderr_thread.join()
-
- return process.poll()
-
-@lru_cache(maxsize=10)
-def run_powershell_command_as_admin(command):
- # Command to run PowerShell as administrator and execute the given command
- # ps_command = f'powershell -Command "Start-Process powershell -ArgumentList \'-NoProfile -ExecutionPolicy Bypass -Command {command}\' -WindowStyle Hidden"'
-
- # Execute the command
- # result = subprocess.call(ps_command, shell=True)
- # Run the command and capture the output
-
- result = subprocess.run(
- ["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", command],
- capture_output=True,
- text=True,
- )
-
- # Ambil output dari perintah PowerShell
- if result:
- return result.stdout or result.stderr
-
- return False
-
-@lru_cache(maxsize=10)
-def run_powershell_File_as_admin(command: str):
- # Command to run PowerShell as administrator and execute the given command
- # ps_command = f'powershell -Command "Start-Process powershell -ArgumentList \'-NoProfile -ExecutionPolicy Bypass -Command {command}\' -WindowStyle Hidden"'
-
- # Execute the command
- # result = subprocess.call(ps_command, shell=True)
- # Run the command and capture the output
- result = subprocess.run(
- ["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", command],
- capture_output=True,
- text=True,
- )
-
- # Ambil output dari perintah PowerShell
- if result:
- return result.stderr or result.stdout
-
- return False
-
-@lru_cache(maxsize=10)
-def run_cmd_command_as_admin(command: str):
- # Command to run commandprompt as administrator and execute the given command
- # Execute the command
- # Run the command and capture the output
-
- command = f"powershell -Command \"Start-Process cmd -ArgumentList \'/c {command}' -Verb runAs -WindowStyle Hidden\""
- time.sleep(2)
- result = subprocess.run(command, capture_output=True, text=True, shell=True)
-
- # Ambil output dari perintah PowerShell
- if result:
- return True
- return False
-
-@lru_cache(maxsize=10)
-def checkcommand(command: str, timeout=None):
- # Mulai proses
- ps = subprocess.Popen(
- command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
- )
-
- # Fungsi untuk menunggu proses selesai
- def target():
- try:
- ps.communicate()
- except Exception as e:
- print(f"Error: {e}")
-
- ouputs = 0
- # Buat dan mulai thread
- thread = threading.Thread(target=target)
- thread.start()
-
- # Tunggu sampai selesai atau timeout
- thread.join(timeout)
- if thread.is_alive():
- ps.kill() # Mengirim sinyal penghentian
- thread.join() # Tunggu sampai thread selesai
- ouputs = 1
- if ouputs:
- ouputs = 0
- return ouputs
-
-@lru_cache(maxsize=10)
-def checkcommandV2(command: str, timeout: int=0) -> str:
- """
- Menjalankan perintah dengan batasan waktu menggunakan thread.
- Jika perintah melebihi waktu yang ditentukan, proses akan dihentikan secara paksa.
-
- Args:
- command (str): Perintah yang akan dijalankan.
- timeout (int): Waktu maksimum (dalam detik) untuk menjalankan perintah.
-
- Returns:
- str: Output dari perintah jika berhasil, atau pesan error jika proses dihentikan atau gagal.
- """
- if timeout <= 0:
- timeout = round(yime())*2
- def target():
- nonlocal result, process
- try:
- process = subprocess.Popen(
- "powershell -Command {command}".format(command=command),
- shell=True,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE
- )
- stdout, stderr = process.communicate()
- result = (process.returncode, stdout, stderr)
- except Exception as e:
- result = e
-
- result = None
- process = None
- thread = threading.Thread(target=target)
- thread.start()
- thread.join(timeout)
-
- if thread.is_alive():
- # Proses melebihi waktu yang ditentukan, hentikan secara paksa
- if process is not None:
- parent = psutil.Process(process.pid)
- for child in parent.children(recursive=True): # Membunuh semua child processes
- child.kill()
- parent.kill()
- thread.join() # Pastikan thread selesai
- return result
-
- if isinstance(result, tuple):
- returncode, stdout, stderr = result
- if returncode == 0:
- return result
- else:
- return result
- elif isinstance(result, Exception):
- return (1, None, None)
- else:
- return (1, None, None)
-
-@lru_cache(maxsize=10)
-def checkcommandPrompt(command: str, timeout: int=0) -> str:
- """
- Menjalankan perintah dengan batasan waktu menggunakan thread.
- Jika perintah melebihi waktu yang ditentukan, proses akan dihentikan secara paksa.
-
- Args:
- command (str): Perintah yang akan dijalankan.
- timeout (int): Waktu maksimum (dalam detik) untuk menjalankan perintah.
-
- Returns:
- str: Output dari perintah jika berhasil, atau pesan error jika proses dihentikan atau gagal.
- """
- if timeout <= 0:
- timeout = round(yime())*2
- def target():
- nonlocal result, process
- try:
- process = subprocess.Popen(
- command,
- shell=True,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE
- )
- stdout, stderr = process.communicate()
- result = (process.returncode, stdout, stderr)
- except Exception as e:
- result = e
-
- result = None
- process = None
- thread = threading.Thread(target=target)
- thread.start()
- thread.join(timeout)
-
- if thread.is_alive():
- # Proses melebihi waktu yang ditentukan, hentikan secara paksa
- if process is not None:
- parent = psutil.Process(process.pid)
- for child in parent.children(recursive=True): # Membunuh semua child processes
- child.kill()
- parent.kill()
- thread.join() # Pastikan thread selesai
- return result
-
- if isinstance(result, tuple):
- returncode, stdout, stderr = result
- if returncode == 0:
- return result
- else:
- return result
- elif isinstance(result, Exception):
- return (1, None, None)
- else:
- return (1, None, None)
-
-def get_console_process_name():
- """
- Mengembalikan nama proses yang menjalankan konsol saat ini (cmd atau powershell).
- """
- kernel32 = ctypes.windll.kernel32
- user32 = ctypes.windll.user32
- psapi = ctypes.windll.psapi
-
- # Mendapatkan handle dari jendela konsol saat ini
- hwnd = kernel32.GetConsoleWindow()
- if not hwnd:
- return None
-
- # Mendapatkan ID proses dari jendela konsol
- pid = wintypes.DWORD()
- user32.GetWindowThreadProcessId(hwnd, ctypes.byref(pid))
-
- # Membuka proses untuk membaca informasi
- h_process = kernel32.OpenProcess(0x1000, False, pid.value)
- if not h_process:
- return None
-
- # Mendapatkan nama file dari proses
- exe_name = ctypes.create_string_buffer(512)
- psapi.GetModuleFileNameExA(h_process, None, exe_name, 512)
- kernel32.CloseHandle(h_process)
-
- head, tail = os.path.split(exe_name.value.decode('utf-8'))
- return head, tail
-
-
-def restart_powershell_as_admin():
- """
- Merestart PowerShell dalam mode administrator, memulai proses PowerShell baru, dan menjalankan file Python itu sendiri.
- """
- def is_admin():
- """
- Mengecek apakah skrip dijalankan dengan hak administrator.
- """
- try:
- return ctypes.windll.shell32.IsUserAnAdmin()
- except:
- return False
- try:
- # Dapatkan path file Python saat ini
- script_path = os.path.abspath(__file__)
-
- # Perintah untuk menjalankan PowerShell sebagai administrator dan menjalankan file Python ini
- command = f'Start-Process powershell -ArgumentList \'-NoExit -Command "Start-Sleep -Seconds 1; Set-Location -Path \'{os.getcwd()}\'; python \\"{script_path}\\" "\'\' -Verb RunAs'
-
- # Jalankan perintah menggunakan subprocess
- subprocess.run(["powershell", "-Command", command], shell=True)
-
- # Keluar dari proses Python saat ini
- os._exit(0)
- except Exception as e:
- print(f"An error occurred: {e}")
-
-
-detectcommandprompt = detectcommand()
-#print(detectcommandprompt.auto())
-
-
-#####################
-
-
-def get_current_directory_partition(current_dir:str):
- partitions = psutil.disk_partitions()
- current_dir = current_dir.replace("\\", "/")
- for partition in partitions:
- if current_dir.lower().startswith(partition.mountpoint.lower().replace("\\", "/")):
- return get_detailed_partition_info(partition)
-
- return None
-
-def get_detailed_partition_info(partition):
- usage = psutil.disk_usage(partition.mountpoint)
- return {
- 'device': partition.device,
- 'mountpoint': partition.mountpoint,
- 'fstype': partition.fstype,
- 'total': usage.total,
- 'used': usage.used,
- 'free': usage.free,
- 'percent': usage.percent
- }
-
-
-
-if __name__ == "__main__":
- command = "$pass = Read-Host '{command}' -AsSecureString; $plainTextPass = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($pass)); Write-Output $plainTextPass".format(command="prompt") # Contoh perintah Python yang tidur selama 60 detik
- #timeout = round(yime()) # Waktu maksimum yang diizinkan (dalam detik)
-
- start_time = time.time()
- output = checkcommandV2(command)
- end_time = time.time()
-
- elapsed_time = end_time - start_time
- print(f"Output:\n{output}")
- print(f"Elapsed time: {elapsed_time:.2f} seconds")
\ No newline at end of file
diff --git a/libs/errrorHandler.py b/libs/errrorHandler.py
deleted file mode 100644
index affd832..0000000
--- a/libs/errrorHandler.py
+++ /dev/null
@@ -1,62 +0,0 @@
-import logging
-import time
-from typing import Callable, Any, Optional
-from functools import wraps
-import traceback
-
-# Configure the logging
-logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
-logger = logging.getLogger(__name__)
-
-
-def handle_errors(func):
- ####Menggunakan fungsi wrap
- @wraps(func)
- def wrapper(*arg, **kwargs):
- start_time = time.time()
- try:
- result = func(*arg, **kwargs)
- except Exception as e:
- execution_time = time.time() - start_time
- tb = traceback.extract_tb(e.__traceback__)
- filename, lineno, funcname, text = tb[-1]
- error_message = (
- f"An error occurred in function {func.__name__} at {filename}:{lineno} - {text}"
- )
- logger.error(f"Function {func.__name__} failed after {execution_time:.4f} seconds with error: {e}")
- logger.error(error_message)
- return wrapper
-
-def complex_handle_errors(loggering=None, log_message: Optional[str] = None, nomessagesNormal:Optional[bool]=False) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
- def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
- @wraps(func)
- def wrapper(*args, **kwargs) -> Any:
- start_time = time.time()
- try:
-
- result = func(*args, **kwargs)
- if nomessagesNormal != False:
- execution_time = time.time() - start_time
- loggering.info(f"Executing function: {func.__name__} with args: {args} and kwargs: {kwargs}")
- loggering.info(f"Function {func.__name__} executed successfully in {execution_time:.4f} seconds")
- return result
- except Exception as e:
- execution_time = time.time() - start_time
- tb = traceback.extract_tb(e.__traceback__)
- filename, lineno, funcname, text = tb[-1]
- error_message = (
- log_message or
- f"An error occurred in function {func.__name__} at {filename}:{lineno} - {text}"
- )
- loggering.error(f"Function {func.__name__} failed after {execution_time:.4f} seconds with error: {e}")
- loggering.error(error_message)
- return None # Atau nilai default sesuai kebutuhan
-
-
- return wrapper
-
- # Check if decorator is used without arguments
- if callable(log_message):
- return decorator(log_message)
-
- return decorator
diff --git a/libs/filemanager.py b/libs/filemanager.py
deleted file mode 100644
index d8b3c6d..0000000
--- a/libs/filemanager.py
+++ /dev/null
@@ -1,840 +0,0 @@
-import os, sys, time, shutil, psutil, inspect, importlib, pkg_resources, pkgutil, json, logging, threading, re
-
-try:
- from .helperegex import (
- searchmissing,
- searching,
- fullmacth,
- rremovelist,
- clean_string,
- rreplace,
- cleanstring,
- split_from_right_with_regex,
- )
- from .cmd_filter import filter_json, safe_load_json
- from .system_manajemen import set_low_priority, SafeProcessExecutor
- from .commandcheck import subprocess
- from .timeout import timeout_v1, timeout_v2
- from .https import Fetch
-except:
- try:
- from helperegex import (
- searchmissing,
- searching,
- fullmacth,
- rremovelist,
- clean_string,
- rreplace,
- cleanstring,
- split_from_right_with_regex,
- )
- from cmd_filter import filter_json, safe_load_json
- from system_manajemen import set_low_priority, SafeProcessExecutor
- from commandcheck import subprocess
- from timeout import timeout_v1, timeout_v2
- from https import Fetch
- except:
- from libs.helperegex import (
- searchmissing,
- searching,
- fullmacth,
- rremovelist,
- clean_string,
- rreplace,
- cleanstring,
- split_from_right_with_regex,
- )
- from libs.cmd_filter import filter_json, safe_load_json
- from libs.system_manajemen import set_low_priority, SafeProcessExecutor
- from libs.commandcheck import subprocess
- from libs.timeout import timeout_v1, timeout_v2
- from libs.https import Fetch
-
-if __name__ == "__main__":
- set_low_priority(os.getpid())
-
-
-
-def removeduplicatejson(my_list:list):
- # Menggunakan dictionary untuk melacak elemen unik
- seen = {}
- for d in my_list:
- key = (d["name"]) # Kunci unik berdasarkan 'name' dan 'age'
- if key not in seen:
- seen[key] = d
-
- # Mengambil nilai dari dictionary
- unique_list = list(seen.values())
- return list(unique_list)
-
-script_dir = os.path.dirname(os.path.realpath(__file__)).replace("\\", "/")
-all_system_paths = ["/".join(script_dir.split("/")[:-1]), script_dir]
-
-
-class StreamFile:
- def __init__(self, file_path: str, buffer_size: int = 8192, print_delay: float = 2):
- """
- Inisialisasi StreamFile untuk membaca file baris demi baris dengan delay dan menulis dengan buffer.
-
- :param file_path: Path ke file yang akan dibaca atau ditulis.
- :param buffer_size: Ukuran buffer sebelum data ditulis ke file.
- :param print_delay: Waktu jeda (dalam detik) antara print setiap baris.
- """
- self.file_path = file_path
- self.buffer_size = buffer_size or 0
- self.print_delay = print_delay
- self.buffer = bytearray()
-
- def readlines(self):
- """
- Membaca file dengan buffer size dan menghasilkan setiap baris satu per satu dengan delay.
-
- :yield: Baris dari file.
- """
-
- with open(self.file_path, "r+") as f:
- buffer = self.buffer
- while True:
- chunk = f.read(self.buffer_size)
- if not chunk:
- break
-
- buffer.extend(
- chunk.encode("utf-8")
- ) # Encode chunk to bytes if necessary
-
- while b"\n" in buffer:
- line, buffer = buffer.split(b"\n", 1)
- yield line.decode("utf-8")
- time.sleep(self.print_delay)
-
- if buffer:
- yield buffer.decode("utf-8")
- self.buffer_size = 0
-
- def write(self, data):
- """
- Menulis data ke buffer dan secara otomatis menulis ke file ketika buffer penuh.
-
- :param data: Data yang akan ditulis ke buffer.
- """
- self.buffer.extend(data)
- while len(self.buffer) >= self.buffer_size:
- with open(self.file_path, "ab+") as f:
- f.write(self.buffer[: self.buffer_size])
- self.buffer = self.buffer[self.buffer_size :]
-
- def writelines(self, lines):
- """
- Menulis baris-baris data ke file dengan delay antara setiap baris.
-
- :param lines: List atau generator yang menghasilkan baris-baris data untuk ditulis.
- """
- for line in lines:
- self.write(line.encode("utf-8"))
- time.sleep(self.print_delay + timeout_v1())
- self.close() # Memastikan buffer ditulis dan ditutup setelah penulisan selesai
-
- def eraseFile(self):
- with open(self.file_path, "rb+") as f:
- f.truncate(0)
-
- def close(self):
- """
- Menulis sisa data di buffer ke file dan membersihkan buffer.
- """
- if self.buffer and self.buffer_size:
- with open(self.file_path, "ab+") as f:
- f.write(self.buffer)
- self.buffer.clear()
- else:
- pass
-
-
-class ModuleInspector:
- def __init__(self):
-
- self.languages = {"languages": "PYTHON"}
- self.modules = self.getsys_module()
- self.curents = self.modules
- self.curentpath = sys.path.copy()
- self.modulepathnow = []
-
- def get_two_level_subfolders(self, root_directory):
- def is_valid_directory(directory_name):
- # Memeriksa apakah nama direktori mengandung spasi atau karakter khusus
- if re.search(r'[ \s@#$%^&*()+=\[\]{};:\'",<>?/\\|`~]', directory_name):
- return False
- return True
-
- result = []
- for root, dirs, files in os.walk(root_directory):
- # Mendapatkan subfolder pada level pertama
- if root == root_directory:
- result.extend(
- [
- resolve_relative_path(root, d).replace("\\", "/")
- for d in dirs
- if d.count(" ") == 0 and is_valid_directory(d)
- ]
- )
- else:
- # Mendapatkan subfolder pada level kedua
- # Hentikan iterasi lebih dalam dengan mengosongkan 'dirs'
- dirs[:] = []
- result.extend(
- [
- resolve_relative_path(root, d).replace("\\", "/")
- for d in dirs
- if d.count(" ") == 0 and is_valid_directory(d)
- ]
- )
- if root_directory.replace("\\", "/") not in result:
- result.append(root_directory.replace("\\", "/"))
- return result
-
- def subprocess(self, filescript: str, file_path: str):
- try:
- result = subprocess.run(
- ["node", resolve_relative_path(all_system_paths[1], filescript), file_path], capture_output=True, text=True
- )
-
- if result.returncode == 0:
- # Parsing output JSON
- return json.loads(result.stdout)
- else:
- return {}
- except:
- return {}
-
- ########################NodeJS module
- def get_global_nodejs_modules(self):
- # Menemukan path global `node_modules`
- node_modules_path = os.path.join(
- os.getenv("APPDATA") or "/usr/local", "npm", "node_modules"
- )
-
- # Mengambil semua modul di direktori
- try:
- self.languages["languages"] = "NODEJS"
- return [
- module
- for module in os.listdir(node_modules_path)
- if not module.startswith(".")
- ] or None
- except FileNotFoundError:
- return []
-
- def inspect_nodejs_module(self, module_name):
- try:
- # Path ke node_modules global
- node_modules_path = os.path.join(
- os.getenv("APPDATA") or "/usr/local", "npm", "node_modules"
- )
- module_path = resolve_relative_path(node_modules_path, module_name)
-
- # Path ke package.json
- package_json_path = os.path.join(module_path, "package.json")
- if not os.path.isfile(package_json_path):
- return None
- # Membaca file package.json
- try:
- with open(package_json_path, "r") as f:
- package_info = json.load(f)
- except:
- return None
- # Mendapatkan file entry point utama dari package.json
- entry_point = package_info.get("main", "index.js")
- # Path lengkap ke entry point
- index_file_path = resolve_relative_path(module_path, entry_point)
-
- # Pastikan file tersebut ada
- if not os.path.isfile(index_file_path):
- return None
-
- result = self.subprocess("ParserNodeModule.js", index_file_path)
- if "module" in result.keys():
- result.update({"module": module_name})
- return result
-
- except FileNotFoundError:
- return {}
-
- ###############################################################
-
- # -------------------------------------------------------------
-
- ########################Python module
- def getsys_module(self):
- self.languages["languages"] = "PYTHON"
- return (
- sorted(
- [
- module.name
- for module in pkgutil.iter_modules([x for x in sys.path if x])
- if not module.name.strip().startswith("~")
- and not module.name.strip().startswith("__pycache__")
- ]
- )
- or None
- )
-
- def get_python_module(self, paths: list = []):
- def getmodules(path: list, result: list):
- result.extend(
- sorted(
- [
- module.name
- for module in pkgutil.iter_modules(path)
- if not module.name.strip().startswith("~")
- and not module.name.strip().startswith("__pycache__")
- ]
- )
- )
-
- threads, result = [[], self.curents]
- if paths.__len__() < 1:
- paths = [os.getcwd()]
- else:
- pass
-
- for path in paths:
- thread = threading.Thread(target=getmodules, args=([path], result))
- thread.start()
- threads.append(thread)
-
- for thread in threads:
- thread.join()
-
- self.modulepathnow = paths
- return result or None
-
- def list_classes(self, module):
- try:
- imported_module = importlib.import_module(module)
- classes = [
- obj
- for name, obj in inspect.getmembers(imported_module)
- if inspect.isclass(obj)
- ]
- if not classes:
- pass
- return classes
- except Exception as e:
- return []
-
- def get_class_details(self, cls):
- details = {"name": cls.__name__, "variables": [], "functions": []}
-
- for name, obj in inspect.getmembers(cls):
- if inspect.isfunction(obj):
- func_details = {"name": name, "params": str(inspect.signature(obj))}
- details["functions"].append(func_details)
- elif not name.startswith("__") and not inspect.ismodule(obj):
- details["variables"].append(name)
-
- return details
-
- def get_function_detail(self, module):
- details = []
- try:
- for name, obj in inspect.getmembers(importlib.import_module(module)):
- if inspect.isfunction(obj):
- func_details = {"name": name, "params": str(inspect.signature(obj))}
- details.append(func_details)
- except:
- pass
- return details
-
- def get_global_variables(self, module):
- try:
- imported_module = importlib.import_module(module)
- # global_vars = {name: self.serialize_value(value) for name, value in vars(imported_module).items()
- # if not (inspect.isclass(value) or inspect.isfunction(value)) and not name.startswith('__')}
- global_vars = [
- name
- for name, value in vars(imported_module).items()
- if not (inspect.isclass(value) or inspect.isfunction(value))
- and not name.startswith("__")
- ]
- return global_vars
- except Exception as e:
- return []
-
- def serialize_value(self, value):
- """Serialize values for JSON compatibility."""
- if isinstance(value, (int, float, str, bool, list, dict)):
- return value
- elif callable(value):
- return f"Function: {value.__name__}"
- else:
- return str(value) # Convert other types to string
-
- def inspect_python_module(self, module_name: str):
- if self.modulepathnow.__len__() >= 1:
- sys.path.extend(self.modulepathnow)
- self.modulepathnow = []
- try:
- classes = self.list_classes(module_name)
- result = {
- "module": module_name,
- "variables": self.get_global_variables(module_name),
- "classes": [],
- "functions": self.get_function_detail(module_name),
- }
-
- for cls in classes:
- class_details = self.get_class_details(cls)
- result["classes"].append(class_details)
-
- # Convert the result to JSON and print it
-
- sys.path = self.curentpath
- return result
- except Exception as e:
- sys.path = self.curentpath
- return None
-
- ########################PHP module
- def get_php_module(self, directory: str):
- """Menemukan semua file .php dalam direktori."""
- php_files = []
- directory = directory.replace("\\", "/")
- for folder in self.get_two_level_subfolders(directory):
- for file in os.listdir(folder):
- if os.path.isfile(resolve_relative_path(folder, file)):
- index_file_path = resolve_relative_path(folder, file).replace(
- "\\", "/"
- )
- if index_file_path.endswith(".php") or index_file_path.endswith(
- ".phpx"
- ):
- modules = [x for x in index_file_path.split(directory) if x]
- php_files.append(modules[0])
- self.languages["languages"] = "PHP"
- return php_files or None
-
- def inspect_php_module(self, file_path: str):
- try:
- result = self.subprocess("ParserPHP.js", file_path)
- if "module" in result.keys():
- head, tail = os.path.split(file_path)
- result.update({"module": tail})
- except:
- result = {}
- return result
-
- ########################C module
- def get_c_module(self, directory: str):
- """Menemukan semua file .c dan .h dalam direktori."""
- c_files = []
- directory = directory.replace("\\", "/")
- for folder in self.get_two_level_subfolders(directory):
- for file in os.listdir(folder):
- if os.path.isfile(resolve_relative_path(folder, file)):
- index_file_path = resolve_relative_path(folder, file).replace(
- "\\", "/"
- )
- if (
- index_file_path.endswith(".c")
- or index_file_path.endswith(".cpp")
- or index_file_path.endswith("csx")
- or index_file_path.endswith(".h")
- ):
- modules = [x for x in index_file_path.split(directory) if x]
- c_files.append(modules[0])
- self.languages["languages"] = "C"
- return c_files or None
-
- def inspect_c_module(self, file_path: str):
- # Menyimpan hasil inspeksi
- result = {"classes": [], "functions": [], "variables": []}
- try:
- import clang.cindex
-
- def get_functions(node):
- """Mengambil informasi tentang fungsi dari node AST."""
- functions = []
- for child in node.get_children():
- if child.kind == clang.cindex.CursorKind.FUNCTION_DECL:
- func_info = {
- "name": child.spelling,
- "params": [
- param.spelling for param in child.get_arguments()
- ],
- }
- functions.append(func_info)
- elif child.kind == clang.cindex.CursorKind.CXX_METHOD:
- # Menangani metode dalam kelas (C++)
- func_info = {
- "name": child.spelling,
- "params": [
- param.spelling for param in child.get_arguments()
- ],
- }
- functions.append(func_info)
- elif child.kind in [
- clang.cindex.CursorKind.STRUCT_DECL,
- clang.cindex.CursorKind.CLASS_DECL,
- ]:
- # Menangani struct atau class
- # Tidak melakukan apa-apa di sini, akan diproses di tempat lain
- pass
- return functions
-
- def get_variables(node):
- """Mengambil informasi tentang variabel dari node AST."""
- variables = []
- for child in node.get_children():
- if child.kind in [
- clang.cindex.CursorKind.VAR_DECL,
- clang.cindex.CursorKind.FIELD_DECL,
- ]:
- variables.append(child.spelling)
- return variables
-
- index = clang.cindex.Index.create()
- translation_unit = index.parse(file_path)
-
- # Menangani fungsi global dan variabel
- result["functions"].extend(get_functions(translation_unit.cursor))
- result["variables"].extend(get_variables(translation_unit.cursor))
-
- # Menangani struct atau class
- for child in translation_unit.cursor.get_children():
- if child.kind in [
- clang.cindex.CursorKind.STRUCT_DECL,
- clang.cindex.CursorKind.CLASS_DECL,
- ]:
- class_info = {
- "name": child.spelling,
- "methods": get_functions(child),
- "variables": get_variables(child),
- }
- result["classes"].append(class_info)
- except:
- with open(file_path, "r+", encoding=sys.getfilesystemencoding()) as f:
- code = f.read()
- # Regex pattern untuk menemukan fungsi dan variabel
- function_pattern = re.compile(
- r"\b[A-Za-z_][A-Za-z_0-9]*\s+\**\s*([A-Za-z_][A-Za-z_0-9]*)\s*\(.*?\)\s*\{",
- re.MULTILINE | re.DOTALL,
- )
- variable_pattern = re.compile(
- r"\b[A-Za-z_][A-Za-z_0-9]*\s+\**\s*([A-Za-z_][A-Za-z_0-9]*)\s*(?=\=|;)",
- re.MULTILINE | re.DOTALL,
- )
-
- # Regex pattern untuk menemukan struct dan field-nya
- struct_pattern = re.compile(
- r"\bstruct\s+(\w+)\s*\{([^}]*)\};", re.MULTILINE | re.DOTALL
- )
- field_pattern = re.compile(
- r"([a-zA-Z_][a-zA-Z_0-9]*)\s+([a-zA-Z_][a-zA-Z_0-9]*)\s*(?:\[\s*\d*\s*\])?\s*(?:;|,)",
- re.MULTILINE | re.DOTALL,
- )
- try:
- # Menemukan fungsi
- for match in function_pattern.finditer(code):
- result["functions"].append(
- {
- "name": match.group(1),
- "params": [
- ""
- ], # Parameter tidak diekstrak dalam regex sederhana ini
- }
- )
- except:
- function_pattern = re.compile(
- r"\b[A-Za-z_][A-Za-z_0-9]*\s+\**\s*([A-Za-z_][A-Za-z_0-9]*)\s*\(.*?\)\s*\{",
- re.MULTILINE | re.DOTALL,
- )
- if function_pattern.finditer(code):
- for match in function_pattern.finditer(code):
- result["functions"].append(
- {
- "name": match.group(1),
- "params": [
- ""
- ], # Parameter tidak diekstrak dalam regex sederhana ini
- }
- )
- result["functions"] = removeduplicatejson(result["functions"])
- try:
- # Menemukan variabel
- result["variables"] = [
- match.group(1) for match in variable_pattern.finditer(code)
- ]
- except:
- variable_pattern = re.compile(
- r"\b[A-Za-z_][A-Za-z_0-9]*\s+\**\s*([A-Za-z_][A-Za-z_0-9]*)\s*(?=\=|;)",
- re.MULTILINE | re.DOTALL,
- )
-
- if variable_pattern.finditer(code):
- result["variables"] = [
- match.group(1) for match in variable_pattern.finditer(code)
- ]
- if result["variables"]:
- result["variables"] = list(set(result["variables"]))
- # Menemukan struct dan field-nya (menganggap struct sebagai class)
- try:
- for match in struct_pattern.finditer(code):
- struct_name = match.group(1)
- struct_body = match.group(2)
- fields = [
- field_match.group(2)
- for field_match in field_pattern.finditer(struct_body)
- ]
-
- result["classes"].append(
- {
- "name": struct_name,
- "methods": [
- ""
- ], # Metode tidak diekstrak dalam regex sederhana ini
- "variables": fields,
- }
- )
- except:
- struct_pattern = re.compile(
- r"\btypedef\s+struct\s+(\w+)\s*\{([^}]*)\}\s*;", re.MULTILINE | re.DOTALL
- )
- field_pattern = re.compile(
- r"([a-zA-Z_][a-zA-Z_0-9]*)\s+([a-zA-Z_][a-zA-Z_0-9]*)\s*(?:\[\s*\d*\s*\])?\s*(?:;|,)",
- re.MULTILINE | re.DOTALL,
- )
- if struct_pattern.finditer(code):
- for match in struct_pattern.finditer(code):
- struct_name = match.group(1)
- struct_body = match.group(2)
- try:
- fields = [
- field_match.group(2)
- for field_match in field_pattern.finditer(struct_body)
- ]
- except:
- fields = []
- result["classes"].append(
- {
- "name": struct_name,
- "methods": [
- "" # Metode tidak diekstrak dalam regex sederhana ini
- ],
- "variables": fields,
- }
- )
- result["classes"] = removeduplicatejson(result["classes"])
- if "module" not in result.keys():
- head, tail = os.path.split(file_path)
- result.update({"module": tail})
-
- return result
-
- #############path sekarang dari file
- def get_moduleV2(self, pathfiles: str):
- results = None
- paths = os.path.dirname(os.path.abspath(pathfiles))
- _, ext = os.path.splitext(pathfiles)
- self.modulepathnow = [paths]
- try:
- if ext.lower() in (".py", ".pyz", "pyx"):
- results = self.getsys_module()
- elif ext.lower() in (".js", ".ts"):
- results = self.get_global_nodejs_modules()
- elif ext.lower() in (".php", ".phpx"):
- results = self.get_php_module(paths)
- elif ext.lower() in (".c", ".cpp", "csx", ".h"):
- results = self.get_c_module(paths)
- else:
- results = []
- except:
- pass
- _, ext, paths = [None, None, None]
- return results
-
- def inspect_module(self, module_name: str):
- if self.modulepathnow.__len__() >= 1:
- sys.path.extend(self.modulepathnow)
- _, ext = os.path.splitext(module_name)
- if ext.__len__()==0:
- node_modules_path = os.path.join(
- os.getenv("APPDATA") or "/usr/local", "npm", "node_modules"
- )
- module_path = resolve_relative_path(node_modules_path, module_name)
- if os.path.isdir(module_path):
- inspectModule = self.inspect_nodejs_module(module_name)
- else:
- inspectModule = self.inspect_python_module(module_name)
- else:
- if module_name.startswith("\\"):
- module_name = module_name.replace("\\", "",1)
- elif module_name.startswith("/"):
- module_name = module_name.replace("/", "",1)
- if ext.lower() in (".php", ".phpx"):
- inspectModule = self.inspect_php_module(
- resolve_relative_path(os.path.join(*self.modulepathnow), module_name)
- )
- elif ext.lower() in (".c", ".cpp", "csx", ".h"):
- inspectModule = self.inspect_c_module(
- resolve_relative_path(os.path.join(*self.modulepathnow), module_name)
- )
- return inspectModule
-
-
-def create_file_or_folder(path: str) -> str:
- """
- Membuat file atau folder di path yang diberikan.
-
- Args:
- path (str): Path lengkap tempat file atau folder akan dibuat.
-
- Returns:
- str: Pesan konfirmasi yang menunjukkan apakah file atau folder berhasil dibuat.
- """
-
- if not path:
- return "Path is empty."
-
- if os.path.isdir(path):
- return f"The folder '{os.path.basename(path)}' already exists."
-
- if os.path.isfile(path):
- return f"The file '{os.path.basename(path)}' already exists."
-
- folder, filename = os.path.split(path)
- if "." in os.path.basename(path) and os.path.exists(folder):
- # Membuat file
- try:
- if folder and not os.path.exists(folder):
- return f"Failed to create the file '{filename}'"
- with open(path, "wb") as f:
- pass # Membuat file kosong
- return f"The file '{filename}' has been successfully created."
- except Exception as e:
- return f"Failed to create the file '{filename}'"
- elif os.path.exists(folder) and folder:
- # Membuat folder
- try:
- os.makedirs(path)
- return (
- f"The folder '{os.path.basename(path)}' has been successfully created."
- )
- except FileExistsError:
- return f"The folder '{os.path.basename(path)}' already exists."
- except Exception as e:
- return f"Failed to create the folder '{os.path.basename(path)}'."
- else:
- return "Something happened."
-
-
-def is_binary_file(file_path):
- """
- Menentukan apakah file adalah file biner atau bukan.
-
- Args:
- file_path (str): Path ke file yang akan diperiksa.
-
- Returns:
- bool: True jika file adalah file biner, False jika bukan.
- """
- try:
- with open(file_path, "rb") as file:
- chunk = file.read(1024) # Membaca bagian pertama file (1KB)
- # Cek apakah file memiliki karakter yang tidak biasa untuk teks
- if b"\0" in chunk: # Null byte adalah indikator umum dari file biner
- return True
- # Cek apakah file sebagian besar berisi karakter teks (misalnya ASCII)
- text_chars = b"".join([bytes((i,)) for i in range(32, 127)]) + b"\n\r\t\b"
- non_text_chars = chunk.translate(None, text_chars)
- if (
- len(non_text_chars) / len(chunk) > 0.30
- ): # Jika lebih dari 30% karakter non-teks
- return True
- return False
- except Exception as e:
- return False
-
-def validate_file(file_path, max_size_mb=100, max_read_time=2):
- try:
- # Periksa ukuran file
- file_size = os.path.getsize(file_path)
- if file_size > max_size_mb * 1024 * 1024:
- return False
-
- # Mulai waktu pembacaan
- start_time = time.time()
-
- # Baca file
- with open(file_path, "rb") as f:
- # Baca bagian pertama file untuk memeriksa apakah file biner
- first_bytes = f.read(1024)
- if b"\x00" in first_bytes:
- return False
-
- # Lanjutkan membaca file
- while f.read(1024):
- # Periksa waktu yang telah digunakan untuk membaca
- elapsed_time = time.time() - start_time
- if elapsed_time > max_read_time:
- return False
-
- # Jika semua pemeriksaan lolos, file valid
- return True
-
- except Exception as e:
- return False
-
-
-def check_class_in_package(package_name, class_name):
- try:
- # Import the package
- package = importlib.import_module(package_name)
- # Cek apakah kelas ada di dalam modul
- if hasattr(package, class_name):
- cls = getattr(package, class_name)
- # Pastikan itu adalah kelas, bukan atribut atau fungsi
- if inspect.isclass(cls):
- return True, "ClassFound"
- return False, "ClassNotFound"
- except ModuleNotFoundError:
- return False, "ModuleNotFoundError"
-
-
-def resolve_relative_path(current_path: str, relative_path: str) -> str:
- # Menggabungkan current_path dengan relative_path (misalnya "../")
- target_path: str = os.path.normpath(os.path.join(current_path, relative_path))
- return target_path
-
-
-def resolve_relative_path_v2(path: str) -> str:
- target_folder: str = resolve_relative_path(
- os.getcwd().replace("\\", "/"), path.replace("\\", "/")
- )
- return target_folder
-
-
-def get_latest_version(package_name):
- with Fetch() as req:
- response = req.get(
- f"https://pypi.org/pypi/{package_name}/json",
- max_retries=3,
- timeout=8,
- follow_redirects=True,
- )
- data = response.json()
- if filter_json(data=data, keys=["info"]):
- return data["info"]["version"]
- return None
-
-
-def check_update(package_name):
- installed_version = pkg_resources.get_distribution(package_name).version
- latest_version = get_latest_version(package_name)
-
- if installed_version != latest_version:
- print(
- f"Package {package_name} can be updated from version {installed_version} to {latest_version}."
- )
- else:
- print(f"Package {package_name} is up to date.")
diff --git a/libs/filterror.py b/libs/filterror.py
deleted file mode 100644
index 9365391..0000000
--- a/libs/filterror.py
+++ /dev/null
@@ -1,682 +0,0 @@
-import re, os, sys, compileall, collections, traceback
-try:
- import gc
-except:
- pass
-from contextlib import contextmanager
-
-class Decoration:
- def __init__(self, param_foo='a', param_bar='b'):
- self.param_foo = param_foo
- self.param_bar = param_bar
-
- def __call__(self, func):
- def my_logic(*args, **kwargs):
- print(self.param_bar)
- # including the call to the decorated function (if you want to do that)
- result = func(*args, **kwargs)
- return result
-
- return my_logic
-
-class PYTHON_ERORCODE:
- all_error = collections.OrderedDict()
- all_error[Exception] = Exception
- all_error[TypeError] = TypeError
- all_error[TimeoutError] = TimeoutError
- all_error[RecursionError] = RecursionError
- all_error[ReferenceError] = RecursionError
- all_error[MemoryError] = MemoryError
- all_error[ModuleNotFoundError] = ModuleNotFoundError
- all_error[ChildProcessError] = ChildProcessError
- all_error[ConnectionAbortedError] = ConnectionAbortedError
- all_error[ConnectionError] = ConnectionError
- all_error[ConnectionRefusedError] = ConnectionRefusedError
- all_error[ConnectionResetError] = ConnectionResetError
- all_error[OSError] = OSError
- all_error[OverflowError] = OverflowError
- all_error[EnvironmentError] =EnvironmentError
- all_error[EOFError] = EOFError
- all_error[UnicodeDecodeError] = UnicodeDecodeError
- all_error[UnicodeEncodeError] = UnicodeEncodeError
- all_error[UnicodeTranslateError] = UnicodeTranslateError
- all_error[UnboundLocalError] = UnboundLocalError
- all_error[AttributeError] = AttributeError
- all_error[ValueError] = ValueError
- all_error[AssertionError] =AssertionError
- all_error[ZeroDivisionError] = ZeroDivisionError
- all_error[FloatingPointError] = FloatingPointError
- all_error[FileNotFoundError] =FileNotFoundError
- all_error[FileExistsError] = FileExistsError
- all_error[KeyboardInterrupt] = KeyboardInterrupt
- all_error[NameError] = NameError
-
-
-class ValidationError(PYTHON_ERORCODE):
- global __clasesserr
- def __init__(self, coderror=None, messages=None):
-
- global messages_error
-
- messages_error= messages
- __, __clasesserr = None, None
-
- if coderror in self.all_error and messages:
- try:
- for keys in self.all_error.keys():
- if coderror == str(keys):
- coderror = str(self.all_error[keys])
- break
- else:
- code = self.all_error[keys]
- if str(keys).count(".") != 0:
- newkeys = re.findall(r"'(.*?)'", str(keys).strip()) or re.findall(r"\"(.*?)\"", str(keys).strip())
- if newkeys.__len__()!=0:
- newkeys = newkeys[-1:][0]
- assert str(newkeys) == code
- coderror = str(newkeys)
- break
- if coderror.startswith(" 0:
-
- try:
-
- __ = eval("{coderrorx}(\"{messages}\")".format(coderrorx=regex[-1:][0], messages=str(messages)))
-
- except:
-
- __ = "\"{messages}\"".format(messages=str(messages))
-
- finally:
- __clasesserr = "{coderrorx}".format(coderrorx=regex[-1:][0])
-
-
- #return __
- self.compliterror = __
- self.classeseror = __clasesserr
- if self.compliterror == None:
- messages_error = str(messages)
-
- def __call__(self, coderror=None, messages=None):
- global messages_error
-
- messages_error= messages
- __, __clasesserr = None, None
-
- if coderror in self.all_error and messages:
-
- for keys in self.all_error.keys():
- if coderror == str(keys):
- coderror = self.all_error[keys]
- break
- else:
- code = self.all_error[keys]
- if keys.count(".") != 0:
- newkeys = re.findall(r"'(.*?)'", str(keys).strip()) or re.findall(r"\"(.*?)\"", str(keys).strip())
- if newkeys.__len__()!=0:
- newkeys = newkeys[-1:][0]
- assert str(newkeys) == code
- coderror = newkeys
- break
-
- try:
- __ = eval("{coderrorx}(\"{messages}\")".format(coderrorx=coderror, messages=str(messages)))
-
- except:
-
- __ = "\"{messages}\"".format(messages=str(messages))
- finally:
- __clasesserr = "{coderrorx}".format(coderrorx=str(coderror))
-
-
-
- else:
-
- regex = []
-
- for keys in self.all_error.keys():
-
- if coderror == str(keys):
-
- regex = re.findall(r"'(.*?)'", str(keys).strip()) or re.findall(r"\"(.*?)\"", str(keys).strip())
- break
- else:
- try:
- code = self.all_error[keys]
- if keys.count(".") != 0:
- newkeys = re.findall(r"'(.*?)'", str(keys).strip()) or re.findall(r"\"(.*?)\"", str(keys).strip())
- if newkeys.__len__()!=0:
- newkeys = newkeys[-1:][0]
- assert str(newkeys) == code
- regex = [newkeys]
- break
- except:
- pass
-
- if regex.__len__() > 0:
-
- try:
-
- __ = eval("{coderrorx}(\"{messages}\")".format(coderrorx=regex[-1:][0], messages=str(messages)))
-
- except:
-
- __ = "\"{messages}\"".format(messages=str(messages))
-
- finally:
- __clasesserr = "{coderrorx}".format(coderrorx=regex[-1:][0])
-
-
- #return __
- exc_type, exc_obj, exc_tb = sys.exc_info()
-
- typess = str(exc_type).split(".")[-1:][0].replace("'>", "", 1).strip()
- self.compliterror = __
- self.classeseror = __clasesserr
- if self.classeseror == None or self.classeseror == "" or self.classeseror == "None":
- if coderror == None:
- coderror = ""
- coderrorx = re.findall(r"'(.*?)'", str(coderror).strip()) or re.findall(r"\"(.*?)\"", str(coderror).strip())
- for keys in self.all_error.keys():
- try:
- splikeys = re.findall(r"'(.*?)'", str(self.all_error[keys]).strip()) or re.findall(r"\"(.*?)\"", str(self.all_error[keys]).strip())
-
- except:
- splikeys = re.findall(r"'(.*?)'", str(keys).strip()) or re.findall(r"\"(.*?)\"", str(keys).strip())
-
- newkeys = str(splikeys[-1:][0]).split(".")[-1:][0]
- if newkeys == str(coderrorx[-1:][0]).split(".")[-1:][0] and newkeys == typess:
- self.classeseror = keys
- break
- elif newkeys == str(coderrorx[-1:][0]).split(".")[-1:][0]:
- self.classeseror = keys
- break
- elif coderror == "" and newkeys == typess or coderror == "_" and newkeys == typess:
- self.classeseror = keys
- break
- if self.compliterror == None or self.compliterror == "":
- if messages == None:
- self.compliterror = str(exc_obj)
- messages_error = self.compliterror
- else:
- self.compliterror = str(messages)
-
-
- return self
-
- def __enter__(self):
- return self
-
-
- def __str__(self):
- return ""
-
- def __exit__(self, exc_type, exc_value, traceback):
- pass
-
- def __str__(self):
- return self.__dict__
-
- def __dir__(self):
- return ['results']
-
- def update(self, run:bool, **kwarg:dict):
- if kwarg.__len__() != 0:
- for x in kwarg.keys():
-
- keys, values = x, kwarg[x]
- if keys:
-
- valuesx = """class {classname}(Exception):\n\tpass""".format(classname=keys)
-
- exec(valuesx)
- self.all_error[eval(keys)] = eval(keys)
- #setattr(__builtins__, "{classname}".format(classname=values), eval(keys))
-
- else:
- if isinstance(values, str):
-
- if values.find(".") != 0 and values.find(".") > 0:
-
- values = values.split(".")[-1:][0]
-
-
- elif values.startswith("."):
-
- if values.count(".") > 0:
-
- values = values.split(".")[-1:][0]
-
- else:
-
- values = values.replace(".", "", 1)
-
- valuesx = """
- class {classname}(Exception):
-
- def __init__(self, code):
- self.code = code
-
- def __str__(self):
- return repr(self.code)
-
- """.format(classname=values)
-
- exec(valuesx)
- self.all_error[eval(values)] = eval(values)
- #setattr(__builtins__, "{classname}".format(classname=values), eval(values))
-
- elif isinstance(values,object):
- self.all_error[eval(values)] = eval(values)
- #setattr(__builtins__, "{classname}".format(classname=values), eval(values))
- else:
- pass
-
- #print(valuesx)
- #try:
- # @Decoration(param_bar=self)
- # def example():
- # return self
- # return example()
- #except:
- # return self.__init__(coderror=values)
-
- @property
- def results(self):
-
- if self.__dict__.keys().__contains__("jsonoutput"):
-
- json = self.jsonoutput
-
- self.jsonoutput = collections.OrderedDict()
-
-
- return json
-
-
- def manual(self):
-
- self.jsonoutput = collections.OrderedDict()
- exc_type, exc_obj, exc_tb = sys.exc_info()
- try:
- class A(eval(self.classeseror)):
-
- def __init__(self, code):
- self.code = code
-
- def __str__(self):
- return repr(self.code)
- except:
- try:
- class A(self.classeseror):
-
- def __init__(self, code):
- self.code = code
-
- def __str__(self):
- return repr(self.code)
- except:
- self.jsonoutput['Type_Error'], self.jsonoutput['pathname'], self.jsonoutput["filename"]\
- , self.jsonoutput['error_line'], self.jsonoutput['error_message'] = None, None, None, None, None
- pname, fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)
- if fname == "":
- try:
- fname = os.chdir()
- except:
- fname = "."
-
- #if exc_type not in self.all_error:
- exc_typeinject = re.findall(r"'(.*?)'", str(exc_type).strip()) or re.findall(r"\"(.*?)\"", str(exc_type).strip())
- exc_typeinject = ".".join(["filterror", str(exc_typeinject[-1:][0].split(".")[-1:][0]) ])
- for keys in self.all_error.keys():
- newkeys = str(keys)
- if newkeys.find(exc_typeinject) != 0 or newkeys.find(exc_type) != 0:
- self.jsonoutput['Type_Error'] = newkeys.split(".")[-1:][0]
- self.jsonoutput['pathname'] = pname
- self.jsonoutput["filename"] = fname
- self.jsonoutput['error_line'] = exc_tb.tb_lineno
- self.jsonoutput['error_message'] = str(exc_obj)
- break
- error_type = re.findall(r"'(.*?)'", str(self.jsonoutput['Type_Error']).strip()) or re.findall(r"\"(.*?)\"", str(self.jsonoutput['Type_Error']).strip())
- if error_type.__len__() == 0:
-
- error_type = str(self.jsonoutput['Type_Error'])
-
- else:
-
- error_type = str(error_type[-1:][0]).split(".")[-1:][0]
- self.jsonoutput.update({"Type_Error": error_type})
- return
-
- messages = str(self.compliterror)
-
- if messages.startswith(str(self.classeseror)):
-
- messages = messages.replace(str(self.classeseror), "", 1)
-
- try:
-
- try:
-
- raise A(messages)
-
- finally:
-
- raise A(messages)
-
- except A as output:
- #print("Type Error:", self.classeseror)
- error_type = re.findall(r"'(.*?)'", str(self.classeseror).strip()) or re.findall(r"\"(.*?)\"", str(self.classeseror).strip())
- if error_type.__len__() == 0:
-
- error_type = str(self.classeseror)
-
- else:
-
- error_type = str(error_type[-1:][0]).split(".")[-1:][0]
-
- #exc_type, exc_obj, exc_tb = sys.exc_info()
- roscz = [e for e in str(output).split("\n") if e.__len__()!=0]
-
- check_filename, lines_X, code_messages = [], "", ""
-
- if messages_error != "None" or messages_error != None:
- __ = traceback.format_exception(exc_type, exc_obj, exc_tb)
- code_messages = str(__[-1:][0]).replace("ValidationError.manual..A:", "", 1).strip()
- else:
- code_messages = str(exc_obj)
-
-
-
- for index_output in roscz:
-
- if str(index_output).strip().startswith(("File \"", "File: \"", "file \"")):
-
- cleanx = str(index_output).strip().rstrip()
-
- check_filename = re.findall(r"'(.*?)'", cleanx) \
- or re.findall(r"\"(.*?)\"", cleanx)
-
- lines_X = cleanx
- else:
- if check_filename.__len__() == 0:
- check_filename = []
-
- if check_filename.__len__() != 0:
-
- pname, fname = os.path.split(check_filename[-1:][0].strip())
-
- linex = re.findall(r"line\s+(.*),", lines_X) or re.findall(r"line\s+(.*,)", lines_X)
- if linex:
- linerror = int(linex[-1:][0])
- #errotypes = roscz[-1:][0]
- self.jsonoutput['Type_Error'] = error_type
-
- self.jsonoutput['pathname'] = pname
-
- self.jsonoutput["filename"] = fname
-
- self.jsonoutput['error_line'] = linerror
- else:
- self.jsonoutput['error_message'] = code_messages
- else:
- self.jsonoutput['Type_Error'] = error_type
- if str(output).find("File"):
- roscz = [e.strip() for e in str(output).split("\n") if e.__len__()!=0]
-
- check_filename, lines_X = [], ""
-
- for xfile in roscz:
-
- if xfile.startswith(("File", "file")):
- cleanx = str(xfile).strip().rstrip()
-
- check_filename = re.findall(r"'(.*?)'", cleanx) \
- or re.findall(r"\"(.*?)\"", cleanx)
-
- lines_X = cleanx
-
- if check_filename.__len__():
-
- linex = re.findall(r"line\s+(.*),", lines_X) or re.findall(r"line\s+(.*,)", lines_X)
-
- linerror = (linex[-1:][0])
- #errotypes = roscz[-1:][0]
- pname, fname = os.path.split(check_filename[-1:][0].strip())
-
- self.jsonoutput['Type_Error'] = error_type
-
- self.jsonoutput['pathname'] = pname
-
- self.jsonoutput["filename"] = fname
-
- self.jsonoutput['error_line'] = linerror
-
- else:
- self.jsonoutput['error_message'] = code_messages
-
- #self.jsonoutput['error_message'] = str(output)
- else:
- self.jsonoutput['error_message'] = code_messages
-
-
- if self.jsonoutput.get('filename') == "":
- del self.jsonoutput['filename']
- try:
- self.jsonoutput['pathname'] = os.chdir()
- except:
- self.jsonoutput['pathname'] = "."
-
- if self.jsonoutput.get('error_message'):
- exc_type, exc_obj, exc_tb = sys.exc_info()
- try:
- _modules = os.path.abspath(str(sys.modules['__main__'].__file__ ))
- pname, fname = os.path.split(str(__file__).strip())
- self.jsonoutput['pathname'] = pname
- self.jsonoutput["filename"] = fname
-
- except:
- self.jsonoutput['pathname'] = "."
-
- #messages = self.jsonoutput["error_message"]
- self.jsonoutput['error_line'] = exc_tb.tb_lineno
- del self.jsonoutput["error_message"]
- self.jsonoutput['error_message'] = code_messages
-
- else:
- self.jsonoutput['error_line'] = exc_tb.tb_lineno
- self.jsonoutput['error_message'] = code_messages
-
- self.compliterror = None
-
- self.classeseror = None
-
- regex = re.findall(r"'(.*?)'", str(error_type).strip()) or re.findall(r"\"(.*?)\"", str(error_type).strip())
- if regex.__len__() != 0:
- self.jsonoutput['Type_Error'].update({'Type_Error': regex[-1:][0] })
- #self.jsonoutput['error_line'] = linex
- #regex = re.findall(r"'(.*?)'", str(errotypes).strip()) or re.findall(r"\"(.*?)\"", str(errotypes).strip())
-
-
- #exc_type, exc_obj, exc_tb = sys.exc_info()
- #pname, fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)
- #print("EROR2 ", exc_type, fname, exc_tb.tb_lineno)
- #print(tb)
-
- def auto(self):
-
- """
-
-
-
- """
- exc_type, exc_obj, exc_tb = sys.exc_info()
-
- pname, fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)
-
- typeserror = re.findall(r"'(.*?)'", str(exc_type).strip()) \
- or re.findall(r"\"(.*?)\"", str(exc_type).strip())
-
- self.jsonoutput = collections.OrderedDict()
-
- self.jsonoutput['Type_Error'] = typeserror[-1:][0]
-
- self.jsonoutput['pathname'] = pname
-
- self.jsonoutput["filename"] = fname
-
- self.jsonoutput['error_line'] = exc_tb.tb_lineno
-
- if self.jsonoutput.get('filename') == "":
- del self.jsonoutput['filename']
- try:
- self.jsonoutput['pathname'] = os.chdir()
- except:
- self.jsonoutput['pathname'] = "."
-
- self.jsonoutput['error_message'] = str(exc_obj)
-
- def capture(self, types=2, *args):
- if types == 2 or types == 1:
- def foo(exctype, value, tb):
- trace_back = traceback.extract_tb(tb)
- stack_trace = []
- for trace in trace_back:
- stack_trace.append("File : %s , Line : %d, Func.Name : %s, Message : %s" % (trace[0], trace[1], trace[2], trace[3]))
- print('My Error Information')
- print('Type:', exctype)
- print('Value:', value)
- print('Traceback:', "\n".join(stack_trace))
-
- sys.excepthook = foo
- else:
- def handle_exception(exc_type, exc_value, exc_traceback):
- if issubclass(exc_type, KeyboardInterrupt):
- sys.__excepthook__(exc_type, exc_value, exc_traceback)
- return
- #logging.critical(exc_value, exc_info=(exc_type, exc_value, exc_traceback))
-
-
- def handle_error(func):
- def __inner(*args, **kwargs):
- try:
- return func(*args, **kwargs)
- except Exception as e:
- stack_trace = []
- exc_type, exc_value, exc_tb = sys.exc_info()
- trace_back = traceback.extract_tb(exc_tb)
- for trace in trace_back:
- stack_trace.append("File : %s , Line : %d, Func.Name : %s, Message : %s" % (trace[0], trace[1], trace[2], trace[3]))
- handle_exception(exc_type, exc_value, exc_tb)
- print('My Error Information')
- print('Type:', exc_type)
- print('Value:', exc_value)
- print('Traceback:', "\n".join(stack_trace))
-
- return __inner
- return handle_error
-
-example = """Traceback (most recent call last):
-File "C:/Users/pc/Documents/WindowsPowerShell/libs/filterror.py", line 136, in
-import sdd
-ModuleNotFoundError: No module named 'sdd'
-"""
-
-
-
-"""""try:
- myfunc()
-except:
- type, val, tb = sys.exc_info()
- #print(sys.exc_info())
- traceback.clear_frames(tb)
-# some cleanup code
-gc.collect()
-# and then use the tb:
-if tb:
- print("EROR2 :", val, type, tb)
-
-import sys, os
-
-"""""
-#simple_test = ValidationError("", example)
-
-#Simple
-#simple_test.capture()
-#simple_test.manual()
-#print(simple_test.results)
-#sgsgs()
-
-#try:
-# gsgs()
-#except Exception as e:
-# with ValidationError() as simple_test:
-# simple_test.auto()
-# print(simple_test.results)
-
-#try:
-# gsgs()
-#except Exception as e:
-# with ValidationError("", messages=e) as simple_test:
-# simple_test.manual()
-# print(simple_test.results)
-
- #print("\nDete:", simple_test.__dict__)
-
-
-
-#@simple_test.capture(1)
-#def main():
-# raise RuntimeError("RuntimeError")
-
-
-#if __name__ == "__main__":
-# for _ in range(1, 20):
-# main()
-__all__ = ['PYTHON_ERORCODE', 'ValidationError']
\ No newline at end of file
diff --git a/libs/hellpper.py b/libs/hellpper.py
deleted file mode 100644
index ebb4fe8..0000000
--- a/libs/hellpper.py
+++ /dev/null
@@ -1,264 +0,0 @@
-import sys, os, platform
-import ctypes
-from ctypes import wintypes as w
-
-def version(osname=None):
- if osname:
- try:
- assert osname in ['windows', 'linux', 'darwin']
- except:
- if ['windows', 'linux', 'darwin'].index(osname):
- pass
- else:
- return
- versions = sys.getwindowsversion().major or platform.version().split('.')[0]
- else:
- versions = "0.2"
- return versions
-
-
-def os_platform():
- platforms = platform.system() or os.name or sys.platform
- if platforms.startswith(("windows", "Windows", "nt", "win")):
- platformname = "windows"
- elif platforms.startswith(("darwin", "Darwin", "Air", "air", "posix")):
- if os.name =="posix" and platform.system() == "Darwin":
- platformname = "darwin"
- else:
- platformname = "darwin"
- elif platforms.startswith(("posix","fedora", "Fedora", "unix", "Unix", "Linux", "linux", "ubuntu", 'Ubuntu', "debian"
- , "Debian", "arch", "Arch", "redhat", "Redhat")):
- platformname = "linux"
- else:
- platformname = "unknow"
- return platformname
-
-def sizewindow():
- from ctypes import windll, byref
- from ctypes.wintypes import SMALL_RECT
-
- STDOUT = -11
-
- hdl = windll.kernel32.GetStdHandle(STDOUT)
- rect = SMALL_RECT(0, 50, 150, 80) # (left, top, right, bottom)
- windll.kernel32.SetConsoleWindowInfo(hdl, True, byref(rect))
-
-#os.system("mode con cols=93 lines=45")
-def fontsize():
- import ctypes
-
- STD_OUTPUT_HANDLE = -11
-
- class COORD(ctypes.Structure):
- _fields_ = [("X", ctypes.c_short), ("Y", ctypes.c_short)]
-
- class CONSOLE_FONT_INFOEX(ctypes.Structure):
- _fields_ = [("cbSize", ctypes.c_ulong),
- ("nFont", ctypes.c_ulong),
- ("dwFontSize", COORD)]
-
- font = CONSOLE_FONT_INFOEX()
- font.cbSize = ctypes.sizeof(CONSOLE_FONT_INFOEX)
- font.nFont = 12
- font.dwFontSize.X = 10 # in your case X=10
- font.dwFontSize.Y = 18 # and Y=18
-
-
-
- handle = ctypes.windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
- ctypes.windll.kernel32.SetCurrentConsoleFontEx(
- handle,False, ctypes.byref(font))
-
-
-def getTerminalSize(platformname):
- #import platform
- current_os = platform.system()
- tuple_xy = None
- try:
- if platformname == 'windows':
- tuple_xy = _getTerminalSize_windows()
- if tuple_xy is None:
- tuple_xy = _getTerminalSize_tput()
- # needed for window's python in cygwin's xterm!
- if platformname == 'linux' or platformname == 'darwin' or current_os.startswith('CYGWIN'):
- tuple_xy = _getTerminalSize_linux()
- if tuple_xy is None:
- tuple_xy = (80, 25) # default value
- except:
- import shutil
- columns, lines = shutil.get_terminal_size((80, 20))
- return columns, lines
- return tuple_xy
-
-def _getTerminalSize_windows():
- res=None
- try:
- from ctypes import windll, create_string_buffer
-
- # stdin handle is -10
- # stdout handle is -11
- # stderr handle is -12
-
- h = windll.kernel32.GetStdHandle(-12)
- csbi = create_string_buffer(22)
- res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi)
- except:
- return None
- if res:
- import struct
- (bufx, bufy, curx, cury, wattr,
- left, top, right, bottom, maxx, maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw)
- sizex = right - left + 1
- sizey = bottom - top + 1
- return sizex, sizey
- else:
- return None
-
-def _getTerminalSize_tput():
- # get terminal width
- # src: http://stackoverflow.com/questions/263890/how-do-i-find-the-width-height-of-a-terminal-window
- try:
- import subprocess
- proc=subprocess.Popen(["tput", "cols"],stdin=subprocess.PIPE,stdout=subprocess.PIPE)
- output=proc.communicate(input=None)
- cols=int(output[0])
- proc=subprocess.Popen(["tput", "lines"],stdin=subprocess.PIPE,stdout=subprocess.PIPE)
- output=proc.communicate(input=None)
- rows=int(output[0])
- return (cols,rows)
- except:
- return None
-
-
-def _getTerminalSize_linux():
- def ioctl_GWINSZ(fd):
- try:
- import fcntl, termios, struct, os
- cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ,'1234'))
- except:
- return None
- return cr
- cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
- if not cr:
- try:
- fd = os.open(os.ctermid(), os.O_RDONLY)
- cr = ioctl_GWINSZ(fd)
- os.close(fd)
- except:
- pass
- if not cr:
- try:
- cr = (env['LINES'], env['COLUMNS'])
- except:
- return None
- return int(cr[1]), int(cr[0])
-
-
-all = ["fontsize"]
-
-from ptpython.python_input import PythonInput
-
-
-def main():
- prompt = PythonInput()
-
- text = prompt.app.run()
- print("You said: " + text)
-
-import pathlib
-import collections
-collections.Callable = collections.abc.Callable
-try:
- import readline
-except ImportError:
- import pyreadline as readline
-
-def complete_path(text, state):
- incomplete_path = pathlib.Path(text)
- if incomplete_path.is_dir():
- completions = [p.as_posix() for p in incomplete_path.iterdir()]
- elif incomplete_path.exists():
- completions = [incomplete_path]
- else:
- exists_parts = pathlib.Path('.')
- for part in incomplete_path.parts:
- test_next_part = exists_parts / part
- if test_next_part.exists():
- exists_parts = test_next_part
-
- completions = []
- for p in exists_parts.iterdir():
- p_str = p.as_posix()
- if p_str.startswith(text):
- completions.append(p_str)
- return completions[state]
-
-
-# we want to treat '/' as part of a word, so override the delimiters
-#readline.set_completer_delims(' \t\n;')
-#readline.parse_and_bind("tab: complete")
-#readline.set_completer(complete_path)
-#while True:
-# print(input('tab complete a filename: '))
-#if __name__ == '__main__':
- #sizex,sizey=getTerminalSize(os_platform())
- #print('width =',sizex,'height =',sizey)
- #answer = prompt('Give me some input: ')
- #print('You said: %s' % answer)
-
-
-# From the documentation at
-# https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messageboxw
-MB_OKCANCEL = 1
-IDCANCEL = 2
-IDOK = 1
-
-user32 = ctypes.WinDLL('user32')
-MessageBox = user32.MessageBoxW
-MessageBox.argtypes = w.HWND,w.LPCWSTR,w.LPCWSTR,w.UINT
-MessageBox.restype = ctypes.c_int
-
-ret = MessageBox(None, 'message', 'title', MB_OKCANCEL)
-if ret == IDOK:
- print('OK')
-elif ret == IDCANCEL:
- print('CANCEL')
-else:
- print('ret =', ret)
-
-import curses, textwrap
-from editor.editor import Editor
-
-
-def xxsx():
- stdscr = curses.initscr()
- xxx = Editor(stdscr, box=False)
- while True:
- xxx.stdscr.move(xxx.cur_pos_y, xxx.cur_pos_x)
- loop = xxx.get_key()
- if loop is False:
- break
- xxx.display()
- print("\n".join(["".join(i) for i in xxx.text]))
-
-screen = curses.initscr()
-screen.immedok(True)
-try:
- screen.border(0)
- screen.addstr("Hello! Dropping you in to a command prompt...\n")
- box1 = curses.newwin(20, 40, 6, 50)
- box2 = curses.newwin(18,38,7,51)
- box1.immedok(True)
- box2.immedok(True)
- text = "I want all of this text to stay inside its box. Why does it keep going outside its borders?"
- text = "The quick brown fox jumped over the lazy dog."
- text = "A long time ago, in a galaxy far, far away, there lived a young man named Luke Skywalker."
- box1.box()
- box2.addstr(1, 0, textwrap.fill(text, 38))
-
- #box1.addstr("Hello World of Curses!")
-
- screen.getch()
-
-finally:
- curses.endwin()
\ No newline at end of file
diff --git a/libs/helperegex.py b/libs/helperegex.py
deleted file mode 100644
index 6074cc6..0000000
--- a/libs/helperegex.py
+++ /dev/null
@@ -1,535 +0,0 @@
-import re, os, string, psutil, random
-from functools import lru_cache
-from typing import List, Tuple, TypeVar, Generic, Any
-try:
- from itertools import izip as zip
-
- range = xrange
-except:
- pass
-
-def analyze_line(line):
- """
- Analyzes a line of text to determine if it contains assignment or comparison.
-
- Parameters:
- - line (str): The line of text to analyze.
-
- Returns:
- - str: Description of the line content.
- """
- # Regex pattern to find single '=' (assignment) and multiple '=' (comparison)
- assignment_pattern = re.compile(r'^\s*[$]?\w+\s*=\s*.*$') #re.compile(r'^\s*\w+\s*=\s*.*$')
- comparison_pattern = re.compile(r'==|>=|<=|!=|>|\<')
-
- # Check for assignment
- if assignment_pattern.match(line):
- # Further check if it contains comparison operators
- if comparison_pattern.search(line):
- return 0
- else:
- return 1
- else:
- return 0
-
-
-def rremovelist(data: list):
- unicude = {}.fromkeys(data)
- return list(unicude)[::-1]
-
-
-def remove_duplicates_from_right(lst: list):
- seen = set()
- result: List[Tuple[str, int, Any]] = []
-
- # Iterasi dari akhir ke awal
- for item in reversed(lst):
- if item not in seen:
- seen.add(item)
- result.append(item)
-
- # Membalikkan hasil untuk mempertahankan urutan awal
- result.reverse()
-
- # Buat list akhir dengan mempertahankan posisi index
- final_result: List[Tuple[str, int, Any]] = []
- seen.clear()
-
- for item in lst:
- if item in result and item not in seen:
- final_result.append(item)
- seen.add(item)
-
- return final_result
-
-def lremove_duplicates_from_left(lst: list, limit: int = 1, total: int = 0):
- removal_count = 0
- result: List[Tuple[str, int, Any]] = []
- if total == 0:
- total = round(lst.__len__() / 2)
- if total % 2 == 0:
- total = int(total / 2)
- elif total % 3:
- total = round(total / 2)
- else:
- pass
- if lst.__len__() > limit + total:
- for item in lst:
- if removal_count < limit:
- removal_count += 1
- else:
- result.append(item)
- else:
- result = lst
- return result
-
-#clean
-def clean_string(strings, regex, newstring):
- s = re.sub(regex, newstring, strings)
- # Hapus spasi putih di awal dan akhir string
- s = s.strip()
- return s
-
-# Fungsi ini menghapus karakter terakhir didalam input string
-
-def remove_laststring(data, spec, new=""):
- maxreplace = data.count(spec) - 1
- return new.join(data.rsplit(spec, maxreplace))
-
-
-# Fungsi ini mereplace karakter terakhir didalam input string dan menggantikannya dengan karakter baru
-
-def rreplace(s:str, old:str, new:str, count:int):
- return (s[::-1].replace(old[::-1], new[::-1], count))[::-1]
-
-
-# Fungsi ini mereplace karakter berdasarkan posisi didalam input string dan menggantikannya dengan karakter baru
-
-def replaceusinglenght(data: str, lenght: int):
- return data[-data.__len__() : data.__len__() - lenght]
-
-
-# Fungsi ini mereplace satu kata atau karakter berdasarkan posisi didalam input string dan menggantikannya dengan karakter baru
-
-def replace_char_at_position(s, position, new_char):
- if position < 0 or position >= len(s):
- return s
-
- # Create a new string with the character replaced
- new_string = s[:position] + new_char + s[position + 1 :]
-
- return new_string
-
-
-# Fungsi ini mereplace semua kata atau karakter berdasarkan posisi didalam input string dan menggantikannya dengan karakter baru menggunakan regex
-
-def replacebypost(data: str, new_character: str, regex=r"\((.*)\)"):
- for position in re.finditer(regex, data):
- if position:
- if (isinstance(position, list) and position.__len__() == 2) or (
- isinstance(position, tuple) and position.__len__() == 2
- ):
- data = (
- data[: position[0]] + new_character + data[position[1] :].strip()
- ) # data[position[1]+1:]
- return data
-
-
-# Fungsi ini untuk memanipulasi string dengan mengganti karakter khusus tertentu dengan karakter lain, kecuali karakter yang berada dalam tanda kutip.
-
-def cleanstring(data: str, spec: str, place: str, maximun=None, xplace=""):
- """
- data: String input yang akan diproses.
- spec: Karakter khusus yang akan diganti.
- place: Karakter pengganti untuk spec.
- maximun: Batas maksimum jumlah penggantian.
- xplace: Karakter pengganti sementara untuk spec di luar tanda kutip."""
-
- sessionplace: List[Tuple[str]] = ["", ""] # yang satu kalimat asli dan satunya kalimat pengganti
- if "$(-%places%-)" in data:
- numb, stop = 0, True
- while stop != False:
- places = "$(-%places%-{number})".format(number=str(numb))
- if stop != False and places not in data:
- data = data.replace("$(-%places%-)", places)
- sessionplace[0] = "$(-%places%-)"
- sessionplace[1] = places
- stop = False
- break
- numb += 1
- numb = 0
-
- try:
- select_x = re.findall(r"\"(.*?)\"", data.strip()) or re.findall(
- r"'(.*?)'", data.strip()
- )
- if select_x.__len__() != 0:
- # mob = data.replace(spec, "${place}".format(place=place))
- for xe in select_x:
- new = xe.replace(spec, "$(-%places%-)")
- data = data.replace(xe, new)
-
- # replacebypost(regex=r"$\((.*)\)")
- if isinstance(maximun, int):
- maxcount = data.count("$(-%places%-)")
- if maximun > 0 and maximun <= maxcount:
- data = data.replace(spec, "").replace(
- "$(-%places%-)", place, maximun
- )
- else:
- data = data.replace(spec, xplace).replace("$(-%places%-)", place)
- else:
- if maximun == None:
- maximun = int(data.count(spec))
- data = data.replace(spec, xplace, maximun)
- except:
- pass
-
- if sessionplace[1].__len__() > 1:
- data = data.replace(sessionplace[1], sessionplace[0])
- return data
-
-
-def fullmacth(macth, data):
- return re.fullmatch(macth, data)
-
-
-def searching(patternRegex, data: str):
- # Define the regex pattern to match the function name
- pattern = re.compile(patternRegex)
-
- # Search for the pattern in the function definition
- match = pattern.search(data)
-
- # If a match is found, return the function name
- if match:
- return match.group(1)
- else:
- return None
-
-####menemkan karakter diluar tanda kutipp
-def count_character_outside_quotes(input_string, character):
- """
- Count occurrences of a specified character outside of quotes.
-
- Parameters:
- - input_string (str): The input string to process.
- - character (str): The character to count.
-
- Returns:
- - int: The number of occurrences of the character outside quotes.
- """
- inside_quotes = False
- count = 0
- quote_char = ''
-
- for i, char in enumerate(input_string):
- if char in ('"', "'"):
- if not inside_quotes:
- # Entering a quoted section
- inside_quotes = True
- quote_char = char
- elif char == quote_char:
- # Exiting a quoted section
- inside_quotes = False
- elif char == character and not inside_quotes:
- count += 1
-
- return count
-
-
-##### menghitung spasi sebelum karakter
-def count_leading_spaces(line):
- """
- Count the number of leading spaces in a given string.
-
- Parameters:
- - line (str): The string to check.
-
- Returns:
- - int: The number of leading spaces.
- """
- match = re.match(r'^\s*', line)
- if match:
- return len(match.group(0))
- return 0
-
-# Menemukan posisi text didalam variable string dengan mencocokan pola menggunakan regex dan menyimpannya kedalam tuple
-
-def findpositions(regex, string: str):
- """
- regex: Pola regex yang digunakan untuk menemukan teks
- string: String input yang akan diproses.
- """
- postion_list: List[Tuple[str, int, Any]] = []
- try:
- compiles = re.compile(regex, re.IGNORECASE)
- for match in compiles.finditer(string):
- if match:
- # postion_list.append( (match.group(), match.span()))
- postion_list.append(zip([match.group()], [match.span()]))
- except:
- for match in re.finditer(regex, string):
- if match:
- # postion_list.append( (match.group(), match.span()))
- postion_list.append(zip([match.group()], [match.span()]))
- return tuple(postion_list)
-
-
-# findpostion("'(.*?)?'", "'sssss' hshs 'cbcbcbc'")
-
-
-def split(regex, string: str, inject: str = "", post: list = [], mode: bool = False):
- result: List[Tuple[str, int, Any]] = []
- if isinstance(regex, list) or isinstance(regex, tuple):
- indices = list(regex)
- if mode == False:
- if indices.count(None) >= 1:
- if indices[-1:][0] == None:
- pass
- else:
- indices.append(None)
- else:
- indices.append(None)
- result = [string[i:j] for i, j in zip(indices, indices[1:])]
- else:
- if indices[0] == 0:
- pass
- else:
- indices.remove(0)
- indices.insert(0, 0)
- if indices[-1:][0] == string.__len__():
- pass
- else:
- indices.append(string.__len__())
- indices = sorted(set(indices))
- result = [string[x:y] for x, y in zip(indices, indices[1:])]
-
- elif isinstance(regex, str):
- try:
- if string.count(regex) > 1:
- position = findpositions(regex=regex, string=string)
- if position.__len__() != 0:
- all_lines: List[Tuple[str, int, Any]] = []
- for x in position:
- for c in x:
- all_lines.append(c[1][0])
- if all_lines.__len__() != 0:
- x = split(all_lines, string, mode=True)
- if regex.count(" ") != 0:
- for y in range(x.__len__()):
- x[y] = x[y].replace(regex, "")
- else:
- for y in range(x.__len__()):
- x[y] = x[y].replace(regex, "")
-
- x = list(filter(lambda v: v != "", x))
- result = x
- else:
- assert 12 == 11
- else:
- try:
- position = findpositions(regex=regex, string=string)
- if position.__len__() != 0:
- all_lines, textsearch = [[], []]
- for x in position:
- for c in x:
- all_lines.append(c[1][0])
- textsearch.append(c[0])
- if all_lines.__len__() != 0:
- x = split(all_lines, string, mode=True)
- for y in range(x.__len__()):
- for regex in textsearch:
- x[y] = x[y].replace(regex, "")
-
- result = x
- else:
- assert 12 == 11
- except:
- m = re.search(regex, string)
- result = [string[: m.start()], string[m.end() :]]
- except:
- result = string.split(str(regex))
- elif isinstance(regex, int):
- result = string[:regex], string[regex:]
- else:
- return
-
- result = list(filter(lambda x: x != "", result))
- if post.__len__() != 0:
- if inject:
- lenght = result.__len__()
- for p in post:
- if p > lenght:
- result.insert(lenght, inject)
- else:
- result.insert(p, inject)
-
- return result
-
-
-# Fungsi ini cukup efisien dalam menemukan semua posisi di mana substring dimulai dalam string lengkap, dengan menggunakan pendekatan langsung dan fallback regex jika diperlukan.
-
-def find_indices_of_substring(full_string, sub_string):
- return [
- index
- for index in range(len(full_string))
- if full_string.startswith(sub_string, index)
- ] or [m.start() for m in re.finditer(re.escape(sub_string), full_string)]
-
-
-def searchmissing(s, t):
- """
- s: Kalimat input yang ingin dibandingkan.
- t: Kalimat input yang ingin dibandingkan.
- \ncontoh:
- s = "I like eating apples and bananas"
- t = "I apples bananas"
- missing_words = searchmissing(s, t)
- print(missing_words) # Output: ['like', 'eating', 'and']
- """
- res: List[Tuple[str, int, Any]] = [] # Daftar untuk menyimpan kata-kata yang hilang
- t_words = t.split() # Memecah string `t` menjadi daftar kata
- s_words = s.split() # Memecah string `s` menjadi daftar kata
- size = s_words.__len__() # Mendapatkan jumlah kata dalam `s`
- i = 0 # Indeks untuk melacak kata dalam `t`
- j = 0 # Indeks untuk melacak kata dalam `s`
- for j in range(size):
- if s_words[j] == t_words[i]:
- i += 1 # Jika kata dalam `s` sama dengan kata dalam `t`, pindah ke kata berikutnya di `t`
- if i >= t_words.__len__():
- break # Jika semua kata dalam `t` ditemukan, hentikan loop
- else:
- res.append(
- s_words[j]
- ) # Jika kata dalam `s` tidak ada di `t`, tambahkan ke hasil
- # Tambahkan kata-kata yang tersisa dalam `s` setelah indeks `j`
- for k in range(j + 1, size):
- res.append(s_words[k])
- return res # Kembalikan daftar kata yang hilang
-
-
-def split_by_length(s, length):
- # Create a list to store the split parts
- parts: List[Tuple[str, int, Any]] = []
-
- # Loop through the string, incrementing by the length each time
- for i in range(0, len(s), length):
- parts.append(s[i : i + length])
-
- return parts
-
-def find_regex_in_list(pattern, lst: list, limit: int = None):
- # Compile the regex pattern
- regex = re.compile(pattern, re.IGNORECASE)
-
- # List to store the positions
- positions: List[Tuple[str, int, Any]] = []
-
- # Iterate through the list and search for the pattern
- for i, item in enumerate(lst):
- if limit == 0:
- break
- if regex.search(item):
- positions.append(i)
-
- if limit != None and limit != 0:
- limit -= 1
- return positions
-
-
-def find_and_split(text, pattern):
- """
- Menemukan teks setelah pola tertentu dan membagi string satu kali berdasarkan pola tersebut.
- :param text: String input yang akan diproses
- :param pattern: Pola regex yang digunakan untuk menemukan teks
- :return: Tuple yang berisi dua bagian string yang dipisahkan oleh pola
- """
- match = re.search(pattern, text)
- if match:
- # Menemukan posisi akhir dari pola yang cocok
- split_pos = match.end()
- # Memisahkan string satu kali berdasarkan posisi akhir dari pola yang cocok
- part1 = text[:split_pos]
- part2 = text[split_pos:]
- return part1, part2
- else:
- return text, None
-
-
-# Fungsi mengembalikan daftar posisi karakter yang berada di luar kutipan.
-
-def find_unquoted_brace_positions(text, searchchar):
- positions: List[Tuple[int, Any]] = []
- in_single_quote = False
- in_double_quote = False
-
- for i, char in enumerate(text):
- if char == "'" and not in_double_quote:
- in_single_quote = not in_single_quote
- elif char == '"' and not in_single_quote:
- in_double_quote = not in_double_quote
- elif char == searchchar and not in_single_quote and not in_double_quote:
- positions.append(i)
-
- return positions
-
-def extract_quoted_text(text):
- # Pola regex untuk menemukan teks di dalam tanda kutip tunggal atau ganda
- pattern = r'(["\'])(.*?)(\1)'
- matches = re.findall(pattern, text)
-
- # Mengambil hanya teks di dalam tanda kutip
- quoted_texts = [match[1] for match in matches]
- return quoted_texts
-
-def reversed(datalist: list | tuple):
- process = datalist[::-1]
- return process
-
-def reindent_function_blocks(input_string):
- """
- Re-indent function blocks in the given string.
-
- Parameters:
- - input_string (str): The input string containing function blocks.
-
- Returns:
- - str: The re-indented string.
- """
- def indent_lines(match):
- """
- Indent lines within a function block.
- """
- lines = match.group(0).splitlines()
- indented_lines = [lines[0]] # Keep the function declaration line
- indented_lines += [" " + line if line.strip() else "" for line in lines[1:]]
- return "\n".join(indented_lines)
-
- # Use regex to find function blocks and apply indentation
- pattern = re.compile(r'function|class\s+\w+\s*\{[^{}]*\}', re.DOTALL)
- result = re.sub(pattern, indent_lines, input_string)
-
- return result
-
-def add_indent_to_lines(text, indent=' '):
- """
- Add a specified indentation to each line of a given text.
-
- Parameters:
- - text (str): The input text to be indented.
- - indent (str): The string to use as indentation (default is 4 spaces).
-
- Returns:
- - str: The text with added indentation.
- """
- # Split text into lines
- lines = text.splitlines()
-
- # Add indentation to each line
- indented_lines = [indent + line for line in lines]
-
- # Join lines back into a single string
- indented_text = '\n'.join(indented_lines)
-
- return indented_text
diff --git a/libs/https.py b/libs/https.py
deleted file mode 100644
index 53f8437..0000000
--- a/libs/https.py
+++ /dev/null
@@ -1,112 +0,0 @@
-try:
- import httpx as requests
-except ImportError:
- import requests
-from requests.exceptions import ConnectionError, Timeout, HTTPError
-import importlib, inspect
-
-DEFAULT_HEADERS = {
- "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36",
- "connection": "keep-alive",
-}
-
-
-class Fetch:
- def __init__(self, headers: dict = {}):
- self.headers = headers or DEFAULT_HEADERS
- try:
- self.sync_client = requests.Client(headers=self.headers)
- self.async_client = requests.AsyncClient(headers=self.headers)
- except AttributeError:
- self.sync_client = requests.Session()
- self.sync_client.headers.update(self.headers)
- self.async_client = self.sync_client
-
- def check_class_in_package(package_name, class_name):
- try:
- # Import the package
- package = importlib.import_module(package_name)
- # Cek apakah kelas ada di dalam modul
- if hasattr(package, class_name):
- cls = getattr(package, class_name)
- # Pastikan itu adalah kelas, bukan atribut atau fungsi
- if inspect.isclass(cls):
- return True, "ClassFound"
- return False, "ClassNotFound"
- except ModuleNotFoundError:
- return False, "ModuleNotFoundError"
-
- self.check_class_in_package = check_class_in_package
-
- def _request_sync(self, method, url, max_retries=5, timeout=5, **kwargs):
- retries = 0
- while retries < max_retries:
- try:
- response = self.sync_client.request(
- method, url, timeout=timeout, **kwargs
- )
- response.raise_for_status()
- return response
- except (ConnectionError, Timeout) as e:
- retries += 1
- print(f"Connection failed ({e}). Retrying {retries}/{max_retries}...")
- except HTTPError as http_err:
- print(f"HTTP error occurred: {http_err}")
- break
- raise Exception(f"Failed to connect to {url} after {max_retries} retries.")
-
- async def _request_async(self, method, url, max_retries=5, timeout=5, **kwargs):
- retries = 0
- itsAsync, _ = self.check_class_in_package(requests.__name__, "AsyncClient")
- while retries < max_retries:
- try:
- if itsAsync:
- response = await self.async_client.request(
- method, url, timeout=timeout, **kwargs
- )
- else:
- response = self.async_client.request(
- method, url, timeout=timeout, **kwargs
- )
- response.raise_for_status()
- return response
- except (ConnectionError, Timeout) as e:
- retries += 1
- print(f"Connection failed ({e}). Retrying {retries}/{max_retries}...")
- except HTTPError as http_err:
- print(f"HTTP error occurred: {http_err}")
- break
- raise Exception(f"Failed to connect to {url} after {max_retries} retries.")
-
- def get(self, url, max_retries=5, timeout=5, async_mode=False, **kwargs):
- """Performs a GET request, either synchronously or asynchronously."""
- if async_mode:
- return self._request_async("GET", url, max_retries, timeout, **kwargs)
- else:
- return self._request_sync("GET", url, max_retries, timeout, **kwargs)
-
- def post(self, url, max_retries=5, timeout=5, async_mode=False, **kwargs):
- """Performs a POST request, either synchronously or asynchronously."""
- if async_mode:
- return self._request_async("POST", url, max_retries, timeout, **kwargs)
- else:
- return self._request_sync("POST", url, max_retries, timeout, **kwargs)
-
- def __enter__(self):
- """Enter the runtime context related to this object."""
- return self
-
- def __exit__(self, exc_type, exc_value, traceback):
- """Exit the runtime context related to this object."""
- self.sync_client.close()
-
- async def __aenter__(self):
- """Enter the async runtime context related to this object."""
- return self
-
- async def __aexit__(self, exc_type, exc_value, traceback):
- """Exit the async runtime context related to this object."""
- try:
- await self.async_client.aclose()
- except:
- self.async_client.close()
diff --git a/libs/randoms.py b/libs/randoms.py
deleted file mode 100644
index 86304cb..0000000
--- a/libs/randoms.py
+++ /dev/null
@@ -1,134 +0,0 @@
-import random
-try:
- from .helperegex import split, findpositions, searchmissing, re as regex
-except:
- from helperegex import split, findpositions, searchmissing, re as regex
-
-
-paterns = regex.compile(r'''
- \s # one whitespace character, though I think this is perhaps unnecessary
- \d* # 0 or more digits
- \. # a dot
- \d{2} # 2 digits
- ''', regex.VERBOSE)
-
-
-
-def randomDigits(digits):
- try:
- lower = 10**(digits-1)
- upper = 10**digits - 1
- return random.randint(lower, upper)
- except:
- lower = "0"*(digits-1)
- upper = "9"*(digits-1)
- if lower != "" and upper != "":
- lower = "".join(["1", lower])
- upper = "".join(["9", upper])
- else:
- lower = "1"
- upper = "9"
- return random.randrange(int(lower), int(upper), upper.__len__())
-
-
-
-
-
-#string = "echo 'fhfhf & ddd' && 'fhfhfddd' && cd \"fhfhf && ddd\""
-"""""def compiltes(string:str):
- if string == "":
- return []
- _output = []
-
- xout = findpositions(r"'(.*?)?'|\"(.*?)?\"", string)
- lenght = xout.__len__()
- cv = 0
- checkpoint = ()
- checkpoint2 = []
- place = "<%%"
- newstring = string
- keyplace = ""
-
- maxpend = []
- digit = 1
- xct = regex.finditer(r"<%%(.*?)?>", string)
- for xc in xct:
- if xc:
- if xc.group(0).count(" ") == 0:
- #search = regex.search("/d", xc.group(0))
- maxdigit = xc.group(1)
- maxpend.append(int(maxdigit))
- #print(maxpend)
-
- if maxpend.__len__() != 0:
- _s_s = str(max(maxpend)).__len__()+1
- digit = int(_s_s)
-
- score_record = []
- for y in xout:
- for x in y:
-
- if x[0].find("&&") and x[0].count("&&") != 0:
- keyplace = "&&"
- elif x[0].find("&") and x[0].count("&") != 0:
- keyplace = "&"
-
- #print(keyplace)
- if lenght != 0:
-
- score = randomDigits(digit)
- if score not in score_record:
- score_record.append(score)
- else:
- if score in score_record:
- score = randomDigits(digit)
- if score in score_record:
- score = randomDigits(digit)
- score_record.clear()
- else:
- score_record.append(score)
-
- newword = "".join([place, str(score), ">"])
- newpad = regex.sub(r"&&|&", newword, x[0]) #.replace(keyplace, newword)####bugss
- if x[0].count("&") == 0:
- cv +=1
- #pass
- else:
- if lenght == xout.__len__() - cv:
- pass
- else:
- minus = newword.__len__()+keyplace.__len__()
- checkpoint2.append((x[1][0]+minus-keyplace.__len__(), x[1][-1:][0]+minus+1))
-
-
- string = string.replace(x[0], newpad, 1)
- checkpoint = checkpoint+((x[0], newword, score, keyplace), )
-
- lenght -= 1
-
- #print("checkpoint:", checkpoint, "\n")
- #print("Newkeys:", string)
- for x in split("&", string):
- for y in checkpoint:
-
- if x.count(y[1]) !=0:
- oldstring = regex.search("'(.*?)?'", x) or regex.search("\"(.*?)?\"", x)
- if oldstring:
- x = x.replace(oldstring.group(0), y[0], 1)
- # print(1)
- else:
- x = x.replace(y[1], y[3])
- # print(2)
- #print(oldstring, y)
-
- _output.append(x.strip())
- checkpoint = tuple(set(checkpoint)- set(checkpoint))
- return _output"""""
-
- #print("Missing :", searchmissing(string, newstring))
- #print(checkpoint, checkpoint2)
- #print(split( checkpoint2[0], string))
-
- #import re
- #re.sub
- #print(string.split(keyplace))
diff --git a/libs/readme.txt b/libs/readme.txt
deleted file mode 100644
index 8b13789..0000000
--- a/libs/readme.txt
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/libs/system_manajemen.py b/libs/system_manajemen.py
deleted file mode 100644
index d42d3b5..0000000
--- a/libs/system_manajemen.py
+++ /dev/null
@@ -1,84 +0,0 @@
-import signal
-import sys
-import psutil
-import os
-import threading
-import time
-import multiprocessing, signal
-from concurrent.futures import ProcessPoolExecutor, as_completed
-
-def terminate_process(pid):
- try:
- process = psutil.Process(pid)
- process.terminate() # Mengirim sinyal untuk menghentikan proses
- process.wait(timeout=3) # Menunggu proses benar-benar berhenti
- except psutil.NoSuchProcess:
- os._exit(0)
- except psutil.TimeoutExpired:
- os._exit(0)
-
-
-def set_low_priority(pid):
- def signal_handler(signum, frame):
- if signum == signal.SIGINT: # Ctrl+C
- print("[INFO] Detected Ctrl+C. Shutting down gracefully...")
- terminate_process(pid)
- # SIGTSTP handling only if available
- elif signum == getattr(signal, 'SIGTSTP', None): # Ctrl+Z (Unix-like only)
- print("[INFO] Detected Ctrl+Z. Process suspended.")
- terminate_process(pid)
-
- # Set up signal handlers
- signal.signal(signal.SIGINT, signal_handler)
- if hasattr(signal, 'SIGTSTP'):
- signal.signal(signal.SIGTSTP, signal_handler)
-
- if os.name == 'nt':
- process = psutil.Process(pid)
- process.nice(psutil.BELOW_NORMAL_PRIORITY_CLASS)
- else: # Unix-like systems
- os.nice(19)
-
-
-def initializer(stop_event):
- global stop_flag
- stop_flag = stop_event
- signal.signal(signal.SIGINT, handle_child_signal)
-
-def handle_child_signal(signum, frame):
- print(f"Child process received signal {signum}.")
- stop_flag.set()
-
-class SafeProcessExecutor:
- def __init__(self, max_workers=None):
- # Membuat Event menggunakan Manager untuk sinkronisasi antar proses
- self.manager = multiprocessing.Manager()
- self.stop_flag = self.manager.Event()
- self.executor = ProcessPoolExecutor(
- max_workers=max_workers,
- initializer=initializer,
- initargs=(self.stop_flag,)
- )
- self.futures = []
-
- def submit(self, fn, *args, **kwargs):
- """Submit a function to the executor for execution."""
- future = self.executor.submit(fn, *args, **kwargs)
- self.futures.append(future)
- return future
-
- def shutdown(self, wait=True):
- """Shut down the executor and terminate all running processes."""
- self.stop_flag.set() # Menghentikan semua proses yang sedang berjalan
- self.executor.shutdown(wait=wait)
-
- def get_results(self):
- """Get results from all completed futures."""
- results = []
- for future in as_completed(self.futures):
- try:
- result = future.result()
- results.append(result)
- except Exception as e:
- results.append(f"Error retrieving result: {e}")
- return results
diff --git a/libs/timeout.py b/libs/timeout.py
deleted file mode 100644
index 333aaca..0000000
--- a/libs/timeout.py
+++ /dev/null
@@ -1,47 +0,0 @@
-import os, sys, psutil, shutil
-try:
- from GPUtil import getGPUs
-except:
- getGPUs = None
-
-def timeout_v1():
- # Get CPU or GPU usage percentage
- if getGPUs==None:
- cpu_usage = psutil.cpu_percent()
- else:
- cpu_usage = getGPUs()
- # Get RAM usage percentage
- ram_usage = psutil.virtual_memory().percent
- # Divide both percentages by 1000
- cpu_usage_divided = cpu_usage / 100
- ram_usage_divided = ram_usage / 100
- if cpu_usage_divided<0.1:
- cpu_usage_divided = cpu_usage_divided*2
- if ram_usage_divided<0.1:
- ram_usage_divided = ram_usage_divided*2
- data = [round(cpu_usage_divided, 1), round(ram_usage_divided, 1)]
- return round(sum(data) / data.__len__(), 1)
-
-
-def timeout_v2(pid=os.getpid()):
- # Mendapatkan ID proses saat ini
- process = psutil.Process(pid)
-
- # Mendapatkan penggunaan CPU dan RAM untuk proses tersebut
- if getGPUs==None:
- cpu_usage = psutil.cpu_percent()
- else:
- cpu_usage = getGPUs()
- ram_usage = process.memory_percent() # Persentase penggunaan RAM oleh proses
-
- # Pembagian dengan 100 untuk mendapatkan nilai antara 0 dan 1
- cpu_usage_divided = cpu_usage / 100
- ram_usage_divided = ram_usage / 100
-
- if cpu_usage_divided < 0.1:
- cpu_usage_divided = cpu_usage_divided * 2
- if ram_usage_divided < 0.1:
- ram_usage_divided = ram_usage_divided * 2
-
- data = [round(cpu_usage_divided, 1), round(ram_usage_divided, 1)]
- return round(sum(data) / len(data), 1)
diff --git a/libs/titlecommand.py b/libs/titlecommand.py
deleted file mode 100644
index 35938f7..0000000
--- a/libs/titlecommand.py
+++ /dev/null
@@ -1,25 +0,0 @@
-import os, ctypes, logging
-try:
- from .errrorHandler import complex_handle_errors
-except:
- try:
- from errrorHandler import complex_handle_errors
- except:
- from libs.errrorHandler import complex_handle_errors
-
-@complex_handle_errors(loggering=logging, nomessagesNormal=False)
-def set_console_title(title):
- """Mengatur judul jendela Command Prompt."""
- ctypes.windll.kernel32.SetConsoleTitleW(title)
-
-@complex_handle_errors(loggering=logging, nomessagesNormal=False)
-def get_console_title():
- """Mendapatkan judul jendela Command Prompt."""
- # Buffer untuk menyimpan judul
- buffer_size = 256
- buffer = ctypes.create_unicode_buffer(buffer_size)
-
- # Memanggil fungsi API Windows untuk mendapatkan judul jendela
- ctypes.windll.kernel32.GetConsoleTitleW(buffer, buffer_size)
-
- return buffer.value
\ No newline at end of file
From fa812a16ca7aae3949705fc7a9d65c66c5644f58 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sun, 1 Sep 2024 12:59:52 +0700
Subject: [PATCH 04/29] Delete requirements.txt
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
requirements.txt | 6 ------
1 file changed, 6 deletions(-)
delete mode 100644 requirements.txt
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index 41cee53..0000000
--- a/requirements.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-windows-curses==2.3.3
-urwid==2.6.15
-pyperclip==1.9.0
-psutil
-logging
-httpx
From 0a2acac0f315a5ba78890c8dd308f483aa623165 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sun, 1 Sep 2024 13:00:52 +0700
Subject: [PATCH 05/29] Delete supernano.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
supernano.py | 974 ---------------------------------------------------
1 file changed, 974 deletions(-)
delete mode 100644 supernano.py
diff --git a/supernano.py b/supernano.py
deleted file mode 100644
index 0212ecd..0000000
--- a/supernano.py
+++ /dev/null
@@ -1,974 +0,0 @@
-import urwid
-import pyperclip
-import os, sys, shutil, logging, time, threading, argparse
-from datetime import datetime
-
-try:
- from libs.helperegex import findpositions
- from libs.titlecommand import get_console_title, set_console_title
- from libs.cmd_filter import shorten_path, validate_folder
- from libs.errrorHandler import complex_handle_errors
- from libs.system_manajemen import set_low_priority, SafeProcessExecutor
- from libs.timeout import timeout_v2, timeout_v1
- from libs.filemanager import (
- StreamFile,
- ModuleInspector,
- validate_file,
- create_file_or_folder,
- resolve_relative_path_v2,
- resolve_relative_path,
- all_system_paths,
- )
-except:
- try:
- from .helperegex import findpositions
- from .titlecommand import get_console_title, set_console_title
- from .cmd_filter import shorten_path, validate_folder
- from .errrorHandler import complex_handle_errors
- from .system_manajemen import set_low_priority, SafeProcessExecutor
- from .timeout import timeout_v2, timeout_v1
- from .filemanager import (
- StreamFile,
- ModuleInspector,
- validate_file,
- create_file_or_folder,
- resolve_relative_path_v2,
- resolve_relative_path,
- all_system_paths,
- )
- except:
- from helperegex import findpositions
- from titlecommand import get_console_title, set_console_title
- from cmd_filter import shorten_path, validate_folder
- from errrorHandler import complex_handle_errors
- from system_manajemen import set_low_priority, SafeProcessExecutor
- from timeout import timeout_v2, timeout_v1
- from filemanager import (
- StreamFile,
- ModuleInspector,
- validate_file,
- create_file_or_folder,
- resolve_relative_path_v2,
- resolve_relative_path,
- all_system_paths,
- )
-
-
-set_low_priority(os.getpid())
-#########mendapatkan process terbaik tanpa membebani ram dan cpu
-thisfolder, _x = all_system_paths
-__version__ = "2.1.0"
-
-fileloogiing = os.path.join(thisfolder, "cache", "file_browser.log").replace("\\", "/")
-
-if not os.path.isfile(fileloogiing):
- open(fileloogiing, "a+")
-elif os.path.getsize(fileloogiing) > 0:
- with open(fileloogiing, "wb+") as f:
- f.truncate(0)
-
-for handler in logging.root.handlers[:]:
- logging.root.removeHandler(handler)
-
-logging.basicConfig(
- filename=fileloogiing,
- filemode="w",
- encoding=sys.getfilesystemencoding(),
- format="%(asctime)s, %(msecs)d %(name)s %(levelname)s [ %(filename)s-%(module)s-%(lineno)d ] : %(message)s",
- datefmt="%H:%M:%S",
- level=logging.ERROR,
-)
-logging.getLogger("urwid").disabled = True
-logger = logging.getLogger("urwid")
-for handler in logger.handlers[:]:
- logger.removeHandler(handler)
-
-
-def setTitle(title: str):
- """
- Fungsi setTitle bertugas untuk mengatur judul konsol (console title) berdasarkan parameter title yang diberikan.\n
- Jika inputan title memiliki panjang lebih dari 30 karakter maka potong karakternya
- """
- process = title
- Getitles = get_console_title()
- if os.path.isdir(process) or os.path.isfile(process):
- length = int(process.__len__() / 2)
- if length < 28:
- x = process.__len__()
- nexts = int(50 - x) - (x / 2)
- if nexts < 28:
- length = int((28 - nexts) + nexts)
- else:
- length = nexts
- elif length > 50:
- length = 28
- process = shorten_path(process, length)
-
- if Getitles.startswith("Win-SuperNano"):
- output = str("Win-SuperNano {titles}".format(titles=process))
- else:
- output = title
- set_console_title(output)
-
-
-@complex_handle_errors(loggering=logging, nomessagesNormal=False)
-def parse_args():
- """
- Fungsi parse_args bertugas untuk mendapatkan\menangkap argument konsol (console title) yang diberikan oleh user.\n
- """
- parser = argparse.ArgumentParser(
- description="An extension on nano for editing directories in CLI."
- )
- parser.add_argument(
- "path",
- default=os.path.split(thisfolder)[0],
- nargs="?",
- type=str,
- help="Target file or directory to edit.",
- )
- args = vars(parser.parse_args())
- path = args.get("path", ".").strip().replace("\\", "/")
- if os.path.exists(path):
- if validate_folder(path=path):
- pass
- else:
- logging.error(f"ERROR - {path} path cannot access")
- exit()
- else:
- logging.error(f"ERROR - {path} path does not exist")
- exit()
-
- return resolve_relative_path_v2(path).replace("\\", "/")
-
-
-class PlainButton(urwid.Button):
- """
- Class PlainButton bertugas untuk mengkoustomisasi button dan menghilangkan karakter < dan >.\n
- """
-
- button_left = urwid.Text("")
- button_right = urwid.Text("")
-
-
-class NumberedEdit(urwid.WidgetWrap):
- def __init__(self, edit_text="", multiline=True):
- self.edit = urwid.Edit(edit_text, multiline=multiline)
- self.line_numbers = urwid.Text("")
- self.update_line_numbers()
-
- columns = urwid.Columns([("fixed", 4, self.line_numbers), self.edit])
- super().__init__(urwid.AttrMap(columns, None, focus_map="reversed"))
-
- # Connect the 'change' signal from the internal Edit widget
- urwid.connect_signal(self.edit, "change", self._on_change)
-
- def update_line_numbers(self):
- text = self.edit.get_edit_text()
- lines = text.splitlines()
- line_numbers = "\n".join([f"{i+1:>3}" for i in range(len(lines))])
- self.line_numbers.set_text(line_numbers)
-
- def keypress(self, size, key):
- key = super().keypress(size, key)
- self.update_line_numbers()
- return key
-
- def mouse_event(self, size, event, button, col, row, focus):
- handled = super().mouse_event(size, event, button, col, row, focus)
- if handled:
- self.update_line_numbers()
- return handled
-
- def _on_change(self, edit, new_text):
- self.update_line_numbers()
- urwid.emit_signal(self, "change", self, new_text)
-
- def set_edit_text(self, text):
- """Set the text of the edit widget and update line numbers."""
- self.edit.set_edit_text(text)
- self.update_line_numbers()
-
- def get_edit_text(self):
- """Get the text from the edit widget."""
- return self.edit.get_edit_text()
-
-
-urwid.register_signal(NumberedEdit, ["change"])
-
-
-class SuperNano:
- """
- Kelas SuperNano yang sedang Anda kembangkan adalah text editor berbasis console yang menggunakan Python 3.6 ke atas dengan dukungan urwid[curses].
-
- Pembuat: Ramsyan Tungga Kiansantang (ID) | Github: LcfherShell
-
- Tanggal dibuat: 21 Agustus 2024
-
- Jika ada bug silahkan kunjungi git yang telah tertera diatas
- """
-
- @complex_handle_errors(loggering=logging, nomessagesNormal=False)
- def __init__(self, start_path="."):
- "Mengatur path awal, judul aplikasi, widget, dan layout utama. Juga mengatur alarm untuk memuat menu utama dan memulai loop aplikasi."
- self.current_path = start_path
- self.current_pathx = self.current_path
-
- self.current_file_name = None # Track current file name
- self.undo_stack, self.redo_stack = [[], []] # Stack for undo # Stack for redo
- self.overlay_POPUP = None # Overlay untuk popup
- self.module_package_Python = ModuleInspector() # memuat module python
- self.module_package_PythonC = self.module_package_Python.curents
-
- # Set title
- setTitle("Win-SuperNano v{version}".format(version=__version__))
-
- # Create widgets
- """
- 1.loading menu
- 2. main menu: search, list file or folder, and inspect module python
- """
-
- ######Create widgets modulepython menu
- def create_button(module_name):
- button = PlainButton(module_name)
- urwid.connect_signal(button, "click", self.inspect_module, module_name)
- return urwid.AttrMap(button, None, focus_map="reversed")
-
- self.listmodules_from_package_Python = urwid.SimpleFocusListWalker(
- [
- create_button(module)
- for module in self.module_package_Python.get_python_module(sys.path)
- ]
- )
- # Footer text and ListBox for scrolling
- self.Text_Deinspect_modules_from_package_Python = urwid.Text(
- "Select a module to inspect."
- )
- MenuText_Inspect_modules_from_package_Python = urwid.ListBox(
- urwid.SimpleFocusListWalker(
- [self.Text_Deinspect_modules_from_package_Python]
- )
- )
- Box_Deinspect_modules_from_package_Python = urwid.BoxAdapter(
- MenuText_Inspect_modules_from_package_Python, 14
- ) # Set max height for the footer
- # Use a Frame to wrap the main content and footer
- self.Inspect_modules_from_package_Python = urwid.Frame(
- body=urwid.LineBox(
- urwid.ListBox(self.listmodules_from_package_Python),
- title="Python Modules",
- ),
- footer=Box_Deinspect_modules_from_package_Python,
- )
-
- ###Create widgets loading menu
- self.title_loading_widget = urwid.Text(
- "Win-SuperNano v{version} CopyRight: LcfherShell@{year}\n".format(
- version=__version__, year=datetime.now().year
- ),
- align="center",
- )
- self.loading_widget = urwid.Text("Loading, please wait...", align="center")
- self.main_layout = urwid.Filler(
- urwid.Pile([self.title_loading_widget, self.loading_widget]),
- valign="middle",
- )
-
- # Create main menu
- self.main_menu_columns = urwid.Columns([])
- self.main_menu_pile = urwid.Pile([self.main_menu_columns])
- self.status_msg_footer_text = urwid.Text(
- "Press ctrl + q to exit, Arrow keys to navigate"
- )
- self.main_footer_text = urwid.Text(
- "Ctrl+S : Save file Ctrl+D : Delete File Ctrl+Z : Undo Edit Ctrl+Y : Redo Edit Ctrl+E : Redirect input Ctrl+R : Refresh UI ESC: Quit "
- )
-
- # Event loop
- self.loop = urwid.MainLoop(self.main_layout, unhandled_input=self.handle_input)
- self.loading_alarm = self.loop.set_alarm_in(
- round(timeout_v1() * timeout_v2(), 1) + 1,
- lambda loop, user_data: self.load_main_menu(),
- )
- self.system_alarm = None
-
- @complex_handle_errors(loggering=logging, nomessagesNormal=False)
- def load_main_menu(self):
- "Menyiapkan dan menampilkan menu utama setelah periode loading, dan menghapus alarm loading."
- # self.loading_widget.set_text("Press key R")
- set_low_priority(os.getpid())
- self.loop.remove_alarm(self.loading_alarm) # Hentikan alarm
- self.loading_alarm = None
- self.switch_to_secondary_layout()
-
- def switch_to_secondary_layout(self):
- "Mengubah layout aplikasi ke menu utama yang telah disiapkan."
- self.setup_main_menu()
- if self.loading_alarm != None:
- self.loop.remove_alarm(
- self.loading_alarm
- ) # Hentikan alarm loading jika masih ada
- self.loading_alarm = None
- self.loop.widget = self.main_layout
-
- @complex_handle_errors(loggering=logging, nomessagesNormal=False)
- def setup_main_menu(self):
- "Menyiapkan dan mengatur widget untuk menu utama, termasuk daftar file, editor teks, dan tombol-tombol fungsional. Mengatur layout untuk tampilan aplikasi."
- # Define widgets
- self.file_list = urwid.SimpleFocusListWalker(self.get_file_list())
- self.file_list_box = urwid.ListBox(self.file_list)
- self.text_editor = NumberedEdit(multiline=True)
- self.current_focus = 0 # 0 for textbox1, 1 for textbox2
- # Wrap text_editor with BoxAdapter for scrollable content
- self.text_editor_scrollable = urwid.LineBox(
- urwid.Filler(self.text_editor, valign="top"), title="TextBox"
- )
-
- # Define menu widgets
- self.quit_button = PlainButton("Quit", align="center")
- urwid.connect_signal(self.quit_button, "click", self.quit_app)
-
- self.search_edit = urwid.Edit(
- "Search, Rename or Create: ", multiline=False, align="left"
- )
- search_limited = urwid.BoxAdapter(
- urwid.Filler(self.search_edit, valign="top"), height=1
- )
-
- self.search_button = PlainButton("Execute", align="center")
- urwid.connect_signal(self.search_button, "click", self.in_search_)
-
- padded_button = urwid.Padding(
- self.search_button, align="center", width=("relative", 50)
- ) # Tombol berada di tengah dengan lebar 50% dari total layar
- padded_button = urwid.AttrMap(
- padded_button, None, focus_map="reversed"
- ) # Mengatur warna saat tombol difokuskan
-
- urwid.connect_signal(
- self.text_editor.base_widget, "change", self.set_focus_on_click, 0
- )
- urwid.connect_signal(
- self.search_edit.base_widget, "change", self.set_focus_on_click, 1
- )
-
- # Menu layout
- self.main_menu_columns = urwid.Columns(
- [
- (
- "weight",
- 3,
- urwid.AttrMap(search_limited, None, focus_map="reversed"),
- ),
- (
- "weight",
- 1,
- urwid.AttrMap(padded_button, None, focus_map="reversed"),
- ),
- (
- "weight",
- 2,
- urwid.AttrMap(self.quit_button, None, focus_map="reversed"),
- ),
- # (
- # "weight",
- # 4,
- # urwid.AttrMap(urwid.Pile(menu_items), None, focus_map="reversed"),
- # ),
- ]
- )
-
- self.main_menu_pile = urwid.Pile([self.main_menu_columns])
-
- # Layout
- self.main_layout = urwid.Frame(
- header=self.main_menu_pile,
- body=urwid.Columns(
- [
- (
- "weight",
- 1,
- urwid.LineBox(self.file_list_box, title="Directory Files"),
- ),
- (
- "weight",
- 1,
- urwid.AttrMap(
- self.Inspect_modules_from_package_Python,
- None,
- focus_map="reversed",
- ),
- ),
- (
- "weight",
- 3,
- self.text_editor_scrollable,
- ),
- ]
- ),
- footer=urwid.Pile([self.status_msg_footer_text, self.main_footer_text]),
- )
- self.loop.set_alarm_in(timeout_v2(), self.update_uiV2)
- self.system_alarm = self.loop.set_alarm_in(
- timeout_v2() + 1,
- lambda loop, user_data: self.system_usage(),
- )
- urwid.TrustedLoop(self.loop).set_widget(self.main_layout)
-
- @complex_handle_errors(loggering=logging, nomessagesNormal=False)
- def create_modules_menus(self, listmodulename: list):
- def create_button(module_name):
- button = PlainButton(module_name)
- urwid.connect_signal(button, "click", self.inspect_module, module_name)
- return urwid.AttrMap(button, None, focus_map="reversed")
-
- return [create_button(module) for module in listmodulename]
-
- @complex_handle_errors(loggering=logging, nomessagesNormal=False)
- def inspect_module(self, button, module_name):
- result = self.module_package_Python.inspect_module(module_name)
- if result:
- if "module" in result.keys():
- keys = result.keys()
- if "classes" in keys or "functions" in keys or "variables" in keys:
- result_text = f"Module: {result['module']}\n\nGlobal Variables:\n"
- result_text += ", ".join(result["variables"])
- if result["classes"]:
- result_text += "\n\nClass:\n"
- for cls in result["classes"]:
- if cls["name"]:
- result_text += f"Class: {cls['name']}\n"
- result_text += " Variables:\n"
- result_text += (
- " " + "\n > ".join(cls["variables"]) + "\n\n"
- )
- if cls["functions"]:
- result_text += " Function:\n"
- for func in cls["functions"]:
- result_text += (
- f" > {func['name']}{func['params']}\n\n"
- )
- for funcs in result["functions"]:
- if funcs["name"]:
- result_text += f"\nFunction: {funcs['name']}\n"
- result_text += f" > {funcs['name']}{funcs['params']}\n\n"
- self.Text_Deinspect_modules_from_package_Python.set_text(
- result_text
- )
- else:
- self.Text_Deinspect_modules_from_package_Python.set_text(
- "Error inspecting module."
- )
-
- @complex_handle_errors(loggering=logging, nomessagesNormal=False)
- def setup_popup(self, options, title, descrip: str = ""):
- "Menyiapkan konten dan layout untuk menu popup dengan judul, deskripsi, dan opsi yang diberikan."
- # Konten popup
- menu_items = []
- if descrip:
- menu_items = [urwid.Text(descrip, align="center"), urwid.Divider("-")]
-
- # Tambahkan opsi ke dalam menu popup
- for option in options:
- menu_items.append(option)
-
- # Tambahkan tombol untuk menutup popup
- menu_items.append(PlainButton("Close", on_press=self.close_popup))
-
- # Buat listbox dari opsi yang sudah ada
- popup_content = urwid.ListBox(urwid.SimpleFocusListWalker(menu_items))
-
- # Tambahkan border dengan judul
- self.popup = urwid.LineBox(popup_content, title=title)
-
- def on_option_selected(self, button):
- "Menangani pilihan opsi dari popup dengan menutup popup dan mengembalikan label opsi yang dipilih."
- urwid.emit_signal(button, "click")
- getbutton = button.get_label()
- self.close_popup(None)
- return getbutton
-
- @complex_handle_errors(loggering=logging, nomessagesNormal=False)
- def show_popup(self, title: str, descrip: str, menus: list):
- "Menampilkan popup menu dengan judul, deskripsi, dan daftar opsi yang diberikan."
- # Siapkan popup dengan judul, descrip, dan opsi
- self.setup_popup(title=title, descrip=descrip, options=menus)
-
- # Tentukan ukuran dan posisi popup
- popup_width = 35
- popup_height = 25
- self.overlay_POPUP = urwid.Overlay(
- self.popup,
- self.main_layout,
- "center",
- ("relative", popup_width),
- "middle",
- ("relative", popup_height),
- )
- self.loop.widget = self.overlay_POPUP
-
- @complex_handle_errors(loggering=logging, nomessagesNormal=False)
- def close_popup(self, button):
- "Menutup popup menu dan mengembalikan tampilan ke layout utama."
- self.overlay_POPUP = None
- self.loop.widget = self.main_layout
-
- @complex_handle_errors(loggering=logging, nomessagesNormal=False)
- def get_file_list(self):
- "Mengambil daftar file dan direktori di path saat ini, termasuk opsi untuk naik satu level di direktori jika bukan di direktori root."
- files = []
- if self.current_path != ".": # Cek apakah bukan di direktori root
- button = PlainButton("...")
- urwid.connect_signal(button, "click", self.go_up_directory)
- files.append(urwid.AttrMap(button, None, focus_map="reversed"))
-
- for f in os.listdir(f"{self.current_path}"):
- if os.path.isdir(resolve_relative_path(self.current_path, f)):
- f = f + "/"
- button = PlainButton(f)
- urwid.connect_signal(button, "click", self.open_file, f)
- files.append(urwid.AttrMap(button, None, focus_map="reversed"))
- return files
-
- def handle_input(self, key):
- "Menangani input keyboard dari pengguna untuk berbagai tindakan seperti keluar, menyimpan, menghapus, undo, redo, copy, paste, dan refresh UI."
- if key in ("ctrl q", "ctrl Q", "esc"):
- self.show_popup(
- menus=[PlainButton("OK", on_press=lambda _x: self.quit_app())],
- title="Confirm Quit",
- descrip="Are you sure you Quit",
- )
-
- elif key in ("ctrl s", "ctrl S"):
- # self.save_file()
- self.show_popup(
- menus=[
- PlainButton(
- "OK",
- on_press=lambda _x: self.close_popup(None)
- if self.save_file()
- else None,
- )
- ],
- title="Save File",
- descrip="Are you sure you want to save the file changes",
- )
-
- elif key in ("ctrl d", "ctrl D"):
- self.show_popup(
- menus=[
- PlainButton(
- "OK",
- on_press=lambda _x: self.close_popup(None)
- if self.delete_file()
- else None,
- )
- ],
- title="Delete File",
- descrip="Are you sure you want to delete the file",
- )
- elif key in ("ctrl z", "ctrl Z"):
- self.undo_edit()
- elif key in ("ctrl y", "ctrl Y"):
- self.redo_edit()
- elif key in ("ctrl c", "ctrl C"):
- self.copy_text_to_clipboard()
- elif key in ("ctrl v", "ctrl V"):
- self.paste_text_from_clipboard()
- elif key in ("ctrl r", "ctrl R"):
- self.switch_to_secondary_layout()
- elif key in ("f1", "ctrl e", "ctrl E"):
- self.current_focus = 1 if self.current_focus == 0 else 0
- self.status_msg_footer_text.set_text(f"focus {self.current_focus}")
-
- @complex_handle_errors(loggering=logging, nomessagesNormal=False)
- def get_current_edit(self):
- "Mengembalikan widget edit yang sedang difokuskan (text editor atau search edit)."
- if self.current_focus == 0:
- return self.text_editor.edit.base_widget
- elif self.current_focus == 1:
- return self.search_edit.base_widget
- return None
-
- def set_focus_on_click(self, widget, new_edit_text, index):
- "Mengatur fokus pada widget edit berdasarkan klik dan indeks."
- self.current_focus = index
-
- @complex_handle_errors(loggering=logging, nomessagesNormal=False)
- def copy_text_to_clipboard(self):
- "Menyalin teks dari widget edit yang sedang aktif ke clipboard."
- current_edit = self.get_current_edit()
- if current_edit:
- if hasattr(current_edit, "edit_pos") and hasattr(
- current_edit, "get_edit_text"
- ):
- self.status_msg_footer_text.set_text("Text copied to clipboard.")
- cursor_position = current_edit.edit_pos
- pyperclip.copy(
- current_edit.get_edit_text()[cursor_position:]
- or current_edit.get_edit_text()
- )
-
- @complex_handle_errors(loggering=logging, nomessagesNormal=False)
- def paste_text_from_clipboard(self):
- "Menempelkan teks dari clipboard ke widget edit yang sedang aktif."
- pasted_text = pyperclip.paste() # Mengambil teks dari clipboard
- current_edit = self.get_current_edit()
- if current_edit:
- if hasattr(current_edit, "edit_pos") and hasattr(
- current_edit, "get_edit_text"
- ):
- current_text = (
- current_edit.get_edit_text()
- ) # Mendapatkan teks saat ini di widget Edit
- cursor_position = (
- current_edit.edit_pos
- ) # Mendapatkan posisi kursor saat ini
-
- # Membagi teks berdasarkan posisi kursor
- text_before_cursor = current_text[:cursor_position]
- text_after_cursor = current_text[cursor_position:]
-
- # Gabungkan teks sebelum kursor, teks yang ditempelkan, dan teks setelah kursor
- new_text = text_before_cursor + pasted_text + text_after_cursor
-
- # Set teks baru dan sesuaikan posisi kursor
- current_edit.set_edit_text(new_text)
- current_edit.set_edit_pos(cursor_position + len(pasted_text))
- self.status_msg_footer_text.set_text("Text paste from clipboard.")
-
- @complex_handle_errors(loggering=logging, nomessagesNormal=False)
- def go_up_directory(self, button):
- "Naik satu level ke direktori atas dan memperbarui daftar file."
- self.current_path = os.path.dirname(self.current_path)
- self.file_list[:] = self.get_file_list()
-
- @complex_handle_errors(loggering=logging, nomessagesNormal=False)
- def open_file(self, button, file_name):
- "Membuka file yang dipilih, membaca isinya, dan menampilkannya di text editor. Jika itu adalah direktori, berpindah ke direktori tersebut."
- file_path = resolve_relative_path(self.current_path, file_name)
- _c, ext = os.path.splitext(file_path)
- if os.path.isdir(file_path):
- if validate_folder(file_path):
- try:
- sys.path.remove(self.current_path)
- except:
- pass
- self.current_path = file_path
- self.file_list[:] = self.get_file_list()
- else:
- self.status_msg_footer_text.set_text("Folder access denied!")
- else:
- if validate_folder(os.path.dirname(file_path)) and validate_file(
- file_path, os.path.getsize(file_path) or 20, 6
- ):
- try:
- with open(
- file_path, "r+", encoding=sys.getfilesystemencoding()
- ) as f:
- content = f.read()
- except UnicodeDecodeError:
- with open(file_path, "r+", encoding="latin-1") as f:
- content = f.read()
- content = content.replace("\t", " " * 4)
- self.undo_stack.append(content)
- self.text_editor.set_edit_text(content)
- if self.current_file_name != file_path:
- modulefile = self.module_package_Python.get_moduleV2(file_path)
- if modulefile:
- if modulefile != self.module_package_Python.curents:
- self.listmodules_from_package_Python[
- :
- ] = self.create_modules_menus(modulefile)
- self.module_package_Python.curents = modulefile
-
- else:
- self.listmodules_from_package_Python[
- :
- ] = self.create_modules_menus(self.module_package_PythonC)
-
- self.current_file_name = file_name # Track the current file name
- # if str(ext).lower() in ( ".pyx", ".pyz", ".py"):
- # self.listmodules_from_package_Python[:] = self.modules_menus(self.current_path)
- if self.module_package_Python.languages:
- self.Inspect_modules_from_package_Python.body.set_title(
- self.module_package_Python.languages["languages"] + " Modules"
- )
- self.main_layout.body.contents[2][0].set_title(file_name)
-
- else:
- self.status_msg_footer_text.set_text("File access denied!")
-
- if str(ext).lower().startswith((".pyx", ".pyz", ".py")) != True:
- self.Text_Deinspect_modules_from_package_Python.set_text(
- "Select a module to inspect."
- )
-
- @complex_handle_errors(loggering=logging, nomessagesNormal=False)
- def save_file(self):
- "Menyimpan perubahan yang dilakukan pada file saat ini dan mengembalikan status keberhasilan."
- if self.current_file_name:
- file_path = os.path.join(self.current_path, self.current_file_name)
- if os.path.isfile(file_path):
- try:
- with open(
- file_path, "w+", encoding=sys.getfilesystemencoding()
- ) as f:
- f.write(self.text_editor.get_edit_text())
- except:
- with open(file_path, "w+", encoding="latin-1") as f:
- f.write(self.text_editor.get_edit_text())
- self.status_msg_footer_text.set_text("File saved successfully!")
- return True
-
- @complex_handle_errors(loggering=logging, nomessagesNormal=False)
- def delete_file(self):
- "Menghapus file yang dipilih dan memperbarui daftar file serta text editor dan mengembalikan status keberhasilan."
- if self.current_file_name:
- file_path = os.path.join(self.current_path, self.current_file_name)
- if os.path.isfile(file_path):
- os.remove(file_path)
- self.text_editor.set_edit_text("")
- self.file_list[:] = self.get_file_list()
- self.status_msg_footer_text.set_text("File deleted successfully!")
- self.current_file_name = None # Clear the current file name
- else:
- self.status_msg_footer_text.set_text("File does not exist!")
- return True
-
- @complex_handle_errors(loggering=logging, nomessagesNormal=False)
- def save_undo_state(self):
- "Menyimpan status saat ini dari text editor ke stack undo dan mengosongkan stack redo."
- # Save the current content of the text editor for undo
- current_text = self.text_editor.get_edit_text()
- self.undo_stack.append(current_text)
- self.redo_stack.clear() # Clear redo stack on new change
-
- @complex_handle_errors(loggering=logging, nomessagesNormal=False)
- def undo_edit(self):
- "Melakukan undo terhadap perubahan terakhir pada text editor dengan mengembalikan status dari stack undo."
- if self.undo_stack:
- # Save the current state to redo stack
- self.redo_stack.append(self.text_editor.get_edit_text())
-
- # Restore the last state
- last_state = self.undo_stack.pop()
- self.text_editor.set_edit_text(last_state)
- self.status_msg_footer_text.set_text("Undo performed.")
-
- @complex_handle_errors(loggering=logging, nomessagesNormal=False)
- def redo_edit(self):
- "Melakukan redo terhadap perubahan terakhir yang diundo dengan mengembalikan status dari stack redo."
- if self.redo_stack:
- # Save the current state to undo stack
- self.undo_stack.append(self.text_editor.get_edit_text())
-
- # Restore the last redone state
- last_state = self.redo_stack.pop()
- self.text_editor.set_edit_text(last_state)
- self.status_msg_footer_text.set_text("Redo performed.")
-
- @complex_handle_errors(loggering=logging, nomessagesNormal=False)
- def highlight_text(self, search_text):
- text = self.text_editor.get_edit_text()
- result = []
- # Pisahkan teks menjadi sebelum, pencarian, dan sesudahnya
- for x in findpositions(f"{search_text}", text):
- if x:
- _x = list(x)
- result.append(str(_x[0][1][1]))
- if result.__len__() > 8:
- return "Total: {total} Pos: {posts}".format(
- total=result.__len__(), posts=", ".join(result[:8]) + "..."
- )
- return "Total: {total} Pos: {posts}".format(
- total=result.__len__(), posts=", ".join(result)
- )
-
- @complex_handle_errors(loggering=logging)
- def in_search_(self, button):
- "Mencari file atau folder berdasarkan input pencarian, membuka file jika ditemukan, atau memperbarui daftar file jika folder ditemukan."
- search_query = self.search_edit.get_edit_text().replace("\\", "/").strip()
- if search_query:
- if ":" in search_query and not search_query.startswith("@[select]"):
- if os.path.isfile(search_query):
- dirname, file_name = os.path.dirname(
- search_query
- ), os.path.basename(search_query)
- try:
- with open(search_query, "r+", encoding="utf-8") as f:
- content = f.read()
- except UnicodeDecodeError:
- with open(search_query, "r+", encoding="latin-1") as f:
- content = f.read()
- content = content.replace("\t", " " * 4)
-
- self.undo_stack.append(content)
- self.text_editor.set_edit_text(content)
- self.current_file_name = file_name # Track the current file name
- self.main_layout.body.contents[1][0].set_title(file_name)
-
- elif os.path.isdir(search_query):
- dirname = search_query
- else:
- x, _y = os.path.split(search_query)
- if self.current_path.replace("\\", "/") == x.replace(
- "\\", "/"
- ) and os.path.isdir(x):
- search_query = str(create_file_or_folder(search_query))
- self.update_ui()
- self.file_list[:] = self.get_file_list()
-
- dirname = None
-
- if dirname:
- self.current_path = dirname
- self.file_list[:] = self.get_file_list()
- self.status_msg_footer_text.set_text(
- f"Search results for '{search_query}'"
- )
- else:
- search_resultsFile = [
- f for f in os.listdir(self.current_path) if search_query in f
- ]
- search_resultsModule = [
- module
- for module in self.module_package_Python.curents
- if search_query in module
- ]
- search_resultsHighlight_Text = self.highlight_text(search_query)
-
- if search_resultsFile and search_resultsModule:
- self.listmodules_from_package_Python[:] = self.create_modules_menus(
- search_resultsModule
- )
- self.file_list[:] = self.create_file_list(search_resultsFile)
- self.status_msg_footer_text.set_text(
- f"Search results for '{search_query}'"
- )
- elif search_resultsFile:
- self.file_list[:] = self.create_file_list(search_resultsFile)
- self.status_msg_footer_text.set_text(
- f"Search results for '{search_query}'"
- )
- else:
- if search_resultsModule:
- self.listmodules_from_package_Python[
- :
- ] = self.create_modules_menus(search_resultsModule)
- self.file_list[:] = self.get_file_list()
- self.status_msg_footer_text.set_text(
- f"Search results for '{search_query}'"
- )
- elif search_resultsHighlight_Text and not search_query.startswith(
- "@[files]"
- ):
- self.status_msg_footer_text.set_text(
- f"Search results for '{search_query}' {search_resultsHighlight_Text}"
- )
- else:
- if (
- search_query.startswith("@[select]")
- and search_query.find("[@rename]") > -1
- ):
- x = search_query.replace("@[select]", "", 1).split(
- "[@rename]", 1
- )
- if x.__len__() == 2:
- getREName = [
- f
- for f in os.listdir(self.current_path)
- if x[0] in f
- ]
- if getREName.__len__() > 0:
- oldfilesorfolder, newplace = [
- os.path.join(self.current_path, getREName[0]),
- os.path.join(self.current_path, x[1]),
- ]
- try:
- os.rename(oldfilesorfolder, newplace)
- self.status_msg_footer_text.set_text(
- f"Rename {getREName[0]} success"
- )
- self.update_ui()
- self.file_list[:] = self.get_file_list()
- except:
- pass
- else:
- self.status_msg_footer_text.set_text(
- f"Search results for {search_query}"
- )
-
- else:
- self.file_list[:] = self.get_file_list()
- self.listmodules_from_package_Python[:] = self.create_modules_menus(
- self.module_package_Python.curents
- )
- self.status_msg_footer_text.set_text("")
-
- @complex_handle_errors(loggering=logging)
- def create_file_list(self, files):
- "Membuat daftar widget untuk file yang ditemukan sesuai hasil pencarian."
- widgets = []
- for f in files:
- if os.path.isdir(os.path.join(self.current_path, f)):
- f = f + "/"
- button = PlainButton(f)
- urwid.connect_signal(button, "click", self.open_file, f)
- widgets.append(urwid.AttrMap(button, None, focus_map="reversed"))
- return widgets
-
- def system_usage(self):
- "Memantau penggunaan CPU dan menampilkan peringatan jika konsumsi CPU tinggi."
- timemming = timeout_v1()
- if timemming > 0.87:
- self.status_msg_footer_text.set_text("High CPU utilization alert")
-
- def update_ui(self):
- "Memperbarui tampilan UI aplikasi."
- self.loop.draw_screen()
-
- def update_uiV2(self, *args, **kwargs):
- "Memperbarui tampilan UI aplikasi."
- self.loop.set_alarm_in(timeout_v2(), self.update_uiV2)
- self.loop.draw_screen()
-
- def quit_app(self, button=None):
- "Menghentikan aplikasi dan menghapus alarm sistem jika ada."
- if self.system_alarm != None:
- self.loop.remove_alarm(self.system_alarm) # Hentikan alarm
- self.system_alarm = None
- raise urwid.ExitMainLoop()
-
- def run(self):
- "Memulai loop utama urwid untuk menjalankan aplikasi."
- self.loop.run()
-
-
-@complex_handle_errors(loggering=logging)
-def main(path: str):
- app = SuperNano(start_path=path)
- app.run()
-
-
-if __name__ == "__main__":
- set_low_priority(os.getpid())
- #########mendapatkan process terbaik tanpa membebani ram dan cpu
-
- safe_executor = SafeProcessExecutor(
- max_workers=2
- ) #########mendapatkan process terbaik tanpa membebani cpu
- safe_executor.submit(main, path=parse_args())
- time.sleep(timeout_v2())
- safe_executor.shutdown(
- wait=True
- ) ###mmenunggu process benar-benar berhenti tanpa memaksanya
- rd = StreamFile(
- file_path=fileloogiing,
- buffer_size=os.path.getsize(fileloogiing) + 2,
- print_delay=timeout_v2(),
- ) #########mendapatkan process terbaik membaca file logging tanpa membebani cpu
- for r in rd.readlines():
- print(r)
- rd.eraseFile() # membersihkan loggging
- rd.close()
From 21904a58c3241c6c5469e797ade4df930f1bc650 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sun, 1 Sep 2024 13:05:41 +0700
Subject: [PATCH 06/29] Add files via upload
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
install.py | 140 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 140 insertions(+)
create mode 100644 install.py
diff --git a/install.py b/install.py
new file mode 100644
index 0000000..21553fa
--- /dev/null
+++ b/install.py
@@ -0,0 +1,140 @@
+import os, sys, time, re
+import subprocess
+import requests as api_requests
+import zipfile
+
+
+username, modulename, filedir = "LcfherShell", "SuperNano", os.path.dirname(os.path.realpath(__file__)).replace("\\", "/")
+urlgitversion = f"https://api.github.com/repos/{username}/{modulename}/branches"
+urlfilezipper = f"https://github.com/{username}/{modulename}/archive/refs/heads/lastverion.zip"
+
+def search_like_regex(data_list, pattern):
+ """ Mencari elemen dalam daftar yang cocok dengan pola regex. """
+ regex = re.compile(pattern, re.IGNORECASE)
+ return [item for item in data_list if regex.search(item)]
+
+def install_packages(packages:list):
+ """ Menginstal beberapa paket Python menggunakan pip. """
+ try:
+ subprocess.check_call([sys.executable, "-m", "pip", "install", *packages])
+ print(f"Packages {', '.join(packages)} installed successfully.")
+ return True
+ except subprocess.CalledProcessError as e:
+ print(f"Error during package installation: {e}")
+ except Exception as e:
+ print(f"General error during package installation: {e}")
+ return False
+
+def module_version():
+ """ Mendapatkan semua versi modul dari GitHub. """
+ maxreplay = 12
+ while maxreplay > 0:
+ try:
+ response = api_requests.get(urlgitversion)
+ response.raise_for_status()
+ results = response.json()
+ return [x["name"] for x in results if x["name"] != "main"]
+ except:
+ time.sleep(2)
+ maxreplay -= 1
+ return []
+
+def download_file(url:str, local_filename:str):
+ """ Mengunduh file dari URL dengan retry jika terjadi kegagalan. """
+ attempt, max_retries, wait_time = 0, 3, 1
+ while attempt < max_retries:
+ try:
+ with api_requests.get(url, stream=True) as response:
+ response.raise_for_status()
+ with open(local_filename, "wb") as file:
+ for chunk in response.iter_content(chunk_size=None):
+ file.write(chunk)
+ if os.path.isfile(local_filename):
+ print(f"File downloaded successfully: {local_filename}")
+ return local_filename
+ except api_requests.RequestException as e:
+ attempt += 1
+ print(f"Download failed: {e}. Attempt {attempt} of {max_retries}.")
+ time.sleep(wait_time)
+ print(f"All download attempts failed for: {url}")
+ return None
+
+def unzip_folder_contents(zip_path: str, extract_to: str, folder_name: str):
+ """
+ Mengekstrak isi folder tertentu di dalam ZIP ke luar folder, langsung ke direktori tujuan.
+
+ :param zip_path: Path dari file ZIP.
+ :param extract_to: Direktori tujuan untuk mengekstrak file.
+ :param folder_name: Nama folder di dalam ZIP yang isinya ingin diekstrak.
+ """
+ with zipfile.ZipFile(zip_path, "r") as zip_ref:
+ # Dapatkan semua file di dalam ZIP
+ all_files = zip_ref.namelist()
+
+ # Filter file yang berada di dalam folder tertentu
+ folder_files = [f for f in all_files if f.startswith(folder_name + "/")]
+
+ if not folder_files:
+ print(f"Folder '{folder_name}' not found in ZIP.")
+ return
+
+ # Ekstrak file yang berada di dalam folder tanpa menyertakan folder_name di path tujuan
+ for file in folder_files:
+ # Hapus nama folder dari path untuk mengekstrak file langsung ke direktori tujuan
+ extracted_path = os.path.join(
+ extract_to, os.path.relpath(file, folder_name)
+ )
+
+ # Jika file adalah folder, buat direktori
+ if file.endswith("/"):
+ os.makedirs(extracted_path, exist_ok=True)
+ else:
+ # Jika file, ekstrak file
+ os.makedirs(os.path.dirname(extracted_path), exist_ok=True)
+ with zip_ref.open(file) as source, open(extracted_path, "wb") as target:
+ target.write(source.read())
+
+ print(f"Contents of folder '{folder_name}' successfully extracted to: {extract_to}")
+
+
+def install_script():
+ versions, session = sorted(module_version()), 4
+ if not versions:
+ print("Failed to get module version.")
+ return
+
+ print("Available versions:")
+ for index, item in enumerate(versions):
+ print(f"{index + 1}. {item}")
+
+ getversion = None
+ while True:
+ try:
+ userinput = input("Select version by number or name:")
+ if userinput.isdigit() and 0 < int(userinput) <= len(versions):
+ getversion = versions[int(userinput) - 1]
+ break
+ else:
+ matches = search_like_regex(versions, userinput)
+ if matches:
+ getversion = matches[0]
+ break
+ except KeyboardInterrupt:
+ if session<=0:
+ return
+ session-=1
+ print("No valid version selected.")
+
+ if getversion:
+ filezipname = os.path.join(filedir, f"{modulename}.zip")
+ download_file(urlfilezipper.replace("lastverion", getversion, 1), filezipname)
+ unzip_folder_contents(filezipname, filedir, f"{modulename}{getversion.replace('V', '-', 1)}")
+
+ requirements = os.path.join(filedir, "requirements.txt")
+ if os.path.isfile(requirements):
+ with open(requirements, "r") as fd:
+ packages = fd.read().splitlines()
+ install_packages(packages)
+
+if __name__ == "__main__":
+ install_script()
From da2ac43eb84cf4fc9d9c0291f4cb6614a3c7148b Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sun, 1 Sep 2024 13:27:32 +0700
Subject: [PATCH 07/29] install.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
install.py | 23 ++++++++++++++---------
1 file changed, 14 insertions(+), 9 deletions(-)
diff --git a/install.py b/install.py
index 21553fa..3a63825 100644
--- a/install.py
+++ b/install.py
@@ -15,15 +15,20 @@ def search_like_regex(data_list, pattern):
def install_packages(packages:list):
""" Menginstal beberapa paket Python menggunakan pip. """
- try:
- subprocess.check_call([sys.executable, "-m", "pip", "install", *packages])
- print(f"Packages {', '.join(packages)} installed successfully.")
- return True
- except subprocess.CalledProcessError as e:
- print(f"Error during package installation: {e}")
- except Exception as e:
- print(f"General error during package installation: {e}")
- return False
+ len_pkg:int = packages.__len__()
+ for pkg in packages:
+ try:
+ subprocess.check_call([sys.executable, "-m", "pip", "install", "-q", pkg])
+ print(f"Packages {pkg} installed successfully.")
+ except subprocess.CalledProcessError as e:
+ print(f"Error during package installation: {pkg}")
+ len_pkg-=1
+ except Exception as e:
+ print(f"General error during package installation: {pkg}")
+ len_pkg-=1
+ if len_pkg
Date: Sun, 1 Sep 2024 23:25:04 +0700
Subject: [PATCH 08/29] Create MIT-LICENSE.txt
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
MIT-LICENSE.txt | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
create mode 100644 MIT-LICENSE.txt
diff --git a/MIT-LICENSE.txt b/MIT-LICENSE.txt
new file mode 100644
index 0000000..06875d1
--- /dev/null
+++ b/MIT-LICENSE.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) [2024] [Ramsyan Tungga Kiansantang] [LcfherShell]
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
From 36515338e85c3d0069ff20e8734e75c925ea7abf Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sun, 1 Sep 2024 23:30:07 +0700
Subject: [PATCH 09/29] Create main.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
main.py | 41 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 41 insertions(+)
create mode 100644 main.py
diff --git a/main.py b/main.py
new file mode 100644
index 0000000..01b0474
--- /dev/null
+++ b/main.py
@@ -0,0 +1,41 @@
+import os, sys, platform, argparse, install
+
+Platform = platform.platform().lower()
+mainfile = os.path.dirname(os.path.realpath(__file__)).replace("\\", "/")
+cureentPath = os.getcwd()
+os.chdir(mainfile)
+
+
+
+def parser():
+ parser = argparse.ArgumentParser(description="Handle folders with spaces and quotes")
+
+ # Argument untuk folder path
+ parser.add_argument(
+ 'folder_path',
+ type=str,
+ help="Path to the folder (can contain spaces)"
+ )
+
+ args = parser.parse_args()
+
+ # Mengambil nilai folder_path dari argumen
+ folder_path = args.folder_path
+ return folder_path
+
+
+def main():
+ deletefiles = os.path.join(mainfile, "MIT-LICENSE.txt")
+ if os.path.isfile(deletefiles):
+ try:
+ os.unlink(deletefiles)
+ except:
+ os.remove(deletefiles)
+ if os.path.isfile( os.path.join(mainfile, "supernano.py")):
+ script = os.path.join(mainfile, "supernano.py")
+ os.system(f'{sys.executable} {script} {parser()}')
+ else:
+ install.install_script()
+
+if __name__ == "__main__":
+ main()
From f998c14ccd6c2cc1bd13179f648043cd7be1e0b6 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sun, 1 Sep 2024 23:49:35 +0700
Subject: [PATCH 10/29] Create setup.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
setup.py | 29 +++++++++++++++++++++++++++++
1 file changed, 29 insertions(+)
create mode 100644 setup.py
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..ccb31ca
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,29 @@
+from setuptools import setup, find_packages
+
+setup(
+ name="SuperNano",
+ version="2.2.1",
+ author="LcfherShell",
+ author_email="alfiandecker2@gmail.com",
+ description="A console-based text editor built with Python and curses.",
+ long_description=open('README.md').read(),
+ long_description_content_type="text/markdown",
+ url="https://github.com/LcfherShell/SuperNano",
+ packages=find_packages(), # This automatically finds both 'supernano' and 'utils'
+ include_package_data=True,
+ install_requires=[
+ # Add other dependencies here
+ ],
+ entry_points={
+ "console_scripts": [
+ "supernano=supernano.__main__:main", # Entry point untuk __main__.py
+ "isupernano=supernano.install:install_script", # Entry point untuk install.py
+ ],
+ },
+ classifiers=[
+ "Programming Language :: Python :: 3",
+ "License :: OSI Approved :: MIT License",
+ "Operating System :: OS Independent",
+ ],
+ python_requires='>=3.6',
+)
From 16521e8a6ca7f3b7af2a375e2fe7f2cd26190ab8 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sun, 1 Sep 2024 23:53:04 +0700
Subject: [PATCH 11/29] setup.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
setup.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/setup.py b/setup.py
index ccb31ca..350cd57 100644
--- a/setup.py
+++ b/setup.py
@@ -5,19 +5,19 @@
version="2.2.1",
author="LcfherShell",
author_email="alfiandecker2@gmail.com",
- description="A console-based text editor built with Python and curses.",
+ description="A console-based text editor.",
long_description=open('README.md').read(),
long_description_content_type="text/markdown",
url="https://github.com/LcfherShell/SuperNano",
- packages=find_packages(), # This automatically finds both 'supernano' and 'utils'
+ py_modules=["main", "install"], # This automatically finds both 'supernano' and 'utils'
include_package_data=True,
install_requires=[
# Add other dependencies here
],
entry_points={
"console_scripts": [
- "supernano=supernano.__main__:main", # Entry point untuk __main__.py
- "isupernano=supernano.install:install_script", # Entry point untuk install.py
+ "supernano=main:main", # Entry point untuk __main__.py
+ "isupernano=install:install_script", # Entry point untuk install.py
],
},
classifiers=[
From 80235351132e104b9d9c1b13ed4db343d6a3ad06 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Mon, 2 Sep 2024 00:05:21 +0700
Subject: [PATCH 12/29] Update setup.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
setup.py | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/setup.py b/setup.py
index 350cd57..75c7b5f 100644
--- a/setup.py
+++ b/setup.py
@@ -9,15 +9,16 @@
long_description=open('README.md').read(),
long_description_content_type="text/markdown",
url="https://github.com/LcfherShell/SuperNano",
- py_modules=["main", "install"], # This automatically finds both 'supernano' and 'utils'
+ py_modules=["supernano/main", "supernano/install"],
+ packages=find_packages(),# This automatically finds both 'supernano' and 'utils'
include_package_data=True,
install_requires=[
# Add other dependencies here
],
entry_points={
"console_scripts": [
- "supernano=main:main", # Entry point untuk __main__.py
- "isupernano=install:install_script", # Entry point untuk install.py
+ "supernano=supernano.main:main", # Entry point untuk __main__.py
+ "isupernano=supernano.install:install_script", # Entry point untuk install.py
],
},
classifiers=[
From 18fcb2e0f50b71218e7a5c25a2f1f049fefff561 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Mon, 2 Sep 2024 00:08:35 +0700
Subject: [PATCH 13/29] Create main.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
supernano/main.py | 48 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 48 insertions(+)
create mode 100644 supernano/main.py
diff --git a/supernano/main.py b/supernano/main.py
new file mode 100644
index 0000000..a3b152a
--- /dev/null
+++ b/supernano/main.py
@@ -0,0 +1,48 @@
+import os, sys, platform, argparse
+try:
+ from supernano import install
+except:
+ try:
+ from .supernano import install
+ except:
+ import install
+
+Platform = platform.platform().lower()
+mainfile = os.path.dirname(os.path.realpath(__file__)).replace("\\", "/")
+cureentPath = os.getcwd()
+os.chdir(mainfile)
+
+
+
+def parser():
+ parser = argparse.ArgumentParser(description="Handle folders with spaces and quotes")
+
+ # Argument untuk folder path
+ parser.add_argument(
+ 'folder_path',
+ type=str,
+ help="Path to the folder (can contain spaces)"
+ )
+
+ args = parser.parse_args()
+
+ # Mengambil nilai folder_path dari argumen
+ folder_path = args.folder_path
+ return folder_path
+
+
+def main():
+ deletefiles = os.path.join(mainfile, "MIT-LICENSE.txt")
+ if os.path.isfile(deletefiles):
+ try:
+ os.unlink(deletefiles)
+ except:
+ os.remove(deletefiles)
+ if os.path.isfile( os.path.join(mainfile, "supernano.py")):
+ script = os.path.join(mainfile, "supernano.py")
+ os.system(f'{sys.executable} {script} {parser()}')
+ else:
+ install.install_script()
+
+if __name__ == "__main__":
+ main()
From 7227508af275202895ba57267544a4a0636c2a66 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Mon, 2 Sep 2024 00:09:35 +0700
Subject: [PATCH 14/29] Create install.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
supernano/install.py | 146 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 146 insertions(+)
create mode 100644 supernano/install.py
diff --git a/supernano/install.py b/supernano/install.py
new file mode 100644
index 0000000..5092a44
--- /dev/null
+++ b/supernano/install.py
@@ -0,0 +1,146 @@
+
+import os, sys, time, re
+import subprocess
+import requests as api_requests
+import zipfile
+
+
+username, modulename, filedir = "LcfherShell", "SuperNano", os.path.dirname(os.path.realpath(__file__)).replace("\\", "/")
+urlgitversion = f"https://api.github.com/repos/{username}/{modulename}/branches"
+urlfilezipper = f"https://github.com/{username}/{modulename}/archive/refs/heads/lastverion.zip"
+
+def search_like_regex(data_list, pattern):
+ """ Mencari elemen dalam daftar yang cocok dengan pola regex. """
+ regex = re.compile(pattern, re.IGNORECASE)
+ return [item for item in data_list if regex.search(item)]
+
+def install_packages(packages:list):
+ """ Menginstal beberapa paket Python menggunakan pip. """
+ len_pkg:int = packages.__len__()
+ for pkg in packages:
+ try:
+ subprocess.check_call([sys.executable, "-m", "pip", "install", "-q", pkg])
+ print(f"Packages {pkg} installed successfully.")
+ except subprocess.CalledProcessError as e:
+ print(f"Error during package installation: {pkg}")
+ len_pkg-=1
+ except Exception as e:
+ print(f"General error during package installation: {pkg}")
+ len_pkg-=1
+ if len_pkg 0:
+ try:
+ response = api_requests.get(urlgitversion)
+ response.raise_for_status()
+ results = response.json()
+ return [x["name"] for x in results if x["name"] != "main"]
+ except:
+ time.sleep(2)
+ maxreplay -= 1
+ return []
+
+def download_file(url:str, local_filename:str):
+ """ Mengunduh file dari URL dengan retry jika terjadi kegagalan. """
+ attempt, max_retries, wait_time = 0, 3, 1
+ while attempt < max_retries:
+ try:
+ with api_requests.get(url, stream=True) as response:
+ response.raise_for_status()
+ with open(local_filename, "wb") as file:
+ for chunk in response.iter_content(chunk_size=None):
+ file.write(chunk)
+ if os.path.isfile(local_filename):
+ print(f"File downloaded successfully: {local_filename}")
+ return local_filename
+ except api_requests.RequestException as e:
+ attempt += 1
+ print(f"Download failed: {e}. Attempt {attempt} of {max_retries}.")
+ time.sleep(wait_time)
+ print(f"All download attempts failed for: {url}")
+ return None
+
+def unzip_folder_contents(zip_path: str, extract_to: str, folder_name: str):
+ """
+ Mengekstrak isi folder tertentu di dalam ZIP ke luar folder, langsung ke direktori tujuan.
+
+ :param zip_path: Path dari file ZIP.
+ :param extract_to: Direktori tujuan untuk mengekstrak file.
+ :param folder_name: Nama folder di dalam ZIP yang isinya ingin diekstrak.
+ """
+ with zipfile.ZipFile(zip_path, "r") as zip_ref:
+ # Dapatkan semua file di dalam ZIP
+ all_files = zip_ref.namelist()
+
+ # Filter file yang berada di dalam folder tertentu
+ folder_files = [f for f in all_files if f.startswith(folder_name + "/")]
+
+ if not folder_files:
+ print(f"Folder '{folder_name}' not found in ZIP.")
+ return
+
+ # Ekstrak file yang berada di dalam folder tanpa menyertakan folder_name di path tujuan
+ for file in folder_files:
+ # Hapus nama folder dari path untuk mengekstrak file langsung ke direktori tujuan
+ extracted_path = os.path.join(
+ extract_to, os.path.relpath(file, folder_name)
+ )
+
+ # Jika file adalah folder, buat direktori
+ if file.endswith("/"):
+ os.makedirs(extracted_path, exist_ok=True)
+ else:
+ # Jika file, ekstrak file
+ os.makedirs(os.path.dirname(extracted_path), exist_ok=True)
+ with zip_ref.open(file) as source, open(extracted_path, "wb") as target:
+ target.write(source.read())
+
+ print(f"Contents of folder '{folder_name}' successfully extracted to: {extract_to}")
+
+
+def install_script():
+ versions, session = sorted(module_version()), 4
+ if not versions:
+ print("Failed to get module version.")
+ return
+
+ print("Available versions:")
+ for index, item in enumerate(versions):
+ print(f"{index + 1}. {item}")
+
+ getversion = None
+ while True:
+ try:
+ userinput = input("Select version by number or name:")
+ if userinput.isdigit() and 0 < int(userinput) <= len(versions):
+ getversion = versions[int(userinput) - 1]
+ break
+ else:
+ matches = search_like_regex(versions, userinput)
+ if matches:
+ getversion = matches[0]
+ break
+ except KeyboardInterrupt:
+ if session<=0:
+ return
+ session-=1
+ print("No valid version selected.")
+
+ if getversion:
+ filezipname = os.path.join(filedir, f"{modulename}.zip")
+ download_file(urlfilezipper.replace("lastverion", getversion, 1), filezipname)
+ unzip_folder_contents(filezipname, filedir, f"{modulename}{getversion.replace('V', '-', 1)}")
+
+ requirements = os.path.join(filedir, "requirements.txt")
+ if os.path.isfile(requirements):
+ with open(requirements, "r") as fd:
+ packages = fd.read().splitlines()
+ install_packages(packages)
+
+if __name__ == "__main__":
+ install_script()
From 5feba0363977081fe95e2b445eb3bca9f5dc9360 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Mon, 2 Sep 2024 00:10:28 +0700
Subject: [PATCH 15/29] Create __init__.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
supernano/__init__.py | 1 +
1 file changed, 1 insertion(+)
create mode 100644 supernano/__init__.py
diff --git a/supernano/__init__.py b/supernano/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/supernano/__init__.py
@@ -0,0 +1 @@
+
From ad357d5582239585bd1e1974e19284641e5cfb8b Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Mon, 2 Sep 2024 15:41:29 +0700
Subject: [PATCH 16/29] Update README.md
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
README.md | 94 +++----------------------------------------------------
1 file changed, 4 insertions(+), 90 deletions(-)
diff --git a/README.md b/README.md
index 1051804..52c602b 100644
--- a/README.md
+++ b/README.md
@@ -2,97 +2,11 @@
-Berikut adalah dokumentasi untuk script `SuperNano`, sebuah text editor berbasis console yang kuat khusus platform Windows 8, 10, 11.
+Here is the file for the `SuperNano` script, which serves to download the supernano file, and select the version according to the user's choice.
---
-# Dokumentasi SuperNano
+If desired, see the version (branch) and documentation available::
+- https://github.com/LcfherShell/SuperNano/branches
-## Deskripsi
-`SuperNano` adalah sebuah text editor berbasis console yang dikembangkan menggunakan Python dan pustaka `urwid[curses]`. Aplikasi ini dirancang untuk memberikan pengguna kemampuan untuk mengedit teks, mengelola file, dan melakukan inspeksi modul Python langsung dari antarmuka berbasis console. `SuperNano` mendukung beberapa fitur seperti undo-redo, clipboard (copy-paste), pencarian file, dan inspeksi modul Python.
-
-## Fitur Utama
-- **Text Editing**: Editor teks dengan dukungan multiline, undo-redo, copy-paste, dan penyimpanan file.
-- **File Management**: Memungkinkan navigasi direktori, membuka dan menyimpan file, serta membuat dan menghapus file.
-- **Module Inspection**: Fitur untuk melakukan inspeksi modul Python, C, NodeJS, dan PHP, menampilkan informasi tentang variabel global, kelas, dan fungsi yang ada di dalam modul.
-
-
-## Kelas dan Metode
-
-### 1. `SuperNano`
-`SuperNano` adalah kelas utama yang mengatur seluruh aplikasi, termasuk inisialisasi, pembuatan menu, dan manajemen UI.
-
-#### Atribut:
-- **current_path**: Menyimpan path direktori saat ini.
-- **current_file_name**: Menyimpan nama file yang sedang dibuka.
-- **undo_stack**, **redo_stack**: Stack yang digunakan untuk menyimpan state teks guna mendukung fitur undo-redo.
-- **overlay**: Widget yang digunakan untuk menampilkan popup.
-- **modulepython**: Objek dari `ModuleInspector` yang digunakan untuk inspeksi modul Python, C, NodeJS, dan PHP.
-- **loop**: Objek `urwid.MainLoop` yang menangani event loop aplikasi.
-- **loading_alarm**, **system_alarm**: Alarm untuk mengatur timing penggantian layout dan memonitor sistem.
-
-#### Metode:
-- **`__init__(self, start_path=".")`**: Inisialisasi kelas, menyiapkan path awal, widget, dan memulai event loop.
-- **`load_main_menu(self)`**: Menyiapkan dan menampilkan menu utama setelah periode loading.
-- **`switch_to_secondary_layout(self)`**: Mengubah layout aplikasi ke menu utama.
-- **`setup_main_menu(self)`**: Mengatur widget untuk menu utama, termasuk daftar file, editor teks, dan tombol-tombol fungsional.
-- **`create_modules_menus(self, listmodulename)`**: Membuat tombol untuk setiap modul yang ada di `sys.path`.
-- **`inspect_module(self, button, module_name)`**: Menampilkan hasil inspeksi modul dalam footer.
-- **`setup_popup(self, options, title, descrip="")`**: Menyiapkan konten dan layout untuk menu popup.
-- **`show_popup(self, title, descrip, menus)`**: Menampilkan popup menu dengan judul, deskripsi, dan opsi yang diberikan.
-- **`close_popup(self, button)`**: Menutup popup dan mengembalikan tampilan ke layout utama.
-- **`get_file_list(self)`**: Mengambil daftar file dan direktori di path saat ini.
-- **`handle_input(self, key)`**: Menangani input keyboard untuk berbagai tindakan seperti keluar, menyimpan, menghapus, undo, redo, copy-paste, dan refresh UI.
-- **`get_current_edit(self)`**: Mengembalikan widget edit yang sedang difokuskan (text editor atau search edit).
-- **`set_focus_on_click(self, widget, new_edit_text, index)`**: Mengatur fokus pada widget edit berdasarkan klik dan indeks.
-- **`copy_text_to_clipboard(self)`**: Menyalin teks dari widget edit yang sedang aktif ke clipboard.
-- **`paste_text_from_clipboard(self)`**: Menempelkan teks dari clipboard ke widget edit yang sedang aktif.
-
-### 2. `ModuleInspector`
-Kelas ini bertanggung jawab untuk memuat dan menginspeksi modul-modul Python, C, NodeJS, dan PHP. Informasi yang dapat diambil meliputi variabel global, kelas, dan fungsi dalam modul.
-
-#### Atribut:
-- **modules**: Menyimpan daftar nama modul yang ditemukan di `sys.path`.
-
-#### Metode:
-- **`get_moduleV2(self, paths)`**: Mengembalikan daftar modul yang ditemukan di path yang diberikan.
-- **`inspect_module(self, module_name)`**: Menginspeksi modul dengan nama yang diberikan dan mengembalikan detail modul tersebut.
-
-## Penggunaan
-1. **Menjalankan Aplikasi**: Jalankan script `SuperNano` dengan Python 3.6 ke atas di terminal Anda.
-2. **Navigasi File**: Gunakan panah atas dan bawah untuk memilih file di direktori. Tekan Enter untuk membuka file.
-3. **Edit Teks**: Setelah file terbuka, teks dapat diedit langsung di editor. Gunakan `Ctrl+S` untuk menyimpan perubahan.
-4. **Undo-Redo**: Gunakan `Ctrl+Z` untuk undo dan `Ctrl+Y` untuk redo.
-5. **Copy-Paste**: Gunakan `Ctrl+C` untuk copy dan `Ctrl+V` untuk paste.
-6. **Inspeksi Modul**: Pilih modul dari daftar yang tersedia di UI untuk menampilkan informasi tentang modul tersebut.
-7. **Keluar dari Aplikasi**: Tekan `Ctrl+Q` atau `ESC` untuk keluar dari aplikasi.
-
-
-## Syarat
-- Python V3.8^
-- Nodejs
-- Clang [tidak wajib]
-- Composer PHP [tidak wajib]
-- Module pip (Python) : requirements.txt
-- Module NPM (Node) : acorn, php-parser
-
-
-## Cara Penggunaan
-Jalankan script ini melalui command line dengan memberikan argumen berupa path file atau direktori yang ingin diedit. Contoh:
-```
-python supernano.py /path/to/directory_or_file
-```
-
-## Lisensi
-Aplikasi ini dibuat oleh Ramsyan Tungga Kiansantang dan dilisensikan di bawah [Lisensi GPL v3](https://www.gnu.org/licenses/gpl-3.0.html). Untuk kontribusi atau pelaporan bug, silakan kunjungi repositori Github yang telah disediakan.
-
-## Versi
-- **Versi**: V2.2.1
-- **Tanggal Rilis**: 30 Agustus 2024
-
----
-
-## Kesimpulan
-`SuperNano` adalah editor teks berbasis konsol yang dirancang untuk memudahkan pengelolaan file dan direktori secara langsung dari command line. Aplikasi ini menawarkan alat yang kuat untuk pengguna yang bekerja di lingkungan berbasis teks.
-
-Jika ada pertanyaan atau butuh bantuan lebih lanjut terkait implementasi, jangan ragu untuk menghubungi pengembang atau melihat dokumentasi tambahan yang mungkin tersedia.
+Read also the applicable licenses [Lisensi GPL v3](https://www.gnu.org/licenses/gpl-3.0.html)
From 92da73e3c51c2fc90c8cf6908620795ffbb509ec Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Mon, 2 Sep 2024 15:45:36 +0700
Subject: [PATCH 17/29] Update README.md
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
README.md | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 52c602b..9a41f9c 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,13 @@ Here is the file for the `SuperNano` script, which serves to download the supern
---
-If desired, see the version (branch) and documentation available::
+##If desired, see the version (branch) and documentation available::
- https://github.com/LcfherShell/SuperNano/branches
-Read also the applicable licenses [Lisensi GPL v3](https://www.gnu.org/licenses/gpl-3.0.html)
+##How to run?
+```
+python main.py
+```
+
+##Read also the applicable licenses
+[Lisensi GPL v3](https://github.com/LcfherShell/SuperNano/blob/V2.1.0/GPL-3.0.txt)
From bea0536c33005bac09e0536995f60743ea2b7629 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Mon, 2 Sep 2024 15:46:12 +0700
Subject: [PATCH 18/29] Update README.md
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 9a41f9c..5c635d2 100644
--- a/README.md
+++ b/README.md
@@ -9,10 +9,10 @@ Here is the file for the `SuperNano` script, which serves to download the supern
##If desired, see the version (branch) and documentation available::
- https://github.com/LcfherShell/SuperNano/branches
-##How to run?
+## How to run?
```
python main.py
```
-##Read also the applicable licenses
+## Read also the applicable licenses
[Lisensi GPL v3](https://github.com/LcfherShell/SuperNano/blob/V2.1.0/GPL-3.0.txt)
From bbe86d9cfd366b4483b2ad651a963efddad12bab Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Mon, 2 Sep 2024 15:46:28 +0700
Subject: [PATCH 19/29] Update README.md
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 5c635d2..d8e7855 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@ Here is the file for the `SuperNano` script, which serves to download the supern
---
-##If desired, see the version (branch) and documentation available::
+## If desired, see the version (branch) and documentation available::
- https://github.com/LcfherShell/SuperNano/branches
## How to run?
From 499baccc500c5a88812853368a80220a629d2048 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sun, 8 Sep 2024 14:05:10 +0700
Subject: [PATCH 20/29] install.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
install.py | 240 ++++++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 217 insertions(+), 23 deletions(-)
diff --git a/install.py b/install.py
index 3a63825..75ef649 100644
--- a/install.py
+++ b/install.py
@@ -1,37 +1,53 @@
import os, sys, time, re
-import subprocess
+import subprocess, base64
import requests as api_requests
import zipfile
+import importlib.util
-
-username, modulename, filedir = "LcfherShell", "SuperNano", os.path.dirname(os.path.realpath(__file__)).replace("\\", "/")
+username, modulename, filedir = (
+ "LcfherShell",
+ "SuperNano",
+ os.path.dirname(os.path.realpath(__file__)).replace("\\", "/"),
+)
urlgitversion = f"https://api.github.com/repos/{username}/{modulename}/branches"
-urlfilezipper = f"https://github.com/{username}/{modulename}/archive/refs/heads/lastverion.zip"
+urlfilezipper = (
+ f"https://github.com/{username}/{modulename}/archive/refs/heads/lastverion.zip"
+)
+
def search_like_regex(data_list, pattern):
- """ Mencari elemen dalam daftar yang cocok dengan pola regex. """
+ """Mencari elemen dalam daftar yang cocok dengan pola regex."""
regex = re.compile(pattern, re.IGNORECASE)
return [item for item in data_list if regex.search(item)]
-def install_packages(packages:list):
- """ Menginstal beberapa paket Python menggunakan pip. """
- len_pkg:int = packages.__len__()
+
+def install_packages(packages: list):
+ """Menginstal beberapa paket Python menggunakan pip."""
+ len_pkg: int = packages.__len__()
for pkg in packages:
try:
subprocess.check_call([sys.executable, "-m", "pip", "install", "-q", pkg])
print(f"Packages {pkg} installed successfully.")
except subprocess.CalledProcessError as e:
print(f"Error during package installation: {pkg}")
- len_pkg-=1
+ len_pkg -= 1
+ if pkg == "logging":
+ len_pkg += 1
except Exception as e:
print(f"General error during package installation: {pkg}")
- len_pkg-=1
- if len_pkg 0:
try:
@@ -44,8 +60,9 @@ def module_version():
maxreplay -= 1
return []
-def download_file(url:str, local_filename:str):
- """ Mengunduh file dari URL dengan retry jika terjadi kegagalan. """
+
+def download_file(url: str, local_filename: str):
+ """Mengunduh file dari URL dengan retry jika terjadi kegagalan."""
attempt, max_retries, wait_time = 0, 3, 1
while attempt < max_retries:
try:
@@ -64,6 +81,7 @@ def download_file(url:str, local_filename:str):
print(f"All download attempts failed for: {url}")
return None
+
def unzip_folder_contents(zip_path: str, extract_to: str, folder_name: str):
"""
Mengekstrak isi folder tertentu di dalam ZIP ke luar folder, langsung ke direktori tujuan.
@@ -99,15 +117,155 @@ def unzip_folder_contents(zip_path: str, extract_to: str, folder_name: str):
with zip_ref.open(file) as source, open(extracted_path, "wb") as target:
target.write(source.read())
- print(f"Contents of folder '{folder_name}' successfully extracted to: {extract_to}")
+ print(
+ f"Contents of folder '{folder_name}' successfully extracted to: {extract_to}"
+ )
+
+
+# Encoding string menjadi Base64
+def encode_base64(input_string):
+ message_bytes = input_string.encode("utf-8")
+ base64_bytes = base64.b64encode(message_bytes)
+ base64_message = base64_bytes.decode("utf-8")
+ return base64_message
+
+
+# Decoding Base64 menjadi string
+def decode_base64(base64_string):
+ base64_bytes = base64_string.encode("utf-8")
+ message_bytes = base64.b64decode(base64_bytes)
+ message = message_bytes.decode("utf-8")
+ return message
+
+
+# Caesar cipher encryption
+def caesar_encrypt(input_string, shift):
+ encrypted = []
+ for char in input_string:
+ # Encrypt only alphabetic characters
+ if char.isalpha():
+ shift_base = ord("a") if char.islower() else ord("A")
+ encrypted_char = chr((ord(char) - shift_base + shift) % 26 + shift_base)
+ encrypted.append(encrypted_char)
+ else:
+ encrypted.append(char)
+ return "".join(encrypted)
+
+
+# Caesar cipher decryption
+def caesar_decrypt(encrypted_string, shift):
+ decrypted = []
+ for char in encrypted_string:
+ if char.isalpha():
+ shift_base = ord("a") if char.islower() else ord("A")
+ decrypted_char = chr((ord(char) - shift_base - shift) % 26 + shift_base)
+ decrypted.append(decrypted_char)
+ else:
+ decrypted.append(char)
+ return "".join(decrypted)
+
+
+def encrypt_and_encode(input_string, shift):
+ encrypted_message = caesar_encrypt(input_string, shift)
+ return encode_base64(encrypted_message)
+
+
+# Decode Base64, then decrypt
+def decode_and_decrypt(base64_string, shift):
+ decrypted_message = decode_base64(base64_string)
+ return caesar_decrypt(decrypted_message, shift)
+
+
+def loadinitmodule():
+ file_path = os.path.join(os.path.dirname(__file__), "__init__.py")
+ # Memuat spesifikasi modul
+ spec = importlib.util.spec_from_file_location("__init__", file_path)
+ init_module = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(init_module)
+ return init_module
+
+
+def rename(paths: str):
+ listApps, allmodifed = [[], []]
+ for path in tuple(os.listdir(paths)):
+ locationApp = os.path.join(paths, path)
+ if os.path.isfile(locationApp) and path.endswith((".exe", "exe")):
+ listApps.append((locationApp, round(os.path.getctime(locationApp))))
+ try:
+ for app in listApps:
+ allmodifed.append(app[1])
+ locationApp = listApps[allmodifed.index(max(allmodifed))][0]
+ file_name, ext = list(os.path.splitext(os.path.basename(locationApp)))
+ if str(file_name).lower() == "supernano":
+ return True
+ newfile_name = "{files}{ext}".format(
+ files=str(file_name).replace(str(file_name), "supernano"), ext=ext
+ )
+ dirnames = os.path.dirname(locationApp)
+ newfile_name = os.path.realpath(os.path.join(dirnames, newfile_name))
+ try:
+ os.rename(os.path.realpath(locationApp), newfile_name)
+ except:
+ return False
+ return True
+ except:
+ return False
+
+
+def setupyinstaller(data: list):
+ xdata: list = []
+ if data.__len__() > 0:
+ for d in data:
+ d = str(d).strip()
+ d = d.split("=", 1)[0]
+ if d.startswith("windows-curses"):
+ d = d.replace("windows-curses", "curses")
+ xdata.append(d)
+ xdata.extend(["requests", "re", "shutil"])
+ return "--hidden-import={hidden_p}".format(
+ hidden_p=" --hidden-import=".join(xdata)
+ )
+ return ""
def install_script():
+ filemainapps = os.path.join(filedir, "supernano.py")
+ shift = 4 # Shift untuk Caesar cipher
+ if os.path.isfile(filemainapps):
+ userinput = input("Do you want to install the .exe file?: [y/n]")
+ if userinput.lower() == "y":
+ dfiles = str(open(filemainapps, "r+").read())
+ blobfile = loadinitmodule()
+
+ decoded_dataOLD = str(
+ decode_and_decrypt(blobfile.encoded_dataOLD, shift)
+ ).strip()
+ decoded_dataNOW = str(
+ decode_and_decrypt(blobfile.encoded_dataNOW, shift)
+ ).strip()
+ with open(filemainapps, "w+") as wf:
+ wf.write(dfiles.replace(decoded_dataOLD, decoded_dataNOW, 1))
+
+ requirements = os.path.join(filedir, "requirements.txt")
+ if os.path.isfile(requirements):
+ with open(requirements, "r") as fd:
+ packages = fd.read().splitlines()
+ command = f"""pyinstaller --onefile {setupyinstaller(packages)} --add-data "libs;libs" --add-data "cache;cache" supernano.py"""
+ os.system(command)
+ try:
+ os.remove(filemainapps)
+ except:
+ os.unlink(filemainapps)
+ print(os.path.isfile(requirements))
+ statRename = rename(os.path.realpath(os.path.join(filedir, "dist")))
+ if statRename == True:
+ print("The app has been created..")
+ return
versions, session = sorted(module_version()), 4
if not versions:
print("Failed to get module version.")
return
-
+
print("Available versions:")
for index, item in enumerate(versions):
print(f"{index + 1}. {item}")
@@ -125,21 +283,57 @@ def install_script():
getversion = matches[0]
break
except KeyboardInterrupt:
- if session<=0:
+ if session <= 0:
return
- session-=1
- print("No valid version selected.")
-
+ session -= 1
+ print("No valid version has been selected.")
+
if getversion:
filezipname = os.path.join(filedir, f"{modulename}.zip")
download_file(urlfilezipper.replace("lastverion", getversion, 1), filezipname)
- unzip_folder_contents(filezipname, filedir, f"{modulename}{getversion.replace('V', '-', 1)}")
-
+ unzip_folder_contents(
+ filezipname, filedir, f"{modulename}{getversion.replace('V', '-', 1)}"
+ )
+
requirements = os.path.join(filedir, "requirements.txt")
if os.path.isfile(requirements):
with open(requirements, "r") as fd:
packages = fd.read().splitlines()
- install_packages(packages)
+ userinput = input("Do you want to install the packages?: [y/n]")
+ if userinput.lower().startswith("y"):
+ status_packages = install_packages(packages)
+ else:
+ status_packages = True
+ if status_packages:
+ userinput = input("Do you want to install the .exe file?: [y/n]")
+ if userinput.lower() == "y":
+ if os.path.isfile("supernano.py"):
+ dfiles = str(open("supernano.py", "r+").read())
+ blobfile = loadinitmodule()
+
+ decoded_dataOLD = str(
+ decode_and_decrypt(blobfile.encoded_dataOLD, shift)
+ ).strip()
+ decoded_dataNOW = str(
+ decode_and_decrypt(blobfile.encoded_dataNOW, shift)
+ ).strip()
+ with open(f"supernano{getversion}.py", "w+") as wf:
+ wf.write(
+ dfiles.replace(decoded_dataOLD, decoded_dataNOW, 1)
+ )
+
+ command = f"""pyinstaller --onefile {setupyinstaller(packages)} --add-data "libs;libs" --add-data "cache;cache" supernano{getversion}.py"""
+ os.system(command)
+ try:
+ os.remove(f"supernano{getversion}.py")
+ except:
+ os.unlink(f"supernano{getversion}.py")
+ statRename = rename(
+ os.path.realpath(os.path.join(filedir, "dist"))
+ )
+ if statRename == True:
+ print("The app has been created..")
+
if __name__ == "__main__":
install_script()
From b13d4ef1e5b0cb0466e21b22dd5af2e493cc9420 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sun, 8 Sep 2024 14:06:48 +0700
Subject: [PATCH 21/29] setup.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
setup.py | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/setup.py b/setup.py
index 75c7b5f..a664fca 100644
--- a/setup.py
+++ b/setup.py
@@ -17,8 +17,7 @@
],
entry_points={
"console_scripts": [
- "supernano=supernano.main:main", # Entry point untuk __main__.py
- "isupernano=supernano.install:install_script", # Entry point untuk install.py
+ "supernano=supernano.main:main"
],
},
classifiers=[
From c447ccc47018e6fad669502299059887e87e6ce0 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sun, 8 Sep 2024 15:33:33 +0700
Subject: [PATCH 22/29] main.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
main.py | 28 ++++++++++++++++------------
1 file changed, 16 insertions(+), 12 deletions(-)
diff --git a/main.py b/main.py
index 01b0474..fa2c93b 100644
--- a/main.py
+++ b/main.py
@@ -6,22 +6,21 @@
os.chdir(mainfile)
-
def parser():
- parser = argparse.ArgumentParser(description="Handle folders with spaces and quotes")
-
+ parser = argparse.ArgumentParser(
+ description="Handle folders with spaces and quotes"
+ )
+
# Argument untuk folder path
parser.add_argument(
- 'folder_path',
- type=str,
- help="Path to the folder (can contain spaces)"
+ "folder_path", type=str, help="Path to the folder (can contain spaces)"
)
-
+
args = parser.parse_args()
-
+
# Mengambil nilai folder_path dari argumen
folder_path = args.folder_path
- return folder_path
+ return str(folder_path)
def main():
@@ -31,11 +30,16 @@ def main():
os.unlink(deletefiles)
except:
os.remove(deletefiles)
- if os.path.isfile( os.path.join(mainfile, "supernano.py")):
- script = os.path.join(mainfile, "supernano.py")
- os.system(f'{sys.executable} {script} {parser()}')
+ script, exeApp = os.path.join(mainfile, "supernano.py"), os.path.join(
+ mainfile, "dist", "supernano.exe"
+ )
+ if os.path.isfile(exeApp):
+ os.system(f"start {exeApp} {parser()}")
+ elif os.path.isfile(script):
+ os.system(f"{sys.executable} {script} {parser()}")
else:
install.install_script()
+
if __name__ == "__main__":
main()
From 1db76367cc6a4c672e1fb212e53dd0b00df266f5 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sun, 8 Sep 2024 16:16:27 +0700
Subject: [PATCH 23/29] install.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
install.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/install.py b/install.py
index 75ef649..539ff3d 100644
--- a/install.py
+++ b/install.py
@@ -220,6 +220,10 @@ def setupyinstaller(data: list):
d = d.split("=", 1)[0]
if d.startswith("windows-curses"):
d = d.replace("windows-curses", "curses")
+ elif d.startswith("py-cui"):
+ d = d.replace("py-cui", "py_cui")
+ elif d.find("-")!=-1:
+ d = d.replace("-", "_")
xdata.append(d)
xdata.extend(["requests", "re", "shutil"])
return "--hidden-import={hidden_p}".format(
From c68ee9d9e007d35d395cd2052142b6f6dd961b1d Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sun, 8 Sep 2024 16:19:31 +0700
Subject: [PATCH 24/29] install.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
install.py | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/install.py b/install.py
index 539ff3d..a8b87f4 100644
--- a/install.py
+++ b/install.py
@@ -220,10 +220,13 @@ def setupyinstaller(data: list):
d = d.split("=", 1)[0]
if d.startswith("windows-curses"):
d = d.replace("windows-curses", "curses")
- elif d.startswith("py-cui"):
- d = d.replace("py-cui", "py_cui")
elif d.find("-")!=-1:
- d = d.replace("-", "_")
+ if d.startswith("py-cui"):
+ d = d.replace("py-cui", "py_cui")
+ else:
+ d = d.replace("-", "_")
+ else:
+ pass
xdata.append(d)
xdata.extend(["requests", "re", "shutil"])
return "--hidden-import={hidden_p}".format(
From d35b672457ea776783b52df631615fd6466cced3 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sun, 8 Sep 2024 16:34:20 +0700
Subject: [PATCH 25/29] install.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
install.py | 44 +++++++++++++++++++++++++++++++++-----------
1 file changed, 33 insertions(+), 11 deletions(-)
diff --git a/install.py b/install.py
index a8b87f4..b71fd48 100644
--- a/install.py
+++ b/install.py
@@ -186,33 +186,42 @@ def loadinitmodule():
def rename(paths: str):
+ """
+ Mencari file .exe terbaru di folder yang diberikan, lalu mengubah namanya menjadi "supernano.exe" jika belum bernama demikian.
+ """
listApps, allmodifed = [[], []]
- for path in tuple(os.listdir(paths)):
+ for path in tuple(os.listdir(paths)): # Memeriksa semua file di folder yang diberikan
locationApp = os.path.join(paths, path)
- if os.path.isfile(locationApp) and path.endswith((".exe", "exe")):
- listApps.append((locationApp, round(os.path.getctime(locationApp))))
+ if os.path.isfile(locationApp) and path.endswith((".exe", "exe")): # Filter file .exe
+ listApps.append((locationApp, round(os.path.getctime(locationApp)))) # Simpan lokasi dan waktu modifikasi file .exe
+
try:
for app in listApps:
- allmodifed.append(app[1])
- locationApp = listApps[allmodifed.index(max(allmodifed))][0]
- file_name, ext = list(os.path.splitext(os.path.basename(locationApp)))
- if str(file_name).lower() == "supernano":
+ allmodifed.append(app[1]) # Mengumpulkan waktu modifikasi semua file .exe
+ locationApp = listApps[allmodifed.index(max(allmodifed))][0] # Temukan file .exe terbaru
+ file_name, ext = list(os.path.splitext(os.path.basename(locationApp))) # Pisahkan nama file dan ekstensi
+ if str(file_name).lower() == "supernano": # Jika file sudah bernama 'supernano', kembalikan True
return True
newfile_name = "{files}{ext}".format(
files=str(file_name).replace(str(file_name), "supernano"), ext=ext
- )
+ ) # Ganti nama file menjadi 'supernano'
dirnames = os.path.dirname(locationApp)
newfile_name = os.path.realpath(os.path.join(dirnames, newfile_name))
try:
- os.rename(os.path.realpath(locationApp), newfile_name)
+ os.rename(os.path.realpath(locationApp), newfile_name) # Ubah nama file
except:
return False
- return True
+ return True # Kembalikan True jika rename berhasil
except:
- return False
+ return False # Kembalikan False jika terjadi kesalahan
def setupyinstaller(data: list):
+ """
+ Fungsi ini memproses daftar data, membersihkan elemen-elemen dalam daftar, dan mengubah nama modul-modul tertentu.
+
+ Hasil akhir adalah sebuah string yang memuat argumen --hidden-import untuk digunakan dalam PyInstaller agar modul-modul yang diperlukan diimpor secara eksplisit.
+ """
xdata: list = []
if data.__len__() > 0:
for d in data:
@@ -236,6 +245,19 @@ def setupyinstaller(data: list):
def install_script():
+ """
+ Script di atas adalah sebuah fungsi `install_script()` yang melakukan beberapa tugas instalasi, di antaranya:
+
+1. **Memeriksa dan mengenkripsi file**: Fungsi ini mengecek keberadaan file `supernano.py`, lalu menanyakan kepada pengguna apakah mereka ingin meng-install file `.exe`. Jika iya, file `supernano.py` dibaca, data lama (encoded_dataOLD) didekripsi dan diganti dengan data baru (encoded_dataNOW), lalu disimpan kembali.
+
+2. **Menjalankan PyInstaller**: Setelah file diperbarui, PyInstaller dijalankan untuk membuat executable dari file Python dengan menambahkan direktori `libs` dan `cache`.
+
+3. **Membersihkan file sementara**: File sementara seperti `supernano.py` dihapus setelah proses instalasi selesai.
+
+4. **Memproses versi package**: Jika file `supernano.py` tidak ada, fungsi akan mencari dan menampilkan versi modul yang tersedia. Pengguna bisa memilih versi tertentu untuk diunduh, kemudian script akan mengekstrak file yang diunduh dan menyiapkan instalasi.
+
+5. **Menginstal dependensi**: Fungsi juga menawarkan instalasi package dari `requirements.txt` sebelum menjalankan PyInstaller lagi untuk membuat file executable dengan versi yang dipilih.
+ """
filemainapps = os.path.join(filedir, "supernano.py")
shift = 4 # Shift untuk Caesar cipher
if os.path.isfile(filemainapps):
From 43144fa6434cf8be3e1dd09bcf44b60b171e4831 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sun, 8 Sep 2024 18:45:58 +0700
Subject: [PATCH 26/29] main.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
main.py | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/main.py b/main.py
index fa2c93b..bada64c 100644
--- a/main.py
+++ b/main.py
@@ -13,13 +13,18 @@ def parser():
# Argument untuk folder path
parser.add_argument(
- "folder_path", type=str, help="Path to the folder (can contain spaces)"
+ "folder_path", type=str,
+ nargs='?', # Mengindikasikan bahwa argumen ini bersifat opsional
+ default= '"{path}"'.format(path=".."), #os.path.realpath(cureentPath) Nilai default jika tidak ada input
+ help="Path to the folder (can contain spaces)"
)
args = parser.parse_args()
# Mengambil nilai folder_path dari argumen
folder_path = args.folder_path
+ if folder_path.__len__()<=0 or not os.path.isdir(folder_path) or not os.path.isfile(folder_path):
+ folder_path = '"{path}"'.format(path=os.path.dirname(cureentPath))
return str(folder_path)
From a83eeaf3420647f6c5deb77e8e7ed2d626948736 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sun, 8 Sep 2024 19:00:07 +0700
Subject: [PATCH 27/29] main.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
main.py | 31 ++++++++++++++++++++++++-------
1 file changed, 24 insertions(+), 7 deletions(-)
diff --git a/main.py b/main.py
index bada64c..e95efa0 100644
--- a/main.py
+++ b/main.py
@@ -3,7 +3,7 @@
Platform = platform.platform().lower()
mainfile = os.path.dirname(os.path.realpath(__file__)).replace("\\", "/")
cureentPath = os.getcwd()
-os.chdir(mainfile)
+# os.chdir(mainfile)
def parser():
@@ -13,18 +13,33 @@ def parser():
# Argument untuk folder path
parser.add_argument(
- "folder_path", type=str,
- nargs='?', # Mengindikasikan bahwa argumen ini bersifat opsional
- default= '"{path}"'.format(path=".."), #os.path.realpath(cureentPath) Nilai default jika tidak ada input
- help="Path to the folder (can contain spaces)"
+ "folder_path",
+ type=str,
+ nargs="?", # Mengindikasikan bahwa argumen ini bersifat opsional
+ default='"{path}"'.format(
+ path=".."
+ ), # os.path.realpath(cureentPath) Nilai default jika tidak ada input
+ help="Path to the folder (can contain spaces)",
)
args = parser.parse_args()
# Mengambil nilai folder_path dari argumen
folder_path = args.folder_path
- if folder_path.__len__()<=0 or not os.path.isdir(folder_path) or not os.path.isfile(folder_path):
- folder_path = '"{path}"'.format(path=os.path.dirname(cureentPath))
+ if (
+ folder_path.__len__() <= 0
+ or not os.path.isdir(folder_path)
+ or not os.path.isfile(folder_path)
+ ):
+ if str(folder_path).startswith(("'", '"')) and str(folder_path).endswith(
+ ("'", '"')
+ ):
+ pass
+ else:
+ folder_path = '"{path}"'.format(path=os.path.dirname(cureentPath))
+ print(
+ "The specified folder could not be found. Please verify that the folder path you entered is correct and that the folder exists."
+ )
return str(folder_path)
@@ -48,3 +63,5 @@ def main():
if __name__ == "__main__":
main()
+else:
+ print("blocked_module")
From 3d969a8e68cc39df5ba0bbf164a2b93d7a1c4926 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Mon, 9 Sep 2024 16:05:40 +0700
Subject: [PATCH 28/29] Create sss.xt
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
demo/sss.xt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 demo/sss.xt
diff --git a/demo/sss.xt b/demo/sss.xt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/demo/sss.xt
@@ -0,0 +1 @@
+
From e5168cdc0fe67315d0e2075a1723b08beffb48b4 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Mon, 9 Sep 2024 16:06:10 +0700
Subject: [PATCH 29/29] Add files via upload
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
demo/ccccc.mp4 | Bin 0 -> 1799402 bytes
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 demo/ccccc.mp4
diff --git a/demo/ccccc.mp4 b/demo/ccccc.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..1d95393110752d47da4f25cb873c4ccad17ac1a6
GIT binary patch
literal 1799402
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
za