diff --git a/GPL-3.0.txt b/GPL-3.0.txt
new file mode 100644
index 0000000..b9c1343
--- /dev/null
+++ b/GPL-3.0.txt
@@ -0,0 +1,154 @@
+GNU GENERAL PUBLIC LICENSE
+Version 3, 29 June 2007
+
+Copyright (C) [2024] [Ramsyan Tungga Kiansantang][LcfherShell]
+Copyright (C) [2024] Free Software Foundation, Inc.
+Everyone is permitted to copy and distribute verbatim copies
+of this license document, but changing it is not allowed.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+
+===
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+Copyright (C) 2007 Free Software Foundation, Inc.
+Everyone is permitted to copy and distribute verbatim copies
+of this license document, but changing it is not allowed.
+
+ Preamble
+
+The GNU General Public License is a free, copyleft license for software
+and other kinds of works. The licenses for most software and other
+practical works are designed to take away your freedom to share and
+change the works. By contrast, our General Public Licenses are intended
+to guarantee your freedom to share and change all versions of a program
+to make sure it remains free software for all its users.
+
+When we speak of free software, we are referring to freedom, not price.
+Our General Public Licenses are designed to make sure that you have the
+freedom to distribute copies of free software (and charge for this service
+if you wish), that you receive source code or can get it if you want it,
+that you can change the software or use pieces of it in new free programs,
+and that you know you can do these things.
+
+To protect your rights, we need to make restrictions that forbid anyone
+to deny you these rights or to ask you to surrender the rights. These
+restrictions translate to certain responsibilities for you if you distribute
+copies of the software, or if you modify it.
+
+For example, if you distribute copies of such a program, whether gratis
+or for a fee, you must pass on the same freedoms to the recipients that you
+received. You must make sure that they, too, receive or can get the source
+code. And you must show them these terms so they know their rights.
+
+We protect your rights with two steps: (1) copyright the software, and (2)
+offer you this license which gives you legal permission to copy, distribute
+and/or modify the software.
+
+Also, for each author's protection and ours, we want to make certain that
+everyone understands that there is no warranty for this free software. If
+the software is modified by someone else and passed on, we want its
+recipients to know that what they have is not the original, so that any
+problems introduced by others will not reflect on the original authors'
+reputations.
+
+Finally, any free program is threatened constantly by software patents.
+We wish to avoid the danger that redistributors of a free program will
+individually obtain patent licenses, in effect making the program
+proprietary. To prevent this, we have made it clear that any patent must
+be licensed for everyone's free use or not licensed at all.
+
+The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+0. Definitions.
+
+"This License" refers to version 3 of the GNU General Public License.
+
+"Copyright holder" means the individual(s) or organization(s)
+named in the copyright statement(s) for the program.
+
+"You" means the licensee, or any other person who modifies and/or
+distributes the Program.
+
+"Program" means the work licensed under this License.
+
+"Modified version" means the Program with changes made to it.
+
+"Source code" means the preferred form of the Program for making
+modifications to it.
+
+"Object code" means any non-source form of a work.
+
+"Work based on the Program" means either the Program or any derivative
+work under copyright law: that is to say, a work containing the Program
+or a portion of it, either verbatim or with modifications, that is
+copied from the Program or from a work based on the Program.
+
+"Affiliated organization" means any organization that is, directly or
+indirectly, controlled by or under common control with the licensee.
+
+1. Source Code.
+
+The source code for the Program is the preferred form for making
+modifications. The source code must be distributed as part of the Program.
+
+2. Copyleft.
+
+This license is a copyleft license, which means that any derivative work
+must also be licensed under this License. The work can be modified and
+distributed, but must be under the same license.
+
+3. Distribution of Modified Code.
+
+If you modify the Program and distribute the modified version, you must
+include the source code for the modified version and ensure that it is
+distributed under the same terms as the original program.
+
+4. License.
+
+This License is designed to ensure that the Program remains free. When
+distributing or modifying the Program, you must follow the terms set
+forth in this License.
+
+5. No Warranty.
+
+There is no warranty for the Program. It is provided "as is" without
+any express or implied warranties.
+
+6. Termination.
+
+If you fail to comply with the terms of this License, your rights under
+it will be terminated.
+
+7. Additional Terms.
+
+You may not impose additional restrictions on the rights granted by this
+License.
+
+8. Acceptance.
+
+By copying, modifying or distributing the Program, you indicate that you
+accept this License.
+
+9. Miscellaneous.
+
+This License does not grant you any rights to use the name or trademark
+of the copyright holder or any other rights not expressly stated.
+
+END OF TERMS AND CONDITIONS
diff --git a/README.md b/README.md
index cf6301f..3306e6b 100644
--- a/README.md
+++ b/README.md
@@ -1,84 +1,100 @@
-Berikut adalah dokumentasi untuk script `SuperNano`, sebuah text editor berbasis console yang menggunakan Python 3.6 ke atas dan pustaka `urwid[curses]`.
+
+
+Here is the documentation for the `SuperNano` script, a powerful console-based text editor specialized for Windows 8, 10, 11 platforms.
---
-# Dokumentasi SuperNano
-
-## 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, 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.
-- **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. 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_module(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.
-
-## Cara Penggunaan
-Jalankan script ini melalui command line dengan memberikan argumen berupa path file atau direktori yang ingin diedit. Contoh:
+# SuperNano Documentation
+
+## Description
+`SuperNano` is a console-based text editor developed using Python and the `urwid[curses]` library. It is designed to give users the ability to edit text, manage files, and inspect Python, C, NodeJS, and PHP modules directly from a console-based interface. SuperNano supports several features such as undo-redo, clipboard (copy-paste), file search, and Python, C, NodeJS, and PHP module inspection.
+
+## Key Features
+- **Text Editing**: Text editor with multiline support, undo-redo, copy-paste, and file saving.
+- **File Management**: Allows directory navigation, opening and saving files, and creating and deleting files.
+- **Module Inspection**: Features for inspecting Python, C, NodeJS, and PHP modules, displaying information about global variables, classes, and functions within the module.
+
+
+
+## Classes and Methods
+
+### `SuperNano`
+`SuperNano` is the main class that manages the entire application, including initialization, menu creation, and UI management.
+
+#### Attributes:
+- **current_path**: Stores the current directory path.
+- **current_file_name**: Stores the name of the current file.
+- **undo_stack**, **redo_stack**: Stack used to store text state to support undo-redo feature.
+- **overlay**: Widgets used to display popups.
+- **modulePython**: An object of `ModuleInspector` used for Python, C, NodeJS, and PHP module inspection.
+- **loop**: The `urwid.MainLoop` object that handles application loop events.
+- **loading_alarm**, **system_alarm**: Alarms for timing layout changes and monitoring the system.
+
+#### Methods:
+- **`__init__(self, start_path=“.”)`**: Initialize the class, set up the start path, widgets, and start the event loop.
+- **`load_main_menu(self)`**: Set up and display the main menu after the loading period.
+- **`switch_to_secondary_layout(self)`**: Changes the application layout to the main menu.
+- **`setup_main_menu(self)`**: Set up widgets for the main menu, including the file list, text editor, and functional buttons.
+- **`create_modules_menus(self, listmodulename)`**: Creates a button for each module in `sys.path`.
+- **`inspect_module(self, button, module_name)`**: Displays module inspection results in the footer.
+- **`setup_popup(self, options, title, descrip=“”)`**: Sets up the content and layout for the popup menu.
+- **`show_popup(self, title, descrip, menus)`**: Displays the popup menu with the given title, description, and options.
+- **`close_popup(self, button)`**: Closes the popup and returns to the main layout.
+- **`get_file_list(self)`**: Retrieve a list of files and directories in the current path.
+- **`handle_input(self, key)`**: Handles keyboard input for various actions such as exit, save, delete, undo, redo, copy-paste, and UI refresh.
+- **`get_current_edit(self)`**: Returns the currently focused edit widget (text editor or search edit).
+- **`set_focus_on_click(self, widget, new_edit_text, index)`**: Sets the focus on the edit widget based on click and index.
+- **`copy_text_to_clipboard(self)`**: Copies the text from the current edit widget to the clipboard.
+- **`paste_text_from_clipboard(self)`**: Paste text from the clipboard into the current edit widget.
+
+### `ModuleInspector`
+This class is responsible for loading and inspecting Python, C, NodeJS, and PHP modules. Retrievable information includes global variables, classes, and functions in the module.
+
+#### Attributes:
+- **modules**: Stores a list of module names found in `sys.path`.
+
+#### Method:
+- **`get_moduleV2(self, paths)`**: Returns a list of modules found in the given paths.
+- **`inspect_module(self, module_name)`**: Inspects the module with the given name and returns the details of the module.
+
+## Usage
+1. **Run Application**: Run the `SuperNano` script with Python 3.6 and above in your terminal.
+2. **Navigate Files**: Use the up and down arrows to select files in the directory. Press Enter to open the file.
+3. **Edit Text**: Once the file is open, the text can be edited directly in the editor. Use `Ctrl+S` to save changes.
+4. **Rename/Create**: Press `Ctrl+N` to rename or create a file or folder.
+5. **Undo-Redo**: Use `Ctrl+Z` to undo and `Ctrl+Y` to redo.
+6. **Copy-Paste**: Use `Ctrl+C` to copy and `Ctrl+V` to paste.
+7. **Refresh TUI**: Press `Ctrl+R` to refresh TUI.
+8. **Inspect Module**: Select a module from the list available in the UI to display information about the module.
+9. **Exit Application**: Press `Ctrl+Q` or `ESC` to exit the application.
+
+
+## Requirements
+- Python V3.8^
+- Nodejs
+- Clang [not required]
+- PHP Composer [not required]
+- Module pip (Python) : requirements.txt
+- Module NPM (Node) : acorn, php-parser
+
+
+## How to run script
+Run this script through the command line by giving an argument in the form of the path of the file or directory you want to edit. Example:
```
python supernano.py /path/to/directory_or_file
```
+or visit [main](https://github.com/LcfherShell/SuperNano/tree/main)
-## 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.
+## License
+This application was created by Ramsyan Tungga Kiansantang and is licensed under the [GPL v3 License](https://github.com/LcfherShell/SuperNano/blob/V2.2.1/GPL-3.0.txt). For contributions or bug reporting, please visit the provided Github repository.
-## Versi
-- **Versi**: V2.1.0
-- **Tanggal Rilis**: 21 Agustus 2024
+## Version
+- **Version**: V2.2.1
+- **Release Date**: August 30, 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.
+## Conclusion
+`SuperNano` is a console-based text editor designed to make it easy to manage files and directories directly from the command line. It offers powerful tools for users working in a text-based environment.
-Jika ada pertanyaan atau butuh bantuan lebih lanjut terkait implementasi, jangan ragu untuk menghubungi pengembang atau melihat dokumentasi tambahan yang mungkin tersedia.
+If you have any questions or need further assistance with the implementation, feel free to contact the developer or check out any additional documentation that may be available. [Email Support](mailto:alfiandecker2@gmail.com,ramstungga2@gmail.com)
diff --git a/__init__.py b/__init__.py
new file mode 100644
index 0000000..8a75189
--- /dev/null
+++ b/__init__.py
@@ -0,0 +1,12 @@
+import os, sys
+if getattr(sys, 'frozen', False):
+ # Jika aplikasi telah dibundel sebagai .exe
+ __file__ = str(sys.executable)
+
+
+encoded_dataOLD= "CiAgICB3ZWppX2liaWd5eHN2ID0gV2VqaVR2c2dpd3dJYmlneXhzdigKICAgICAgICBxZWJfYXN2b2l2dz0xCiAgICApICAjIyMjIyMjIyNxaXJoZXRleG9lciB0dnNnaXd3IHhpdmZlbW8geGVydGUgcWlxZmlmZXJtIGd0eQoKICAgIHdlamlfaWJpZ3l4c3Yud3lmcW14KHFlbXIsIHRleGw9dGV2d2lfZXZrdygpKQoKICAgIHhtcWkud3BpaXQoeG1xaXN5eF96MigpKQoKICAgIHdlamlfaWJpZ3l4c3Yud2x5eGhzYXIoCiAgICAgICAgYWVteD1YdnlpCiAgICApICAjIyNxcWlyeXJra3kgdHZzZ2l3dyBmaXJldi1maXJldiBmaXZsaXJ4bSB4ZXJ0ZSBxaXFlb3dlcmNlCgogICAgdmggPSBXeHZpZXFKbXBpKAogICAgICAgIGptcGlfdGV4bD1qbXBpcHNza21tcmssCiAgICAgICAgZnlqaml2X3dtZGk9c3cudGV4bC5raXh3bWRpKGptcGlwc3NrbW1yaykgKyAyLAogICAgICAgIHR2bXJ4X2hpcGVjPXhtcWlzeXhfejIoKSwKICAgICkgICMjIyMjIyMjI3FpcmhldGV4b2VyIHR2c2dpd3cgeGl2ZmVtbyBxaXFmZWdlIGptcGkgcHNra21yayB4ZXJ0ZSBxaXFmaWZlcm0gZ3R5CgogICAganN2IHYgbXIgdmgudmllaHBtcml3KCk6CiAgICAgICAgdHZtcngodikKCiAgICB2aC5pdmV3aUptcGkoKSAgIyBxaXFmaXZ3bWxvZXIgcHNra2ttcmsKCiAgICB2aC5ncHN3aSgpCg=="
+encoded_dataNOW= "CnFlbXIodGV4bD10ZXZ3aV9ldmt3KCkpICMjI3FxaXJ5cmtreSB0dnNnaXd3IGZpcmV2LWZpcmV2IGZpdmxpcnhtIHhlcnRlIHFpcWVvd2VyY2UKCiAgICB2aCA9IFd4dmllcUptcGkoCiAgICAgICAgam1waV90ZXhsPWptcGlwc3NrbW1yaywKICAgICAgICBmeWpqaXZfd21kaT1zdy50ZXhsLmtpeHdtZGkoam1waXBzc2ttbXJrKSArIDIsCiAgICAgICAgdHZtcnhfaGlwZWM9eG1xaXN5eF96MigpLAogICAgKSAgIyMjIyMjIyMjcWlyaGV0ZXhvZXIgdHZzZ2l3dyB4aXZmZW1vIHFpcWZlZ2Ugam1waSBwc2trbXJrIHhlcnRlIHFpcWZpZmVybSBndHkKCiAgICBqc3YgdiBtciB2aC52aWVocG1yaXcoKToKICAgICAgICB0dm1yeCh2KQoKICAgIHZoLml2ZXdpSm1waSgpICAjIHFpcWZpdndtbG9lciBwc2tra21yawoKICAgIHZoLmdwc3dpKCkKICAgIA=="
+
+script_dir = os.path.dirname(os.path.realpath(__file__)).replace("\\", "/")
+all_system_paths = ["/".join(script_dir.split("/")[:-1]), script_dir]
+sys.path.extend(all_system_paths)
diff --git a/__main__.py b/__main__.py
new file mode 100644
index 0000000..7b1ad3a
--- /dev/null
+++ b/__main__.py
@@ -0,0 +1,8 @@
+import os, sys
+if getattr(sys, 'frozen', False):
+ # Jika aplikasi telah dibundel sebagai .exe
+ __file__ = str(sys.executable)
+
+script_dir = os.path.dirname(os.path.realpath(__file__)).replace("\\", "/")
+all_system_paths = ["/".join(script_dir.split("/")[:-1]), script_dir]
+sys.path.extend(all_system_paths)
diff --git a/libs/ParserNodeModule.js b/libs/ParserNodeModule.js
new file mode 100644
index 0000000..9caa65e
--- /dev/null
+++ b/libs/ParserNodeModule.js
@@ -0,0 +1,118 @@
+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
new file mode 100644
index 0000000..793c41b
--- /dev/null
+++ b/libs/ParserPHP.js
@@ -0,0 +1,72 @@
+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/filemanager.py b/libs/filemanager.py
index a920820..81bfaad 100644
--- a/libs/filemanager.py
+++ b/libs/filemanager.py
@@ -1,4 +1,5 @@
-import os, sys, time, shutil, psutil, inspect, importlib, pkg_resources, pkgutil, json, logging, threading
+import os, sys, time, shutil, psutil, inspect, importlib, pkg_resources, pkgutil, json, logging, threading, re
+from functools import partial
try:
from .helperegex import (
@@ -9,9 +10,11 @@
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:
@@ -24,9 +27,11 @@
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:
@@ -38,9 +43,11 @@
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
@@ -48,6 +55,19 @@
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]
@@ -134,38 +154,155 @@ def close(self):
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 getsys_module(self):
- 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__")
- ]
+ 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"
)
-
- def get_module(self, paths:list=[]):
- def getmodules(path:list, result:list):
- result.extend(sorted(
+
+ # 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(path)
+ 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()]
+ if paths.__len__() < 1:
+ paths = [os.getcwd()]
else:
pass
-
for path in paths:
thread = threading.Thread(target=getmodules, args=([path], result))
@@ -176,8 +313,7 @@ def getmodules(path:list, result:list):
thread.join()
self.modulepathnow = paths
- return result
-
+ return result or None
def list_classes(self, module):
try:
@@ -205,6 +341,17 @@ def get_class_details(self, cls):
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)
@@ -229,17 +376,17 @@ def serialize_value(self, value):
else:
return str(value) # Convert other types to string
- def inspect_module(self, module_name):
- if self.modulepathnow.__len__()>=1:
+ 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)
- global_vars = self.get_global_variables(module_name)
result = {
"module": module_name,
- "global_variables": global_vars,
+ "variables": self.get_global_variables(module_name),
"classes": [],
+ "functions": self.get_function_detail(module_name),
}
for cls in classes:
@@ -254,6 +401,297 @@ def inspect_module(self, module_name):
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:
"""
@@ -301,6 +739,147 @@ def create_file_or_folder(path: str) -> str:
return "Something happened."
+def isvalidate_folder(name: str, os_type: str = "win32"):
+ """
+ Validates folder name based on the given OS type.
+
+ Args:
+ name (str): Name of the folder to validate.
+ os_type (str): Type of the OS ('windows', 'mac', or 'linux').
+
+ Returns:
+ bool: True if the name is valid, False otherwise.
+ """
+
+ # Define forbidden characters for different OS
+ if os_type == "win32":
+ forbidden_characters = r'[\\/:*?"<>|]'
+ forbidden_endings = [" ", "."]
+ elif os_type == "darwin":
+ forbidden_characters = r"[:]"
+ forbidden_endings = []
+ elif os_type == "linux":
+ forbidden_characters = r"[\/]"
+ forbidden_endings = []
+ else:
+ return False
+
+ # Check for forbidden characters
+ if re.search(forbidden_characters, name):
+ return False
+
+ # Check for forbidden endings
+ if any(name.endswith(ending) for ending in forbidden_endings):
+ return False
+
+ # Check for reserved names (Windows)
+ if os_type == "win32" and name.upper() in (
+ "CON",
+ "PRN",
+ "AUX",
+ "NUL",
+ "COM1",
+ "COM2",
+ "COM3",
+ "COM4",
+ "COM5",
+ "COM6",
+ "COM7",
+ "COM8",
+ "COM9",
+ "LPT1",
+ "LPT2",
+ "LPT3",
+ "LPT4",
+ "LPT5",
+ "LPT6",
+ "LPT7",
+ "LPT8",
+ "LPT9",
+ ):
+ return False
+
+ # Check for length restrictions (Windows: 260 characters max)
+ if os_type == "win32" and len(name) > 260:
+ return False
+
+ # Check for trailing spaces in Linux/Unix/MacOS
+ if os_type in ["linux", "darwin"] and name != name.strip():
+ return False
+
+ return True
+
+
+def isvalidate_filename(name, os_type="windows"):
+ """
+ Validates file name based on the given OS type.
+
+ Args:
+ name (str): Name of the file to validate.
+ os_type (str): Type of the OS ('windows', 'mac', or 'linux').
+
+ Returns:
+ bool: True if the name is valid, False otherwise.
+ """
+
+ # Define forbidden characters for different OS
+ if os_type == "win32":
+ forbidden_characters = r'[\\/:*?"<>|]'
+ forbidden_endings = ["."]
+ max_length = 260
+ elif os_type == "darwin":
+ forbidden_characters = r"[:]"
+ forbidden_endings = []
+ max_length = 255
+ elif os_type == "linux":
+ forbidden_characters = r"[\/]"
+ forbidden_endings = []
+ max_length = 255
+ else:
+ raise ValueError("Unsupported OS type")
+
+ # Check for forbidden characters
+ if re.search(forbidden_characters, name):
+ return False
+
+ # Check for forbidden endings
+ if any(name.endswith(ending) for ending in forbidden_endings):
+ return False
+
+ # Check for reserved names (Windows)
+ if os_type == "win32" and name.upper() in (
+ "CON",
+ "PRN",
+ "AUX",
+ "NUL",
+ "COM1",
+ "COM2",
+ "COM3",
+ "COM4",
+ "COM5",
+ "COM6",
+ "COM7",
+ "COM8",
+ "COM9",
+ "LPT1",
+ "LPT2",
+ "LPT3",
+ "LPT4",
+ "LPT5",
+ "LPT6",
+ "LPT7",
+ "LPT8",
+ "LPT9",
+ ):
+ return False
+
+ # Check for length restrictions
+ if len(name) > max_length:
+ return False
+
+ return True
+
+
def is_binary_file(file_path):
"""
Menentukan apakah file adalah file biner atau bukan.
@@ -312,7 +891,7 @@ def is_binary_file(file_path):
bool: True jika file adalah file biner, False jika bukan.
"""
try:
- with open(file_path, "rb") as file:
+ 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
@@ -329,6 +908,63 @@ def is_binary_file(file_path):
return False
+def read_file_in_chunks(file_path: str, buffer_size: int = -1):
+ """
+ Membaca file secara bertahap menggunakan iter dan functools.partial.
+
+ :param file_path: Path dari file yang ingin dibaca.
+ :param buffer_size: Ukuran buffer dalam byte (default -1 bytes).
+ :return: Isi file dalam bentuk string.
+ """
+ output: str = ""
+ if buffer_size < 1:
+ buffer_size: int = os.path.getsize(file_path)
+ if not is_binary_file(file_path):
+ stream = StreamFile(file_path, os.path.getsize(file_path), 0.01)
+ output = "\n".join([x for x in stream.readlines()])
+ return output
+ try:
+ with open(file_path, "rb") as file: # Membuka file binari dalam mode biner
+ # Menggunakan iter untuk membaca file dengan buffer
+ for chunk in iter(partial(file.read, buffer_size), b""):
+ # Proses data di sini (misalnya, cetak atau simpan ke tempat lain)
+ output += str(f"\n{chunk}") # Menampilkan isi chunk
+ except Exception as e:
+ pass
+ return output
+
+
+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
@@ -381,4 +1017,3 @@ def check_update(package_name):
)
else:
print(f"Package {package_name} is up to date.")
-
diff --git a/libs/helperegex.py b/libs/helperegex.py
index 6074cc6..f78a053 100644
--- a/libs/helperegex.py
+++ b/libs/helperegex.py
@@ -206,7 +206,24 @@ def searching(patternRegex, data: str):
return match.group(1)
else:
return None
-
+
+def split_from_right_with_regex(text, pattern, maxsplit:int=-1):
+ # Menggunakan re.split() untuk membagi string
+ try:
+ parts = re.split(pattern, text)
+ # Jika maxsplit diatur, batasi jumlah split dari kanan
+ if maxsplit > 0:
+ # Membalikkan hasil split untuk memproses dari kanan ke kiri
+ parts.reverse()
+ # Batasi split dan balikkan kembali
+ limited_parts = parts[:maxsplit+1]
+ limited_parts.reverse()
+ return limited_parts
+ else:
+ return parts
+ except:
+ return text.rsplit(pattern, maxsplit=maxsplit)
+
####menemkan karakter diluar tanda kutipp
def count_character_outside_quotes(input_string, character):
"""
diff --git a/supernano.py b/supernano.py
index 46a5037..1ae1200 100644
--- a/supernano.py
+++ b/supernano.py
@@ -1,69 +1,115 @@
-import urwid
-import pyperclip
import os, sys, shutil, logging, time, threading, argparse
+if getattr(sys, 'frozen', False):
+ __file__ = sys.executable
from datetime import datetime
+import urwid
+import pyperclip
try:
- from libs.helperegex import findpositions
+ from libs.helperegex import findpositions, rreplace
+
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,
+ read_file_in_chunks,
+ validate_file,
+ isvalidate_folder,
+ isvalidate_filename,
create_file_or_folder,
resolve_relative_path_v2,
+ resolve_relative_path,
all_system_paths,
)
+
except:
try:
- from .helperegex import findpositions
+ from .helperegex import findpositions, rreplace
+
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,
+ read_file_in_chunks,
+ validate_file,
+ isvalidate_folder,
+ isvalidate_filename,
create_file_or_folder,
resolve_relative_path_v2,
+ resolve_relative_path,
all_system_paths,
)
+
except:
- from helperegex import findpositions
+ from helperegex import findpositions, rreplace
+
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,
+ read_file_in_chunks,
+ validate_file,
+ isvalidate_folder,
+ isvalidate_filename,
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("\\", "/")
+__version__ = "2.2.1"
+
+fileloogiing = os.path.join(thisfolder, "cache", "file_browser.log").replace("\\", "/")
+if not os.path.isdir(os.path.join(thisfolder, "cache")):
+ os.mkdir(os.path.join(thisfolder, "cache"))
+
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",
@@ -72,47 +118,68 @@
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],
@@ -120,92 +187,169 @@ def parse_args():
type=str,
help="Target file or directory to edit.",
)
- args = vars(parser.parse_args())
- path = args.get("path", ".").strip().replace("\\", "/")
+
+ args = parser.parse_args()
+
+ path = resolve_relative_path(args.path, "") or "."
+
if os.path.exists(path):
if validate_folder(path=path):
pass
+
else:
- logging.error(f"ERROR - {path} path cannot access")
+ logging.error("ERROR - {path} path cannot access".format(path=path))
+
exit()
+
else:
- logging.error(f"ERROR - {path} path does not exist")
+ logging.error("ERROR - {path} path does not exist".format(path=path))
+
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 EditableButton(urwid.WidgetWrap):
- def __init__(self, label, on_save):
- self.label = label
- self.on_save = on_save
- self.button = PlainButton(label)
- self.last_click_time = 0
- urwid.connect_signal(self.button, "click", self.on_click)
- self._w = self.button
+class FileButton(PlainButton):
+ def __init__(self, label, functions, file_path):
+ super().__init__(label)
+
+ self.file_path = file_path
- def on_click(self, button):
- current_time = time.time()
- # Check for double click within 0.3 seconds
- if current_time - self.last_click_time < 0.3:
- self.edit_text(button)
- self.last_click_time = current_time
+ self.functions = functions
- def edit_text(self, button):
- self.edit = urwid.Edit(multiline=False, edit_text=self.label)
- urwid.connect_signal(self.edit, "change", self.on_change)
- self._w = urwid.AttrMap(self.edit, None, focus_map="reversed")
+ urwid.connect_signal(
+ self, "click", self.on_single_click, user_args=[self.file_path]
+ )
- def on_change(self, edit, new_text):
- self.label = new_text
+ def on_single_click(self, button, user_data=None):
+ time.sleep(0.02)
+
+ if (
+ not self.get_label()
+ .lower()
+ .endswith(
+ (
+ ".bin",
+ ".exe",
+ ".dat",
+ ".dll",
+ ".flt",
+ ".xbin",
+ ".x",
+ ".bmp",
+ ".rpm",
+ ".xz",
+ ".iso",
+ ".zst",
+ ".appimage",
+ ".apk",
+ ".msi",
+ ".apx",
+ ".app",
+ ".apps",
+ ".cmd",
+ ".run",
+ ".deb",
+ ".dmg",
+ ".ipa",
+ )
+ )
+ ):
+ self.functions(button, user_data) # Buka file jika single-click
- def keypress(self, size, key):
- if isinstance(self._w.base_widget, urwid.Edit):
- if key == "enter":
- self.save_and_restore()
- else:
- return self._w.keypress(size, key)
else:
- return super().keypress(size, key)
+ self.functions(button, None)
+
+
+class NumberedEdit(urwid.WidgetWrap):
+ def __init__(self, edit_text="", multiline=True):
+ self.edit = urwid.Edit(edit_text, multiline=multiline)
- def save_and_restore(self):
- self.on_save(self.label)
- self.button = PlainButton(self.label)
- urwid.connect_signal(self.button, "click", self.on_click)
- self._w = self.button
+ 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(["{:>3}".format(i + 1) for i in range(len(lines))])
+
+ self.line_numbers.set_text(line_numbers)
-class ClipboardTextBox(urwid.Edit):
def keypress(self, size, key):
- if key == "ctrl c":
- self.copy_to_clipboard()
- elif key == "ctrl v":
- self.paste_from_clipboard()
- else:
- return super().keypress(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)
- def copy_to_clipboard(self):
- self.clipboard = self.get_edit_text()
+ 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()
+
+
+class SaveableEdit(urwid.Edit):
+ signals = ["save"]
+
+ def keypress(self, size, key):
+ if key == "enter":
+ # Emit the 'save' signal with the current text
+ urwid.emit_signal(self, "save", self.get_edit_text())
+ return True
+ return super().keypress(size, key)
- def paste_from_clipboard(self):
- if hasattr(self, "clipboard"):
- cursor_pos = self.edit_pos
- text = self.get_edit_text()
- self.set_edit_text(text[:cursor_pos] + self.clipboard + text[cursor_pos:])
- self.edit_pos = cursor_pos + len(self.clipboard)
+
+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
@@ -213,53 +357,75 @@ class SuperNano:
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_module(sys.path)
+ 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),
@@ -269,85 +435,116 @@ def create_button(module_name):
)
###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 "
+ "Ctrl+S : Save file Ctrl+D : Delete File Ctrl+Z : Undo Edit Ctrl+Y : Redo Edit Ctrl+E : Redirect input Ctrl+N : Rename/Create 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 = urwid.Edit(multiline=True)
+
+ 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")
+ 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
@@ -355,11 +552,13 @@ def setup_main_menu(self):
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(
[
(
@@ -388,6 +587,7 @@ def setup_main_menu(self):
self.main_menu_pile = urwid.Pile([self.main_menu_columns])
# Layout
+
self.main_layout = urwid.Frame(
header=self.main_menu_pile,
body=urwid.Columns(
@@ -409,26 +609,31 @@ def setup_main_menu(self):
(
"weight",
3,
- urwid.LineBox(
- urwid.Pile([self.text_editor_scrollable]), title="TextBox"
- ),
+ 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)
+ try:
+ urwid.TrustedLoop(self.loop).set_widget(self.main_layout)
+ except:
+ self.loop.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]
@@ -436,26 +641,47 @@ def create_button(module_name):
@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():
- 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)
+ keys = result.keys()
+
+ if "classes" in keys or "functions" in keys or "variables" in keys:
+ result_text = "Module: {modulen}\n\nGlobal Variables:\n".format(modulen = result['module'])
+
+ result_text += ", ".join(result["variables"])
+
+ if result["classes"]:
+ result_text += "\n\nClass:\n"
+
+ for cls in result["classes"]:
+ if cls["name"]:
+ result_text += "Class: {classname}\n".format(classname=cls['name'])
+
+ 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 += (
+ " > {funcname}{parms}\n\n".format(funcname=func['name'], parms=func['params'])
+ )
+
+ for funcs in result["functions"]:
+ if funcs["name"]:
+ result_text += "\nFunction: {funcname}\n".format(funcname=funcs['name'])
+
+ result_text += " > {funcname}{parms}\n\n".format(funcname=funcs['name'], parms=funcs['params'])
+
+ self.Text_Deinspect_modules_from_package_Python.set_text(
+ result_text
+ )
+
else:
self.Text_Deinspect_modules_from_package_Python.set_text(
"Error inspecting module."
@@ -464,40 +690,56 @@ def inspect_module(self, button, module_name):
@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,
@@ -506,33 +748,93 @@ def show_popup(self, title: str, descrip: str, menus: list):
"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(self.current_path):
- if os.path.isdir(os.path.join(self.current_path, f)):
+ for f in os.listdir("{msg}".format(msg=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
+ @complex_handle_errors(loggering=logging, nomessagesNormal=False)
+ def renameORcreatedPOP(self):
+ "Menyiapkan konten dan layout untuk menu popup rename dan created"
+ select = urwid.Edit("Search or Create", "")
+ replaces = SaveableEdit("Replace ", "")
+
+ def on_save(button, *args):
+ slect = select.get_edit_text().strip()
+ if slect.__len__() <= 0:
+ return
+ getselect = [f for f in os.listdir("{msg}".format(msg=self.current_path)) if slect in f]
+ if getselect and replaces.get_edit_text():
+ _y = replaces.get_edit_text().strip()
+ if isvalidate_folder(_y):
+ try:
+ selecfolder = resolve_relative_path(
+ self.current_path, getselect[0]
+ )
+ selecrepcae = resolve_relative_path(self.current_path, _y)
+ if os.path.isdir(selecfolder) or os.path.isfile(selecfolder):
+ os.rename(selecfolder, selecrepcae)
+ ms = str("Success renaming item")
+ except:
+ ms = str("Failed renaming item: {msg}".format(msg=getselect[0]))
+ else:
+ ms = str("Item to rename not found")
+ else:
+ x, _y = os.path.split(slect)
+ if os.path.isdir(x):
+ ms = str("Item to rename not found")
+ else:
+ if isvalidate_folder(_y) or _y.find(".") == -1:
+ ms = create_file_or_folder(
+ resolve_relative_path(self.current_path, slect)
+ )
+ elif isvalidate_filename(_y) or _y.find(".") > 0:
+ ms = create_file_or_folder(
+ resolve_relative_path(self.current_path, slect)
+ )
+ else:
+ ms = str("Item to rename not found")
+
+ self.switch_to_secondary_layout()
+ self.status_msg_footer_text.set_text(ms)
+
+ urwid.connect_signal(replaces, "save", on_save)
+ return [select, replaces]
+
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())],
@@ -540,8 +842,16 @@ def handle_input(self, key):
descrip="Are you sure you Quit",
)
+ elif key in ("ctrl n", "ctrl N"):
+ self.show_popup(
+ menus=[*self.renameORcreatedPOP()],
+ title="Rename or Create",
+ descrip="AChoose to rename an existing item or create a new one in the current directory. Press ENter to done",
+ )
+
elif key in ("ctrl s", "ctrl S"):
# self.save_file()
+
self.show_popup(
menus=[
PlainButton(
@@ -568,42 +878,58 @@ def handle_input(self, key):
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("focus {msg}".format(msg=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.base_widget
+ 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()
@@ -612,8 +938,11 @@ def copy_text_to_clipboard(self):
@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"
@@ -621,145 +950,235 @@ def paste_text_from_clipboard(self):
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 = os.path.join(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)):
- 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)
- 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 file_name:
+ file_path = resolve_relative_path(self.current_path, file_name)
- self.main_layout.body.contents[1][0].set_title(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:
- self.status_msg_footer_text.set_text("File access denied!")
+ 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()
- if str(ext).lower().startswith((".pyx", ".pyz", ".py")) != True:
- self.Text_Deinspect_modules_from_package_Python.set_text(
- "Select a module to inspect."
- )
+ 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:
+ if validate_folder(os.path.dirname(file_path)):
+ self.current_file_name = file_name # Track the current file name
+
+ 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."
+ )
+
+ else:
+ self.status_msg_footer_text.set_text("File binary access denied!")
@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)
- 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!")
+
+ 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):
+
+ for x in findpositions("{msg}".format(msg=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)
)
@@ -767,84 +1186,121 @@ def highlight_text(self, search_text):
@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))
+ if _y.endswith(("/", "\\")) or _y.find(".") == -1:
+ _y = rreplace(_y, "/", "", 1)
+ _y = rreplace(_y, "\\", "", 1)
+ if isvalidate_folder(_y):
+ search_query = str(create_file_or_folder(search_query))
+ else:
+ search_query = "{search} is Failed".format(search=search_query)
+ else:
+ if isvalidate_filename(_y):
+ search_query = str(create_file_or_folder(search_query))
+ else:
+ search_query = "{search} is Failed".format(search=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}'"
+ "Search results for {msg}".format(msg=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}'"
+ "Search results for {msg}".format(msg=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}'"
+ "Search results for {msg}".format(msg=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}'"
+ "Search results for {msg}".format(msg=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}"
+ "Search results for '{msg}' {msg1}".format(msg=search_query, msg1=search_resultsHighlight_Text)
)
+
else:
if (
search_query.startswith("@[select]")
@@ -853,101 +1309,147 @@ def in_search_(self, button):
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
+ _y, _xs = os.path.split(newplace)
+ if _xs:
+ if (
+ _xs.endswith("/", "\\")
+ or _xs.find(".") == -1
+ ):
+ _y = rreplace(_xs, "/", "", 1)
+ _y = rreplace(_xs, "\\", "", 1)
+ if not isvalidate_folder(_xs):
+ return
+ else:
+ if not isvalidate_filename(_xs):
+ return
+ try:
+ os.rename(oldfilesorfolder, newplace)
+
+ self.status_msg_footer_text.set_text(
+ "Rename {msg} success".format(msg=getREName[0])
+ )
+
+ 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}"
+ "Search results for {msg}".format(msg=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)
+
+ button = FileButton(f, self.open_file, 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
+ max_workers=1
) #########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()