├── .nojekyll ├── tests ├── __init__.py ├── test_fileFilter.py ├── test_utils.py ├── test_allFileMetadata.py ├── test_fileMetadata.py └── test_flagController.py ├── twinTrim ├── __init__.py ├── dataStructures │ ├── __init__.py │ ├── fileFilter.py │ ├── fileMetadata.py │ └── allFileMetadata.py ├── main.py ├── utils.py ├── flagController.py └── flags.py ├── MANIFEST.in ├── assets ├── javascripts │ └── lunr │ │ └── min │ │ ├── lunr.jp.min.js │ │ ├── lunr.vi.min.js │ │ ├── lunr.multi.min.js │ │ ├── lunr.th.min.js │ │ ├── lunr.hy.min.js │ │ ├── lunr.te.min.js │ │ ├── lunr.ta.min.js │ │ ├── lunr.zh.min.js │ │ ├── lunr.ja.min.js │ │ ├── lunr.hi.min.js │ │ ├── lunr.kn.min.js │ │ ├── lunr.sa.min.js │ │ ├── lunr.stemmer.support.min.js │ │ ├── lunr.ko.min.js │ │ ├── lunr.sv.min.js │ │ ├── lunr.da.min.js │ │ ├── lunr.no.min.js │ │ ├── lunr.he.min.js │ │ ├── lunr.nl.min.js │ │ ├── lunr.de.min.js │ │ ├── lunr.du.min.js │ │ ├── lunr.ru.min.js │ │ ├── lunr.fi.min.js │ │ ├── lunr.hu.min.js │ │ ├── lunr.pt.min.js │ │ ├── lunr.el.min.js │ │ ├── lunr.fr.min.js │ │ └── lunr.ro.min.js ├── images │ └── favicon.png └── stylesheets │ └── palette.06af60db.min.css.map ├── sitemap.xml.gz ├── docs ├── License.md ├── Contributing.md ├── Features.md ├── How-It-Works.md ├── Usage.md └── index.md ├── .vscode └── settings.json ├── .github ├── ISSUE_TEMPLATE │ ├── test_request.md │ ├── documentation_request.md │ ├── workflow_request.md │ ├── bug_report.md │ ├── feature_request.md │ └── enhancement_request.md ├── workflows │ └── test.yml └── PULL_REQUEST_TEMPLATE.md ├── mkdocs.yml ├── pyproject.toml ├── sitemap.xml ├── LICENSE ├── setup.py ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTION_GUIDELINES.md ├── README.md └── search └── search_index.json /.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /twinTrim/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /twinTrim/dataStructures/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include LICENSE 3 | -------------------------------------------------------------------------------- /assets/javascripts/lunr/min/lunr.jp.min.js: -------------------------------------------------------------------------------- 1 | module.exports=require("./lunr.ja"); -------------------------------------------------------------------------------- /sitemap.xml.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kota-Karthik/twinTrim/HEAD/sitemap.xml.gz -------------------------------------------------------------------------------- /twinTrim/main.py: -------------------------------------------------------------------------------- 1 | from twinTrim.flags import cli 2 | 3 | if __name__ == "__main__": 4 | cli() 5 | -------------------------------------------------------------------------------- /assets/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kota-Karthik/twinTrim/HEAD/assets/images/favicon.png -------------------------------------------------------------------------------- /docs/License.md: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.testing.pytestArgs": [ 3 | "tests" 4 | ], 5 | "python.testing.unittestEnabled": false, 6 | "python.testing.pytestEnabled": true 7 | } -------------------------------------------------------------------------------- /docs/Contributing.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributing 3 | 4 | Contributions are welcome! Whether you have ideas for improving the internal workings of TwinTrim, such as optimizing performance or refining algorithms, or you want to enhance the user interface of the CLI tool for a better user experience, your input is valuable. Please fork the repository and submit a pull request with your improvements or new features. 5 | 6 | Please refer to the [CONTRIBUTING GUIDELINES](../CONTRIBUTION_GUIDELINES.md) for guidelines on how to contribute. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/test_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Test Request 3 | about: Request to add or modify tests for the project 4 | title: "[Test] " 5 | labels: test 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the test request** 11 | A clear and concise description of what you want to test or modify in the existing tests. 12 | 13 | **Motivation** 14 | Explain why this test is important. 15 | 16 | **Steps to Reproduce** 17 | Provide any relevant steps or code snippets. 18 | 19 | **Additional context** 20 | Add any other context about the test request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Documentation Request 3 | about: Suggest changes or additions to the project documentation 4 | title: "[Documentation] " 5 | labels: documentation 6 | assignees: '' 7 | 8 | --- 9 | 10 | **What documentation needs to be updated?** 11 | A clear and concise description of the documentation that needs to be updated. 12 | 13 | **Describe the changes needed** 14 | What specific changes or additions do you propose? 15 | 16 | **Additional context** 17 | Add any other context about the documentation request here. 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/workflow_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Workflow Request 3 | about: Suggest changes to the project's workflow or process 4 | title: "[Workflow] " 5 | labels: workflow 6 | assignees: '' 7 | 8 | --- 9 | 10 | **What workflow needs to be updated?** 11 | A clear and concise description of the workflow that needs to be updated. 12 | 13 | **Describe the proposed changes** 14 | What specific changes do you propose for the workflow? 15 | 16 | **Motivation** 17 | Explain why this change is necessary. 18 | 19 | **Additional context** 20 | Add any other context about the workflow request here. 21 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | 2 | site_name: TwinTrim Docs 3 | site_url: https://twintrim.com/docs 4 | repo_url: https://github.com/Kota-Karthik/twinTrim 5 | repo_name: Kota-Karthik/twinTrim 6 | docs_dir: docs 7 | site_dir: site 8 | theme: 9 | name: material 10 | features: 11 | - navigation.tabs 12 | - navigation.sections 13 | - navigation.instant 14 | - navigation.instant.progress 15 | icon: 16 | repo: fontawesome/brands/github 17 | 18 | palette: 19 | scheme: slate 20 | 21 | nav: 22 | - Home: index.md 23 | - Usage: Usage.md 24 | - Contributing: Contributing.md 25 | - How It Works: How-It-Works.md 26 | - License: License.md -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a report to help us improve 4 | title: "[Bug] " 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '...' 17 | 3. Scroll down to '...' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for this project 4 | title: "[Feature] " 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of the problem you're trying to solve. 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | pull_request: 8 | branches: 9 | - '**' 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v2 18 | 19 | - name: Set up Python 20 | uses: actions/setup-python@v2 21 | with: 22 | python-version: '3.8' 23 | 24 | - name: Install dependencies 25 | run: | 26 | python -m pip install --upgrade pip 27 | pip install poetry 28 | poetry install 29 | 30 | - name: Run tests 31 | run: | 32 | poetry run pytest tests/*.py 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/enhancement_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enhancement Request 3 | about: Suggest an improvement or enhancement for the project 4 | title: "[Enhancement] " 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the enhancement** 11 | A clear and concise description of the proposed enhancement or improvement. 12 | 13 | **Why is this enhancement necessary?** 14 | Explain why this enhancement would be beneficial for the project. 15 | 16 | **Proposed solution** 17 | Describe how you would implement this enhancement. 18 | 19 | **Alternatives considered** 20 | If applicable, mention any alternative solutions you've considered. 21 | 22 | **Additional context** 23 | Add any other context or screenshots related to the enhancement here. 24 | -------------------------------------------------------------------------------- /assets/javascripts/lunr/min/lunr.vi.min.js: -------------------------------------------------------------------------------- 1 | !function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.vi=function(){this.pipeline.reset(),this.pipeline.add(e.vi.stopWordFilter,e.vi.trimmer)},e.vi.wordCharacters="[A-Za-ẓ̀͐́͑̉̃̓ÂâÊêÔôĂ-ăĐ-đƠ-ơƯ-ư]",e.vi.trimmer=e.trimmerSupport.generateTrimmer(e.vi.wordCharacters),e.Pipeline.registerFunction(e.vi.trimmer,"trimmer-vi"),e.vi.stopWordFilter=e.generateStopWordFilter("là cái nhưng mà".split(" "))}}); -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "twinTrim" 3 | version = "0.1.2" 4 | description = "A CLI tool to find and delete duplicate files in a directory." 5 | authors = ["Kota-Karthik "] 6 | license = "MIT" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.8.1" 10 | click = "^8.0" # For creating CLI commands 11 | tqdm = "^4.66.5" 12 | mkdocs = "^1.6.1" 13 | mkdocs-material = "^9.5.44" 14 | inquirer = "^3.4.0" 15 | 16 | 17 | [tool.poetry.group.dev.dependencies] 18 | pytest-mock = "^3.14.0" 19 | pytest = "^8.3.3" 20 | 21 | [build-system] 22 | requires = ["setuptools", "wheel"] 23 | build-backend = "setuptools.build_meta" 24 | 25 | 26 | [tool.pytest.ini_options] 27 | testpaths = ["tests"] 28 | 29 | [tool.poetry.scripts] 30 | twinTrim = "twinTrim.main:cli" 31 | -------------------------------------------------------------------------------- /docs/Features.md: -------------------------------------------------------------------------------- 1 | # Features 2 | 3 | - **Duplicate Detection**: Scans directories to detect duplicate files based on file content rather than just filenames. 4 | - **Automatic or Manual Removal**: Choose to handle duplicates automatically using the `--all` flag or manually select which files to delete. 5 | - **Customizable Filters**: Set filters for minimum and maximum file sizes, file types, and specific filenames to exclude from the scan. 6 | - **Multi-Threaded Processing**: Utilizes multi-threading to quickly scan and process large numbers of files concurrently. 7 | - **Deadlock Prevention**: Implements locks to prevent deadlocks during multi-threaded operations, ensuring smooth and safe execution. 8 | - **User-Friendly Interface**: Offers clear prompts and feedback via the command line, making the process straightforward and interactive. -------------------------------------------------------------------------------- /assets/javascripts/lunr/min/lunr.multi.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():t()(e.lunr)}(this,function(){return function(e){e.multiLanguage=function(){for(var t=Array.prototype.slice.call(arguments),i=t.join("-"),r="",n=[],s=[],p=0;p 2 | 3 | 4 | https://twintrim.com/docs/ 5 | 2024-11-14 6 | 7 | 8 | https://twintrim.com/docs/Contributing/ 9 | 2024-11-14 10 | 11 | 12 | https://twintrim.com/docs/Features/ 13 | 2024-11-14 14 | 15 | 16 | https://twintrim.com/docs/How-It-Works/ 17 | 2024-11-14 18 | 19 | 20 | https://twintrim.com/docs/License/ 21 | 2024-11-14 22 | 23 | 24 | https://twintrim.com/docs/Usage/ 25 | 2024-11-14 26 | 27 | -------------------------------------------------------------------------------- /assets/javascripts/lunr/min/lunr.th.min.js: -------------------------------------------------------------------------------- 1 | !function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r="2"==e.version[0];e.th=function(){this.pipeline.reset(),this.pipeline.add(e.th.trimmer),r?this.tokenizer=e.th.tokenizer:(e.tokenizer&&(e.tokenizer=e.th.tokenizer),this.tokenizerFn&&(this.tokenizerFn=e.th.tokenizer))},e.th.wordCharacters="[฀-๿]",e.th.trimmer=e.trimmerSupport.generateTrimmer(e.th.wordCharacters),e.Pipeline.registerFunction(e.th.trimmer,"trimmer-th");var t=e.wordcut;t.init(),e.th.tokenizer=function(i){if(!arguments.length||null==i||void 0==i)return[];if(Array.isArray(i))return i.map(function(t){return r?new e.Token(t):t});var n=i.toString().replace(/^\s+/,"");return t.cut(n).split("|")}}}); -------------------------------------------------------------------------------- /twinTrim/dataStructures/fileFilter.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | 4 | class FileFilter: 5 | def __init__(self): 6 | self.minFileSize = "0kb" 7 | self.maxFileSize = "1gb" 8 | self.fileType = r"^.+\.*$" 9 | self.fileExclude = [] 10 | 11 | def setMinFileSize(self, size): 12 | self.minFileSize = size 13 | 14 | def setMaxFileSize(self, size): 15 | self.maxFileSize = size 16 | 17 | def setFileType(self, file_type): 18 | self.fileType = rf"^.+\.{file_type}$" 19 | 20 | def addFileExclude(self, file_name): 21 | self.fileExclude.append(file_name) 22 | 23 | def filter_files(self, file_path): 24 | """Check if a file meets the filter criteria.""" 25 | if os.path.getsize(file_path) < self.minFileSize or os.path.getsize(file_path) > self.maxFileSize: 26 | return False 27 | if not re.match(self.fileType, os.path.basename(file_path)): 28 | return False 29 | if os.path.basename(file_path).strip() in self.fileExclude: 30 | return False 31 | return True 32 | -------------------------------------------------------------------------------- /assets/javascripts/lunr/min/lunr.hy.min.js: -------------------------------------------------------------------------------- 1 | !function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.hy=function(){this.pipeline.reset(),this.pipeline.add(e.hy.trimmer,e.hy.stopWordFilter)},e.hy.wordCharacters="[A-Za-z԰-֏ff-ﭏ]",e.hy.trimmer=e.trimmerSupport.generateTrimmer(e.hy.wordCharacters),e.Pipeline.registerFunction(e.hy.trimmer,"trimmer-hy"),e.hy.stopWordFilter=e.generateStopWordFilter("դու և եք էիր էիք հետո նաև նրանք որը վրա է որ պիտի են այս մեջ ն իր ու ի այդ որոնք այն կամ էր մի ես համար այլ իսկ էին ենք հետ ին թ էինք մենք նրա նա դուք եմ էի ըստ որպես ում".split(" ")),e.Pipeline.registerFunction(e.hy.stopWordFilter,"stopWordFilter-hy"),e.hy.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}(),e.Pipeline.registerFunction(e.hy.stemmer,"stemmer-hy")}}); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Kota Karthik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | Please include a summary of the change and which issue is fixed. Also include relevant motivation and context. 4 | 5 | - Fixes #(issue number) 6 | 7 | ## Type of change 8 | 9 | Please delete options that are not relevant. 10 | 11 | - [ ] Bug fix (non-breaking change which fixes an issue) 12 | - [ ] New feature (non-breaking change which adds functionality) 13 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 14 | - [ ] Documentation update 15 | - [ ] Tests update 16 | 17 | ## Checklist 18 | 19 | - [ ] My code follows the style guidelines of this project 20 | - [ ] I have performed a self-review of my own code 21 | - [ ] I have commented my code, particularly in hard-to-understand areas 22 | - [ ] My changes generate no new warnings 23 | - [ ] New and existing unit tests pass locally with my changes 24 | - [ ] I have maintained a clean commit history by using the necessary Git commands 25 | - [ ] I have checked that my code does not cause any merge conflicts 26 | 27 | ## Screenshots (if applicable) 28 | 29 | Add screenshots to help explain the changes (if necessary). 30 | -------------------------------------------------------------------------------- /twinTrim/dataStructures/fileMetadata.py: -------------------------------------------------------------------------------- 1 | import threading 2 | from typing import Dict, List 3 | from datetime import datetime 4 | from twinTrim.utils import get_file_hash, handle_and_remove 5 | 6 | 7 | class FileMetadata: 8 | def __init__(self, filepaths: List[str]): 9 | self.filepaths = filepaths 10 | 11 | def insert_file(self, filepath: str): 12 | """Inserts a new file path into the metadata if it's not already present.""" 13 | if filepath not in self.filepaths: #this line is not necessary most probably , written by me 14 | self.filepaths.append(filepath) 15 | 16 | 17 | normalStore: Dict[str, 'FileMetadata'] = {} 18 | normalStore_lock = threading.Lock() 19 | def add_or_update_normal_file(file_path: str): 20 | """Adds a new file's metadata to the normalStore or updates it if a duplicate is found.""" 21 | file_hash = get_file_hash(file_path) 22 | new_file_metadata = FileMetadata([file_path]) 23 | 24 | with normalStore_lock: 25 | existing_file_metadata = normalStore.get(file_hash) 26 | 27 | if existing_file_metadata is None: 28 | normalStore[file_hash] = new_file_metadata 29 | else: 30 | existing_file_metadata.insert_file(file_path) -------------------------------------------------------------------------------- /docs/How-It-Works.md: -------------------------------------------------------------------------------- 1 | # How It Works 2 | 3 | ## Core Components 4 | 5 | 1. **File Metadata Management**: 6 | - Uses `AllFileMetadata` and `FileMetadata` classes to manage file information, such as modification time and file paths. 7 | - Maintains metadata in two dictionaries (`store` and `normalStore`) for handling different levels of duplicate management. 8 | 9 | 2. **File Hashing**: 10 | - Generates a unique hash for each file using MD5 to identify duplicates by content. 11 | 12 | 3. **File Filtering**: 13 | - The `FileFilter` class provides functionality to filter files based on size, type, and exclusions. 14 | 15 | 4. **Duplicate Handling**: 16 | - Duplicate files are identified by comparing their hashes. 17 | - Based on file modification time, the latest file is retained, and older duplicates are removed. 18 | 19 | 5. **Deadlock Prevention**: 20 | - Uses locks within multi-threaded processes to ensure that resources are accessed safely, preventing deadlocks that could otherwise halt execution. 21 | 22 | ## Key Functions 23 | 24 | - **add_or_update_file**: Adds new files to the metadata store or updates existing entries if a duplicate is detected. 25 | - **add_or_update_normal_file**: Similar to `add_or_update_file` but manages duplicates in a separate store. 26 | - **handleAllFlag**: Handles duplicate removal automatically without user intervention. 27 | - **find_duplicates**: Finds duplicate files in the specified directory and prepares them for user review or automatic handling. -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name='twinTrim', # Name of the package 5 | version='0.1.2', # Version number 6 | description='A CLI tool to find and delete duplicate files in a directory.', # Short description 7 | long_description=open('README.md').read(), # Readme for long description 8 | long_description_content_type='text/markdown', # Type of long description 9 | author='Kota-Karthik', # Author name 10 | author_email='kotakarthik2307@gmail.com', # Author's email 11 | url='https://github.com/kota-karthik/twinTrim', # URL of the project 12 | packages=find_packages(), # Automatically finds and includes all packages 13 | install_requires=[ # Dependencies 14 | 'click>=8.0', 15 | 'tqdm>=4.66.5', 16 | 'mkdocs>=1.6.1', 17 | 'mkdocs-material>=9.5.44', 18 | 'inquirer >= 3.4.0' 19 | ], 20 | extras_require={ # Optional dependencies for development 21 | 'dev': [ 22 | 'pytest>=8.3.3', 23 | 'pytest-mock>=3.14.0', 24 | ], 25 | }, 26 | classifiers=[ # Classifiers to help PyPI users find your package 27 | 'Programming Language :: Python :: 3', 28 | 'License :: OSI Approved :: MIT License', 29 | 'Operating System :: OS Independent', 30 | ], 31 | python_requires='>=3.8', # Minimum Python version 32 | entry_points={ # CLI tool entry point 33 | 'console_scripts': [ 34 | 'twinTrim=twinTrim.main:cli', # Adjusted entry point based on Poetry 35 | ], 36 | }, 37 | ) 38 | -------------------------------------------------------------------------------- /twinTrim/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import hashlib 3 | from concurrent.futures import ThreadPoolExecutor, as_completed 4 | import click 5 | 6 | BUF_SIZE = 131072 7 | 8 | def handle_and_remove(filepath): 9 | try: 10 | os.remove(filepath) 11 | click.echo(click.style(f"Deleted: {filepath}", fg='green')) 12 | except FileNotFoundError: 13 | click.echo(click.style(f"File not found (skipped): {filepath}", fg='red')) 14 | except PermissionError: 15 | click.echo(click.style(f"Permission denied (skipped): {filepath}", fg='red')) 16 | except Exception as e: 17 | click.echo(click.style(f"Error deleting {filepath}: {e}", fg='red')) 18 | 19 | def get_file_hash(file_path): 20 | """Generate a hash for a given file.""" 21 | hash_algo = hashlib.md5() 22 | with open(file_path, "rb") as file: 23 | for chunk in iter(lambda: file.read(BUF_SIZE), b""): 24 | hash_algo.update(chunk) 25 | return hash_algo.hexdigest() 26 | 27 | def parse_size(size_str): 28 | size_str = size_str.lower() 29 | try: 30 | if size_str.endswith('kb'): 31 | return int(float(size_str[:-2]) * 1024) 32 | elif size_str.endswith('mb'): 33 | return int(float(size_str[:-2]) * 1024 * 1024) 34 | elif size_str.endswith('gb'): 35 | return int(float(size_str[:-2]) * 1024 * 1024 * 1024) 36 | else: 37 | # Attempt to convert size_str to an integer 38 | return int(size_str) # This can also be modified to handle sizes without suffix 39 | except ValueError: 40 | # If conversion fails, return 0 for invalid formats 41 | return 0 42 | -------------------------------------------------------------------------------- /assets/javascripts/lunr/min/lunr.te.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():t()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.te=function(){this.pipeline.reset(),this.pipeline.add(e.te.trimmer,e.te.stopWordFilter,e.te.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.te.stemmer))},e.te.wordCharacters="ఀ-ఄఅ-ఔక-హా-ౌౕ-ౖౘ-ౚౠ-ౡౢ-ౣ౦-౯౸-౿఼ఽ్ౝ౷౤౥",e.te.trimmer=e.trimmerSupport.generateTrimmer(e.te.wordCharacters),e.Pipeline.registerFunction(e.te.trimmer,"trimmer-te"),e.te.stopWordFilter=e.generateStopWordFilter("అందరూ అందుబాటులో అడగండి అడగడం అడ్డంగా అనుగుణంగా అనుమతించు అనుమతిస్తుంది అయితే ఇప్పటికే ఉన్నారు ఎక్కడైనా ఎప్పుడు ఎవరైనా ఎవరో ఏ ఏదైనా ఏమైనప్పటికి ఒక ఒకరు కనిపిస్తాయి కాదు కూడా గా గురించి చుట్టూ చేయగలిగింది తగిన తర్వాత దాదాపు దూరంగా నిజంగా పై ప్రకారం ప్రక్కన మధ్య మరియు మరొక మళ్ళీ మాత్రమే మెచ్చుకో వద్ద వెంట వేరుగా వ్యతిరేకంగా సంబంధం".split(" ")),e.te.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}();var t=e.wordcut;t.init(),e.te.tokenizer=function(r){if(!arguments.length||null==r||void 0==r)return[];if(Array.isArray(r))return r.map(function(t){return isLunr2?new e.Token(t.toLowerCase()):t.toLowerCase()});var i=r.toString().toLowerCase().replace(/^\s+/,"");return t.cut(i).split("|")},e.Pipeline.registerFunction(e.te.stemmer,"stemmer-te"),e.Pipeline.registerFunction(e.te.stopWordFilter,"stopWordFilter-te")}}); -------------------------------------------------------------------------------- /assets/javascripts/lunr/min/lunr.ta.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():t()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.ta=function(){this.pipeline.reset(),this.pipeline.add(e.ta.trimmer,e.ta.stopWordFilter,e.ta.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.ta.stemmer))},e.ta.wordCharacters="஀-உஊ-ஏஐ-ஙச-ட஠-னப-யர-ஹ஺-ிீ-௉ொ-௏ௐ-௙௚-௟௠-௩௪-௯௰-௹௺-௿a-zA-Za-zA-Z0-90-9",e.ta.trimmer=e.trimmerSupport.generateTrimmer(e.ta.wordCharacters),e.Pipeline.registerFunction(e.ta.trimmer,"trimmer-ta"),e.ta.stopWordFilter=e.generateStopWordFilter("அங்கு அங்கே அது அதை அந்த அவர் அவர்கள் அவள் அவன் அவை ஆக ஆகவே ஆகையால் ஆதலால் ஆதலினால் ஆனாலும் ஆனால் இங்கு இங்கே இது இதை இந்த இப்படி இவர் இவர்கள் இவள் இவன் இவை இவ்வளவு உனக்கு உனது உன் உன்னால் எங்கு எங்கே எது எதை எந்த எப்படி எவர் எவர்கள் எவள் எவன் எவை எவ்வளவு எனக்கு எனது எனவே என் என்ன என்னால் ஏது ஏன் தனது தன்னால் தானே தான் நாங்கள் நாம் நான் நீ நீங்கள்".split(" ")),e.ta.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}();var t=e.wordcut;t.init(),e.ta.tokenizer=function(r){if(!arguments.length||null==r||void 0==r)return[];if(Array.isArray(r))return r.map(function(t){return isLunr2?new e.Token(t.toLowerCase()):t.toLowerCase()});var i=r.toString().toLowerCase().replace(/^\s+/,"");return t.cut(i).split("|")},e.Pipeline.registerFunction(e.ta.stemmer,"stemmer-ta"),e.Pipeline.registerFunction(e.ta.stopWordFilter,"stopWordFilter-ta")}}); -------------------------------------------------------------------------------- /assets/javascripts/lunr/min/lunr.zh.min.js: -------------------------------------------------------------------------------- 1 | !function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r(require("@node-rs/jieba")):r()(e.lunr)}(this,function(e){return function(r,t){if(void 0===r)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===r.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var i="2"==r.version[0];r.zh=function(){this.pipeline.reset(),this.pipeline.add(r.zh.trimmer,r.zh.stopWordFilter,r.zh.stemmer),i?this.tokenizer=r.zh.tokenizer:(r.tokenizer&&(r.tokenizer=r.zh.tokenizer),this.tokenizerFn&&(this.tokenizerFn=r.zh.tokenizer))},r.zh.tokenizer=function(n){if(!arguments.length||null==n||void 0==n)return[];if(Array.isArray(n))return n.map(function(e){return i?new r.Token(e.toLowerCase()):e.toLowerCase()});t&&e.load(t);var o=n.toString().trim().toLowerCase(),s=[];e.cut(o,!0).forEach(function(e){s=s.concat(e.split(" "))}),s=s.filter(function(e){return!!e});var u=0;return s.map(function(e,t){if(i){var n=o.indexOf(e,u),s={};return s.position=[n,e.length],s.index=t,u=n,new r.Token(e,s)}return e})},r.zh.wordCharacters="\\w一-龥",r.zh.trimmer=r.trimmerSupport.generateTrimmer(r.zh.wordCharacters),r.Pipeline.registerFunction(r.zh.trimmer,"trimmer-zh"),r.zh.stemmer=function(){return function(e){return e}}(),r.Pipeline.registerFunction(r.zh.stemmer,"stemmer-zh"),r.zh.stopWordFilter=r.generateStopWordFilter("的 一 不 在 人 有 是 为 為 以 于 於 上 他 而 后 後 之 来 來 及 了 因 下 可 到 由 这 這 与 與 也 此 但 并 並 个 個 其 已 无 無 小 我 们 們 起 最 再 今 去 好 只 又 或 很 亦 某 把 那 你 乃 它 吧 被 比 别 趁 当 當 从 從 得 打 凡 儿 兒 尔 爾 该 該 各 给 給 跟 和 何 还 還 即 几 幾 既 看 据 據 距 靠 啦 另 么 麽 每 嘛 拿 哪 您 凭 憑 且 却 卻 让 讓 仍 啥 如 若 使 谁 誰 虽 雖 随 隨 同 所 她 哇 嗡 往 些 向 沿 哟 喲 用 咱 则 則 怎 曾 至 致 着 著 诸 諸 自".split(" ")),r.Pipeline.registerFunction(r.zh.stopWordFilter,"stopWordFilter-zh")}}); -------------------------------------------------------------------------------- /assets/javascripts/lunr/min/lunr.ja.min.js: -------------------------------------------------------------------------------- 1 | !function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r="2"==e.version[0];e.ja=function(){this.pipeline.reset(),this.pipeline.add(e.ja.trimmer,e.ja.stopWordFilter,e.ja.stemmer),r?this.tokenizer=e.ja.tokenizer:(e.tokenizer&&(e.tokenizer=e.ja.tokenizer),this.tokenizerFn&&(this.tokenizerFn=e.ja.tokenizer))};var t=new e.TinySegmenter;e.ja.tokenizer=function(i){var n,o,s,p,a,u,m,l,c,f;if(!arguments.length||null==i||void 0==i)return[];if(Array.isArray(i))return i.map(function(t){return r?new e.Token(t.toLowerCase()):t.toLowerCase()});for(o=i.toString().toLowerCase().replace(/^\s+/,""),n=o.length-1;n>=0;n--)if(/\S/.test(o.charAt(n))){o=o.substring(0,n+1);break}for(a=[],s=o.length,c=0,l=0;c<=s;c++)if(u=o.charAt(c),m=c-l,u.match(/\s/)||c==s){if(m>0)for(p=t.segment(o.slice(l,c)).filter(function(e){return!!e}),f=l,n=0;n [OPTIONS] 8 | ``` 9 | 10 | ### Options 11 | 12 | - `--all`: Automatically delete duplicates without asking for confirmation. 13 | - `--min-size`: Specify the minimum file size to include in the scan (e.g., `10kb`). 14 | - `--max-size`: Specify the maximum file size to include in the scan (e.g., `1gb`). 15 | - `--file-type`: Specify the file type to include (e.g., `.txt`, `.jpg`). 16 | - `--exclude`: Exclude specific files by name. 17 | - `--label-color`: Set the font color of the output label of the progress bar. 18 | - `--bar-color`: Set the color of the progress bar. 19 | 20 | ### Examples 21 | 22 | 1. **Automatic Duplicate Removal**: 23 | ```bash 24 | python -m twinTrim.main /path/to/directory --all 25 | ``` 26 | 27 | 2. **Manual Review and Removal**: 28 | ```bash 29 | python -m twinTrim.main /path/to/directory 30 | ``` 31 | 32 | 3. **Filtered Scan by File Size and Type**: 33 | ```bash 34 | python -m twinTrim.main /path/to/directory --min-size "50kb" --max-size "500mb" --file-type "txt" 35 | ``` 36 | 37 | ## Dependencies 38 | 39 | - Python 3.6+ 40 | - `click` for command-line interaction 41 | - `tqdm` for progress bars 42 | - `concurrent.futures` for multi-threaded processing 43 | - `beaupy` for interactive selection 44 | 45 | ## Installation 46 | 47 | ### From PyPI 48 | 49 | Install the latest release from PyPI using pip: 50 | 51 | ```bash 52 | pip install twinTrim 53 | ``` 54 | 55 | You can find the project on [PyPI](https://pypi.org/project/twinTrim/). 56 | 57 | ### Setup for Development 58 | 59 | Clone the repository and install the required dependencies using Poetry: 60 | 61 | ```bash 62 | git clone https://github.com/Kota-Karthik/twinTrim.git 63 | cd twinTrim 64 | poetry install 65 | poetry shell 66 | ``` 67 | 68 | If you haven't installed Poetry yet, you can do so by following the instructions on the [Poetry website](https://python-poetry.org/docs/#installation). -------------------------------------------------------------------------------- /assets/javascripts/lunr/min/lunr.hi.min.js: -------------------------------------------------------------------------------- 1 | !function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.hi=function(){this.pipeline.reset(),this.pipeline.add(e.hi.trimmer,e.hi.stopWordFilter,e.hi.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.hi.stemmer))},e.hi.wordCharacters="ऀ-ःऄ-एऐ-टठ-यर-िी-ॏॐ-य़ॠ-९॰-ॿa-zA-Za-zA-Z0-90-9",e.hi.trimmer=e.trimmerSupport.generateTrimmer(e.hi.wordCharacters),e.Pipeline.registerFunction(e.hi.trimmer,"trimmer-hi"),e.hi.stopWordFilter=e.generateStopWordFilter("अत अपना अपनी अपने अभी अंदर आदि आप इत्यादि इन इनका इन्हीं इन्हें इन्हों इस इसका इसकी इसके इसमें इसी इसे उन उनका उनकी उनके उनको उन्हीं उन्हें उन्हों उस उसके उसी उसे एक एवं एस ऐसे और कई कर करता करते करना करने करें कहते कहा का काफ़ी कि कितना किन्हें किन्हों किया किर किस किसी किसे की कुछ कुल के को कोई कौन कौनसा गया घर जब जहाँ जा जितना जिन जिन्हें जिन्हों जिस जिसे जीधर जैसा जैसे जो तक तब तरह तिन तिन्हें तिन्हों तिस तिसे तो था थी थे दबारा दिया दुसरा दूसरे दो द्वारा न नके नहीं ना निहायत नीचे ने पर पहले पूरा पे फिर बनी बही बहुत बाद बाला बिलकुल भी भीतर मगर मानो मे में यदि यह यहाँ यही या यिह ये रखें रहा रहे ऱ्वासा लिए लिये लेकिन व वग़ैरह वर्ग वह वहाँ वहीं वाले वुह वे वो सकता सकते सबसे सभी साथ साबुत साभ सारा से सो संग ही हुआ हुई हुए है हैं हो होता होती होते होना होने".split(" ")),e.hi.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}();var r=e.wordcut;r.init(),e.hi.tokenizer=function(i){if(!arguments.length||null==i||void 0==i)return[];if(Array.isArray(i))return i.map(function(r){return isLunr2?new e.Token(r.toLowerCase()):r.toLowerCase()});var t=i.toString().toLowerCase().replace(/^\s+/,"");return r.cut(t).split("|")},e.Pipeline.registerFunction(e.hi.stemmer,"stemmer-hi"),e.Pipeline.registerFunction(e.hi.stopWordFilter,"stopWordFilter-hi")}}); -------------------------------------------------------------------------------- /assets/javascripts/lunr/min/lunr.kn.min.js: -------------------------------------------------------------------------------- 1 | !function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.kn=function(){this.pipeline.reset(),this.pipeline.add(e.kn.trimmer,e.kn.stopWordFilter,e.kn.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.kn.stemmer))},e.kn.wordCharacters="ಀ-಄ಅ-ಔಕ-ಹಾ-ೌ಼-ಽೕ-ೖೝ-ೞೠ-ೡೢ-ೣ೤೥೦-೯ೱ-ೳ",e.kn.trimmer=e.trimmerSupport.generateTrimmer(e.kn.wordCharacters),e.Pipeline.registerFunction(e.kn.trimmer,"trimmer-kn"),e.kn.stopWordFilter=e.generateStopWordFilter("ಮತ್ತು ಈ ಒಂದು ರಲ್ಲಿ ಹಾಗೂ ಎಂದು ಅಥವಾ ಇದು ರ ಅವರು ಎಂಬ ಮೇಲೆ ಅವರ ತನ್ನ ಆದರೆ ತಮ್ಮ ನಂತರ ಮೂಲಕ ಹೆಚ್ಚು ನ ಆ ಕೆಲವು ಅನೇಕ ಎರಡು ಹಾಗು ಪ್ರಮುಖ ಇದನ್ನು ಇದರ ಸುಮಾರು ಅದರ ಅದು ಮೊದಲ ಬಗ್ಗೆ ನಲ್ಲಿ ರಂದು ಇತರ ಅತ್ಯಂತ ಹೆಚ್ಚಿನ ಸಹ ಸಾಮಾನ್ಯವಾಗಿ ನೇ ಹಲವಾರು ಹೊಸ ದಿ ಕಡಿಮೆ ಯಾವುದೇ ಹೊಂದಿದೆ ದೊಡ್ಡ ಅನ್ನು ಇವರು ಪ್ರಕಾರ ಇದೆ ಮಾತ್ರ ಕೂಡ ಇಲ್ಲಿ ಎಲ್ಲಾ ವಿವಿಧ ಅದನ್ನು ಹಲವು ರಿಂದ ಕೇವಲ ದ ದಕ್ಷಿಣ ಗೆ ಅವನ ಅತಿ ನೆಯ ಬಹಳ ಕೆಲಸ ಎಲ್ಲ ಪ್ರತಿ ಇತ್ಯಾದಿ ಇವು ಬೇರೆ ಹೀಗೆ ನಡುವೆ ಇದಕ್ಕೆ ಎಸ್ ಇವರ ಮೊದಲು ಶ್ರೀ ಮಾಡುವ ಇದರಲ್ಲಿ ರೀತಿಯ ಮಾಡಿದ ಕಾಲ ಅಲ್ಲಿ ಮಾಡಲು ಅದೇ ಈಗ ಅವು ಗಳು ಎ ಎಂಬುದು ಅವನು ಅಂದರೆ ಅವರಿಗೆ ಇರುವ ವಿಶೇಷ ಮುಂದೆ ಅವುಗಳ ಮುಂತಾದ ಮೂಲ ಬಿ ಮೀ ಒಂದೇ ಇನ್ನೂ ಹೆಚ್ಚಾಗಿ ಮಾಡಿ ಅವರನ್ನು ಇದೇ ಯ ರೀತಿಯಲ್ಲಿ ಜೊತೆ ಅದರಲ್ಲಿ ಮಾಡಿದರು ನಡೆದ ಆಗ ಮತ್ತೆ ಪೂರ್ವ ಆತ ಬಂದ ಯಾವ ಒಟ್ಟು ಇತರೆ ಹಿಂದೆ ಪ್ರಮಾಣದ ಗಳನ್ನು ಕುರಿತು ಯು ಆದ್ದರಿಂದ ಅಲ್ಲದೆ ನಗರದ ಮೇಲಿನ ಏಕೆಂದರೆ ರಷ್ಟು ಎಂಬುದನ್ನು ಬಾರಿ ಎಂದರೆ ಹಿಂದಿನ ಆದರೂ ಆದ ಸಂಬಂಧಿಸಿದ ಮತ್ತೊಂದು ಸಿ ಆತನ ".split(" ")),e.kn.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}();var r=e.wordcut;r.init(),e.kn.tokenizer=function(t){if(!arguments.length||null==t||void 0==t)return[];if(Array.isArray(t))return t.map(function(r){return isLunr2?new e.Token(r.toLowerCase()):r.toLowerCase()});var n=t.toString().toLowerCase().replace(/^\s+/,"");return r.cut(n).split("|")},e.Pipeline.registerFunction(e.kn.stemmer,"stemmer-kn"),e.Pipeline.registerFunction(e.kn.stopWordFilter,"stopWordFilter-kn")}}); -------------------------------------------------------------------------------- /twinTrim/dataStructures/allFileMetadata.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import os 3 | from typing import Dict 4 | from twinTrim.utils import get_file_hash, handle_and_remove 5 | 6 | class AllFileMetadata: 7 | def __init__(self, filepath: str): 8 | self.filepath = filepath 9 | self.file_modified_time = self.get_modification_time() 10 | self.__lock = threading.Lock() 11 | # Returns the modification time of the file. 12 | def get_modification_time(self): 13 | try: 14 | return os.path.getmtime(self.filepath) 15 | except FileNotFoundError: 16 | return -1 17 | def compare_and_replace(self, new_metadata: 'AllFileMetadata'): 18 | """Compares the current file's metadata with new metadata and retains the latest file.""" 19 | if self.file_modified_time == -1: 20 | return 21 | with self.__lock: 22 | new_mod_time = new_metadata.get_modification_time() 23 | # Ignore if the new file is missing 24 | if new_mod_time == -1: 25 | return 26 | print("file2:", new_mod_time,"file1", self.file_modified_time) 27 | # Keep the most recently modified file and delete the other 28 | if new_mod_time > self.file_modified_time: 29 | handle_and_remove(self.filepath) # Remove current file 30 | self.filepath = new_metadata.filepath # Update with the new file's path 31 | self.file_modified_time = new_mod_time # Update the modification time 32 | else: 33 | handle_and_remove(new_metadata.filepath) # Remove the new file 34 | 35 | # Store for file metadata, keyed by file hash 36 | store: Dict[str, AllFileMetadata] = {} 37 | store_lock = threading.Lock() 38 | def add_or_update_file(file_path: str): 39 | """Adds a new file's metadata to the store or updates it if a duplicate is found.""" 40 | file_hash = get_file_hash(file_path) 41 | new_file_metadata = AllFileMetadata(file_path) 42 | with store_lock: 43 | existing_file_metadata = store.get(file_hash) 44 | if existing_file_metadata is None: 45 | store[file_hash] = new_file_metadata 46 | else: 47 | existing_file_metadata.compare_and_replace(new_file_metadata) 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Virtual environment 10 | .venv/ 11 | env/ 12 | venv/ 13 | ENV/ 14 | env.bak/ 15 | venv.bak/ 16 | 17 | # Poetry virtual environment 18 | .cache/ 19 | .pdm.toml 20 | 21 | # Distribution / packaging 22 | .Python 23 | build/ 24 | develop-eggs/ 25 | dist/ 26 | eggs/ 27 | lib/ 28 | lib64/ 29 | parts/ 30 | sdist/ 31 | var/ 32 | *.egg-info/ 33 | .installed.cfg 34 | *.egg 35 | *.whl 36 | 37 | # PyInstaller 38 | *.manifest 39 | *.spec 40 | 41 | # Installer logs 42 | pip-log.txt 43 | pip-delete-this-directory.txt 44 | 45 | # Unit test / coverage reports 46 | htmlcov/ 47 | .tox/ 48 | .nox/ 49 | .coverage 50 | .coverage.* 51 | .cache 52 | nosetests.xml 53 | coverage.xml 54 | *.cover 55 | *.py,cover 56 | .hypothesis/ 57 | .pytest_cache/ 58 | 59 | # Translations 60 | *.mo 61 | *.pot 62 | 63 | # Django stuff: 64 | *.log 65 | local_settings.py 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | Pipfile 89 | Pipfile.lock 90 | 91 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 92 | __pypackages__/ 93 | 94 | # Celery stuff 95 | celerybeat-schedule 96 | *.celerybeat.pid 97 | 98 | # SageMath parsed files 99 | *.sage.py 100 | 101 | # Environments 102 | .env 103 | .venv 104 | env/ 105 | venv/ 106 | ENV/ 107 | env.bak/ 108 | venv.bak/ 109 | 110 | # Spyder project settings 111 | .spyderproject 112 | .spyproject 113 | 114 | # Rope project settings 115 | .ropeproject 116 | 117 | # mkdocs documentation 118 | /site 119 | 120 | # mypy 121 | .mypy_cache/ 122 | .dmypy.json 123 | dmypy.json 124 | 125 | # Miscellaneous 126 | *.db-journal 127 | *.db 128 | 129 | # Issue tracker files 130 | /issue 131 | 132 | # Python test coverage cache 133 | /.pytest_cache/ 134 | 135 | # Build files 136 | build/ 137 | dist/ 138 | *.whl 139 | *.tar.gz 140 | 141 | # TwinTrim build artifacts 142 | build/ 143 | dist/ 144 | *.egg-info/ 145 | 146 | # VSCode settings 147 | .vscode/ 148 | 149 | # Test files 150 | test.py 151 | 152 | # Ignore the search index 153 | search/search_index.json 154 | -------------------------------------------------------------------------------- /assets/javascripts/lunr/min/lunr.sa.min.js: -------------------------------------------------------------------------------- 1 | !function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.sa=function(){this.pipeline.reset(),this.pipeline.add(e.sa.trimmer,e.sa.stopWordFilter,e.sa.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.sa.stemmer))},e.sa.wordCharacters="ऀ-ःऄ-एऐ-टठ-यर-िी-ॏॐ-य़ॠ-९॰-ॿ꣠-꣱ꣲ-ꣷ꣸-ꣻ꣼-ꣽꣾ-ꣿᆰ0-ᆰ9",e.sa.trimmer=e.trimmerSupport.generateTrimmer(e.sa.wordCharacters),e.Pipeline.registerFunction(e.sa.trimmer,"trimmer-sa"),e.sa.stopWordFilter=e.generateStopWordFilter('तथा अयम्‌ एकम्‌ इत्यस्मिन्‌ तथा तत्‌ वा अयम्‌ इत्यस्य ते आहूत उपरि तेषाम्‌ किन्तु तेषाम्‌ तदा इत्यनेन अधिकः इत्यस्य तत्‌ केचन बहवः द्वि तथा महत्वपूर्णः अयम्‌ अस्य विषये अयं अस्ति तत्‌ प्रथमः विषये इत्युपरि इत्युपरि इतर अधिकतमः अधिकः अपि सामान्यतया ठ इतरेतर नूतनम्‌ द न्यूनम्‌ कश्चित्‌ वा विशालः द सः अस्ति तदनुसारम् तत्र अस्ति केवलम्‌ अपि अत्र सर्वे विविधाः तत्‌ बहवः यतः इदानीम्‌ द दक्षिण इत्यस्मै तस्य उपरि नथ अतीव कार्यम्‌ सर्वे एकैकम्‌ इत्यादि। एते सन्ति उत इत्थम्‌ मध्ये एतदर्थं . स कस्य प्रथमः श्री. करोति अस्मिन् प्रकारः निर्मिता कालः तत्र कर्तुं समान अधुना ते सन्ति स एकः अस्ति सः अर्थात् तेषां कृते . स्थितम् विशेषः अग्रिम तेषाम्‌ समान स्रोतः ख म समान इदानीमपि अधिकतया करोतु ते समान इत्यस्य वीथी सह यस्मिन् कृतवान्‌ धृतः तदा पुनः पूर्वं सः आगतः किम्‌ कुल इतर पुरा मात्रा स विषये उ अतएव अपि नगरस्य उपरि यतः प्रतिशतं कतरः कालः साधनानि भूत तथापि जात सम्बन्धि अन्यत्‌ ग अतः अस्माकं स्वकीयाः अस्माकं इदानीं अन्तः इत्यादयः भवन्तः इत्यादयः एते एताः तस्य अस्य इदम् एते तेषां तेषां तेषां तान् तेषां तेषां तेषां समानः सः एकः च तादृशाः बहवः अन्ये च वदन्ति यत् कियत् कस्मै कस्मै यस्मै यस्मै यस्मै यस्मै न अतिनीचः किन्तु प्रथमं सम्पूर्णतया ततः चिरकालानन्तरं पुस्तकं सम्पूर्णतया अन्तः किन्तु अत्र वा इह इव श्रद्धाय अवशिष्यते परन्तु अन्ये वर्गाः सन्ति ते सन्ति शक्नुवन्ति सर्वे मिलित्वा सर्वे एकत्र"'.split(" ")),e.sa.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}();var r=e.wordcut;r.init(),e.sa.tokenizer=function(t){if(!arguments.length||null==t||void 0==t)return[];if(Array.isArray(t))return t.map(function(r){return isLunr2?new e.Token(r.toLowerCase()):r.toLowerCase()});var i=t.toString().toLowerCase().replace(/^\s+/,"");return r.cut(i).split("|")},e.Pipeline.registerFunction(e.sa.stemmer,"stemmer-sa"),e.Pipeline.registerFunction(e.sa.stopWordFilter,"stopWordFilter-sa")}}); -------------------------------------------------------------------------------- /assets/stylesheets/palette.06af60db.min.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["src/templates/assets/stylesheets/palette/_scheme.scss","../../../../src/templates/assets/stylesheets/palette.scss","src/templates/assets/stylesheets/palette/_accent.scss","src/templates/assets/stylesheets/palette/_primary.scss","src/templates/assets/stylesheets/utilities/_break.scss"],"names":[],"mappings":"AA2BA,cAGE,6BAME,sDAAA,CACA,6DAAA,CACA,+DAAA,CACA,gEAAA,CACA,mDAAA,CACA,6DAAA,CACA,+DAAA,CACA,gEAAA,CAGA,mDAAA,CACA,gDAAA,CAGA,0BAAA,CACA,mCAAA,CAGA,iCAAA,CACA,kCAAA,CACA,mCAAA,CACA,mCAAA,CACA,kCAAA,CACA,iCAAA,CACA,+CAAA,CACA,6DAAA,CACA,gEAAA,CACA,4DAAA,CACA,4DAAA,CACA,6DAAA,CAGA,6CAAA,CAGA,+CAAA,CAGA,uDAAA,CACA,6DAAA,CACA,2DAAA,CAGA,iCAAA,CAGA,yDAAA,CACA,iEAAA,CAGA,mDAAA,CACA,mDAAA,CAGA,qDAAA,CACA,uDAAA,CAGA,8DAAA,CAKA,8DAAA,CAKA,0DAAA,CAvEA,iBCeF,CD6DE,kHAEE,YC3DJ,CDkFE,yDACE,4BChFJ,CD+EE,2DACE,4BC7EJ,CD4EE,gEACE,4BC1EJ,CDyEE,2DACE,4BCvEJ,CDsEE,yDACE,4BCpEJ,CDmEE,0DACE,4BCjEJ,CDgEE,gEACE,4BC9DJ,CD6DE,0DACE,4BC3DJ,CD0DE,2OACE,4BC/CJ,CDsDA,+FAGE,iCCpDF,CACF,CC/CE,2BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCD2CN,CCrDE,4BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDkDN,CC5DE,8BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDyDN,CCnEE,mCACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDgEN,CC1EE,8BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDuEN,CCjFE,4BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCD8EN,CCxFE,kCACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDqFN,CC/FE,4BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCD4FN,CCtGE,4BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDmGN,CC7GE,6BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCD0GN,CCpHE,mCACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDiHN,CC3HE,4BACE,4BAAA,CACA,2CAAA,CAIE,8BAAA,CACA,qCD2HN,CClIE,8BACE,4BAAA,CACA,2CAAA,CAIE,8BAAA,CACA,qCDkIN,CCzIE,6BACE,yBAAA,CACA,2CAAA,CAIE,8BAAA,CACA,qCDyIN,CChJE,8BACE,4BAAA,CACA,2CAAA,CAIE,8BAAA,CACA,qCDgJN,CCvJE,mCACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDoJN,CEzJE,4BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFsJN,CEjKE,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCF8JN,CEzKE,+BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFsKN,CEjLE,oCACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCF8KN,CEzLE,+BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFsLN,CEjME,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCF8LN,CEzME,mCACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFsMN,CEjNE,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCF8MN,CEzNE,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFsNN,CEjOE,8BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCF8NN,CEzOE,oCACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFsON,CEjPE,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAIE,+BAAA,CACA,sCFiPN,CEzPE,+BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAIE,+BAAA,CACA,sCFyPN,CEjQE,8BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAIE,+BAAA,CACA,sCFiQN,CEzQE,+BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAIE,+BAAA,CACA,sCFyQN,CEjRE,oCACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCF8QN,CEzRE,8BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFsRN,CEjSE,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCAAA,CAKA,4BF0RN,CE1SE,kCACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCAAA,CAKA,4BFmSN,CEpRE,sEACE,4BFuRJ,CExRE,+DACE,4BF2RJ,CE5RE,iEACE,4BF+RJ,CEhSE,gEACE,4BFmSJ,CEpSE,iEACE,4BFuSJ,CE9RA,8BACE,mDAAA,CACA,4DAAA,CACA,0DAAA,CACA,oDAAA,CACA,2DAAA,CAGA,4BF+RF,CE5RE,yCACE,+BF8RJ,CE3RI,kDAEE,0CAAA,CACA,sCAAA,CAFA,mCF+RN,CG3MI,mCD1EA,+CACE,8CFwRJ,CErRI,qDACE,8CFuRN,CElRE,iEACE,mCFoRJ,CACF,CGtNI,sCDvDA,uCACE,oCFgRJ,CACF,CEvQA,8BACE,kDAAA,CACA,4DAAA,CACA,wDAAA,CACA,oDAAA,CACA,6DAAA,CAGA,4BFwQF,CErQE,yCACE,+BFuQJ,CEpQI,kDAEE,0CAAA,CACA,sCAAA,CAFA,mCFwQN,CEjQE,yCACE,6CFmQJ,CG5NI,0CDhCA,8CACE,gDF+PJ,CACF,CGjOI,0CDvBA,iFACE,6CF2PJ,CACF,CGzPI,sCDKA,uCACE,6CFuPJ,CACF","file":"palette.css"} -------------------------------------------------------------------------------- /assets/javascripts/lunr/min/lunr.stemmer.support.min.js: -------------------------------------------------------------------------------- 1 | !function(r,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():t()(r.lunr)}(this,function(){return function(r){r.stemmerSupport={Among:function(r,t,i,s){if(this.toCharArray=function(r){for(var t=r.length,i=new Array(t),s=0;s=i&&(e-=i,t[e>>3]&1<<(7&e)))return this.cursor++,!0}return!1},in_grouping_b:function(t,i,s){if(this.cursor>this.limit_backward){var e=r.charCodeAt(this.cursor-1);if(e<=s&&e>=i&&(e-=i,t[e>>3]&1<<(7&e)))return this.cursor--,!0}return!1},out_grouping:function(t,i,s){if(this.cursors||e>3]&1<<(7&e)))return this.cursor++,!0}return!1},out_grouping_b:function(t,i,s){if(this.cursor>this.limit_backward){var e=r.charCodeAt(this.cursor-1);if(e>s||e>3]&1<<(7&e)))return this.cursor--,!0}return!1},eq_s:function(t,i){if(this.limit-this.cursor>1),f=0,l=o0||e==s||c)break;c=!0}}for(;;){var _=t[s];if(o>=_.s_size){if(this.cursor=n+_.s_size,!_.method)return _.result;var b=_.method();if(this.cursor=n+_.s_size,b)return _.result}if((s=_.substring_i)<0)return 0}},find_among_b:function(t,i){for(var s=0,e=i,n=this.cursor,u=this.limit_backward,o=0,h=0,c=!1;;){for(var a=s+(e-s>>1),f=0,l=o=0;m--){if(n-l==u){f=-1;break}if(f=r.charCodeAt(n-1-l)-_.s[m])break;l++}if(f<0?(e=a,h=l):(s=a,o=l),e-s<=1){if(s>0||e==s||c)break;c=!0}}for(;;){var _=t[s];if(o>=_.s_size){if(this.cursor=n-_.s_size,!_.method)return _.result;var b=_.method();if(this.cursor=n-_.s_size,b)return _.result}if((s=_.substring_i)<0)return 0}},replace_s:function(t,i,s){var e=s.length-(i-t),n=r.substring(0,t),u=r.substring(i);return r=n+s+u,this.limit+=e,this.cursor>=i?this.cursor+=e:this.cursor>t&&(this.cursor=t),e},slice_check:function(){if(this.bra<0||this.bra>this.ket||this.ket>this.limit||this.limit>r.length)throw"faulty slice operation"},slice_from:function(r){this.slice_check(),this.replace_s(this.bra,this.ket,r)},slice_del:function(){this.slice_from("")},insert:function(r,t,i){var s=this.replace_s(r,t,i);r<=this.bra&&(this.bra+=s),r<=this.ket&&(this.ket+=s)},slice_to:function(){return this.slice_check(),r.substring(this.bra,this.ket)},eq_v_b:function(r){return this.eq_s_b(r.length,r)}}}},r.trimmerSupport={generateTrimmer:function(r){var t=new RegExp("^[^"+r+"]+"),i=new RegExp("[^"+r+"]+$");return function(r){return"function"==typeof r.update?r.update(function(r){return r.replace(t,"").replace(i,"")}):r.replace(t,"").replace(i,"")}}}}}); -------------------------------------------------------------------------------- /assets/javascripts/lunr/min/lunr.ko.min.js: -------------------------------------------------------------------------------- 1 | !function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.ko=function(){this.pipeline.reset(),this.pipeline.add(e.ko.trimmer,e.ko.stopWordFilter)},e.ko.wordCharacters="[A-Za-z가-힣]",e.ko.trimmer=e.trimmerSupport.generateTrimmer(e.ko.wordCharacters),e.Pipeline.registerFunction(e.ko.trimmer,"trimmer-ko"),e.ko.stopWordFilter=e.generateStopWordFilter("아 휴 아이구 아이쿠 아이고 어 나 우리 저희 따라 의해 을 를 에 의 가 으로 로 에게 뿐이다 의거하여 근거하여 입각하여 기준으로 예하면 예를 들면 예를 들자면 저 소인 소생 저희 지말고 하지마 하지마라 다른 물론 또한 그리고 비길수 없다 해서는 안된다 뿐만 아니라 만이 아니다 만은 아니다 막론하고 관계없이 그치지 않다 그러나 그런데 하지만 든간에 논하지 않다 따지지 않다 설사 비록 더라도 아니면 만 못하다 하는 편이 낫다 불문하고 향하여 향해서 향하다 쪽으로 틈타 이용하여 타다 오르다 제외하고 이 외에 이 밖에 하여야 비로소 한다면 몰라도 외에도 이곳 여기 부터 기점으로 따라서 할 생각이다 하려고하다 이리하여 그리하여 그렇게 함으로써 하지만 일때 할때 앞에서 중에서 보는데서 으로써 로써 까지 해야한다 일것이다 반드시 할줄알다 할수있다 할수있어 임에 틀림없다 한다면 등 등등 제 겨우 단지 다만 할뿐 딩동 댕그 대해서 대하여 대하면 훨씬 얼마나 얼마만큼 얼마큼 남짓 여 얼마간 약간 다소 좀 조금 다수 몇 얼마 지만 하물며 또한 그러나 그렇지만 하지만 이외에도 대해 말하자면 뿐이다 다음에 반대로 반대로 말하자면 이와 반대로 바꾸어서 말하면 바꾸어서 한다면 만약 그렇지않으면 까악 툭 딱 삐걱거리다 보드득 비걱거리다 꽈당 응당 해야한다 에 가서 각 각각 여러분 각종 각자 제각기 하도록하다 와 과 그러므로 그래서 고로 한 까닭에 하기 때문에 거니와 이지만 대하여 관하여 관한 과연 실로 아니나다를가 생각한대로 진짜로 한적이있다 하곤하였다 하 하하 허허 아하 거바 와 오 왜 어째서 무엇때문에 어찌 하겠는가 무슨 어디 어느곳 더군다나 하물며 더욱이는 어느때 언제 야 이봐 어이 여보시오 흐흐 흥 휴 헉헉 헐떡헐떡 영차 여차 어기여차 끙끙 아야 앗 아야 콸콸 졸졸 좍좍 뚝뚝 주룩주룩 솨 우르르 그래도 또 그리고 바꾸어말하면 바꾸어말하자면 혹은 혹시 답다 및 그에 따르는 때가 되어 즉 지든지 설령 가령 하더라도 할지라도 일지라도 지든지 몇 거의 하마터면 인젠 이젠 된바에야 된이상 만큼\t어찌됏든 그위에 게다가 점에서 보아 비추어 보아 고려하면 하게될것이다 일것이다 비교적 좀 보다더 비하면 시키다 하게하다 할만하다 의해서 연이서 이어서 잇따라 뒤따라 뒤이어 결국 의지하여 기대여 통하여 자마자 더욱더 불구하고 얼마든지 마음대로 주저하지 않고 곧 즉시 바로 당장 하자마자 밖에 안된다 하면된다 그래 그렇지 요컨대 다시 말하자면 바꿔 말하면 즉 구체적으로 말하자면 시작하여 시초에 이상 허 헉 허걱 바와같이 해도좋다 해도된다 게다가 더구나 하물며 와르르 팍 퍽 펄렁 동안 이래 하고있었다 이었다 에서 로부터 까지 예하면 했어요 해요 함께 같이 더불어 마저 마저도 양자 모두 습니다 가까스로 하려고하다 즈음하여 다른 다른 방면으로 해봐요 습니까 했어요 말할것도 없고 무릎쓰고 개의치않고 하는것만 못하다 하는것이 낫다 매 매번 들 모 어느것 어느 로써 갖고말하자면 어디 어느쪽 어느것 어느해 어느 년도 라 해도 언젠가 어떤것 어느것 저기 저쪽 저것 그때 그럼 그러면 요만한걸 그래 그때 저것만큼 그저 이르기까지 할 줄 안다 할 힘이 있다 너 너희 당신 어찌 설마 차라리 할지언정 할지라도 할망정 할지언정 구토하다 게우다 토하다 메쓰겁다 옆사람 퉤 쳇 의거하여 근거하여 의해 따라 힘입어 그 다음 버금 두번째로 기타 첫번째로 나머지는 그중에서 견지에서 형식으로 쓰여 입장에서 위해서 단지 의해되다 하도록시키다 뿐만아니라 반대로 전후 전자 앞의것 잠시 잠깐 하면서 그렇지만 다음에 그러한즉 그런즉 남들 아무거나 어찌하든지 같다 비슷하다 예컨대 이럴정도로 어떻게 만약 만일 위에서 서술한바와같이 인 듯하다 하지 않는다면 만약에 무엇 무슨 어느 어떤 아래윗 조차 한데 그럼에도 불구하고 여전히 심지어 까지도 조차도 하지 않도록 않기 위하여 때 시각 무렵 시간 동안 어때 어떠한 하여금 네 예 우선 누구 누가 알겠는가 아무도 줄은모른다 줄은 몰랏다 하는 김에 겸사겸사 하는바 그런 까닭에 한 이유는 그러니 그러니까 때문에 그 너희 그들 너희들 타인 것 것들 너 위하여 공동으로 동시에 하기 위하여 어찌하여 무엇때문에 붕붕 윙윙 나 우리 엉엉 휘익 윙윙 오호 아하 어쨋든 만 못하다\t하기보다는 차라리 하는 편이 낫다 흐흐 놀라다 상대적으로 말하자면 마치 아니라면 쉿 그렇지 않으면 그렇지 않다면 안 그러면 아니었다면 하든지 아니면 이라면 좋아 알았어 하는것도 그만이다 어쩔수 없다 하나 일 일반적으로 일단 한켠으로는 오자마자 이렇게되면 이와같다면 전부 한마디 한항목 근거로 하기에 아울러 하지 않도록 않기 위해서 이르기까지 이 되다 로 인하여 까닭으로 이유만으로 이로 인하여 그래서 이 때문에 그러므로 그런 까닭에 알 수 있다 결론을 낼 수 있다 으로 인하여 있다 어떤것 관계가 있다 관련이 있다 연관되다 어떤것들 에 대해 이리하여 그리하여 여부 하기보다는 하느니 하면 할수록 운운 이러이러하다 하구나 하도다 다시말하면 다음으로 에 있다 에 달려 있다 우리 우리들 오히려 하기는한데 어떻게 어떻해 어찌됏어 어때 어째서 본대로 자 이 이쪽 여기 이것 이번 이렇게말하자면 이런 이러한 이와 같은 요만큼 요만한 것 얼마 안 되는 것 이만큼 이 정도의 이렇게 많은 것 이와 같다 이때 이렇구나 것과 같이 끼익 삐걱 따위 와 같은 사람들 부류의 사람들 왜냐하면 중의하나 오직 오로지 에 한하다 하기만 하면 도착하다 까지 미치다 도달하다 정도에 이르다 할 지경이다 결과에 이르다 관해서는 여러분 하고 있다 한 후 혼자 자기 자기집 자신 우에 종합한것과같이 총적으로 보면 총적으로 말하면 총적으로 대로 하다 으로서 참 그만이다 할 따름이다 쿵 탕탕 쾅쾅 둥둥 봐 봐라 아이야 아니 와아 응 아이 참나 년 월 일 령 영 일 이 삼 사 오 육 륙 칠 팔 구 이천육 이천칠 이천팔 이천구 하나 둘 셋 넷 다섯 여섯 일곱 여덟 아홉 령 영".split(" ")),e.Pipeline.registerFunction(e.ko.stopWordFilter,"stopWordFilter-ko"),e.ko.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}(),e.Pipeline.registerFunction(e.ko.stemmer,"stemmer-ko")}}); -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Getting Started with TwinTrim 2 | 3 | Welcome to the TwinTrim getting started guide! This document will walk you through the steps to get started with the project. 4 | 5 | ### Prerequisites 6 | 7 | Before you begin, ensure you have met the following requirements: 8 | 9 | - **Python Installation**: Make sure you have Python 3.6 or above installed on your system. You can download it from the [official Python website](https://www.python.org/downloads/). 10 | - **Git**: Ensure you have Git installed to clone the repository. You can get it from the [official Git website](https://git-scm.com/downloads). 11 | - **GitHub Account**: A GitHub account is required if you plan to contribute to the project. 12 | 13 | ### Installing TwinTrim 14 | 15 | To install TwinTrim, follow these steps: 16 | 17 | 1. **Clone the Repository**: Clone the repository from GitHub to your local machine. 18 | 19 | ```bash 20 | git clone https://github.com/Kota-Karthik/twinTrim.git 21 | ``` 22 | 23 | 2. **Navigate to the Project Directory**: Change into the project directory. 24 | 25 | ```bash 26 | cd twinTrim 27 | ``` 28 | 29 | 3. **Install Dependencies**: Install the required dependencies using the following command. If you are using `poetry`, you can install dependencies with: 30 | 31 | ```bash 32 | poetry install 33 | ``` 34 | 35 | Alternatively, if you are using `pip`, install the dependencies from `requirements.txt`: 36 | 37 | ```bash 38 | pip install -r requirements.txt 39 | ``` 40 | 41 | ### Running TwinTrim 42 | 43 | To run TwinTrim, follow these steps: 44 | 45 | 1. **Navigate to the Project Directory** (if not already there): 46 | 47 | ```bash 48 | cd twinTrim 49 | ``` 50 | 51 | 2. **Run the Application**: Start the application with the following command: 52 | 53 | ```bash 54 | python -m twinTrim.main [OPTIONS] 55 | ``` 56 | 57 | Replace `` with the path to the directory you want to scan. You can add options such as `--all` to automatically remove duplicates or specify filters (e.g., `--min-size` or `--file-type`). 58 | 59 | The application will scan the directory and, based on your options, either prompt you to review the duplicates or automatically remove them. 60 | 61 | ### Contributing to TwinTrim 62 | 63 | We welcome contributions! Follow these steps to contribute: 64 | 65 | 1. **Fork the Repository**: Fork the repository on GitHub from [here](https://github.com/Kota-Karthik/twinTrim/fork). 66 | 67 | 2. **Clone Your Fork**: Clone the forked repository to your local machine. 68 | 69 | ```bash 70 | git clone https://github.com//twinTrim.git 71 | ``` 72 | 73 | 3. **Create a New Branch**: Create a new branch to work on your changes. 74 | 75 | ```bash 76 | git checkout -b new-branch-name 77 | ``` 78 | 79 | 4. **Make Changes**: Make the necessary changes to the code. 80 | 81 | 5. **Commit Changes**: Commit your changes with a descriptive message. 82 | 83 | ```bash 84 | git commit -m "Descriptive commit message" 85 | ``` 86 | 87 | 6. **Push Changes**: Push your changes to your fork on GitHub. 88 | 89 | ```bash 90 | git push origin new-branch-name 91 | ``` 92 | 93 | 7. **Create a Pull Request**: Go to the GitHub repository and create a pull request from your fork's branch to the main repository's `main` branch. 94 | 95 | Your changes will be reviewed, and if everything looks good, they will be merged into the main project. 96 | 97 | ### Contact 98 | 99 | If you have any questions or need further assistance, feel free to reach out to [Karthik Kota](https://www.linkedin.com/in/kota-karthikk/). 100 | -------------------------------------------------------------------------------- /assets/javascripts/lunr/min/lunr.sv.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Lunr languages, `Swedish` language 3 | * https://github.com/MihaiValentin/lunr-languages 4 | * 5 | * Copyright 2014, Mihai Valentin 6 | * http://www.mozilla.org/MPL/ 7 | */ 8 | /*! 9 | * based on 10 | * Snowball JavaScript Library v0.3 11 | * http://code.google.com/p/urim/ 12 | * http://snowball.tartarus.org/ 13 | * 14 | * Copyright 2010, Oleg Mazko 15 | * http://www.mozilla.org/MPL/ 16 | */ 17 | 18 | !function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.sv=function(){this.pipeline.reset(),this.pipeline.add(e.sv.trimmer,e.sv.stopWordFilter,e.sv.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.sv.stemmer))},e.sv.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.sv.trimmer=e.trimmerSupport.generateTrimmer(e.sv.wordCharacters),e.Pipeline.registerFunction(e.sv.trimmer,"trimmer-sv"),e.sv.stemmer=function(){var r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,t=new function(){function e(){var e,r=w.cursor+3;if(o=w.limit,0<=r||r<=w.limit){for(a=r;;){if(e=w.cursor,w.in_grouping(l,97,246)){w.cursor=e;break}if(w.cursor=e,w.cursor>=w.limit)return;w.cursor++}for(;!w.out_grouping(l,97,246);){if(w.cursor>=w.limit)return;w.cursor++}o=w.cursor,o=o&&(w.limit_backward=o,w.cursor=w.limit,w.ket=w.cursor,e=w.find_among_b(u,37),w.limit_backward=r,e))switch(w.bra=w.cursor,e){case 1:w.slice_del();break;case 2:w.in_grouping_b(d,98,121)&&w.slice_del()}}function i(){var e=w.limit_backward;w.cursor>=o&&(w.limit_backward=o,w.cursor=w.limit,w.find_among_b(c,7)&&(w.cursor=w.limit,w.ket=w.cursor,w.cursor>w.limit_backward&&(w.bra=--w.cursor,w.slice_del())),w.limit_backward=e)}function s(){var e,r;if(w.cursor>=o){if(r=w.limit_backward,w.limit_backward=o,w.cursor=w.limit,w.ket=w.cursor,e=w.find_among_b(m,5))switch(w.bra=w.cursor,e){case 1:w.slice_del();break;case 2:w.slice_from("lös");break;case 3:w.slice_from("full")}w.limit_backward=r}}var a,o,u=[new r("a",-1,1),new r("arna",0,1),new r("erna",0,1),new r("heterna",2,1),new r("orna",0,1),new r("ad",-1,1),new r("e",-1,1),new r("ade",6,1),new r("ande",6,1),new r("arne",6,1),new r("are",6,1),new r("aste",6,1),new r("en",-1,1),new r("anden",12,1),new r("aren",12,1),new r("heten",12,1),new r("ern",-1,1),new r("ar",-1,1),new r("er",-1,1),new r("heter",18,1),new r("or",-1,1),new r("s",-1,2),new r("as",21,1),new r("arnas",22,1),new r("ernas",22,1),new r("ornas",22,1),new r("es",21,1),new r("ades",26,1),new r("andes",26,1),new r("ens",21,1),new r("arens",29,1),new r("hetens",29,1),new r("erns",21,1),new r("at",-1,1),new r("andet",-1,1),new r("het",-1,1),new r("ast",-1,1)],c=[new r("dd",-1,-1),new r("gd",-1,-1),new r("nn",-1,-1),new r("dt",-1,-1),new r("gt",-1,-1),new r("kt",-1,-1),new r("tt",-1,-1)],m=[new r("ig",-1,1),new r("lig",0,1),new r("els",-1,1),new r("fullt",-1,3),new r("löst",-1,2)],l=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,24,0,32],d=[119,127,149],w=new n;this.setCurrent=function(e){w.setCurrent(e)},this.getCurrent=function(){return w.getCurrent()},this.stem=function(){var r=w.cursor;return e(),w.limit_backward=r,w.cursor=w.limit,t(),w.cursor=w.limit,i(),w.cursor=w.limit,s(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return t.setCurrent(e),t.stem(),t.getCurrent()}):(t.setCurrent(e),t.stem(),t.getCurrent())}}(),e.Pipeline.registerFunction(e.sv.stemmer,"stemmer-sv"),e.sv.stopWordFilter=e.generateStopWordFilter("alla allt att av blev bli blir blivit de dem den denna deras dess dessa det detta dig din dina ditt du där då efter ej eller en er era ert ett från för ha hade han hans har henne hennes hon honom hur här i icke ingen inom inte jag ju kan kunde man med mellan men mig min mina mitt mot mycket ni nu när någon något några och om oss på samma sedan sig sin sina sitta själv skulle som så sådan sådana sådant till under upp ut utan vad var vara varför varit varje vars vart vem vi vid vilka vilkas vilken vilket vår våra vårt än är åt över".split(" ")),e.Pipeline.registerFunction(e.sv.stopWordFilter,"stopWordFilter-sv")}}); -------------------------------------------------------------------------------- /twinTrim/flagController.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | from tqdm import tqdm 4 | from concurrent.futures import ThreadPoolExecutor, as_completed 5 | import click 6 | from twinTrim.dataStructures.allFileMetadata import add_or_update_file 7 | from twinTrim.dataStructures.fileMetadata import FileMetadata, normalStore , add_or_update_normal_file 8 | from tqdm import tqdm 9 | 10 | #bar function to be used by scanner thread pools 11 | progress_bar_format = "{desc}: {n_fmt}/{total_fmt} | ETA={remaining} | {rate_fmt} {bar} {percentage:.3f}%" 12 | """adaptive progress bar, returns tqdm object""" 13 | def progress_bar_func(bar_desc, total, unit='file',color="yellow", bar_color='white'): 14 | try: #default to yellow 15 | bar_desc_obj = click.style(bar_desc, fg=color, bold=True) # set to bold by default 16 | except: 17 | print(f"Warning, invalid ColorType: {color} falling back to default yellow.") 18 | bar_desc_obj = click.style(bar_desc, fg='yellow', bold=True) 19 | return tqdm(total=total, desc=bar_desc_obj, unit=unit, bar_format= progress_bar_format, colour=bar_color) 20 | 21 | def handleAllFlag(directory,file_filter,pb_color,bar_color): 22 | """Handle all duplicates automatically without asking if --all flag is set.""" 23 | all_start_time = time.time() 24 | 25 | # Collect all file paths to process 26 | all_files = [os.path.join(root, file_name) for root, _, files in os.walk(directory) for file_name in files] 27 | all_files = [f for f in all_files if file_filter.filter_files(f)] 28 | total_files = len(all_files) 29 | 30 | # Use ThreadPoolExecutor to handle files concurrently 31 | with ThreadPoolExecutor() as executor, progress_bar_func("Scanning", total_files, color=pb_color, bar_color=bar_color) as progress_bar: 32 | futures = {executor.submit(add_or_update_file, file_path): file_path for file_path in all_files} 33 | 34 | # Update progress bar as files are processed 35 | for future in as_completed(futures): 36 | try: 37 | future.result() # Ensures exception handling for each future 38 | except Exception as e: 39 | click.echo(click.style(f"Error processing file {futures[future]}: {str(e)}", fg='red')) 40 | progress_bar.update(1) 41 | 42 | click.echo(click.style("All files scanned and duplicates handled.", fg='green')) 43 | 44 | all_end_time = time.time() 45 | all_delete_time_taken = all_end_time - all_start_time 46 | click.echo(click.style(f"Time taken to delete all duplicate files: {all_delete_time_taken:.2f} seconds.", fg='green')) 47 | click.echo(click.style("All duplicates deleted!", fg='green')) 48 | 49 | 50 | def find_duplicates(directory, file_filter, pb_color, bar_color): 51 | """Find duplicate files in the given directory and store them in normalStore.""" 52 | # Collect all file paths first and apply filters 53 | start_time=time.time() 54 | all_files = [os.path.join(root, file_name) for root, _, files in os.walk(directory) for file_name in files] 55 | all_files = [f for f in all_files if file_filter.filter_files(f)] # Apply filters 56 | 57 | # Calculate the total number of files and ensure it is finite 58 | total_files = len(all_files) 59 | 60 | def process_file(file_path): 61 | add_or_update_normal_file(file_path) 62 | 63 | with ThreadPoolExecutor() as executor, progress_bar_func("Hashing", total_files, color=pb_color, bar_color=bar_color) as progress_bar: 64 | # Submit tasks to the executor 65 | futures = {executor.submit(process_file, file_path): file_path for file_path in all_files} 66 | 67 | for future in as_completed(futures): 68 | try: 69 | future.result() # Ensures exception handling for each future 70 | except Exception as e: 71 | click.echo(click.style(f"Error processing file {futures[future]}: {str(e)}", fg='red')) 72 | progress_bar.update(1) 73 | 74 | end_time=time.time() 75 | click.echo(click.style(f"Time taken to find all duplicate files: {end_time-start_time:.2f} seconds.", fg='green')) 76 | duplicates = [] 77 | for _, metadata in normalStore.items(): 78 | if len(metadata.filepaths) > 1: 79 | original_path = metadata.filepaths[0] 80 | for duplicate_path in metadata.filepaths[1:]: 81 | duplicates.append((original_path, duplicate_path)) 82 | 83 | return duplicates 84 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## My Pledge 4 | 5 | As the owner and maintainer of the **TwinTrim** project, I am committed to fostering a welcoming, inclusive, and harassment-free environment for everyone involved. Whether you are contributing code, writing documentation, submitting issues, or participating in discussions, I am dedicated to ensuring that the **TwinTrim** community remains a positive and collaborative space for individuals of all ages, body sizes, visible or invisible disabilities, cultural backgrounds, ethnicities, gender identities and expressions, levels of experience, education, socioeconomic status, nationalities, personal appearances, races, religions, or sexual identities and orientations. 6 | 7 | I believe that the strength of the **TwinTrim** project is built on the diversity of perspectives and the collaborative nature of the community. I am actively committed to promoting an atmosphere where everyone feels respected, valued, and heard. 8 | 9 | ## Standards of Behavior 10 | 11 | To help create a positive, productive, and welcoming environment, I expect all participants in the **TwinTrim** project to uphold the following standards of behavior: 12 | 13 | ### Positive and Respectful Behavior 14 | 15 | - **Use inclusive language**: Refrain from discriminatory or exclusionary language. Communications should be accessible and easy to understand for all participants. 16 | - **Respect differing viewpoints and experiences**: Recognize that others may have different ideas or experiences, and diverse perspectives make the project better. 17 | - **Accept constructive feedback gracefully**: Feedback is an important part of collaboration. Embrace it with an open mind and focus on improvement. 18 | - **Focus on the well-being of the project and community**: Prioritize the interests of the project and support fellow contributors. 19 | - **Show empathy**: Be kind and understanding. Offer support to those who may need it, acknowledging that others may have personal challenges. 20 | 21 | ### Unacceptable Behavior 22 | 23 | Behaviors that violate the values of respect, inclusivity, and professionalism are unacceptable. This includes, but is not limited to: 24 | 25 | - **Sexualized language or imagery**: Including inappropriate jokes, comments, or imagery that can make others uncomfortable. Unwelcome advances or attention based on gender are strictly prohibited. 26 | - **Trolling and derogatory comments**: Verbal attacks, personal insults, or inflammatory comments intended to provoke, belittle, or demean others are unacceptable. 27 | - **Harassment**: Any form of public or private harassment, bullying, repeated unsolicited communication, or attempts to intimidate are not tolerated. 28 | - **Publishing private information**: Sharing someone’s personal details, such as their contact information, without their explicit consent is prohibited. 29 | - **Unprofessional conduct**: Any behavior that is inappropriate in a professional or collaborative setting, including threats or actions that compromise the project’s integrity, will not be tolerated. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies to all interactions associated with the **TwinTrim** project, including but not limited to: 34 | 35 | - **Project spaces**: This includes all online platforms associated with TwinTrim, such as issue trackers, code repositories, project wikis, and forums. 36 | - **Public spaces**: The standards set in this Code of Conduct also extend to public spaces where members of the project may be representing **TwinTrim**, such as online conferences, meetups, or social media platforms. 37 | - **Representation**: An individual is considered to be representing the **TwinTrim** project when they are using an official project email, posting via an official social media account, or acting as a representative at an event. 38 | 39 | ## Reporting and Resolution 40 | 41 | If you encounter or witness any unacceptable behavior or violations of this Code of Conduct, you are encouraged to report them directly to me at [kotakarthik2307@gmail.com]. I take all concerns seriously and will take appropriate actions to ensure that the **TwinTrim** community remains a safe and respectful place for all. 42 | 43 | ## Acknowledgment 44 | 45 | I value the contributions of every member of the **TwinTrim** community and am dedicated to leading this project with integrity, inclusivity, and a focus on collaboration. Thank you for being a part of the **TwinTrim** community and for helping make this project a positive experience for everyone! 46 | -------------------------------------------------------------------------------- /assets/javascripts/lunr/min/lunr.da.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Lunr languages, `Danish` language 3 | * https://github.com/MihaiValentin/lunr-languages 4 | * 5 | * Copyright 2014, Mihai Valentin 6 | * http://www.mozilla.org/MPL/ 7 | */ 8 | /*! 9 | * based on 10 | * Snowball JavaScript Library v0.3 11 | * http://code.google.com/p/urim/ 12 | * http://snowball.tartarus.org/ 13 | * 14 | * Copyright 2010, Oleg Mazko 15 | * http://www.mozilla.org/MPL/ 16 | */ 17 | 18 | !function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.da=function(){this.pipeline.reset(),this.pipeline.add(e.da.trimmer,e.da.stopWordFilter,e.da.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.da.stemmer))},e.da.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.da.trimmer=e.trimmerSupport.generateTrimmer(e.da.wordCharacters),e.Pipeline.registerFunction(e.da.trimmer,"trimmer-da"),e.da.stemmer=function(){var r=e.stemmerSupport.Among,i=e.stemmerSupport.SnowballProgram,n=new function(){function e(){var e,r=f.cursor+3;if(d=f.limit,0<=r&&r<=f.limit){for(a=r;;){if(e=f.cursor,f.in_grouping(w,97,248)){f.cursor=e;break}if(f.cursor=e,e>=f.limit)return;f.cursor++}for(;!f.out_grouping(w,97,248);){if(f.cursor>=f.limit)return;f.cursor++}d=f.cursor,d=d&&(r=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,e=f.find_among_b(c,32),f.limit_backward=r,e))switch(f.bra=f.cursor,e){case 1:f.slice_del();break;case 2:f.in_grouping_b(p,97,229)&&f.slice_del()}}function t(){var e,r=f.limit-f.cursor;f.cursor>=d&&(e=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,f.find_among_b(l,4)?(f.bra=f.cursor,f.limit_backward=e,f.cursor=f.limit-r,f.cursor>f.limit_backward&&(f.cursor--,f.bra=f.cursor,f.slice_del())):f.limit_backward=e)}function s(){var e,r,i,n=f.limit-f.cursor;if(f.ket=f.cursor,f.eq_s_b(2,"st")&&(f.bra=f.cursor,f.eq_s_b(2,"ig")&&f.slice_del()),f.cursor=f.limit-n,f.cursor>=d&&(r=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,e=f.find_among_b(m,5),f.limit_backward=r,e))switch(f.bra=f.cursor,e){case 1:f.slice_del(),i=f.limit-f.cursor,t(),f.cursor=f.limit-i;break;case 2:f.slice_from("løs")}}function o(){var e;f.cursor>=d&&(e=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,f.out_grouping_b(w,97,248)?(f.bra=f.cursor,u=f.slice_to(u),f.limit_backward=e,f.eq_v_b(u)&&f.slice_del()):f.limit_backward=e)}var a,d,u,c=[new r("hed",-1,1),new r("ethed",0,1),new r("ered",-1,1),new r("e",-1,1),new r("erede",3,1),new r("ende",3,1),new r("erende",5,1),new r("ene",3,1),new r("erne",3,1),new r("ere",3,1),new r("en",-1,1),new r("heden",10,1),new r("eren",10,1),new r("er",-1,1),new r("heder",13,1),new r("erer",13,1),new r("s",-1,2),new r("heds",16,1),new r("es",16,1),new r("endes",18,1),new r("erendes",19,1),new r("enes",18,1),new r("ernes",18,1),new r("eres",18,1),new r("ens",16,1),new r("hedens",24,1),new r("erens",24,1),new r("ers",16,1),new r("ets",16,1),new r("erets",28,1),new r("et",-1,1),new r("eret",30,1)],l=[new r("gd",-1,-1),new r("dt",-1,-1),new r("gt",-1,-1),new r("kt",-1,-1)],m=[new r("ig",-1,1),new r("lig",0,1),new r("elig",1,1),new r("els",-1,1),new r("løst",-1,2)],w=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,48,0,128],p=[239,254,42,3,0,0,0,0,0,0,0,0,0,0,0,0,16],f=new i;this.setCurrent=function(e){f.setCurrent(e)},this.getCurrent=function(){return f.getCurrent()},this.stem=function(){var r=f.cursor;return e(),f.limit_backward=r,f.cursor=f.limit,n(),f.cursor=f.limit,t(),f.cursor=f.limit,s(),f.cursor=f.limit,o(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}}(),e.Pipeline.registerFunction(e.da.stemmer,"stemmer-da"),e.da.stopWordFilter=e.generateStopWordFilter("ad af alle alt anden at blev blive bliver da de dem den denne der deres det dette dig din disse dog du efter eller en end er et for fra ham han hans har havde have hende hendes her hos hun hvad hvis hvor i ikke ind jeg jer jo kunne man mange med meget men mig min mine mit mod ned noget nogle nu når og også om op os over på selv sig sin sine sit skal skulle som sådan thi til ud under var vi vil ville vor være været".split(" ")),e.Pipeline.registerFunction(e.da.stopWordFilter,"stopWordFilter-da")}}); -------------------------------------------------------------------------------- /assets/javascripts/lunr/min/lunr.no.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Lunr languages, `Norwegian` language 3 | * https://github.com/MihaiValentin/lunr-languages 4 | * 5 | * Copyright 2014, Mihai Valentin 6 | * http://www.mozilla.org/MPL/ 7 | */ 8 | /*! 9 | * based on 10 | * Snowball JavaScript Library v0.3 11 | * http://code.google.com/p/urim/ 12 | * http://snowball.tartarus.org/ 13 | * 14 | * Copyright 2010, Oleg Mazko 15 | * http://www.mozilla.org/MPL/ 16 | */ 17 | 18 | !function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.no=function(){this.pipeline.reset(),this.pipeline.add(e.no.trimmer,e.no.stopWordFilter,e.no.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.no.stemmer))},e.no.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.no.trimmer=e.trimmerSupport.generateTrimmer(e.no.wordCharacters),e.Pipeline.registerFunction(e.no.trimmer,"trimmer-no"),e.no.stemmer=function(){var r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,i=new function(){function e(){var e,r=w.cursor+3;if(a=w.limit,0<=r||r<=w.limit){for(s=r;;){if(e=w.cursor,w.in_grouping(d,97,248)){w.cursor=e;break}if(e>=w.limit)return;w.cursor=e+1}for(;!w.out_grouping(d,97,248);){if(w.cursor>=w.limit)return;w.cursor++}a=w.cursor,a=a&&(r=w.limit_backward,w.limit_backward=a,w.ket=w.cursor,e=w.find_among_b(m,29),w.limit_backward=r,e))switch(w.bra=w.cursor,e){case 1:w.slice_del();break;case 2:n=w.limit-w.cursor,w.in_grouping_b(c,98,122)?w.slice_del():(w.cursor=w.limit-n,w.eq_s_b(1,"k")&&w.out_grouping_b(d,97,248)&&w.slice_del());break;case 3:w.slice_from("er")}}function t(){var e,r=w.limit-w.cursor;w.cursor>=a&&(e=w.limit_backward,w.limit_backward=a,w.ket=w.cursor,w.find_among_b(u,2)?(w.bra=w.cursor,w.limit_backward=e,w.cursor=w.limit-r,w.cursor>w.limit_backward&&(w.cursor--,w.bra=w.cursor,w.slice_del())):w.limit_backward=e)}function o(){var e,r;w.cursor>=a&&(r=w.limit_backward,w.limit_backward=a,w.ket=w.cursor,e=w.find_among_b(l,11),e?(w.bra=w.cursor,w.limit_backward=r,1==e&&w.slice_del()):w.limit_backward=r)}var s,a,m=[new r("a",-1,1),new r("e",-1,1),new r("ede",1,1),new r("ande",1,1),new r("ende",1,1),new r("ane",1,1),new r("ene",1,1),new r("hetene",6,1),new r("erte",1,3),new r("en",-1,1),new r("heten",9,1),new r("ar",-1,1),new r("er",-1,1),new r("heter",12,1),new r("s",-1,2),new r("as",14,1),new r("es",14,1),new r("edes",16,1),new r("endes",16,1),new r("enes",16,1),new r("hetenes",19,1),new r("ens",14,1),new r("hetens",21,1),new r("ers",14,1),new r("ets",14,1),new r("et",-1,1),new r("het",25,1),new r("ert",-1,3),new r("ast",-1,1)],u=[new r("dt",-1,-1),new r("vt",-1,-1)],l=[new r("leg",-1,1),new r("eleg",0,1),new r("ig",-1,1),new r("eig",2,1),new r("lig",2,1),new r("elig",4,1),new r("els",-1,1),new r("lov",-1,1),new r("elov",7,1),new r("slov",7,1),new r("hetslov",9,1)],d=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,48,0,128],c=[119,125,149,1],w=new n;this.setCurrent=function(e){w.setCurrent(e)},this.getCurrent=function(){return w.getCurrent()},this.stem=function(){var r=w.cursor;return e(),w.limit_backward=r,w.cursor=w.limit,i(),w.cursor=w.limit,t(),w.cursor=w.limit,o(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}}(),e.Pipeline.registerFunction(e.no.stemmer,"stemmer-no"),e.no.stopWordFilter=e.generateStopWordFilter("alle at av bare begge ble blei bli blir blitt både båe da de deg dei deim deira deires dem den denne der dere deres det dette di din disse ditt du dykk dykkar då eg ein eit eitt eller elles en enn er et ett etter for fordi fra før ha hadde han hans har hennar henne hennes her hjå ho hoe honom hoss hossen hun hva hvem hver hvilke hvilken hvis hvor hvordan hvorfor i ikke ikkje ikkje ingen ingi inkje inn inni ja jeg kan kom korleis korso kun kunne kva kvar kvarhelst kven kvi kvifor man mange me med medan meg meget mellom men mi min mine mitt mot mykje ned no noe noen noka noko nokon nokor nokre nå når og også om opp oss over på samme seg selv si si sia sidan siden sin sine sitt sjøl skal skulle slik so som som somme somt så sånn til um upp ut uten var vart varte ved vere verte vi vil ville vore vors vort vår være være vært å".split(" ")),e.Pipeline.registerFunction(e.no.stopWordFilter,"stopWordFilter-no")}}); -------------------------------------------------------------------------------- /tests/test_fileFilter.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from twinTrim.dataStructures.fileFilter import FileFilter 3 | 4 | 5 | # Test setting valid max file size values 6 | def test_set_max_file_size_valid(): 7 | """Test setting valid max file size values.""" 8 | file_filter = FileFilter() 9 | 10 | # Test with a smaller size 11 | file_filter.setMaxFileSize("500mb") 12 | assert file_filter.maxFileSize == "500mb", "Failed to set max file size to 500mb" 13 | 14 | # Test with a larger size 15 | file_filter.setMaxFileSize("2gb") 16 | assert file_filter.maxFileSize == "2gb", "Failed to set max file size to 2gb" 17 | 18 | # Test with a minimum size (edge case) 19 | file_filter.setMaxFileSize("1kb") 20 | assert file_filter.maxFileSize == "1kb", "Failed to set max file size to 1kb" 21 | 22 | # Test setting the same max file size 23 | def test_set_max_file_size_same_value(): 24 | """Test setting the same max file size value multiple times.""" 25 | file_filter = FileFilter() 26 | 27 | # Test setting the max file size to the default value 28 | file_filter.setMaxFileSize("1gb") 29 | assert file_filter.maxFileSize == "1gb", "Failed to set max file size to 1gb" 30 | 31 | # Test setting the max file size to the same value again 32 | file_filter.setMaxFileSize("1gb") 33 | assert file_filter.maxFileSize == "1gb", "Failed to set max file size to 1gb again" 34 | 35 | # Test boundary values for max file size 36 | def test_set_max_file_size_boundary(): 37 | """Test boundary values for max file size.""" 38 | file_filter = FileFilter() 39 | 40 | # Test setting a value just under the default 41 | file_filter.setMaxFileSize("999mb") 42 | assert file_filter.maxFileSize == "999mb", "Failed to set max file size to 999mb" 43 | 44 | # Test setting a value just over the default 45 | file_filter.setMaxFileSize("1001mb") 46 | assert file_filter.maxFileSize == "1001mb", "Failed to set max file size to 1001mb" 47 | 48 | # Test setting an empty value to max file size 49 | def test_set_max_file_size_empty_value(): 50 | """Test setting an empty value to max file size.""" 51 | file_filter = FileFilter() 52 | 53 | # Since there is no validation, an empty value would still set it to the empty string 54 | file_filter.setMaxFileSize("") 55 | assert file_filter.maxFileSize == "", "Failed to set max file size to empty value" 56 | 57 | # Test adding files to the exclude list 58 | def test_add_file_exclude_adds_file(): 59 | """Test adding a file to the exclude list.""" 60 | file_filter = FileFilter() 61 | file_filter.addFileExclude("test_file.txt") 62 | assert "test_file.txt" in file_filter.fileExclude, "File was not added to the exclude list" 63 | 64 | 65 | # Test setting valid file types 66 | def test_set_file_type_valid(): 67 | """Test setting valid file types.""" 68 | file_filter = FileFilter() 69 | 70 | # Test setting a common file type 71 | file_filter.setFileType("txt") 72 | assert file_filter.fileType == r"^.+\.txt$", "Failed to set file type regex for .txt files" 73 | 74 | # Test setting a file type with multiple extensions 75 | file_filter.setFileType("tar.gz") 76 | assert file_filter.fileType == r"^.+\.tar.gz$", "Failed to set file type regex for .tar.gz files" 77 | 78 | # Test setting a single character file type 79 | file_filter.setFileType("c") 80 | assert file_filter.fileType == r"^.+\.c$", "Failed to set file type regex for .c files" 81 | 82 | # Test setting an empty string as file type 83 | def test_set_file_type_empty_string(): 84 | """Test setting an empty string as file type.""" 85 | file_filter = FileFilter() 86 | 87 | # The empty string should be accepted and form a regex matching any file without an extension 88 | file_filter.setFileType("") 89 | assert file_filter.fileType == r"^.+\.$", "Failed to set file type regex for empty file type" 90 | 91 | # Test setting file types with special characters 92 | def test_set_file_type_special_characters(): 93 | """Test setting file types with special characters.""" 94 | file_filter = FileFilter() 95 | 96 | # Special characters should be part of the file extension in the regex 97 | file_filter.setFileType("mp4#") 98 | assert file_filter.fileType == r"^.+\.mp4#$", "Failed to set file type regex for special characters" 99 | 100 | # Test setting numeric file type extensions 101 | def test_set_file_type_numeric(): 102 | """Test setting numeric file type extensions.""" 103 | file_filter = FileFilter() 104 | 105 | # File types with numbers should be accepted 106 | file_filter.setFileType("123") 107 | assert file_filter.fileType == r"^.+\.123$", "Failed to set file type regex for numeric file type" 108 | -------------------------------------------------------------------------------- /assets/javascripts/lunr/min/lunr.he.min.js: -------------------------------------------------------------------------------- 1 | !function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.he=function(){this.pipeline.reset(),this.pipeline.add(e.he.trimmer,e.he.stopWordFilter,e.he.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.he.stemmer))},e.he.wordCharacters="֑-״א-תa-zA-Za-zA-Z0-90-9",e.he.trimmer=e.trimmerSupport.generateTrimmer(e.he.wordCharacters),e.Pipeline.registerFunction(e.he.trimmer,"trimmer-he"),e.he.stemmer=function(){var e=this;return e.result=!1,e.preRemoved=!1,e.sufRemoved=!1,e.pre={pre1:"ה ו י ת",pre2:"ב כ ל מ ש כש",pre3:"הב הכ הל המ הש בש לכ",pre4:"וב וכ ול ומ וש",pre5:"מה שה כל",pre6:"מב מכ מל ממ מש",pre7:"בה בו בי בת כה כו כי כת לה לו לי לת",pre8:"ובה ובו ובי ובת וכה וכו וכי וכת ולה ולו ולי ולת"},e.suf={suf1:"ך כ ם ן נ",suf2:"ים ות וך וכ ום ון ונ הם הן יכ יך ינ ים",suf3:"תי תך תכ תם תן תנ",suf4:"ותי ותך ותכ ותם ותן ותנ",suf5:"נו כם כן הם הן",suf6:"ונו וכם וכן והם והן",suf7:"תכם תכן תנו תהם תהן",suf8:"הוא היא הם הן אני אתה את אנו אתם אתן",suf9:"ני נו כי כו כם כן תי תך תכ תם תן",suf10:"י ך כ ם ן נ ת"},e.patterns=JSON.parse('{"hebrewPatterns": [{"pt1": [{"c": "ה", "l": 0}]}, {"pt2": [{"c": "ו", "l": 0}]}, {"pt3": [{"c": "י", "l": 0}]}, {"pt4": [{"c": "ת", "l": 0}]}, {"pt5": [{"c": "מ", "l": 0}]}, {"pt6": [{"c": "ל", "l": 0}]}, {"pt7": [{"c": "ב", "l": 0}]}, {"pt8": [{"c": "כ", "l": 0}]}, {"pt9": [{"c": "ש", "l": 0}]}, {"pt10": [{"c": "כש", "l": 0}]}, {"pt11": [{"c": "בה", "l": 0}]}, {"pt12": [{"c": "וב", "l": 0}]}, {"pt13": [{"c": "וכ", "l": 0}]}, {"pt14": [{"c": "ול", "l": 0}]}, {"pt15": [{"c": "ומ", "l": 0}]}, {"pt16": [{"c": "וש", "l": 0}]}, {"pt17": [{"c": "הב", "l": 0}]}, {"pt18": [{"c": "הכ", "l": 0}]}, {"pt19": [{"c": "הל", "l": 0}]}, {"pt20": [{"c": "המ", "l": 0}]}, {"pt21": [{"c": "הש", "l": 0}]}, {"pt22": [{"c": "מה", "l": 0}]}, {"pt23": [{"c": "שה", "l": 0}]}, {"pt24": [{"c": "כל", "l": 0}]}]}'),e.execArray=["cleanWord","removeDiacritics","removeStopWords","normalizeHebrewCharacters"],e.stem=function(){var r=0;for(e.result=!1,e.preRemoved=!1,e.sufRemoved=!1;r=0)return!0},e.normalizeHebrewCharacters=function(){return e.word=e.word.replace("ך","כ"),e.word=e.word.replace("ם","מ"),e.word=e.word.replace("ן","נ"),e.word=e.word.replace("ף","פ"),e.word=e.word.replace("ץ","צ"),!1},function(r){return"function"==typeof r.update?r.update(function(r){return e.setCurrent(r),e.stem(),e.getCurrent()}):(e.setCurrent(r),e.stem(),e.getCurrent())}}(),e.Pipeline.registerFunction(e.he.stemmer,"stemmer-he"),e.he.stopWordFilter=e.generateStopWordFilter("אבל או אולי אותו אותי אותך אותם אותן אותנו אז אחר אחרות אחרי אחריכן אחרים אחרת אי איזה איך אין איפה אל אלה אלו אם אנחנו אני אף אפשר את אתה אתכם אתכן אתם אתן באיזה באיזו בגלל בין בלבד בעבור בעזרת בכל בכן בלי במידה במקום שבו ברוב בשביל בשעה ש בתוך גם דרך הוא היא היה היי היכן היתה היתי הם הן הנה הסיבה שבגללה הרי ואילו ואת זאת זה זות יהיה יוכל יוכלו יותר מדי יכול יכולה יכולות יכולים יכל יכלה יכלו יש כאן כאשר כולם כולן כזה כי כיצד כך כל כלל כמו כן כפי כש לא לאו לאיזותך לאן לבין לה להיות להם להן לו לזה לזות לי לך לכם לכן למה למעלה למעלה מ למטה למטה מ למעט למקום שבו למרות לנו לעבר לעיכן לפיכך לפני מאד מאחורי מאיזו סיבה מאין מאיפה מבלי מבעד מדוע מה מהיכן מול מחוץ מי מידע מכאן מכל מכן מלבד מן מנין מסוגל מעט מעטים מעל מצד מקום בו מתחת מתי נגד נגר נו עד עז על עלי עליו עליה עליהם עליך עלינו עם עצמה עצמהם עצמהן עצמו עצמי עצמם עצמן עצמנו פה רק שוב של שלה שלהם שלהן שלו שלי שלך שלכה שלכם שלכן שלנו שם תהיה תחת".split(" ")),e.Pipeline.registerFunction(e.he.stopWordFilter,"stopWordFilter-he")}}); -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pytest 3 | import tempfile 4 | import unittest 5 | from unittest import mock 6 | from twinTrim.utils import parse_size, get_file_hash,handle_and_remove # Adjust based on actual import path 7 | import click 8 | import io 9 | import sys 10 | # Tests for parse_size function 11 | class TestHandleAndRemove(unittest.TestCase): 12 | 13 | def setUp(self): 14 | # Create a temporary file to test deletion 15 | self.temp_file = tempfile.NamedTemporaryFile(delete=True) 16 | self.temp_file_path = self.temp_file.name # Get the file path 17 | 18 | def tearDown(self): 19 | # Cleanup: Make sure the temp file is removed 20 | try: 21 | os.remove(self.temp_file_path) 22 | except Exception: 23 | pass # If it doesn't exist, ignore the error 24 | 25 | def test_handle_and_remove_success(self): 26 | # Test successful file deletion 27 | result = handle_and_remove(self.temp_file_path) 28 | self.assertFalse(os.path.exists(self.temp_file_path)) # File should not exist anymore 29 | 30 | @mock.patch("os.remove") 31 | 32 | def test_handle_and_remove_file_not_found(self, mock_remove): 33 | # Simulate FileNotFoundError 34 | mock_remove.side_effect = FileNotFoundError 35 | with mock.patch('sys.stdout', new=io.StringIO()) as fake_out: 36 | handle_and_remove("non_existent_file.txt") 37 | output = fake_out.getvalue() # Capture the printed output 38 | self.assertIn("File not found (skipped): non_existent_file.txt", output) 39 | 40 | @mock.patch("os.remove") 41 | def test_handle_and_remove_permission_error(self, mock_remove): 42 | # Simulate PermissionError 43 | mock_remove.side_effect = PermissionError 44 | with mock.patch('sys.stdout', new=io.StringIO()) as fake_out: 45 | handle_and_remove(self.temp_file_path) # Using temp file to ensure we have a real file 46 | output = fake_out.getvalue() # Capture the printed output 47 | self.assertIn("Permission denied (skipped): {}".format(self.temp_file_path), output) 48 | 49 | if __name__ == "__main__": 50 | unittest.main() 51 | 52 | def test_parse_size_valid(): 53 | # Test valid size strings 54 | assert parse_size("10kb") == 10 * 1024 # 10 * 1024 bytes = 10240 55 | assert parse_size("1mb") == 1 * 1024 * 1024 # 1 * 1024 * 1024 bytes = 1048576 56 | assert parse_size("1.5gb") == int(1.5 * 1024 * 1024 * 1024) # 1610612736 bytes 57 | assert parse_size("0kb") == 0 # Edge case: 0kb should be 0 58 | assert parse_size("0mb") == 0 # Edge case: 0mb should be 0 59 | assert parse_size("0gb") == 0 # Edge case: 0gb should be 0 60 | 61 | def test_parse_size_invalid(): 62 | # Test invalid size strings 63 | assert parse_size("abc") == 0 # Non-numeric input should return 0 64 | assert parse_size("10x") == 0 65 | assert parse_size("100.5xyz") == 0 66 | 67 | # Uncomment to raise exceptions for invalid formats: 68 | # with pytest.raises(ValueError): 69 | # parse_size("abc") 70 | # with pytest.raises(ValueError): 71 | # parse_size("10x") 72 | # with pytest.raises(ValueError): 73 | # parse_size("100.5xyz") 74 | 75 | # Fixture for creating a temporary file with 'hello' content 76 | @pytest.fixture 77 | def create_temp_file(): 78 | """Fixture to create a temporary file with known content.""" 79 | with tempfile.NamedTemporaryFile(delete=False, mode='w+') as tmp_file: 80 | tmp_file.write("hello") # Write 'hello' to the file for hashing 81 | tmp_file.flush() # Flush to ensure the content is written 82 | yield tmp_file.name # Provide the name for the test 83 | os.remove(tmp_file.name) # Cleanup after test 84 | 85 | @pytest.fixture 86 | def create_empty_file(): 87 | """Fixture to create an empty temporary file.""" 88 | with tempfile.NamedTemporaryFile(delete=False) as tmp_file: 89 | yield tmp_file.name # Provide the name for the test 90 | os.remove(tmp_file.name) # Cleanup after test 91 | 92 | # Tests for get_file_hash function 93 | def test_get_file_hash_regular_file(create_temp_file): 94 | """Test hashing for a regular file with known content.""" 95 | hash_value = get_file_hash(create_temp_file) 96 | expected_hash = "5d41402abc4b2a76b9719d911017c592" # MD5 hash for 'hello' 97 | assert hash_value == expected_hash 98 | 99 | def test_get_file_hash_empty_file(create_empty_file): 100 | """Test hashing for an empty file.""" 101 | hash_value = get_file_hash(create_empty_file) 102 | expected_hash = "d41d8cd98f00b204e9800998ecf8427e" # MD5 hash for an empty file 103 | assert hash_value == expected_hash 104 | 105 | def test_get_file_hash_non_existent_file(): 106 | """Test handling of non-existent file.""" 107 | with pytest.raises(FileNotFoundError): 108 | get_file_hash("non_existent_file.txt") 109 | -------------------------------------------------------------------------------- /CONTRIBUTION_GUIDELINES.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | We appreciate your interest in contributing to our project! Contributions of all kinds are welcome, including bug fixes, feature improvements, documentation updates, and more. 4 | 5 | Before contributing, please take a moment to review and follow these guidelines to ensure a smooth process for everyone. 6 | 7 | ## Table of Contents 8 | - [Important](#important) 9 | - [Know the Project](#know-the-project) 10 | - [How to Contribute](#how-to-contribute) 11 | - [Coding Standards](#coding-standards) 12 | - [Commit Messages](#commit-messages) 13 | - [Issue Reporting](#issue-reporting) 14 | - [Pull Requests](#pull-requests) 15 | - [Code of Conduct](#code-of-conduct) 16 | 17 | 18 | ## Important 19 | - Reduntant or spam issues are not accepted. 20 | - No documentation contributions allowed, this includes readme, contribution guidelines and code of conduct changes. 21 | - Follow the rules or else you will get three warnings, ignoring or passing which you will be liable to face consequences including but limited to ban from the repository and the program the project is associated with. 22 | - Respect everyone, especially the maintainers and fellow contributors. 23 | - Follow the format and do not use offensive language. 24 | - Understand the project, ask questions but be polite. 25 | - No direct Pull Requests allowed. 26 | - We follow Frist Come First Serve policy. 27 | 28 | ## Know the Project 29 | twinTrim is a python package, planned to be published soon. This package helps you weed out duplicates of files across directories using efficient test cases and modules to make the process smooth. 30 | How to Contribute 31 | 32 | This section highlights two crucial parts of contribution, both the development and solution phase along with the searching-for-issues phase. Do read till the bottom of this for your own good. 33 | 34 | ## How to Contribute 35 | 1. **Fork the repository:** Since this is a python package, you will need to run this in order to understand and find bugs or think of suitable features. 36 | - Click the "Fork" button in the top-right corner of the repository page to create your own copy of the project. 37 | 38 | - Clone your fork locally using: 39 | ```bash 40 | git clone https://github.com/YOUR-USERNAME/PROJECT-NAME.git 41 | ``` 42 | 43 | 2. **Create a new branch for your feature or bug fix:** 44 | - It’s a good practice to create a separate branch for each contribution: 45 | ```bash 46 | git checkout -b feature-or-bugfix-name 47 | ``` 48 | 49 | 3. **Make your changes:** 50 | - Implement your changes or fixes. 51 | - Ensure your code adheres to the [Coding Standards](#coding-standards) described below. 52 | - Test thoroughly to make sure your contribution doesn’t break any existing features. 53 | 54 | 4. **Commit your changes:** 55 | - Write clear, concise commit messages as outlined in the [Commit Messages](#commit-messages) section. 56 | - Stage and commit your changes: 57 | ```bash 58 | git add . 59 | git commit -m "Brief explanation of your changes" 60 | ``` 61 | 62 | 5. **Push your branch to GitHub:** 63 | - Push the branch to your forked repository: 64 | ```bash 65 | git push origin feature-or-bugfix-name 66 | ``` 67 | 68 | 6. **Submit a pull request (PR):** 69 | - Go to the original repository and submit a pull request from your fork. 70 | - Fill out the PR template and include details about the changes you’ve made. 71 | 72 | ## Coding Standards 73 | 74 | - **Consistency:** Follow the existing code style of the project. 75 | - **Indentation:** Use 2/4 spaces for indentation (no tabs). 76 | - **Naming Conventions:** Use meaningful names for variables, functions, and methods. Follow `snake_case` for functions and variables, and `CamelCase` for classes. 77 | - **Comments:** Comment your code where necessary to explain complex logic or decisions. 78 | - **Testing:** Ensure your changes pass all tests and add new tests if required. 79 | 80 | ## Commit Messages 81 | 82 | Follow these guidelines for writing clear and helpful commit messages: 83 | 84 | 1. Use the present tense (e.g., "Add feature" not "Added feature"). 85 | 2. Use imperative mood (e.g., "Fix bug" not "Fixes bug"). 86 | 3. Keep messages concise but descriptive. 87 | 4. Reference issues or PRs if applicable (e.g., `Fixes #123`). 88 | 89 | ## Issue Reporting 90 | 91 | 1. **Search Existing Issues:** Before creating a new issue, check if it has already been reported. 92 | 2. **Provide Detailed Information:** Include steps to reproduce the bug, expected behavior, screenshots if necessary, and any relevant logs. 93 | 3. **Label the Issue:** Use appropriate labels to classify the issue (e.g., bug, enhancement, question). 94 | 95 | ## Pull Requests 96 | 97 | 1. **One PR, One Feature:** Ensure that each PR is focused on a single feature or bug fix. 98 | 2. **Description:** Clearly explain the purpose of the PR, the changes made, and any relevant issues that it addresses. 99 | 3. **Review and Tests:** Your PR will be reviewed by maintainers. Ensure that all automated tests pass before submitting your PR. 100 | 4. **Keep it Simple:** Try to keep your PR small and focused to make reviewing easier and faster. 101 | 102 | ## Code of Conduct 103 | 104 | This project adheres to a Contributor Covenant [Code of Conduct](link-to-code-of-conduct). By participating, you are expected to uphold this code and maintain a respectful and collaborative environment. 105 | 106 | --- 107 | 108 | Thank you for contributing! We look forward to reviewing your submissions and appreciate your efforts to improve the project. 109 | -------------------------------------------------------------------------------- /assets/javascripts/lunr/min/lunr.nl.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Lunr languages, `Dutch` language 3 | * https://github.com/MihaiValentin/lunr-languages 4 | * 5 | * Copyright 2014, Mihai Valentin 6 | * http://www.mozilla.org/MPL/ 7 | */ 8 | /*! 9 | * based on 10 | * Snowball JavaScript Library v0.3 11 | * http://code.google.com/p/urim/ 12 | * http://snowball.tartarus.org/ 13 | * 14 | * Copyright 2010, Oleg Mazko 15 | * http://www.mozilla.org/MPL/ 16 | */ 17 | 18 | !function(r,e){"function"==typeof define&&define.amd?define(e):"object"==typeof exports?module.exports=e():e()(r.lunr)}(this,function(){return function(r){if(void 0===r)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===r.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");r.nl=function(){this.pipeline.reset(),this.pipeline.add(r.nl.trimmer,r.nl.stopWordFilter,r.nl.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(r.nl.stemmer))},r.nl.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",r.nl.trimmer=r.trimmerSupport.generateTrimmer(r.nl.wordCharacters),r.Pipeline.registerFunction(r.nl.trimmer,"trimmer-nl"),r.nl.stemmer=function(){var e=r.stemmerSupport.Among,i=r.stemmerSupport.SnowballProgram,n=new function(){function r(){for(var r,e,i,o=C.cursor;;){if(C.bra=C.cursor,r=C.find_among(b,11))switch(C.ket=C.cursor,r){case 1:C.slice_from("a");continue;case 2:C.slice_from("e");continue;case 3:C.slice_from("i");continue;case 4:C.slice_from("o");continue;case 5:C.slice_from("u");continue;case 6:if(C.cursor>=C.limit)break;C.cursor++;continue}break}for(C.cursor=o,C.bra=o,C.eq_s(1,"y")?(C.ket=C.cursor,C.slice_from("Y")):C.cursor=o;;)if(e=C.cursor,C.in_grouping(q,97,232)){if(i=C.cursor,C.bra=i,C.eq_s(1,"i"))C.ket=C.cursor,C.in_grouping(q,97,232)&&(C.slice_from("I"),C.cursor=e);else if(C.cursor=i,C.eq_s(1,"y"))C.ket=C.cursor,C.slice_from("Y"),C.cursor=e;else if(n(e))break}else if(n(e))break}function n(r){return C.cursor=r,r>=C.limit||(C.cursor++,!1)}function o(){_=C.limit,d=_,t()||(_=C.cursor,_<3&&(_=3),t()||(d=C.cursor))}function t(){for(;!C.in_grouping(q,97,232);){if(C.cursor>=C.limit)return!0;C.cursor++}for(;!C.out_grouping(q,97,232);){if(C.cursor>=C.limit)return!0;C.cursor++}return!1}function s(){for(var r;;)if(C.bra=C.cursor,r=C.find_among(p,3))switch(C.ket=C.cursor,r){case 1:C.slice_from("y");break;case 2:C.slice_from("i");break;case 3:if(C.cursor>=C.limit)return;C.cursor++}}function u(){return _<=C.cursor}function c(){return d<=C.cursor}function a(){var r=C.limit-C.cursor;C.find_among_b(g,3)&&(C.cursor=C.limit-r,C.ket=C.cursor,C.cursor>C.limit_backward&&(C.cursor--,C.bra=C.cursor,C.slice_del()))}function l(){var r;w=!1,C.ket=C.cursor,C.eq_s_b(1,"e")&&(C.bra=C.cursor,u()&&(r=C.limit-C.cursor,C.out_grouping_b(q,97,232)&&(C.cursor=C.limit-r,C.slice_del(),w=!0,a())))}function m(){var r;u()&&(r=C.limit-C.cursor,C.out_grouping_b(q,97,232)&&(C.cursor=C.limit-r,C.eq_s_b(3,"gem")||(C.cursor=C.limit-r,C.slice_del(),a())))}function f(){var r,e,i,n,o,t,s=C.limit-C.cursor;if(C.ket=C.cursor,r=C.find_among_b(h,5))switch(C.bra=C.cursor,r){case 1:u()&&C.slice_from("heid");break;case 2:m();break;case 3:u()&&C.out_grouping_b(j,97,232)&&C.slice_del()}if(C.cursor=C.limit-s,l(),C.cursor=C.limit-s,C.ket=C.cursor,C.eq_s_b(4,"heid")&&(C.bra=C.cursor,c()&&(e=C.limit-C.cursor,C.eq_s_b(1,"c")||(C.cursor=C.limit-e,C.slice_del(),C.ket=C.cursor,C.eq_s_b(2,"en")&&(C.bra=C.cursor,m())))),C.cursor=C.limit-s,C.ket=C.cursor,r=C.find_among_b(k,6))switch(C.bra=C.cursor,r){case 1:if(c()){if(C.slice_del(),i=C.limit-C.cursor,C.ket=C.cursor,C.eq_s_b(2,"ig")&&(C.bra=C.cursor,c()&&(n=C.limit-C.cursor,!C.eq_s_b(1,"e")))){C.cursor=C.limit-n,C.slice_del();break}C.cursor=C.limit-i,a()}break;case 2:c()&&(o=C.limit-C.cursor,C.eq_s_b(1,"e")||(C.cursor=C.limit-o,C.slice_del()));break;case 3:c()&&(C.slice_del(),l());break;case 4:c()&&C.slice_del();break;case 5:c()&&w&&C.slice_del()}C.cursor=C.limit-s,C.out_grouping_b(z,73,232)&&(t=C.limit-C.cursor,C.find_among_b(v,4)&&C.out_grouping_b(q,97,232)&&(C.cursor=C.limit-t,C.ket=C.cursor,C.cursor>C.limit_backward&&(C.cursor--,C.bra=C.cursor,C.slice_del())))}var d,_,w,b=[new e("",-1,6),new e("á",0,1),new e("ä",0,1),new e("é",0,2),new e("ë",0,2),new e("í",0,3),new e("ï",0,3),new e("ó",0,4),new e("ö",0,4),new e("ú",0,5),new e("ü",0,5)],p=[new e("",-1,3),new e("I",0,2),new e("Y",0,1)],g=[new e("dd",-1,-1),new e("kk",-1,-1),new e("tt",-1,-1)],h=[new e("ene",-1,2),new e("se",-1,3),new e("en",-1,2),new e("heden",2,1),new e("s",-1,3)],k=[new e("end",-1,1),new e("ig",-1,2),new e("ing",-1,1),new e("lijk",-1,3),new e("baar",-1,4),new e("bar",-1,5)],v=[new e("aa",-1,-1),new e("ee",-1,-1),new e("oo",-1,-1),new e("uu",-1,-1)],q=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],z=[1,0,0,17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],j=[17,67,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],C=new i;this.setCurrent=function(r){C.setCurrent(r)},this.getCurrent=function(){return C.getCurrent()},this.stem=function(){var e=C.cursor;return r(),C.cursor=e,o(),C.limit_backward=e,C.cursor=C.limit,f(),C.cursor=C.limit_backward,s(),!0}};return function(r){return"function"==typeof r.update?r.update(function(r){return n.setCurrent(r),n.stem(),n.getCurrent()}):(n.setCurrent(r),n.stem(),n.getCurrent())}}(),r.Pipeline.registerFunction(r.nl.stemmer,"stemmer-nl"),r.nl.stopWordFilter=r.generateStopWordFilter(" aan al alles als altijd andere ben bij daar dan dat de der deze die dit doch doen door dus een eens en er ge geen geweest haar had heb hebben heeft hem het hier hij hoe hun iemand iets ik in is ja je kan kon kunnen maar me meer men met mij mijn moet na naar niet niets nog nu of om omdat onder ons ook op over reeds te tegen toch toen tot u uit uw van veel voor want waren was wat werd wezen wie wil worden wordt zal ze zelf zich zij zijn zo zonder zou".split(" ")),r.Pipeline.registerFunction(r.nl.stopWordFilter,"stopWordFilter-nl")}}); -------------------------------------------------------------------------------- /assets/javascripts/lunr/min/lunr.de.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Lunr languages, `German` language 3 | * https://github.com/MihaiValentin/lunr-languages 4 | * 5 | * Copyright 2014, Mihai Valentin 6 | * http://www.mozilla.org/MPL/ 7 | */ 8 | /*! 9 | * based on 10 | * Snowball JavaScript Library v0.3 11 | * http://code.google.com/p/urim/ 12 | * http://snowball.tartarus.org/ 13 | * 14 | * Copyright 2010, Oleg Mazko 15 | * http://www.mozilla.org/MPL/ 16 | */ 17 | 18 | !function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.de=function(){this.pipeline.reset(),this.pipeline.add(e.de.trimmer,e.de.stopWordFilter,e.de.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.de.stemmer))},e.de.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.de.trimmer=e.trimmerSupport.generateTrimmer(e.de.wordCharacters),e.Pipeline.registerFunction(e.de.trimmer,"trimmer-de"),e.de.stemmer=function(){var r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,i=new function(){function e(e,r,n){return!(!v.eq_s(1,e)||(v.ket=v.cursor,!v.in_grouping(p,97,252)))&&(v.slice_from(r),v.cursor=n,!0)}function i(){for(var r,n,i,s,t=v.cursor;;)if(r=v.cursor,v.bra=r,v.eq_s(1,"ß"))v.ket=v.cursor,v.slice_from("ss");else{if(r>=v.limit)break;v.cursor=r+1}for(v.cursor=t;;)for(n=v.cursor;;){if(i=v.cursor,v.in_grouping(p,97,252)){if(s=v.cursor,v.bra=s,e("u","U",i))break;if(v.cursor=s,e("y","Y",i))break}if(i>=v.limit)return void(v.cursor=n);v.cursor=i+1}}function s(){for(;!v.in_grouping(p,97,252);){if(v.cursor>=v.limit)return!0;v.cursor++}for(;!v.out_grouping(p,97,252);){if(v.cursor>=v.limit)return!0;v.cursor++}return!1}function t(){m=v.limit,l=m;var e=v.cursor+3;0<=e&&e<=v.limit&&(d=e,s()||(m=v.cursor,m=v.limit)return;v.cursor++}}}function c(){return m<=v.cursor}function u(){return l<=v.cursor}function a(){var e,r,n,i,s=v.limit-v.cursor;if(v.ket=v.cursor,(e=v.find_among_b(w,7))&&(v.bra=v.cursor,c()))switch(e){case 1:v.slice_del();break;case 2:v.slice_del(),v.ket=v.cursor,v.eq_s_b(1,"s")&&(v.bra=v.cursor,v.eq_s_b(3,"nis")&&v.slice_del());break;case 3:v.in_grouping_b(g,98,116)&&v.slice_del()}if(v.cursor=v.limit-s,v.ket=v.cursor,(e=v.find_among_b(f,4))&&(v.bra=v.cursor,c()))switch(e){case 1:v.slice_del();break;case 2:if(v.in_grouping_b(k,98,116)){var t=v.cursor-3;v.limit_backward<=t&&t<=v.limit&&(v.cursor=t,v.slice_del())}}if(v.cursor=v.limit-s,v.ket=v.cursor,(e=v.find_among_b(_,8))&&(v.bra=v.cursor,u()))switch(e){case 1:v.slice_del(),v.ket=v.cursor,v.eq_s_b(2,"ig")&&(v.bra=v.cursor,r=v.limit-v.cursor,v.eq_s_b(1,"e")||(v.cursor=v.limit-r,u()&&v.slice_del()));break;case 2:n=v.limit-v.cursor,v.eq_s_b(1,"e")||(v.cursor=v.limit-n,v.slice_del());break;case 3:if(v.slice_del(),v.ket=v.cursor,i=v.limit-v.cursor,!v.eq_s_b(2,"er")&&(v.cursor=v.limit-i,!v.eq_s_b(2,"en")))break;v.bra=v.cursor,c()&&v.slice_del();break;case 4:v.slice_del(),v.ket=v.cursor,e=v.find_among_b(b,2),e&&(v.bra=v.cursor,u()&&1==e&&v.slice_del())}}var d,l,m,h=[new r("",-1,6),new r("U",0,2),new r("Y",0,1),new r("ä",0,3),new r("ö",0,4),new r("ü",0,5)],w=[new r("e",-1,2),new r("em",-1,1),new r("en",-1,2),new r("ern",-1,1),new r("er",-1,1),new r("s",-1,3),new r("es",5,2)],f=[new r("en",-1,1),new r("er",-1,1),new r("st",-1,2),new r("est",2,1)],b=[new r("ig",-1,1),new r("lich",-1,1)],_=[new r("end",-1,1),new r("ig",-1,2),new r("ung",-1,1),new r("lich",-1,3),new r("isch",-1,2),new r("ik",-1,2),new r("heit",-1,3),new r("keit",-1,4)],p=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,8,0,32,8],g=[117,30,5],k=[117,30,4],v=new n;this.setCurrent=function(e){v.setCurrent(e)},this.getCurrent=function(){return v.getCurrent()},this.stem=function(){var e=v.cursor;return i(),v.cursor=e,t(),v.limit_backward=e,v.cursor=v.limit,a(),v.cursor=v.limit_backward,o(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}}(),e.Pipeline.registerFunction(e.de.stemmer,"stemmer-de"),e.de.stopWordFilter=e.generateStopWordFilter("aber alle allem allen aller alles als also am an ander andere anderem anderen anderer anderes anderm andern anderr anders auch auf aus bei bin bis bist da damit dann das dasselbe dazu daß dein deine deinem deinen deiner deines dem demselben den denn denselben der derer derselbe derselben des desselben dessen dich die dies diese dieselbe dieselben diesem diesen dieser dieses dir doch dort du durch ein eine einem einen einer eines einig einige einigem einigen einiger einiges einmal er es etwas euch euer eure eurem euren eurer eures für gegen gewesen hab habe haben hat hatte hatten hier hin hinter ich ihm ihn ihnen ihr ihre ihrem ihren ihrer ihres im in indem ins ist jede jedem jeden jeder jedes jene jenem jenen jener jenes jetzt kann kein keine keinem keinen keiner keines können könnte machen man manche manchem manchen mancher manches mein meine meinem meinen meiner meines mich mir mit muss musste nach nicht nichts noch nun nur ob oder ohne sehr sein seine seinem seinen seiner seines selbst sich sie sind so solche solchem solchen solcher solches soll sollte sondern sonst um und uns unse unsem unsen unser unses unter viel vom von vor war waren warst was weg weil weiter welche welchem welchen welcher welches wenn werde werden wie wieder will wir wird wirst wo wollen wollte während würde würden zu zum zur zwar zwischen über".split(" ")),e.Pipeline.registerFunction(e.de.stopWordFilter,"stopWordFilter-de")}}); -------------------------------------------------------------------------------- /twinTrim/flags.py: -------------------------------------------------------------------------------- 1 | import os 2 | import click 3 | import time 4 | import logging 5 | import inquirer 6 | from collections import defaultdict 7 | from twinTrim.utils import handle_and_remove, parse_size 8 | from twinTrim.flagController import handleAllFlag, find_duplicates 9 | from twinTrim.dataStructures.fileFilter import FileFilter 10 | 11 | # Setting up logging configuration 12 | logging.basicConfig( 13 | filename='duplicate_file_manager.log', 14 | level=logging.INFO, 15 | format='%(asctime)s - %(levelname)s - %(message)s' 16 | ) 17 | 18 | # Define color constants for consistency 19 | INFO_COLOR = 'blue' 20 | WARNING_COLOR = 'yellow' 21 | SUCCESS_COLOR = 'green' 22 | ERROR_COLOR = 'red' 23 | 24 | @click.command() 25 | @click.argument("directory", type=click.Path(exists=True)) 26 | @click.option("--all", is_flag=True, help="Delete duplicates automatically without asking.") 27 | @click.option("--min-size", default="0kb", type=str, help="Minimum file size in bytes.") 28 | @click.option("--max-size", default="1gb", type=str, help="Maximum file size in bytes.") 29 | @click.option("--file-type", default=".*", help="File type to include (e.g., .txt, .jpg).") 30 | @click.option("--exclude", multiple=True, help="Files to exclude by name.") 31 | @click.option("--label-color", default="yellow", type=str, help="Color of the label of progress bar.") 32 | @click.option("--bar-color", default='#aaaaaa', type=str, help="Color of the progress bar.") 33 | @click.option("--dry-run", is_flag=True, help="Simulate the deletion without actually removing any files.") 34 | def cli(directory, all, min_size, max_size, file_type, exclude, label_color, bar_color, dry_run): 35 | """Find and manage duplicate files in the specified DIRECTORY.""" 36 | 37 | # Initialize the FileFilter object 38 | file_filter = FileFilter() 39 | file_filter.setMinFileSize(parse_size(min_size)) 40 | file_filter.setMaxFileSize(parse_size(max_size)) 41 | file_filter.setFileType(file_type) 42 | for file_name in exclude: 43 | file_filter.addFileExclude(file_name) 44 | 45 | if all: 46 | logging.info("Deleting all duplicate files without asking.") 47 | handleAllFlag(directory, file_filter, label_color, bar_color, dry_run) 48 | return 49 | 50 | start_time = time.time() 51 | logging.info(f"Searching for duplicates in directory: {directory}") 52 | 53 | try: 54 | duplicates = find_duplicates(directory, file_filter, label_color, bar_color) 55 | except Exception as e: 56 | logging.error(f"Error finding duplicates: {str(e)}") 57 | click.echo(click.style("Error while finding duplicates. Check the log for details.", fg=ERROR_COLOR)) 58 | return 59 | 60 | if not duplicates: 61 | click.echo(click.style("No duplicate files found.", fg='green')) 62 | logging.info("No duplicate files found.") 63 | return 64 | 65 | click.echo(click.style(f"Found {len(duplicates)} sets of duplicate files:", fg=WARNING_COLOR)) 66 | logging.info(f"Found {len(duplicates)} set of duplicate files") 67 | 68 | duplicates_dict = defaultdict(list) 69 | for original, duplicate in duplicates: 70 | duplicates_dict[original].append(duplicate) 71 | 72 | # Process each set of duplicates 73 | for original, duplicates_list in duplicates_dict.items(): 74 | click.echo(click.style(f"Original file: \"{original}\"", fg=INFO_COLOR)) 75 | click.echo(click.style(f"Number of duplicate files found: {len(duplicates_list)}", fg=INFO_COLOR)) 76 | logging.info(f"Original file: \"{original}\" with {len(duplicates_list)} duplicates") 77 | 78 | click.echo(click.style("They are:", fg=INFO_COLOR)) 79 | 80 | # Create file options with additional information 81 | file_options = [ 82 | f"{idx + 1}) {duplicate} (Size: {os.path.getsize(duplicate)} bytes)" for idx, duplicate in enumerate(duplicates_list) 83 | ] 84 | 85 | if dry_run: 86 | # Log and display which files would be deleted in dry-run mode 87 | click.echo(click.style("Dry Run Mode: These files would be deleted:", fg='yellow')) 88 | for option in file_options: 89 | click.echo(click.style(option, fg='red')) 90 | logging.info(f"[Dry Run] Would delete files: {file_options}") 91 | else: 92 | # If not dry run, prompt for deletion 93 | answers = inquirer.prompt( 94 | [ 95 | inquirer.Checkbox( 96 | 'files', 97 | message="Select files to delete (Use space to select, enter to confirm, or ctrl+c to cancel, arrow key to navigate.)", 98 | choices=file_options, 99 | validate=lambda answer, current: len(answer) > 0 or "You must choose at least one file.", 100 | ), 101 | inquirer.Confirm( 102 | 'confirm', 103 | message="Are you sure you want to delete the selected files?", 104 | default=True 105 | ) 106 | ] 107 | ) 108 | 109 | if answers and answers['confirm']: 110 | selected_files = answers['files'] 111 | # Convert the selected options back to the original file paths 112 | files_to_delete = [duplicates_list[int(option.split(")")[0]) - 1] for option in selected_files] 113 | 114 | for file_path in files_to_delete: 115 | handle_and_remove(file_path) 116 | else: 117 | click.echo(click.style("File deletion canceled.", fg=WARNING_COLOR)) 118 | 119 | end_time = time.time() 120 | time_taken = end_time - start_time 121 | click.echo(click.style(f"Time taken: {time_taken:.2f} seconds.", fg=SUCCESS_COLOR)) 122 | logging.info(f"Total time taken: {time_taken:.2f} seconds.") 123 | -------------------------------------------------------------------------------- /assets/javascripts/lunr/min/lunr.du.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Lunr languages, `Dutch` language 3 | * https://github.com/MihaiValentin/lunr-languages 4 | * 5 | * Copyright 2014, Mihai Valentin 6 | * http://www.mozilla.org/MPL/ 7 | */ 8 | /*! 9 | * based on 10 | * Snowball JavaScript Library v0.3 11 | * http://code.google.com/p/urim/ 12 | * http://snowball.tartarus.org/ 13 | * 14 | * Copyright 2010, Oleg Mazko 15 | * http://www.mozilla.org/MPL/ 16 | */ 17 | 18 | !function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");console.warn('[Lunr Languages] Please use the "nl" instead of the "du". The "nl" code is the standard code for Dutch language, and "du" will be removed in the next major versions.'),e.du=function(){this.pipeline.reset(),this.pipeline.add(e.du.trimmer,e.du.stopWordFilter,e.du.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.du.stemmer))},e.du.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.du.trimmer=e.trimmerSupport.generateTrimmer(e.du.wordCharacters),e.Pipeline.registerFunction(e.du.trimmer,"trimmer-du"),e.du.stemmer=function(){var r=e.stemmerSupport.Among,i=e.stemmerSupport.SnowballProgram,n=new function(){function e(){for(var e,r,i,o=C.cursor;;){if(C.bra=C.cursor,e=C.find_among(b,11))switch(C.ket=C.cursor,e){case 1:C.slice_from("a");continue;case 2:C.slice_from("e");continue;case 3:C.slice_from("i");continue;case 4:C.slice_from("o");continue;case 5:C.slice_from("u");continue;case 6:if(C.cursor>=C.limit)break;C.cursor++;continue}break}for(C.cursor=o,C.bra=o,C.eq_s(1,"y")?(C.ket=C.cursor,C.slice_from("Y")):C.cursor=o;;)if(r=C.cursor,C.in_grouping(q,97,232)){if(i=C.cursor,C.bra=i,C.eq_s(1,"i"))C.ket=C.cursor,C.in_grouping(q,97,232)&&(C.slice_from("I"),C.cursor=r);else if(C.cursor=i,C.eq_s(1,"y"))C.ket=C.cursor,C.slice_from("Y"),C.cursor=r;else if(n(r))break}else if(n(r))break}function n(e){return C.cursor=e,e>=C.limit||(C.cursor++,!1)}function o(){_=C.limit,f=_,t()||(_=C.cursor,_<3&&(_=3),t()||(f=C.cursor))}function t(){for(;!C.in_grouping(q,97,232);){if(C.cursor>=C.limit)return!0;C.cursor++}for(;!C.out_grouping(q,97,232);){if(C.cursor>=C.limit)return!0;C.cursor++}return!1}function s(){for(var e;;)if(C.bra=C.cursor,e=C.find_among(p,3))switch(C.ket=C.cursor,e){case 1:C.slice_from("y");break;case 2:C.slice_from("i");break;case 3:if(C.cursor>=C.limit)return;C.cursor++}}function u(){return _<=C.cursor}function c(){return f<=C.cursor}function a(){var e=C.limit-C.cursor;C.find_among_b(g,3)&&(C.cursor=C.limit-e,C.ket=C.cursor,C.cursor>C.limit_backward&&(C.cursor--,C.bra=C.cursor,C.slice_del()))}function l(){var e;w=!1,C.ket=C.cursor,C.eq_s_b(1,"e")&&(C.bra=C.cursor,u()&&(e=C.limit-C.cursor,C.out_grouping_b(q,97,232)&&(C.cursor=C.limit-e,C.slice_del(),w=!0,a())))}function m(){var e;u()&&(e=C.limit-C.cursor,C.out_grouping_b(q,97,232)&&(C.cursor=C.limit-e,C.eq_s_b(3,"gem")||(C.cursor=C.limit-e,C.slice_del(),a())))}function d(){var e,r,i,n,o,t,s=C.limit-C.cursor;if(C.ket=C.cursor,e=C.find_among_b(h,5))switch(C.bra=C.cursor,e){case 1:u()&&C.slice_from("heid");break;case 2:m();break;case 3:u()&&C.out_grouping_b(z,97,232)&&C.slice_del()}if(C.cursor=C.limit-s,l(),C.cursor=C.limit-s,C.ket=C.cursor,C.eq_s_b(4,"heid")&&(C.bra=C.cursor,c()&&(r=C.limit-C.cursor,C.eq_s_b(1,"c")||(C.cursor=C.limit-r,C.slice_del(),C.ket=C.cursor,C.eq_s_b(2,"en")&&(C.bra=C.cursor,m())))),C.cursor=C.limit-s,C.ket=C.cursor,e=C.find_among_b(k,6))switch(C.bra=C.cursor,e){case 1:if(c()){if(C.slice_del(),i=C.limit-C.cursor,C.ket=C.cursor,C.eq_s_b(2,"ig")&&(C.bra=C.cursor,c()&&(n=C.limit-C.cursor,!C.eq_s_b(1,"e")))){C.cursor=C.limit-n,C.slice_del();break}C.cursor=C.limit-i,a()}break;case 2:c()&&(o=C.limit-C.cursor,C.eq_s_b(1,"e")||(C.cursor=C.limit-o,C.slice_del()));break;case 3:c()&&(C.slice_del(),l());break;case 4:c()&&C.slice_del();break;case 5:c()&&w&&C.slice_del()}C.cursor=C.limit-s,C.out_grouping_b(j,73,232)&&(t=C.limit-C.cursor,C.find_among_b(v,4)&&C.out_grouping_b(q,97,232)&&(C.cursor=C.limit-t,C.ket=C.cursor,C.cursor>C.limit_backward&&(C.cursor--,C.bra=C.cursor,C.slice_del())))}var f,_,w,b=[new r("",-1,6),new r("á",0,1),new r("ä",0,1),new r("é",0,2),new r("ë",0,2),new r("í",0,3),new r("ï",0,3),new r("ó",0,4),new r("ö",0,4),new r("ú",0,5),new r("ü",0,5)],p=[new r("",-1,3),new r("I",0,2),new r("Y",0,1)],g=[new r("dd",-1,-1),new r("kk",-1,-1),new r("tt",-1,-1)],h=[new r("ene",-1,2),new r("se",-1,3),new r("en",-1,2),new r("heden",2,1),new r("s",-1,3)],k=[new r("end",-1,1),new r("ig",-1,2),new r("ing",-1,1),new r("lijk",-1,3),new r("baar",-1,4),new r("bar",-1,5)],v=[new r("aa",-1,-1),new r("ee",-1,-1),new r("oo",-1,-1),new r("uu",-1,-1)],q=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],j=[1,0,0,17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],z=[17,67,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],C=new i;this.setCurrent=function(e){C.setCurrent(e)},this.getCurrent=function(){return C.getCurrent()},this.stem=function(){var r=C.cursor;return e(),C.cursor=r,o(),C.limit_backward=r,C.cursor=C.limit,d(),C.cursor=C.limit_backward,s(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}}(),e.Pipeline.registerFunction(e.du.stemmer,"stemmer-du"),e.du.stopWordFilter=e.generateStopWordFilter(" aan al alles als altijd andere ben bij daar dan dat de der deze die dit doch doen door dus een eens en er ge geen geweest haar had heb hebben heeft hem het hier hij hoe hun iemand iets ik in is ja je kan kon kunnen maar me meer men met mij mijn moet na naar niet niets nog nu of om omdat onder ons ook op over reeds te tegen toch toen tot u uit uw van veel voor want waren was wat werd wezen wie wil worden wordt zal ze zelf zich zij zijn zo zonder zou".split(" ")),e.Pipeline.registerFunction(e.du.stopWordFilter,"stopWordFilter-du")}}); -------------------------------------------------------------------------------- /tests/test_allFileMetadata.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import pytest 4 | import threading 5 | import tempfile 6 | from unittest.mock import patch 7 | from twinTrim.dataStructures.allFileMetadata import AllFileMetadata 8 | from twinTrim.dataStructures.allFileMetadata import add_or_update_file, store, store_lock 9 | from twinTrim.utils import get_file_hash 10 | 11 | # Fixtures 12 | @pytest.fixture 13 | def temp_file(tmp_path): 14 | """Fixture to create a temporary file for testing.""" 15 | temp_file_path = tmp_path / "test_file.txt" 16 | with open(temp_file_path, "w") as f: 17 | f.write("Sample content") 18 | return str(temp_file_path) 19 | 20 | @pytest.fixture(autouse=True) 21 | def reset_store(): 22 | """Fixture to reset the store before each test.""" 23 | store.clear() 24 | 25 | # Mocking the get_file_hash function 26 | def mock_get_file_hash(file_path): 27 | return f"hash_{file_path}" 28 | 29 | @pytest.fixture(autouse=True) 30 | def mock_file_hash_function(monkeypatch): 31 | """Mock get_file_hash to provide predictable outputs for tests.""" 32 | monkeypatch.setattr("twinTrim.dataStructures.allFileMetadata.get_file_hash", mock_get_file_hash) 33 | 34 | # Test cases 35 | def test_add_new_file_metadata(temp_file): 36 | store.clear() 37 | add_or_update_file(temp_file) 38 | file_hash = mock_get_file_hash(temp_file) 39 | 40 | assert file_hash in store, "File hash should be in the store after adding" 41 | assert store[file_hash].filepath == temp_file, "Stored file path should match the added file" 42 | 43 | 44 | def test_add_or_update_file_concurrently(): 45 | file_paths = ["file_1", "file_2", "file_3"] 46 | 47 | def worker(file_path): 48 | add_or_update_file(file_path) 49 | 50 | threads = [threading.Thread(target=worker, args=(file_path,)) for file_path in file_paths] 51 | 52 | for thread in threads: 53 | thread.start() 54 | for thread in threads: 55 | thread.join() 56 | 57 | assert len(store) == 3 58 | assert all(f"hash_{file_path}" in store for file_path in file_paths) 59 | for file_path in file_paths: 60 | assert store[f"hash_{file_path}"].filepath == file_path 61 | 62 | def test_add_or_update_file_with_duplicates_concurrently(): 63 | file_paths = ["file_1", "file_1", "file_1"] 64 | 65 | def worker(file_path): 66 | add_or_update_file(file_path) 67 | 68 | threads = [threading.Thread(target=worker, args=(file_path,)) for file_path in file_paths] 69 | 70 | for thread in threads: 71 | thread.start() 72 | for thread in threads: 73 | thread.join() 74 | 75 | assert len(store) == 1 76 | assert "hash_file_1" in store 77 | assert store["hash_file_1"].filepath == "file_1" 78 | 79 | def test_add_or_update_file_mixed_concurrently(): 80 | file_paths = ["file_1", "file_1", "file_2", "file_3", "file_3"] 81 | 82 | def worker(file_path): 83 | add_or_update_file(file_path) 84 | 85 | threads = [threading.Thread(target=worker, args=(file_path,)) for file_path in file_paths] 86 | 87 | for thread in threads: 88 | thread.start() 89 | for thread in threads: 90 | thread.join() 91 | 92 | assert len(store) == 3 93 | assert "hash_file_1" in store 94 | assert "hash_file_2" in store 95 | assert "hash_file_3" in store 96 | assert store["hash_file_1"].filepath == "file_1" 97 | assert store["hash_file_2"].filepath == "file_2" 98 | assert store["hash_file_3"].filepath == "file_3" 99 | 100 | def test_add_or_update_file_when_file_doesnt_exist(mocker): 101 | mockfile = "mockfile.txt" 102 | mocker.patch("twinTrim.dataStructures.allFileMetadata.os.path.exists", return_value=False) 103 | mocker.patch("twinTrim.dataStructures.allFileMetadata.get_file_hash", side_effect=FileNotFoundError) 104 | 105 | with pytest.raises(FileNotFoundError): 106 | add_or_update_file(mockfile) 107 | 108 | @pytest.fixture 109 | def setup_files(): 110 | temp_file1 = tempfile.NamedTemporaryFile(delete=False) 111 | temp_file2 = tempfile.NamedTemporaryFile(delete=False) 112 | 113 | temp_file1.write(b'Initial content for file 1.') 114 | temp_file1.close() 115 | 116 | temp_file2.write(b'Initial content for file 2.') 117 | temp_file2.close() 118 | 119 | yield temp_file1.name, temp_file2.name 120 | 121 | if os.path.exists(temp_file1.name): 122 | os.remove(temp_file1.name) 123 | if os.path.exists(temp_file2.name): 124 | os.remove(temp_file2.name) 125 | 126 | def test_compare_and_replace_removes_correct_file(setup_files): 127 | file1, file2 = setup_files 128 | 129 | with patch('os.path.getmtime') as mock_getmtime, \ 130 | patch('twinTrim.dataStructures.allFileMetadata.handle_and_remove') as mock_remove: 131 | 132 | mock_getmtime.side_effect = lambda path: 1000 if path == file1 else 2000 133 | 134 | metadata1 = AllFileMetadata(file1) 135 | metadata2 = AllFileMetadata(file2) 136 | 137 | metadata1.compare_and_replace(metadata2) 138 | 139 | mock_remove.assert_called_once_with(file1) 140 | 141 | def test_compare_and_replace_no_file_removed_when_file_missing(setup_files): 142 | file1, _ = setup_files 143 | metadata1 = AllFileMetadata(file1) 144 | nonexistent_file = 'nonexistent_file.txt' 145 | metadata2 = AllFileMetadata(nonexistent_file) 146 | 147 | with patch('twinTrim.utils.handle_and_remove') as mock_remove: 148 | metadata1.compare_and_replace(metadata2) 149 | mock_remove.assert_not_called() 150 | 151 | def test_compare_and_replace_no_file_removed_when_both_files_missing(setup_files): 152 | file1, _ = setup_files 153 | metadata1 = AllFileMetadata(file1) 154 | nonexistent_file1 = 'nonexistent_file1.txt' 155 | nonexistent_file2 = 'nonexistent_file2.txt' 156 | metadata2 = AllFileMetadata(nonexistent_file1) 157 | metadata3 = AllFileMetadata(nonexistent_file2) 158 | 159 | with patch('twinTrim.utils.handle_and_remove') as mock_remove: 160 | metadata1.compare_and_replace(metadata2) 161 | mock_remove.assert_not_called() 162 | 163 | with patch('twinTrim.utils.handle_and_remove') as mock_remove: 164 | metadata1.compare_and_replace(metadata3) 165 | mock_remove.assert_not_called() 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TwinTrim 2 | 3 | TwinTrim is a powerful and efficient tool designed to find and manage duplicate files across directories. It provides a streamlined way to scan files, identify duplicates based on their content, and remove them automatically or with user guidance, helping you save storage space and keep your file system organized. 4 | 5 | ## Table of Contents 6 | 7 | - [Features](#features) 8 | - [How It Works](#how-it-works) 9 | - [Core Components](#core-components) 10 | - [Key Functions](#key-functions) 11 | - [Usage](#usage) 12 | - [Command Line Interface](#command-line-interface) 13 | - [Options](#options) 14 | - [Examples](#examples) 15 | - [Dependencies](#dependencies) 16 | - [Installation](#installation) 17 | - [From PyPI](#from-pypi) 18 | - [Setup for Development](#setup-for-development) 19 | - [Contributing](#contributing) 20 | - [Code of Conduct](#code-of-conduct) 21 | - [License](#license) 22 | 23 | ## Features 24 | 25 | - **Duplicate Detection**: Scans directories to detect duplicate files based on file content rather than just filenames. 26 | - **Automatic or Manual Removal**: Choose to handle duplicates automatically using the `--all` flag or manually select which files to delete. 27 | - **Customizable Filters**: Set filters for minimum and maximum file sizes, file types, and specific filenames to exclude from the scan. 28 | - **Multi-Threaded Processing**: Utilizes multi-threading to quickly scan and process large numbers of files concurrently. 29 | - **Deadlock Prevention**: Implements locks to prevent deadlocks during multi-threaded operations, ensuring smooth and safe execution. 30 | - **User-Friendly Interface**: Offers clear prompts and feedback via the command line, making the process straightforward and interactive. 31 | 32 | ## How It Works 33 | 34 | ### Core Components 35 | 36 | 1. **File Metadata Management**: 37 | - Uses `AllFileMetadata` and `FileMetadata` classes to manage file information, such as modification time and file paths. 38 | - Maintains metadata in two dictionaries (`store` and `normalStore`) for handling different levels of duplicate management. 39 | 40 | 2. **File Hashing**: 41 | - Generates a unique hash for each file using MD5 to identify duplicates by content. 42 | 43 | 3. **File Filtering**: 44 | - The `FileFilter` class provides functionality to filter files based on size, type, and exclusions. 45 | 46 | 4. **Duplicate Handling**: 47 | - Duplicate files are identified by comparing their hashes. 48 | - Based on file modification time, the latest file is retained, and older duplicates are removed. 49 | 50 | 5. **Deadlock Prevention**: 51 | - Uses locks within multi-threaded processes to ensure that resources are accessed safely, preventing deadlocks that could otherwise halt execution. 52 | 53 | ### Key Functions 54 | 55 | - **add_or_update_file**: Adds new files to the metadata store or updates existing entries if a duplicate is detected. 56 | - **add_or_update_normal_file**: Similar to `add_or_update_file` but manages duplicates in a separate store. 57 | - **handleAllFlag**: Handles duplicate removal automatically without user intervention. 58 | - **find_duplicates**: Finds duplicate files in the specified directory and prepares them for user review or automatic handling. 59 | 60 | ## Usage 61 | 62 | ### Command Line Interface 63 | 64 | Run the script using the following command: 65 | ```bash 66 | python -m twinTrim.main [OPTIONS] 67 | ``` 68 | 69 | ### Options 70 | 71 | - `--all`: Automatically delete duplicates without asking for confirmation. 72 | - `--min-size`: Specify the minimum file size to include in the scan (e.g., `10kb`). 73 | - `--max-size`: Specify the maximum file size to include in the scan (e.g., `1gb`). 74 | - `--file-type`: Specify the file type to include (e.g., `.txt`, `.jpg`). 75 | - `--exclude`: Exclude specific files by name. 76 | - `--label-color`: Set the font color of the output label of the progress bar. 77 | - `--bar-color`: Set the color of the progress bar. 78 | 79 | ### Examples 80 | 81 | 1. **Automatic Duplicate Removal**: 82 | ```bash 83 | python -m twinTrim.main /path/to/directory --all 84 | ``` 85 | 86 | 2. **Manual Review and Removal**: 87 | ```bash 88 | python -m twinTrim.main /path/to/directory 89 | ``` 90 | 91 | 3. **Filtered Scan by File Size and Type**: 92 | ```bash 93 | python -m twinTrim.main /path/to/directory --min-size "50kb" --max-size "500mb" --file-type "txt" 94 | ``` 95 | 96 | ## Dependencies 97 | 98 | - Python 3.6+ 99 | - `click` for command-line interaction 100 | - `tqdm` for progress bars 101 | - `concurrent.futures` for multi-threaded processing 102 | - `beaupy` for interactive selection 103 | 104 | ## Installation 105 | 106 | ### From PyPI 107 | 108 | Install the latest release from PyPI using pip: 109 | 110 | ```bash 111 | pip install twinTrim 112 | ``` 113 | 114 | You can find the project on [PyPI](https://pypi.org/project/twinTrim/). 115 | 116 | ### Setup for Development 117 | 118 | Clone the repository and install the required dependencies using Poetry: 119 | 120 | ```bash 121 | git clone https://github.com/Kota-Karthik/twinTrim.git 122 | cd twinTrim 123 | poetry install 124 | poetry shell 125 | ``` 126 | 127 | If you haven't installed Poetry yet, you can do so by following the instructions on the [Poetry website](https://python-poetry.org/docs/#installation). 128 | 129 | ## Contributing 130 | 131 | Contributions are welcome! Whether you have ideas for improving the internal workings of TwinTrim, such as optimizing performance or refining algorithms, or you want to enhance the user interface of the CLI tool for a better user experience, your input is valuable. Please fork the repository and submit a pull request with your improvements or new features. 132 | 133 | Please refer to the [CONTRIBUTION_GUIDELINES.md](./CONTRIBUTION_GUIDELINES.md) for guidelines on how to contribute. 134 | 135 | ## Code of Conduct 136 | 137 | We value and prioritize creating a positive, welcoming, and inclusive environment for everyone involved in the **TwinTrim** project. We encourage all participants to be respectful, collaborative, and supportive of each other. 138 | 139 | Please take a moment to review our [Code of Conduct](./CODE_OF_CONDUCT.md) to understand the expected behavior when contributing to the project. 140 | 141 | By participating in **TwinTrim**, you agree to abide by these guidelines and help us maintain a healthy, harassment-free community. 142 | 143 | ## License 144 | 145 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. -------------------------------------------------------------------------------- /tests/test_fileMetadata.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from unittest.mock import patch, mock_open 3 | from twinTrim.dataStructures.fileMetadata import ( 4 | add_or_update_normal_file, 5 | normalStore, 6 | FileMetadata, 7 | normalStore_lock, 8 | ) 9 | import threading 10 | import os 11 | import tempfile 12 | 13 | # Mock the get_file_hash function to return a predictable hash 14 | def mock_get_file_hash(file_path): 15 | return f"hash_{file_path}" 16 | 17 | # Automatically replace the get_file_hash function with the mock in all tests 18 | @pytest.fixture(autouse=True) 19 | def mock_get_file_hash_func(monkeypatch): 20 | monkeypatch.setattr( 21 | "twinTrim.dataStructures.fileMetadata.get_file_hash", mock_get_file_hash 22 | ) 23 | 24 | # Automatically reset the normalStore before each test 25 | @pytest.fixture(autouse=True) 26 | def reset_normal_store(): 27 | normalStore.clear() 28 | 29 | # Test inserting a new file 30 | def test_insert_new_file(): 31 | with tempfile.NamedTemporaryFile(delete=False) as temp_file: 32 | file_path = temp_file.name 33 | metadata = FileMetadata([]) 34 | metadata.insert_file(file_path) 35 | 36 | assert len(metadata.filepaths) == 1 37 | assert metadata.filepaths[0] == file_path 38 | 39 | # Test inserting a duplicate file 40 | def test_insert_duplicate_file(): 41 | with tempfile.NamedTemporaryFile(delete=False) as temp_file: 42 | file_path = temp_file.name 43 | metadata = FileMetadata([file_path]) 44 | metadata.insert_file(file_path) 45 | 46 | assert len(metadata.filepaths) == 1 47 | assert metadata.filepaths[0] == file_path 48 | 49 | # Test inserting multiple files 50 | def test_insert_multiple_files(): 51 | with tempfile.NamedTemporaryFile(delete=False) as temp_file1, tempfile.NamedTemporaryFile(delete=False) as temp_file2: 52 | file_path1 = temp_file1.name 53 | file_path2 = temp_file2.name 54 | 55 | metadata = FileMetadata([file_path1]) 56 | metadata.insert_file(file_path2) 57 | 58 | assert len(metadata.filepaths) == 2 59 | assert file_path1 in metadata.filepaths 60 | assert file_path2 in metadata.filepaths 61 | 62 | # Test adding or updating a new file 63 | def test_add_or_update_new_file(): 64 | with tempfile.NamedTemporaryFile(delete=False) as temp_file: 65 | file_path = temp_file.name 66 | expected_file_hash = "hash_" + file_path 67 | 68 | with patch("builtins.open", mock_open(read_data=b"some binary content")): 69 | normalStore.clear() 70 | add_or_update_normal_file(file_path) 71 | 72 | assert expected_file_hash in normalStore 73 | assert normalStore[expected_file_hash].filepaths == [file_path] 74 | 75 | # Test adding a new file to an empty store 76 | def test_add_new_file_to_empty_store(): 77 | with tempfile.NamedTemporaryFile(delete=False) as temp_file: 78 | file_path = temp_file.name 79 | expected_file_hash = "hash_" + file_path 80 | 81 | with patch("builtins.open", mock_open(read_data=b"some binary content")): 82 | normalStore.clear() 83 | add_or_update_normal_file(file_path) 84 | 85 | assert len(normalStore) == 1 86 | assert expected_file_hash in normalStore 87 | assert normalStore[expected_file_hash].filepaths == [file_path] 88 | 89 | 90 | # Test no duplicate insertion in metadata 91 | def test_no_duplicate_insertion_in_metadata(): 92 | with tempfile.NamedTemporaryFile(delete=False) as temp_file: 93 | file_path = temp_file.name 94 | expected_file_hash = "hash_" + file_path 95 | 96 | with patch("builtins.open", mock_open(read_data=b"some binary content")): 97 | normalStore.clear() 98 | add_or_update_normal_file(file_path) 99 | add_or_update_normal_file(file_path) 100 | 101 | assert len(normalStore) == 1 102 | assert expected_file_hash in normalStore 103 | assert normalStore[expected_file_hash].filepaths == [file_path] 104 | 105 | # Test adding multiple files with different hashes 106 | def test_multiple_files_different_hashes(): 107 | with tempfile.NamedTemporaryFile(delete=False) as temp_file1, tempfile.NamedTemporaryFile(delete=False) as temp_file2, tempfile.NamedTemporaryFile(delete=False) as temp_file3: 108 | file_path1 = temp_file1.name 109 | file_path2 = temp_file2.name 110 | file_path3 = temp_file3.name 111 | 112 | with patch("builtins.open", mock_open(read_data=b"some binary content")): 113 | normalStore.clear() 114 | add_or_update_normal_file(file_path1) 115 | add_or_update_normal_file(file_path2) 116 | add_or_update_normal_file(file_path3) 117 | 118 | assert len(normalStore) == 3 119 | assert "hash_" + file_path1 in normalStore and normalStore["hash_" + file_path1].filepaths == [file_path1] 120 | assert "hash_" + file_path2 in normalStore and normalStore["hash_" + file_path2].filepaths == [file_path2] 121 | assert "hash_" + file_path3 in normalStore and normalStore["hash_" + file_path3].filepaths == [file_path3] 122 | 123 | # Test concurrent add or update 124 | def test_add_or_update_normal_file_concurrently(): 125 | file_paths = [f"file_{i}.txt" for i in range(10)] 126 | 127 | def worker(file_path): 128 | add_or_update_normal_file(file_path) 129 | 130 | threads = [threading.Thread(target=worker, args=(fp,)) for fp in file_paths] 131 | 132 | for thread in threads: 133 | thread.start() 134 | 135 | for thread in threads: 136 | thread.join() 137 | 138 | assert len(normalStore) == 10 139 | for file_path in file_paths: 140 | file_hash = "hash_" + file_path 141 | assert file_hash in normalStore 142 | assert normalStore[file_hash].filepaths == [file_path] 143 | 144 | # Test adding duplicates concurrently 145 | def test_add_or_update_normal_file_with_duplicates_concurrently(): 146 | file_path = "duplicate_file.txt" 147 | num_threads = 5 148 | 149 | def worker(): 150 | add_or_update_normal_file(file_path) 151 | 152 | threads = [threading.Thread(target=worker) for _ in range(num_threads)] 153 | 154 | for thread in threads: 155 | thread.start() 156 | 157 | for thread in threads: 158 | thread.join() 159 | 160 | file_hash = "hash_" + file_path 161 | assert len(normalStore) == 1 162 | assert file_hash in normalStore 163 | assert normalStore[file_hash].filepaths == [file_path] 164 | 165 | -------------------------------------------------------------------------------- /tests/test_flagController.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pytest 3 | from unittest.mock import patch, MagicMock 4 | from twinTrim.flagController import handleAllFlag, find_duplicates 5 | from twinTrim.dataStructures.fileFilter import FileFilter 6 | from twinTrim.dataStructures.fileMetadata import normalStore 7 | 8 | # Tests for handleAllFlag 9 | @patch('os.walk') 10 | @patch('twinTrim.flagController.add_or_update_file') 11 | @patch('twinTrim.flagController.progress_bar_func') 12 | def test_handle_all_flag_no_files(mock_progress_bar, mock_add_or_update_file, mock_os_walk): 13 | """Test handleAllFlag when directory has no files.""" 14 | # Mock the directory structure to return no files 15 | mock_os_walk.return_value = [] 16 | 17 | # Mock the progress bar to avoid actual printing 18 | mock_progress_bar.return_value.__enter__.return_value = MagicMock() 19 | 20 | # Call the function with mock parameters 21 | handleAllFlag('test_directory', MagicMock(), 'yellow', 'white') 22 | 23 | # Assertions 24 | mock_add_or_update_file.assert_not_called() # Since there are no files 25 | mock_progress_bar.assert_called_once() # Progress bar should still be created 26 | 27 | 28 | @patch('os.walk') 29 | @patch('twinTrim.flagController.add_or_update_file') 30 | @patch('twinTrim.flagController.progress_bar_func') 31 | def test_handle_all_flag_filtered_files(mock_progress_bar, mock_add_or_update_file, mock_os_walk): 32 | """Test handleAllFlag with files that pass the filter.""" 33 | # Simulate a directory with 3 files 34 | mock_os_walk.return_value = [ 35 | (os.path.normpath('/path/to/files'), [], ['file1.txt', 'file2.txt', 'file3.txt']) 36 | ] 37 | 38 | # Mock file_filter to filter out some files 39 | mock_file_filter = MagicMock() 40 | mock_file_filter.filter_files.side_effect = lambda x: 'file2.txt' in x # Only file2.txt passes the filter 41 | 42 | # Mock the progress bar 43 | mock_progress_bar.return_value.__enter__.return_value = MagicMock() 44 | 45 | # Call the function 46 | handleAllFlag(os.path.normpath('/path/to/files'), mock_file_filter, 'yellow', 'white') 47 | 48 | # Normalizing the expected file path to handle platform-specific slashes (Windows != \\) 49 | expected_path = os.path.normpath('/path/to/files/file2.txt') 50 | 51 | # Assertions 52 | mock_add_or_update_file.assert_called_once_with(expected_path) # Only file2.txt should be processed 53 | mock_progress_bar.assert_called_once() # Progress bar should still be created 54 | 55 | 56 | @patch('os.walk') 57 | @patch('twinTrim.flagController.add_or_update_file') 58 | @patch('twinTrim.flagController.progress_bar_func') 59 | def test_handle_all_flag_success(mock_progress_bar, mock_add_or_update_file, mock_os_walk): 60 | """Test handleAllFlag successful execution.""" 61 | # Simulate a directory with 3 files 62 | mock_os_walk.return_value = [ 63 | (os.path.normpath('/path/to/files'), [], ['file1.txt', 'file2.txt', 'file3.txt']) 64 | ] 65 | 66 | # Mock file_filter to allow all files 67 | mock_file_filter = MagicMock() 68 | mock_file_filter.filter_files.return_value = True 69 | 70 | # Mock the progress bar 71 | mock_progress_bar.return_value.__enter__.return_value = MagicMock() 72 | 73 | # Call the function 74 | handleAllFlag(os.path.normpath('/path/to/files'), mock_file_filter, 'yellow', 'white') 75 | 76 | # Assertions 77 | assert mock_add_or_update_file.call_count == 3 # All 3 files should be processed 78 | mock_progress_bar.assert_called_once() # Progress bar should still be created 79 | 80 | 81 | # Tests for find_duplicates 82 | @patch('os.walk') 83 | @patch('twinTrim.flagController.add_or_update_normal_file') 84 | @patch('twinTrim.flagController.progress_bar_func') 85 | def test_find_duplicates_with_duplicates(mock_progress_bar, mock_add_or_update_normal_file, mock_os_walk): 86 | """Test finding duplicates in a directory with multiple files.""" 87 | # Simulate a directory with duplicate files 88 | mock_os_walk.return_value = [ 89 | (os.path.normpath('/path/to/files'), [], ['file1.txt', 'file1_copy.txt', 'file2.txt']) 90 | ] 91 | 92 | # Mock file filter to allow all files 93 | mock_file_filter = MagicMock() 94 | mock_file_filter.filter_files.return_value = True 95 | 96 | # Mock the progress bar 97 | mock_progress_bar.return_value.__enter__.return_value = MagicMock() 98 | 99 | # Mock the normalStore to contain duplicates 100 | normalStore.clear() 101 | normalStore['file1_hash'] = MagicMock(filepaths=[os.path.normpath('/path/to/files/file1.txt'), os.path.normpath('/path/to/files/file1_copy.txt')]) 102 | normalStore['file2_hash'] = MagicMock(filepaths=[os.path.normpath('/path/to/files/file2.txt')]) 103 | 104 | # Call the function 105 | duplicates = find_duplicates(os.path.normpath('/path/to/files'), mock_file_filter, 'yellow', 'white') 106 | 107 | # Assertions 108 | assert len(duplicates) == 1 # Should find one duplicate pair 109 | assert duplicates[0][0] == os.path.normpath('/path/to/files/file1.txt') 110 | assert duplicates[0][1] == os.path.normpath('/path/to/files/file1_copy.txt') 111 | 112 | 113 | @patch('os.walk') 114 | @patch('twinTrim.flagController.progress_bar_func') 115 | def test_find_duplicates_no_files(mock_progress_bar, mock_os_walk): 116 | """Test handling of a directory with no files.""" 117 | # Mock the directory structure to return no files 118 | mock_os_walk.return_value = [] 119 | 120 | # Clear normalStore to remove any leftover data from previous tests 121 | normalStore.clear() 122 | 123 | # Mock file filter 124 | mock_file_filter = MagicMock() 125 | 126 | # Mock the progress bar 127 | mock_progress_bar.return_value.__enter__.return_value = MagicMock() 128 | 129 | # Call the function 130 | duplicates = find_duplicates('test_directory', mock_file_filter, 'yellow', 'white') 131 | 132 | # Assertions 133 | assert duplicates == [] # Should be empty since there are no files 134 | 135 | 136 | 137 | @patch('os.walk') 138 | @patch('twinTrim.flagController.add_or_update_normal_file') 139 | @patch('twinTrim.flagController.progress_bar_func') 140 | def test_find_duplicates_with_filter(mock_progress_bar, mock_add_or_update_normal_file, mock_os_walk): 141 | """Test proper filtering of files using file_filter.""" 142 | # Simulate a directory with 3 files 143 | mock_os_walk.return_value = [ 144 | (os.path.normpath('/path/to/files'), [], ['file1.txt', 'file2.txt', 'file3.txt']) 145 | ] 146 | 147 | # Mock file filter to allow only 'file1.txt' and 'file2.txt' 148 | mock_file_filter = MagicMock() 149 | mock_file_filter.filter_files.side_effect = lambda f: 'file1.txt' in f or 'file2.txt' in f 150 | 151 | # Mock the progress bar 152 | mock_progress_bar.return_value.__enter__.return_value = MagicMock() 153 | 154 | # Mock the normalStore to contain a duplicate entry 155 | normalStore.clear() 156 | normalStore['file1_hash'] = MagicMock(filepaths=[os.path.normpath('/path/to/files/file1.txt'), os.path.normpath('/path/to/files/file2.txt')]) 157 | 158 | # Call the function 159 | duplicates = find_duplicates(os.path.normpath('/path/to/files'), mock_file_filter, 'yellow', 'white') 160 | 161 | # Assertions 162 | assert len(duplicates) == 1 # Should find one duplicate pair 163 | assert duplicates[0][0] == os.path.normpath('/path/to/files/file1.txt') 164 | assert duplicates[0][1] == os.path.normpath('/path/to/files/file2.txt') 165 | -------------------------------------------------------------------------------- /assets/javascripts/lunr/min/lunr.ru.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Lunr languages, `Russian` language 3 | * https://github.com/MihaiValentin/lunr-languages 4 | * 5 | * Copyright 2014, Mihai Valentin 6 | * http://www.mozilla.org/MPL/ 7 | */ 8 | /*! 9 | * based on 10 | * Snowball JavaScript Library v0.3 11 | * http://code.google.com/p/urim/ 12 | * http://snowball.tartarus.org/ 13 | * 14 | * Copyright 2010, Oleg Mazko 15 | * http://www.mozilla.org/MPL/ 16 | */ 17 | 18 | !function(e,n){"function"==typeof define&&define.amd?define(n):"object"==typeof exports?module.exports=n():n()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.ru=function(){this.pipeline.reset(),this.pipeline.add(e.ru.trimmer,e.ru.stopWordFilter,e.ru.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.ru.stemmer))},e.ru.wordCharacters="Ѐ-҄҇-ԯᴫᵸⷠ-ⷿꙀ-ꚟ︮︯",e.ru.trimmer=e.trimmerSupport.generateTrimmer(e.ru.wordCharacters),e.Pipeline.registerFunction(e.ru.trimmer,"trimmer-ru"),e.ru.stemmer=function(){var n=e.stemmerSupport.Among,r=e.stemmerSupport.SnowballProgram,t=new function(){function e(){for(;!W.in_grouping(S,1072,1103);){if(W.cursor>=W.limit)return!1;W.cursor++}return!0}function t(){for(;!W.out_grouping(S,1072,1103);){if(W.cursor>=W.limit)return!1;W.cursor++}return!0}function w(){b=W.limit,_=b,e()&&(b=W.cursor,t()&&e()&&t()&&(_=W.cursor))}function i(){return _<=W.cursor}function u(e,n){var r,t;if(W.ket=W.cursor,r=W.find_among_b(e,n)){switch(W.bra=W.cursor,r){case 1:if(t=W.limit-W.cursor,!W.eq_s_b(1,"а")&&(W.cursor=W.limit-t,!W.eq_s_b(1,"я")))return!1;case 2:W.slice_del()}return!0}return!1}function o(){return u(h,9)}function s(e,n){var r;return W.ket=W.cursor,!!(r=W.find_among_b(e,n))&&(W.bra=W.cursor,1==r&&W.slice_del(),!0)}function c(){return s(g,26)}function m(){return!!c()&&(u(C,8),!0)}function f(){return s(k,2)}function l(){return u(P,46)}function a(){s(v,36)}function p(){var e;W.ket=W.cursor,(e=W.find_among_b(F,2))&&(W.bra=W.cursor,i()&&1==e&&W.slice_del())}function d(){var e;if(W.ket=W.cursor,e=W.find_among_b(q,4))switch(W.bra=W.cursor,e){case 1:if(W.slice_del(),W.ket=W.cursor,!W.eq_s_b(1,"н"))break;W.bra=W.cursor;case 2:if(!W.eq_s_b(1,"н"))break;case 3:W.slice_del()}}var _,b,h=[new n("в",-1,1),new n("ив",0,2),new n("ыв",0,2),new n("вши",-1,1),new n("ивши",3,2),new n("ывши",3,2),new n("вшись",-1,1),new n("ившись",6,2),new n("ывшись",6,2)],g=[new n("ее",-1,1),new n("ие",-1,1),new n("ое",-1,1),new n("ые",-1,1),new n("ими",-1,1),new n("ыми",-1,1),new n("ей",-1,1),new n("ий",-1,1),new n("ой",-1,1),new n("ый",-1,1),new n("ем",-1,1),new n("им",-1,1),new n("ом",-1,1),new n("ым",-1,1),new n("его",-1,1),new n("ого",-1,1),new n("ему",-1,1),new n("ому",-1,1),new n("их",-1,1),new n("ых",-1,1),new n("ею",-1,1),new n("ою",-1,1),new n("ую",-1,1),new n("юю",-1,1),new n("ая",-1,1),new n("яя",-1,1)],C=[new n("ем",-1,1),new n("нн",-1,1),new n("вш",-1,1),new n("ивш",2,2),new n("ывш",2,2),new n("щ",-1,1),new n("ющ",5,1),new n("ующ",6,2)],k=[new n("сь",-1,1),new n("ся",-1,1)],P=[new n("ла",-1,1),new n("ила",0,2),new n("ыла",0,2),new n("на",-1,1),new n("ена",3,2),new n("ете",-1,1),new n("ите",-1,2),new n("йте",-1,1),new n("ейте",7,2),new n("уйте",7,2),new n("ли",-1,1),new n("или",10,2),new n("ыли",10,2),new n("й",-1,1),new n("ей",13,2),new n("уй",13,2),new n("л",-1,1),new n("ил",16,2),new n("ыл",16,2),new n("ем",-1,1),new n("им",-1,2),new n("ым",-1,2),new n("н",-1,1),new n("ен",22,2),new n("ло",-1,1),new n("ило",24,2),new n("ыло",24,2),new n("но",-1,1),new n("ено",27,2),new n("нно",27,1),new n("ет",-1,1),new n("ует",30,2),new n("ит",-1,2),new n("ыт",-1,2),new n("ют",-1,1),new n("уют",34,2),new n("ят",-1,2),new n("ны",-1,1),new n("ены",37,2),new n("ть",-1,1),new n("ить",39,2),new n("ыть",39,2),new n("ешь",-1,1),new n("ишь",-1,2),new n("ю",-1,2),new n("ую",44,2)],v=[new n("а",-1,1),new n("ев",-1,1),new n("ов",-1,1),new n("е",-1,1),new n("ие",3,1),new n("ье",3,1),new n("и",-1,1),new n("еи",6,1),new n("ии",6,1),new n("ами",6,1),new n("ями",6,1),new n("иями",10,1),new n("й",-1,1),new n("ей",12,1),new n("ией",13,1),new n("ий",12,1),new n("ой",12,1),new n("ам",-1,1),new n("ем",-1,1),new n("ием",18,1),new n("ом",-1,1),new n("ям",-1,1),new n("иям",21,1),new n("о",-1,1),new n("у",-1,1),new n("ах",-1,1),new n("ях",-1,1),new n("иях",26,1),new n("ы",-1,1),new n("ь",-1,1),new n("ю",-1,1),new n("ию",30,1),new n("ью",30,1),new n("я",-1,1),new n("ия",33,1),new n("ья",33,1)],F=[new n("ост",-1,1),new n("ость",-1,1)],q=[new n("ейше",-1,1),new n("н",-1,2),new n("ейш",-1,1),new n("ь",-1,3)],S=[33,65,8,232],W=new r;this.setCurrent=function(e){W.setCurrent(e)},this.getCurrent=function(){return W.getCurrent()},this.stem=function(){return w(),W.cursor=W.limit,!(W.cursor=A.limit)return!0;A.cursor++}for(A.cursor=i;!A.out_grouping(W,97,246);){if(A.cursor>=A.limit)return!0;A.cursor++}return!1}function t(){return d<=A.cursor}function s(){var i,e;if(A.cursor>=f)if(e=A.limit_backward,A.limit_backward=f,A.ket=A.cursor,i=A.find_among_b(h,10)){switch(A.bra=A.cursor,A.limit_backward=e,i){case 1:if(!A.in_grouping_b(x,97,246))return;break;case 2:if(!t())return}A.slice_del()}else A.limit_backward=e}function o(){var i,e,r;if(A.cursor>=f)if(e=A.limit_backward,A.limit_backward=f,A.ket=A.cursor,i=A.find_among_b(v,9))switch(A.bra=A.cursor,A.limit_backward=e,i){case 1:r=A.limit-A.cursor,A.eq_s_b(1,"k")||(A.cursor=A.limit-r,A.slice_del());break;case 2:A.slice_del(),A.ket=A.cursor,A.eq_s_b(3,"kse")&&(A.bra=A.cursor,A.slice_from("ksi"));break;case 3:A.slice_del();break;case 4:A.find_among_b(p,6)&&A.slice_del();break;case 5:A.find_among_b(g,6)&&A.slice_del();break;case 6:A.find_among_b(j,2)&&A.slice_del()}else A.limit_backward=e}function l(){return A.find_among_b(q,7)}function a(){return A.eq_s_b(1,"i")&&A.in_grouping_b(L,97,246)}function u(){var i,e,r;if(A.cursor>=f)if(e=A.limit_backward,A.limit_backward=f,A.ket=A.cursor,i=A.find_among_b(C,30)){switch(A.bra=A.cursor,A.limit_backward=e,i){case 1:if(!A.eq_s_b(1,"a"))return;break;case 2:case 9:if(!A.eq_s_b(1,"e"))return;break;case 3:if(!A.eq_s_b(1,"i"))return;break;case 4:if(!A.eq_s_b(1,"o"))return;break;case 5:if(!A.eq_s_b(1,"ä"))return;break;case 6:if(!A.eq_s_b(1,"ö"))return;break;case 7:if(r=A.limit-A.cursor,!l()&&(A.cursor=A.limit-r,!A.eq_s_b(2,"ie"))){A.cursor=A.limit-r;break}if(A.cursor=A.limit-r,A.cursor<=A.limit_backward){A.cursor=A.limit-r;break}A.cursor--,A.bra=A.cursor;break;case 8:if(!A.in_grouping_b(W,97,246)||!A.out_grouping_b(W,97,246))return}A.slice_del(),k=!0}else A.limit_backward=e}function c(){var i,e,r;if(A.cursor>=d)if(e=A.limit_backward,A.limit_backward=d,A.ket=A.cursor,i=A.find_among_b(P,14)){if(A.bra=A.cursor,A.limit_backward=e,1==i){if(r=A.limit-A.cursor,A.eq_s_b(2,"po"))return;A.cursor=A.limit-r}A.slice_del()}else A.limit_backward=e}function m(){var i;A.cursor>=f&&(i=A.limit_backward,A.limit_backward=f,A.ket=A.cursor,A.find_among_b(F,2)?(A.bra=A.cursor,A.limit_backward=i,A.slice_del()):A.limit_backward=i)}function w(){var i,e,r,n,t,s;if(A.cursor>=f){if(e=A.limit_backward,A.limit_backward=f,A.ket=A.cursor,A.eq_s_b(1,"t")&&(A.bra=A.cursor,r=A.limit-A.cursor,A.in_grouping_b(W,97,246)&&(A.cursor=A.limit-r,A.slice_del(),A.limit_backward=e,n=A.limit-A.cursor,A.cursor>=d&&(A.cursor=d,t=A.limit_backward,A.limit_backward=A.cursor,A.cursor=A.limit-n,A.ket=A.cursor,i=A.find_among_b(S,2))))){if(A.bra=A.cursor,A.limit_backward=t,1==i){if(s=A.limit-A.cursor,A.eq_s_b(2,"po"))return;A.cursor=A.limit-s}return void A.slice_del()}A.limit_backward=e}}function _(){var i,e,r,n;if(A.cursor>=f){for(i=A.limit_backward,A.limit_backward=f,e=A.limit-A.cursor,l()&&(A.cursor=A.limit-e,A.ket=A.cursor,A.cursor>A.limit_backward&&(A.cursor--,A.bra=A.cursor,A.slice_del())),A.cursor=A.limit-e,A.ket=A.cursor,A.in_grouping_b(y,97,228)&&(A.bra=A.cursor,A.out_grouping_b(W,97,246)&&A.slice_del()),A.cursor=A.limit-e,A.ket=A.cursor,A.eq_s_b(1,"j")&&(A.bra=A.cursor,r=A.limit-A.cursor,A.eq_s_b(1,"o")?A.slice_del():(A.cursor=A.limit-r,A.eq_s_b(1,"u")&&A.slice_del())),A.cursor=A.limit-e,A.ket=A.cursor,A.eq_s_b(1,"o")&&(A.bra=A.cursor,A.eq_s_b(1,"j")&&A.slice_del()),A.cursor=A.limit-e,A.limit_backward=i;;){if(n=A.limit-A.cursor,A.out_grouping_b(W,97,246)){A.cursor=A.limit-n;break}if(A.cursor=A.limit-n,A.cursor<=A.limit_backward)return;A.cursor--}A.ket=A.cursor,A.cursor>A.limit_backward&&(A.cursor--,A.bra=A.cursor,b=A.slice_to(),A.eq_v_b(b)&&A.slice_del())}}var k,b,d,f,h=[new e("pa",-1,1),new e("sti",-1,2),new e("kaan",-1,1),new e("han",-1,1),new e("kin",-1,1),new e("hän",-1,1),new e("kään",-1,1),new e("ko",-1,1),new e("pä",-1,1),new e("kö",-1,1)],p=[new e("lla",-1,-1),new e("na",-1,-1),new e("ssa",-1,-1),new e("ta",-1,-1),new e("lta",3,-1),new e("sta",3,-1)],g=[new e("llä",-1,-1),new e("nä",-1,-1),new e("ssä",-1,-1),new e("tä",-1,-1),new e("ltä",3,-1),new e("stä",3,-1)],j=[new e("lle",-1,-1),new e("ine",-1,-1)],v=[new e("nsa",-1,3),new e("mme",-1,3),new e("nne",-1,3),new e("ni",-1,2),new e("si",-1,1),new e("an",-1,4),new e("en",-1,6),new e("än",-1,5),new e("nsä",-1,3)],q=[new e("aa",-1,-1),new e("ee",-1,-1),new e("ii",-1,-1),new e("oo",-1,-1),new e("uu",-1,-1),new e("ää",-1,-1),new e("öö",-1,-1)],C=[new e("a",-1,8),new e("lla",0,-1),new e("na",0,-1),new e("ssa",0,-1),new e("ta",0,-1),new e("lta",4,-1),new e("sta",4,-1),new e("tta",4,9),new e("lle",-1,-1),new e("ine",-1,-1),new e("ksi",-1,-1),new e("n",-1,7),new e("han",11,1),new e("den",11,-1,a),new e("seen",11,-1,l),new e("hen",11,2),new e("tten",11,-1,a),new e("hin",11,3),new e("siin",11,-1,a),new e("hon",11,4),new e("hän",11,5),new e("hön",11,6),new e("ä",-1,8),new e("llä",22,-1),new e("nä",22,-1),new e("ssä",22,-1),new e("tä",22,-1),new e("ltä",26,-1),new e("stä",26,-1),new e("ttä",26,9)],P=[new e("eja",-1,-1),new e("mma",-1,1),new e("imma",1,-1),new e("mpa",-1,1),new e("impa",3,-1),new e("mmi",-1,1),new e("immi",5,-1),new e("mpi",-1,1),new e("impi",7,-1),new e("ejä",-1,-1),new e("mmä",-1,1),new e("immä",10,-1),new e("mpä",-1,1),new e("impä",12,-1)],F=[new e("i",-1,-1),new e("j",-1,-1)],S=[new e("mma",-1,1),new e("imma",0,-1)],y=[17,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8],W=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,8,0,32],L=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,32],x=[17,97,24,1,0,0,0,0,0,0,0,0,0,0,0,0,8,0,32],A=new r;this.setCurrent=function(i){A.setCurrent(i)},this.getCurrent=function(){return A.getCurrent()},this.stem=function(){var e=A.cursor;return i(),k=!1,A.limit_backward=e,A.cursor=A.limit,s(),A.cursor=A.limit,o(),A.cursor=A.limit,u(),A.cursor=A.limit,c(),A.cursor=A.limit,k?(m(),A.cursor=A.limit):(A.cursor=A.limit,w(),A.cursor=A.limit),_(),!0}};return function(i){return"function"==typeof i.update?i.update(function(i){return n.setCurrent(i),n.stem(),n.getCurrent()}):(n.setCurrent(i),n.stem(),n.getCurrent())}}(),i.Pipeline.registerFunction(i.fi.stemmer,"stemmer-fi"),i.fi.stopWordFilter=i.generateStopWordFilter("ei eivät emme en et ette että he heidän heidät heihin heille heillä heiltä heissä heistä heitä hän häneen hänelle hänellä häneltä hänen hänessä hänestä hänet häntä itse ja johon joiden joihin joiksi joilla joille joilta joina joissa joista joita joka joksi jolla jolle jolta jona jonka jos jossa josta jota jotka kanssa keiden keihin keiksi keille keillä keiltä keinä keissä keistä keitä keneen keneksi kenelle kenellä keneltä kenen kenenä kenessä kenestä kenet ketkä ketkä ketä koska kuin kuka kun me meidän meidät meihin meille meillä meiltä meissä meistä meitä mihin miksi mikä mille millä miltä minkä minkä minua minulla minulle minulta minun minussa minusta minut minuun minä minä missä mistä mitkä mitä mukaan mutta ne niiden niihin niiksi niille niillä niiltä niin niin niinä niissä niistä niitä noiden noihin noiksi noilla noille noilta noin noina noissa noista noita nuo nyt näiden näihin näiksi näille näillä näiltä näinä näissä näistä näitä nämä ole olemme olen olet olette oli olimme olin olisi olisimme olisin olisit olisitte olisivat olit olitte olivat olla olleet ollut on ovat poikki se sekä sen siihen siinä siitä siksi sille sillä sillä siltä sinua sinulla sinulle sinulta sinun sinussa sinusta sinut sinuun sinä sinä sitä tai te teidän teidät teihin teille teillä teiltä teissä teistä teitä tuo tuohon tuoksi tuolla tuolle tuolta tuon tuona tuossa tuosta tuota tähän täksi tälle tällä tältä tämä tämän tänä tässä tästä tätä vaan vai vaikka yli".split(" ")),i.Pipeline.registerFunction(i.fi.stopWordFilter,"stopWordFilter-fi")}}); -------------------------------------------------------------------------------- /assets/javascripts/lunr/min/lunr.hu.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Lunr languages, `Hungarian` language 3 | * https://github.com/MihaiValentin/lunr-languages 4 | * 5 | * Copyright 2014, Mihai Valentin 6 | * http://www.mozilla.org/MPL/ 7 | */ 8 | /*! 9 | * based on 10 | * Snowball JavaScript Library v0.3 11 | * http://code.google.com/p/urim/ 12 | * http://snowball.tartarus.org/ 13 | * 14 | * Copyright 2010, Oleg Mazko 15 | * http://www.mozilla.org/MPL/ 16 | */ 17 | 18 | !function(e,n){"function"==typeof define&&define.amd?define(n):"object"==typeof exports?module.exports=n():n()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.hu=function(){this.pipeline.reset(),this.pipeline.add(e.hu.trimmer,e.hu.stopWordFilter,e.hu.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.hu.stemmer))},e.hu.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.hu.trimmer=e.trimmerSupport.generateTrimmer(e.hu.wordCharacters),e.Pipeline.registerFunction(e.hu.trimmer,"trimmer-hu"),e.hu.stemmer=function(){var n=e.stemmerSupport.Among,r=e.stemmerSupport.SnowballProgram,i=new function(){function e(){var e,n=L.cursor;if(d=L.limit,L.in_grouping(W,97,252))for(;;){if(e=L.cursor,L.out_grouping(W,97,252))return L.cursor=e,L.find_among(g,8)||(L.cursor=e,e=L.limit)return void(d=e);L.cursor++}if(L.cursor=n,L.out_grouping(W,97,252)){for(;!L.in_grouping(W,97,252);){if(L.cursor>=L.limit)return;L.cursor++}d=L.cursor}}function i(){return d<=L.cursor}function a(){var e;if(L.ket=L.cursor,(e=L.find_among_b(h,2))&&(L.bra=L.cursor,i()))switch(e){case 1:L.slice_from("a");break;case 2:L.slice_from("e")}}function t(){var e=L.limit-L.cursor;return!!L.find_among_b(p,23)&&(L.cursor=L.limit-e,!0)}function s(){if(L.cursor>L.limit_backward){L.cursor--,L.ket=L.cursor;var e=L.cursor-1;L.limit_backward<=e&&e<=L.limit&&(L.cursor=e,L.bra=e,L.slice_del())}}function c(){var e;if(L.ket=L.cursor,(e=L.find_among_b(_,2))&&(L.bra=L.cursor,i())){if((1==e||2==e)&&!t())return;L.slice_del(),s()}}function o(){L.ket=L.cursor,L.find_among_b(v,44)&&(L.bra=L.cursor,i()&&(L.slice_del(),a()))}function w(){var e;if(L.ket=L.cursor,(e=L.find_among_b(z,3))&&(L.bra=L.cursor,i()))switch(e){case 1:L.slice_from("e");break;case 2:case 3:L.slice_from("a")}}function l(){var e;if(L.ket=L.cursor,(e=L.find_among_b(y,6))&&(L.bra=L.cursor,i()))switch(e){case 1:case 2:L.slice_del();break;case 3:L.slice_from("a");break;case 4:L.slice_from("e")}}function u(){var e;if(L.ket=L.cursor,(e=L.find_among_b(j,2))&&(L.bra=L.cursor,i())){if((1==e||2==e)&&!t())return;L.slice_del(),s()}}function m(){var e;if(L.ket=L.cursor,(e=L.find_among_b(C,7))&&(L.bra=L.cursor,i()))switch(e){case 1:L.slice_from("a");break;case 2:L.slice_from("e");break;case 3:case 4:case 5:case 6:case 7:L.slice_del()}}function k(){var e;if(L.ket=L.cursor,(e=L.find_among_b(P,12))&&(L.bra=L.cursor,i()))switch(e){case 1:case 4:case 7:case 9:L.slice_del();break;case 2:case 5:case 8:L.slice_from("e");break;case 3:case 6:L.slice_from("a")}}function f(){var e;if(L.ket=L.cursor,(e=L.find_among_b(F,31))&&(L.bra=L.cursor,i()))switch(e){case 1:case 4:case 7:case 8:case 9:case 12:case 13:case 16:case 17:case 18:L.slice_del();break;case 2:case 5:case 10:case 14:case 19:L.slice_from("a");break;case 3:case 6:case 11:case 15:case 20:L.slice_from("e")}}function b(){var e;if(L.ket=L.cursor,(e=L.find_among_b(S,42))&&(L.bra=L.cursor,i()))switch(e){case 1:case 4:case 5:case 6:case 9:case 10:case 11:case 14:case 15:case 16:case 17:case 20:case 21:case 24:case 25:case 26:case 29:L.slice_del();break;case 2:case 7:case 12:case 18:case 22:case 27:L.slice_from("a");break;case 3:case 8:case 13:case 19:case 23:case 28:L.slice_from("e")}}var d,g=[new n("cs",-1,-1),new n("dzs",-1,-1),new n("gy",-1,-1),new n("ly",-1,-1),new n("ny",-1,-1),new n("sz",-1,-1),new n("ty",-1,-1),new n("zs",-1,-1)],h=[new n("á",-1,1),new n("é",-1,2)],p=[new n("bb",-1,-1),new n("cc",-1,-1),new n("dd",-1,-1),new n("ff",-1,-1),new n("gg",-1,-1),new n("jj",-1,-1),new n("kk",-1,-1),new n("ll",-1,-1),new n("mm",-1,-1),new n("nn",-1,-1),new n("pp",-1,-1),new n("rr",-1,-1),new n("ccs",-1,-1),new n("ss",-1,-1),new n("zzs",-1,-1),new n("tt",-1,-1),new n("vv",-1,-1),new n("ggy",-1,-1),new n("lly",-1,-1),new n("nny",-1,-1),new n("tty",-1,-1),new n("ssz",-1,-1),new n("zz",-1,-1)],_=[new n("al",-1,1),new n("el",-1,2)],v=[new n("ba",-1,-1),new n("ra",-1,-1),new n("be",-1,-1),new n("re",-1,-1),new n("ig",-1,-1),new n("nak",-1,-1),new n("nek",-1,-1),new n("val",-1,-1),new n("vel",-1,-1),new n("ul",-1,-1),new n("nál",-1,-1),new n("nél",-1,-1),new n("ból",-1,-1),new n("ról",-1,-1),new n("tól",-1,-1),new n("bõl",-1,-1),new n("rõl",-1,-1),new n("tõl",-1,-1),new n("ül",-1,-1),new n("n",-1,-1),new n("an",19,-1),new n("ban",20,-1),new n("en",19,-1),new n("ben",22,-1),new n("képpen",22,-1),new n("on",19,-1),new n("ön",19,-1),new n("képp",-1,-1),new n("kor",-1,-1),new n("t",-1,-1),new n("at",29,-1),new n("et",29,-1),new n("ként",29,-1),new n("anként",32,-1),new n("enként",32,-1),new n("onként",32,-1),new n("ot",29,-1),new n("ért",29,-1),new n("öt",29,-1),new n("hez",-1,-1),new n("hoz",-1,-1),new n("höz",-1,-1),new n("vá",-1,-1),new n("vé",-1,-1)],z=[new n("án",-1,2),new n("én",-1,1),new n("ánként",-1,3)],y=[new n("stul",-1,2),new n("astul",0,1),new n("ástul",0,3),new n("stül",-1,2),new n("estül",3,1),new n("éstül",3,4)],j=[new n("á",-1,1),new n("é",-1,2)],C=[new n("k",-1,7),new n("ak",0,4),new n("ek",0,6),new n("ok",0,5),new n("ák",0,1),new n("ék",0,2),new n("ök",0,3)],P=[new n("éi",-1,7),new n("áéi",0,6),new n("ééi",0,5),new n("é",-1,9),new n("ké",3,4),new n("aké",4,1),new n("eké",4,1),new n("oké",4,1),new n("áké",4,3),new n("éké",4,2),new n("öké",4,1),new n("éé",3,8)],F=[new n("a",-1,18),new n("ja",0,17),new n("d",-1,16),new n("ad",2,13),new n("ed",2,13),new n("od",2,13),new n("ád",2,14),new n("éd",2,15),new n("öd",2,13),new n("e",-1,18),new n("je",9,17),new n("nk",-1,4),new n("unk",11,1),new n("ánk",11,2),new n("énk",11,3),new n("ünk",11,1),new n("uk",-1,8),new n("juk",16,7),new n("ájuk",17,5),new n("ük",-1,8),new n("jük",19,7),new n("éjük",20,6),new n("m",-1,12),new n("am",22,9),new n("em",22,9),new n("om",22,9),new n("ám",22,10),new n("ém",22,11),new n("o",-1,18),new n("á",-1,19),new n("é",-1,20)],S=[new n("id",-1,10),new n("aid",0,9),new n("jaid",1,6),new n("eid",0,9),new n("jeid",3,6),new n("áid",0,7),new n("éid",0,8),new n("i",-1,15),new n("ai",7,14),new n("jai",8,11),new n("ei",7,14),new n("jei",10,11),new n("ái",7,12),new n("éi",7,13),new n("itek",-1,24),new n("eitek",14,21),new n("jeitek",15,20),new n("éitek",14,23),new n("ik",-1,29),new n("aik",18,26),new n("jaik",19,25),new n("eik",18,26),new n("jeik",21,25),new n("áik",18,27),new n("éik",18,28),new n("ink",-1,20),new n("aink",25,17),new n("jaink",26,16),new n("eink",25,17),new n("jeink",28,16),new n("áink",25,18),new n("éink",25,19),new n("aitok",-1,21),new n("jaitok",32,20),new n("áitok",-1,22),new n("im",-1,5),new n("aim",35,4),new n("jaim",36,1),new n("eim",35,4),new n("jeim",38,1),new n("áim",35,2),new n("éim",35,3)],W=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,1,17,52,14],L=new r;this.setCurrent=function(e){L.setCurrent(e)},this.getCurrent=function(){return L.getCurrent()},this.stem=function(){var n=L.cursor;return e(),L.limit_backward=n,L.cursor=L.limit,c(),L.cursor=L.limit,o(),L.cursor=L.limit,w(),L.cursor=L.limit,l(),L.cursor=L.limit,u(),L.cursor=L.limit,k(),L.cursor=L.limit,f(),L.cursor=L.limit,b(),L.cursor=L.limit,m(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}}(),e.Pipeline.registerFunction(e.hu.stemmer,"stemmer-hu"),e.hu.stopWordFilter=e.generateStopWordFilter("a abban ahhoz ahogy ahol aki akik akkor alatt amely amelyek amelyekben amelyeket amelyet amelynek ami amikor amit amolyan amíg annak arra arról az azok azon azonban azt aztán azután azzal azért be belül benne bár cikk cikkek cikkeket csak de e ebben eddig egy egyes egyetlen egyik egyre egyéb egész ehhez ekkor el ellen elsõ elég elõ elõször elõtt emilyen ennek erre ez ezek ezen ezt ezzel ezért fel felé hanem hiszen hogy hogyan igen ill ill. illetve ilyen ilyenkor ismét ison itt jobban jó jól kell kellett keressünk keresztül ki kívül között közül legalább legyen lehet lehetett lenne lenni lesz lett maga magát majd majd meg mellett mely melyek mert mi mikor milyen minden mindenki mindent mindig mint mintha mit mivel miért most már más másik még míg nagy nagyobb nagyon ne nekem neki nem nincs néha néhány nélkül olyan ott pedig persze rá s saját sem semmi sok sokat sokkal szemben szerint szinte számára talán tehát teljes tovább továbbá több ugyanis utolsó után utána vagy vagyis vagyok valaki valami valamint való van vannak vele vissza viszont volna volt voltak voltam voltunk által általában át én éppen és így õ õk õket össze úgy új újabb újra".split(" ")),e.Pipeline.registerFunction(e.hu.stopWordFilter,"stopWordFilter-hu")}}); -------------------------------------------------------------------------------- /search/search_index.json: -------------------------------------------------------------------------------- 1 | {"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Getting Started with TwinTrim","text":"

Welcome to the TwinTrim getting started guide! This document will walk you through the steps to get started with the project.

"},{"location":"#prerequisites","title":"Prerequisites","text":"

Before you begin, ensure you have met the following requirements:

  • Python Installation: Make sure you have Python 3.6 or above installed on your system. You can download it from the official Python website.
  • Git: Ensure you have Git installed to clone the repository. You can get it from the official Git website.
  • GitHub Account: A GitHub account is required if you plan to contribute to the project.
"},{"location":"#installing-twintrim","title":"Installing TwinTrim","text":"

To install TwinTrim, follow these steps:

  1. Clone the Repository: Clone the repository from GitHub to your local machine.

    bash git clone https://github.com/Kota-Karthik/twinTrim.git

  2. Navigate to the Project Directory: Change into the project directory.

    bash cd twinTrim

  3. Install Dependencies: Install the required dependencies using the following command. If you are using poetry, you can install dependencies with:

    bash poetry install

    Alternatively, if you are using pip, install the dependencies from requirements.txt:

    bash pip install -r requirements.txt

"},{"location":"#running-twintrim","title":"Running TwinTrim","text":"

To run TwinTrim, follow these steps:

  1. Navigate to the Project Directory (if not already there):

    bash cd twinTrim

  2. Run the Application: Start the application with the following command:

    bash python -m twinTrim.main <directory> [OPTIONS]

    Replace <directory> with the path to the directory you want to scan. You can add options such as --all to automatically remove duplicates or specify filters (e.g., --min-size or --file-type).

    The application will scan the directory and, based on your options, either prompt you to review the duplicates or automatically remove them.

"},{"location":"#contributing-to-twintrim","title":"Contributing to TwinTrim","text":"

We welcome contributions! Follow these steps to contribute:

  1. Fork the Repository: Fork the repository on GitHub from here.

  2. Clone Your Fork: Clone the forked repository to your local machine.

    bash git clone https://github.com/<your-username>/twinTrim.git

  3. Create a New Branch: Create a new branch to work on your changes.

    bash git checkout -b new-branch-name

  4. Make Changes: Make the necessary changes to the code.

  5. Commit Changes: Commit your changes with a descriptive message.

    bash git commit -m \"Descriptive commit message\"

  6. Push Changes: Push your changes to your fork on GitHub.

    bash git push origin new-branch-name

  7. Create a Pull Request: Go to the GitHub repository and create a pull request from your fork's branch to the main repository's main branch.

Your changes will be reviewed, and if everything looks good, they will be merged into the main project.

"},{"location":"#contact","title":"Contact","text":"

If you have any questions or need further assistance, feel free to reach out to Karthik Kota.

"},{"location":"Contributing/","title":"Contributing","text":"

Contributions are welcome! Whether you have ideas for improving the internal workings of TwinTrim, such as optimizing performance or refining algorithms, or you want to enhance the user interface of the CLI tool for a better user experience, your input is valuable. Please fork the repository and submit a pull request with your improvements or new features.

Please refer to the CONTRIBUTING GUIDELINES for guidelines on how to contribute.

"},{"location":"Features/","title":"Features","text":"
  • Duplicate Detection: Scans directories to detect duplicate files based on file content rather than just filenames.
  • Automatic or Manual Removal: Choose to handle duplicates automatically using the --all flag or manually select which files to delete.
  • Customizable Filters: Set filters for minimum and maximum file sizes, file types, and specific filenames to exclude from the scan.
  • Multi-Threaded Processing: Utilizes multi-threading to quickly scan and process large numbers of files concurrently.
  • Deadlock Prevention: Implements locks to prevent deadlocks during multi-threaded operations, ensuring smooth and safe execution.
  • User-Friendly Interface: Offers clear prompts and feedback via the command line, making the process straightforward and interactive.
"},{"location":"How-It-Works/","title":"How It Works","text":""},{"location":"How-It-Works/#core-components","title":"Core Components","text":"
  1. File Metadata Management:

    • Uses AllFileMetadata and FileMetadata classes to manage file information, such as modification time and file paths.
    • Maintains metadata in two dictionaries (store and normalStore) for handling different levels of duplicate management.
  2. File Hashing:

    • Generates a unique hash for each file using MD5 to identify duplicates by content.
  3. File Filtering:

    • The FileFilter class provides functionality to filter files based on size, type, and exclusions.
  4. Duplicate Handling:

    • Duplicate files are identified by comparing their hashes.
    • Based on file modification time, the latest file is retained, and older duplicates are removed.
  5. Deadlock Prevention:

    • Uses locks within multi-threaded processes to ensure that resources are accessed safely, preventing deadlocks that could otherwise halt execution.
"},{"location":"How-It-Works/#key-functions","title":"Key Functions","text":"
  • add_or_update_file: Adds new files to the metadata store or updates existing entries if a duplicate is detected.
  • add_or_update_normal_file: Similar to add_or_update_file but manages duplicates in a separate store.
  • handleAllFlag: Handles duplicate removal automatically without user intervention.
  • find_duplicates: Finds duplicate files in the specified directory and prepares them for user review or automatic handling.
"},{"location":"License/","title":"License","text":"

This project is licensed under the MIT License - see the LICENSE file for details.

"},{"location":"Usage/","title":"Usage","text":""},{"location":"Usage/#usage","title":"Usage","text":""},{"location":"Usage/#command-line-interface","title":"Command Line Interface","text":"

Run the script using the following command:

python -m twinTrim.main <directory> [OPTIONS]\n
"},{"location":"Usage/#options","title":"Options","text":"
  • --all: Automatically delete duplicates without asking for confirmation.
  • --min-size: Specify the minimum file size to include in the scan (e.g., 10kb).
  • --max-size: Specify the maximum file size to include in the scan (e.g., 1gb).
  • --file-type: Specify the file type to include (e.g., .txt, .jpg).
  • --exclude: Exclude specific files by name.
  • --label-color: Set the font color of the output label of the progress bar.
  • --bar-color: Set the color of the progress bar.
"},{"location":"Usage/#examples","title":"Examples","text":"
  1. Automatic Duplicate Removal: bash python -m twinTrim.main /path/to/directory --all

  2. Manual Review and Removal: bash python -m twinTrim.main /path/to/directory

  3. Filtered Scan by File Size and Type: bash python -m twinTrim.main /path/to/directory --min-size \"50kb\" --max-size \"500mb\" --file-type \"txt\"

"},{"location":"Usage/#dependencies","title":"Dependencies","text":"
  • Python 3.6+
  • click for command-line interaction
  • tqdm for progress bars
  • concurrent.futures for multi-threaded processing
  • beaupy for interactive selection
"},{"location":"Usage/#installation","title":"Installation","text":"

Clone the repository and install the required dependencies using Poetry:

git clone https://github.com/Kota-Karthik/twinTrim.git\ncd twinTrim\npoetry install\npoetry shell\n

If you haven't installed Poetry yet, you can do so by following the instructions on the Poetry website.

"}]} -------------------------------------------------------------------------------- /assets/javascripts/lunr/min/lunr.pt.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Lunr languages, `Portuguese` language 3 | * https://github.com/MihaiValentin/lunr-languages 4 | * 5 | * Copyright 2014, Mihai Valentin 6 | * http://www.mozilla.org/MPL/ 7 | */ 8 | /*! 9 | * based on 10 | * Snowball JavaScript Library v0.3 11 | * http://code.google.com/p/urim/ 12 | * http://snowball.tartarus.org/ 13 | * 14 | * Copyright 2010, Oleg Mazko 15 | * http://www.mozilla.org/MPL/ 16 | */ 17 | 18 | !function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.pt=function(){this.pipeline.reset(),this.pipeline.add(e.pt.trimmer,e.pt.stopWordFilter,e.pt.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.pt.stemmer))},e.pt.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.pt.trimmer=e.trimmerSupport.generateTrimmer(e.pt.wordCharacters),e.Pipeline.registerFunction(e.pt.trimmer,"trimmer-pt"),e.pt.stemmer=function(){var r=e.stemmerSupport.Among,s=e.stemmerSupport.SnowballProgram,n=new function(){function e(){for(var e;;){if(z.bra=z.cursor,e=z.find_among(k,3))switch(z.ket=z.cursor,e){case 1:z.slice_from("a~");continue;case 2:z.slice_from("o~");continue;case 3:if(z.cursor>=z.limit)break;z.cursor++;continue}break}}function n(){if(z.out_grouping(y,97,250)){for(;!z.in_grouping(y,97,250);){if(z.cursor>=z.limit)return!0;z.cursor++}return!1}return!0}function i(){if(z.in_grouping(y,97,250))for(;!z.out_grouping(y,97,250);){if(z.cursor>=z.limit)return!1;z.cursor++}return g=z.cursor,!0}function o(){var e,r,s=z.cursor;if(z.in_grouping(y,97,250))if(e=z.cursor,n()){if(z.cursor=e,i())return}else g=z.cursor;if(z.cursor=s,z.out_grouping(y,97,250)){if(r=z.cursor,n()){if(z.cursor=r,!z.in_grouping(y,97,250)||z.cursor>=z.limit)return;z.cursor++}g=z.cursor}}function t(){for(;!z.in_grouping(y,97,250);){if(z.cursor>=z.limit)return!1;z.cursor++}for(;!z.out_grouping(y,97,250);){if(z.cursor>=z.limit)return!1;z.cursor++}return!0}function a(){var e=z.cursor;g=z.limit,b=g,h=g,o(),z.cursor=e,t()&&(b=z.cursor,t()&&(h=z.cursor))}function u(){for(var e;;){if(z.bra=z.cursor,e=z.find_among(q,3))switch(z.ket=z.cursor,e){case 1:z.slice_from("ã");continue;case 2:z.slice_from("õ");continue;case 3:if(z.cursor>=z.limit)break;z.cursor++;continue}break}}function w(){return g<=z.cursor}function m(){return b<=z.cursor}function c(){return h<=z.cursor}function l(){var e;if(z.ket=z.cursor,!(e=z.find_among_b(F,45)))return!1;switch(z.bra=z.cursor,e){case 1:if(!c())return!1;z.slice_del();break;case 2:if(!c())return!1;z.slice_from("log");break;case 3:if(!c())return!1;z.slice_from("u");break;case 4:if(!c())return!1;z.slice_from("ente");break;case 5:if(!m())return!1;z.slice_del(),z.ket=z.cursor,e=z.find_among_b(j,4),e&&(z.bra=z.cursor,c()&&(z.slice_del(),1==e&&(z.ket=z.cursor,z.eq_s_b(2,"at")&&(z.bra=z.cursor,c()&&z.slice_del()))));break;case 6:if(!c())return!1;z.slice_del(),z.ket=z.cursor,e=z.find_among_b(C,3),e&&(z.bra=z.cursor,1==e&&c()&&z.slice_del());break;case 7:if(!c())return!1;z.slice_del(),z.ket=z.cursor,e=z.find_among_b(P,3),e&&(z.bra=z.cursor,1==e&&c()&&z.slice_del());break;case 8:if(!c())return!1;z.slice_del(),z.ket=z.cursor,z.eq_s_b(2,"at")&&(z.bra=z.cursor,c()&&z.slice_del());break;case 9:if(!w()||!z.eq_s_b(1,"e"))return!1;z.slice_from("ir")}return!0}function f(){var e,r;if(z.cursor>=g){if(r=z.limit_backward,z.limit_backward=g,z.ket=z.cursor,e=z.find_among_b(S,120))return z.bra=z.cursor,1==e&&z.slice_del(),z.limit_backward=r,!0;z.limit_backward=r}return!1}function d(){var e;z.ket=z.cursor,(e=z.find_among_b(W,7))&&(z.bra=z.cursor,1==e&&w()&&z.slice_del())}function v(e,r){if(z.eq_s_b(1,e)){z.bra=z.cursor;var s=z.limit-z.cursor;if(z.eq_s_b(1,r))return z.cursor=z.limit-s,w()&&z.slice_del(),!1}return!0}function p(){var e;if(z.ket=z.cursor,e=z.find_among_b(L,4))switch(z.bra=z.cursor,e){case 1:w()&&(z.slice_del(),z.ket=z.cursor,z.limit-z.cursor,v("u","g")&&v("i","c"));break;case 2:z.slice_from("c")}}function _(){if(!l()&&(z.cursor=z.limit,!f()))return z.cursor=z.limit,void d();z.cursor=z.limit,z.ket=z.cursor,z.eq_s_b(1,"i")&&(z.bra=z.cursor,z.eq_s_b(1,"c")&&(z.cursor=z.limit,w()&&z.slice_del()))}var h,b,g,k=[new r("",-1,3),new r("ã",0,1),new r("õ",0,2)],q=[new r("",-1,3),new r("a~",0,1),new r("o~",0,2)],j=[new r("ic",-1,-1),new r("ad",-1,-1),new r("os",-1,-1),new r("iv",-1,1)],C=[new r("ante",-1,1),new r("avel",-1,1),new r("ível",-1,1)],P=[new r("ic",-1,1),new r("abil",-1,1),new r("iv",-1,1)],F=[new r("ica",-1,1),new r("ância",-1,1),new r("ência",-1,4),new r("ira",-1,9),new r("adora",-1,1),new r("osa",-1,1),new r("ista",-1,1),new r("iva",-1,8),new r("eza",-1,1),new r("logía",-1,2),new r("idade",-1,7),new r("ante",-1,1),new r("mente",-1,6),new r("amente",12,5),new r("ável",-1,1),new r("ível",-1,1),new r("ución",-1,3),new r("ico",-1,1),new r("ismo",-1,1),new r("oso",-1,1),new r("amento",-1,1),new r("imento",-1,1),new r("ivo",-1,8),new r("aça~o",-1,1),new r("ador",-1,1),new r("icas",-1,1),new r("ências",-1,4),new r("iras",-1,9),new r("adoras",-1,1),new r("osas",-1,1),new r("istas",-1,1),new r("ivas",-1,8),new r("ezas",-1,1),new r("logías",-1,2),new r("idades",-1,7),new r("uciones",-1,3),new r("adores",-1,1),new r("antes",-1,1),new r("aço~es",-1,1),new r("icos",-1,1),new r("ismos",-1,1),new r("osos",-1,1),new r("amentos",-1,1),new r("imentos",-1,1),new r("ivos",-1,8)],S=[new r("ada",-1,1),new r("ida",-1,1),new r("ia",-1,1),new r("aria",2,1),new r("eria",2,1),new r("iria",2,1),new r("ara",-1,1),new r("era",-1,1),new r("ira",-1,1),new r("ava",-1,1),new r("asse",-1,1),new r("esse",-1,1),new r("isse",-1,1),new r("aste",-1,1),new r("este",-1,1),new r("iste",-1,1),new r("ei",-1,1),new r("arei",16,1),new r("erei",16,1),new r("irei",16,1),new r("am",-1,1),new r("iam",20,1),new r("ariam",21,1),new r("eriam",21,1),new r("iriam",21,1),new r("aram",20,1),new r("eram",20,1),new r("iram",20,1),new r("avam",20,1),new r("em",-1,1),new r("arem",29,1),new r("erem",29,1),new r("irem",29,1),new r("assem",29,1),new r("essem",29,1),new r("issem",29,1),new r("ado",-1,1),new r("ido",-1,1),new r("ando",-1,1),new r("endo",-1,1),new r("indo",-1,1),new r("ara~o",-1,1),new r("era~o",-1,1),new r("ira~o",-1,1),new r("ar",-1,1),new r("er",-1,1),new r("ir",-1,1),new r("as",-1,1),new r("adas",47,1),new r("idas",47,1),new r("ias",47,1),new r("arias",50,1),new r("erias",50,1),new r("irias",50,1),new r("aras",47,1),new r("eras",47,1),new r("iras",47,1),new r("avas",47,1),new r("es",-1,1),new r("ardes",58,1),new r("erdes",58,1),new r("irdes",58,1),new r("ares",58,1),new r("eres",58,1),new r("ires",58,1),new r("asses",58,1),new r("esses",58,1),new r("isses",58,1),new r("astes",58,1),new r("estes",58,1),new r("istes",58,1),new r("is",-1,1),new r("ais",71,1),new r("eis",71,1),new r("areis",73,1),new r("ereis",73,1),new r("ireis",73,1),new r("áreis",73,1),new r("éreis",73,1),new r("íreis",73,1),new r("ásseis",73,1),new r("ésseis",73,1),new r("ísseis",73,1),new r("áveis",73,1),new r("íeis",73,1),new r("aríeis",84,1),new r("eríeis",84,1),new r("iríeis",84,1),new r("ados",-1,1),new r("idos",-1,1),new r("amos",-1,1),new r("áramos",90,1),new r("éramos",90,1),new r("íramos",90,1),new r("ávamos",90,1),new r("íamos",90,1),new r("aríamos",95,1),new r("eríamos",95,1),new r("iríamos",95,1),new r("emos",-1,1),new r("aremos",99,1),new r("eremos",99,1),new r("iremos",99,1),new r("ássemos",99,1),new r("êssemos",99,1),new r("íssemos",99,1),new r("imos",-1,1),new r("armos",-1,1),new r("ermos",-1,1),new r("irmos",-1,1),new r("ámos",-1,1),new r("arás",-1,1),new r("erás",-1,1),new r("irás",-1,1),new r("eu",-1,1),new r("iu",-1,1),new r("ou",-1,1),new r("ará",-1,1),new r("erá",-1,1),new r("irá",-1,1)],W=[new r("a",-1,1),new r("i",-1,1),new r("o",-1,1),new r("os",-1,1),new r("á",-1,1),new r("í",-1,1),new r("ó",-1,1)],L=[new r("e",-1,1),new r("ç",-1,2),new r("é",-1,1),new r("ê",-1,1)],y=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,3,19,12,2],z=new s;this.setCurrent=function(e){z.setCurrent(e)},this.getCurrent=function(){return z.getCurrent()},this.stem=function(){var r=z.cursor;return e(),z.cursor=r,a(),z.limit_backward=r,z.cursor=z.limit,_(),z.cursor=z.limit,p(),z.cursor=z.limit_backward,u(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}}(),e.Pipeline.registerFunction(e.pt.stemmer,"stemmer-pt"),e.pt.stopWordFilter=e.generateStopWordFilter("a ao aos aquela aquelas aquele aqueles aquilo as até com como da das de dela delas dele deles depois do dos e ela elas ele eles em entre era eram essa essas esse esses esta estamos estas estava estavam este esteja estejam estejamos estes esteve estive estivemos estiver estivera estiveram estiverem estivermos estivesse estivessem estivéramos estivéssemos estou está estávamos estão eu foi fomos for fora foram forem formos fosse fossem fui fôramos fôssemos haja hajam hajamos havemos hei houve houvemos houver houvera houveram houverei houverem houveremos houveria houveriam houvermos houverá houverão houveríamos houvesse houvessem houvéramos houvéssemos há hão isso isto já lhe lhes mais mas me mesmo meu meus minha minhas muito na nas nem no nos nossa nossas nosso nossos num numa não nós o os ou para pela pelas pelo pelos por qual quando que quem se seja sejam sejamos sem serei seremos seria seriam será serão seríamos seu seus somos sou sua suas são só também te tem temos tenha tenham tenhamos tenho terei teremos teria teriam terá terão teríamos teu teus teve tinha tinham tive tivemos tiver tivera tiveram tiverem tivermos tivesse tivessem tivéramos tivéssemos tu tua tuas tém tínhamos um uma você vocês vos à às éramos".split(" ")),e.Pipeline.registerFunction(e.pt.stopWordFilter,"stopWordFilter-pt")}}); -------------------------------------------------------------------------------- /assets/javascripts/lunr/min/lunr.el.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():t()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.el=function(){this.pipeline.reset(),void 0===this.searchPipeline&&this.pipeline.add(e.el.trimmer,e.el.normilizer),this.pipeline.add(e.el.stopWordFilter,e.el.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.el.stemmer))},e.el.wordCharacters="A-Za-zΑαΒβΓγΔδΕεΖζΗηΘθΙιΚκΛλΜμΝνΞξΟοΠπΡρΣσςΤτΥυΦφΧχΨψΩωΆάΈέΉήΊίΌόΎύΏώΪΐΫΰΐΰ",e.el.trimmer=e.trimmerSupport.generateTrimmer(e.el.wordCharacters),e.Pipeline.registerFunction(e.el.trimmer,"trimmer-el"),e.el.stemmer=function(){function e(e){return s.test(e)}function t(e){return/[ΑΕΗΙΟΥΩ]$/.test(e)}function r(e){return/[ΑΕΗΙΟΩ]$/.test(e)}function n(n){var s=n;if(n.length<3)return s;if(!e(n))return s;if(i.indexOf(n)>=0)return s;var u=new RegExp("(.*)("+Object.keys(l).join("|")+")$"),o=u.exec(s);return null!==o&&(s=o[1]+l[o[2]]),null!==(o=/^(.+?)(ΑΔΕΣ|ΑΔΩΝ)$/.exec(s))&&(s=o[1],/(ΟΚ|ΜΑΜ|ΜΑΝ|ΜΠΑΜΠ|ΠΑΤΕΡ|ΓΙΑΓΙ|ΝΤΑΝΤ|ΚΥΡ|ΘΕΙ|ΠΕΘΕΡ|ΜΟΥΣΑΜ|ΚΑΠΛΑΜ|ΠΑΡ|ΨΑΡ|ΤΖΟΥΡ|ΤΑΜΠΟΥΡ|ΓΑΛΑΤ|ΦΑΦΛΑΤ)$/.test(o[1])||(s+="ΑΔ")),null!==(o=/^(.+?)(ΕΔΕΣ|ΕΔΩΝ)$/.exec(s))&&(s=o[1],/(ΟΠ|ΙΠ|ΕΜΠ|ΥΠ|ΓΗΠ|ΔΑΠ|ΚΡΑΣΠ|ΜΙΛ)$/.test(o[1])&&(s+="ΕΔ")),null!==(o=/^(.+?)(ΟΥΔΕΣ|ΟΥΔΩΝ)$/.exec(s))&&(s=o[1],/(ΑΡΚ|ΚΑΛΙΑΚ|ΠΕΤΑΛ|ΛΙΧ|ΠΛΕΞ|ΣΚ|Σ|ΦΛ|ΦΡ|ΒΕΛ|ΛΟΥΛ|ΧΝ|ΣΠ|ΤΡΑΓ|ΦΕ)$/.test(o[1])&&(s+="ΟΥΔ")),null!==(o=/^(.+?)(ΕΩΣ|ΕΩΝ|ΕΑΣ|ΕΑ)$/.exec(s))&&(s=o[1],/^(Θ|Δ|ΕΛ|ΓΑΛ|Ν|Π|ΙΔ|ΠΑΡ|ΣΤΕΡ|ΟΡΦ|ΑΝΔΡ|ΑΝΤΡ)$/.test(o[1])&&(s+="Ε")),null!==(o=/^(.+?)(ΕΙΟ|ΕΙΟΣ|ΕΙΟΙ|ΕΙΑ|ΕΙΑΣ|ΕΙΕΣ|ΕΙΟΥ|ΕΙΟΥΣ|ΕΙΩΝ)$/.exec(s))&&o[1].length>4&&(s=o[1]),null!==(o=/^(.+?)(ΙΟΥΣ|ΙΑΣ|ΙΕΣ|ΙΟΣ|ΙΟΥ|ΙΟΙ|ΙΩΝ|ΙΟΝ|ΙΑ|ΙΟ)$/.exec(s))&&(s=o[1],(t(s)||s.length<2||/^(ΑΓ|ΑΓΓΕΛ|ΑΓΡ|ΑΕΡ|ΑΘΛ|ΑΚΟΥΣ|ΑΞ|ΑΣ|Β|ΒΙΒΛ|ΒΥΤ|Γ|ΓΙΑΓ|ΓΩΝ|Δ|ΔΑΝ|ΔΗΛ|ΔΗΜ|ΔΟΚΙΜ|ΕΛ|ΖΑΧΑΡ|ΗΛ|ΗΠ|ΙΔ|ΙΣΚ|ΙΣΤ|ΙΟΝ|ΙΩΝ|ΚΙΜΩΛ|ΚΟΛΟΝ|ΚΟΡ|ΚΤΗΡ|ΚΥΡ|ΛΑΓ|ΛΟΓ|ΜΑΓ|ΜΠΑΝ|ΜΠΡ|ΝΑΥΤ|ΝΟΤ|ΟΠΑΛ|ΟΞ|ΟΡ|ΟΣ|ΠΑΝΑΓ|ΠΑΤΡ|ΠΗΛ|ΠΗΝ|ΠΛΑΙΣ|ΠΟΝΤ|ΡΑΔ|ΡΟΔ|ΣΚ|ΣΚΟΡΠ|ΣΟΥΝ|ΣΠΑΝ|ΣΤΑΔ|ΣΥΡ|ΤΗΛ|ΤΙΜ|ΤΟΚ|ΤΟΠ|ΤΡΟΧ|ΦΙΛ|ΦΩΤ|Χ|ΧΙΛ|ΧΡΩΜ|ΧΩΡ)$/.test(o[1]))&&(s+="Ι"),/^(ΠΑΛ)$/.test(o[1])&&(s+="ΑΙ")),null!==(o=/^(.+?)(ΙΚΟΣ|ΙΚΟΝ|ΙΚΕΙΣ|ΙΚΟΙ|ΙΚΕΣ|ΙΚΟΥΣ|ΙΚΗ|ΙΚΗΣ|ΙΚΟ|ΙΚΑ|ΙΚΟΥ|ΙΚΩΝ|ΙΚΩΣ)$/.exec(s))&&(s=o[1],(t(s)||/^(ΑΔ|ΑΛ|ΑΜΑΝ|ΑΜΕΡ|ΑΜΜΟΧΑΛ|ΑΝΗΘ|ΑΝΤΙΔ|ΑΠΛ|ΑΤΤ|ΑΦΡ|ΒΑΣ|ΒΡΩΜ|ΓΕΝ|ΓΕΡ|Δ|ΔΙΚΑΝ|ΔΥΤ|ΕΙΔ|ΕΝΔ|ΕΞΩΔ|ΗΘ|ΘΕΤ|ΚΑΛΛΙΝ|ΚΑΛΠ|ΚΑΤΑΔ|ΚΟΥΖΙΝ|ΚΡ|ΚΩΔ|ΛΟΓ|Μ|ΜΕΡ|ΜΟΝΑΔ|ΜΟΥΛ|ΜΟΥΣ|ΜΠΑΓΙΑΤ|ΜΠΑΝ|ΜΠΟΛ|ΜΠΟΣ|ΜΥΣΤ|Ν|ΝΙΤ|ΞΙΚ|ΟΠΤ|ΠΑΝ|ΠΕΤΣ|ΠΙΚΑΝΤ|ΠΙΤΣ|ΠΛΑΣΤ|ΠΛΙΑΤΣ|ΠΟΝΤ|ΠΟΣΤΕΛΝ|ΠΡΩΤΟΔ|ΣΕΡΤ|ΣΗΜΑΝΤ|ΣΤΑΤ|ΣΥΝΑΔ|ΣΥΝΟΜΗΛ|ΤΕΛ|ΤΕΧΝ|ΤΡΟΠ|ΤΣΑΜ|ΥΠΟΔ|Φ|ΦΙΛΟΝ|ΦΥΛΟΔ|ΦΥΣ|ΧΑΣ)$/.test(o[1])||/(ΦΟΙΝ)$/.test(o[1]))&&(s+="ΙΚ")),"ΑΓΑΜΕ"===s&&(s="ΑΓΑΜ"),null!==(o=/^(.+?)(ΑΓΑΜΕ|ΗΣΑΜΕ|ΟΥΣΑΜΕ|ΗΚΑΜΕ|ΗΘΗΚΑΜΕ)$/.exec(s))&&(s=o[1]),null!==(o=/^(.+?)(ΑΜΕ)$/.exec(s))&&(s=o[1],/^(ΑΝΑΠ|ΑΠΟΘ|ΑΠΟΚ|ΑΠΟΣΤ|ΒΟΥΒ|ΞΕΘ|ΟΥΛ|ΠΕΘ|ΠΙΚΡ|ΠΟΤ|ΣΙΧ|Χ)$/.test(o[1])&&(s+="ΑΜ")),null!==(o=/^(.+?)(ΑΓΑΝΕ|ΗΣΑΝΕ|ΟΥΣΑΝΕ|ΙΟΝΤΑΝΕ|ΙΟΤΑΝΕ|ΙΟΥΝΤΑΝΕ|ΟΝΤΑΝΕ|ΟΤΑΝΕ|ΟΥΝΤΑΝΕ|ΗΚΑΝΕ|ΗΘΗΚΑΝΕ)$/.exec(s))&&(s=o[1],/^(ΤΡ|ΤΣ)$/.test(o[1])&&(s+="ΑΓΑΝ")),null!==(o=/^(.+?)(ΑΝΕ)$/.exec(s))&&(s=o[1],(r(s)||/^(ΒΕΤΕΡ|ΒΟΥΛΚ|ΒΡΑΧΜ|Γ|ΔΡΑΔΟΥΜ|Θ|ΚΑΛΠΟΥΖ|ΚΑΣΤΕΛ|ΚΟΡΜΟΡ|ΛΑΟΠΛ|ΜΩΑΜΕΘ|Μ|ΜΟΥΣΟΥΛΜΑΝ|ΟΥΛ|Π|ΠΕΛΕΚ|ΠΛ|ΠΟΛΙΣ|ΠΟΡΤΟΛ|ΣΑΡΑΚΑΤΣ|ΣΟΥΛΤ|ΤΣΑΡΛΑΤ|ΟΡΦ|ΤΣΙΓΓ|ΤΣΟΠ|ΦΩΤΟΣΤΕΦ|Χ|ΨΥΧΟΠΛ|ΑΓ|ΟΡΦ|ΓΑΛ|ΓΕΡ|ΔΕΚ|ΔΙΠΛ|ΑΜΕΡΙΚΑΝ|ΟΥΡ|ΠΙΘ|ΠΟΥΡΙΤ|Σ|ΖΩΝΤ|ΙΚ|ΚΑΣΤ|ΚΟΠ|ΛΙΧ|ΛΟΥΘΗΡ|ΜΑΙΝΤ|ΜΕΛ|ΣΙΓ|ΣΠ|ΣΤΕΓ|ΤΡΑΓ|ΤΣΑΓ|Φ|ΕΡ|ΑΔΑΠ|ΑΘΙΓΓ|ΑΜΗΧ|ΑΝΙΚ|ΑΝΟΡΓ|ΑΠΗΓ|ΑΠΙΘ|ΑΤΣΙΓΓ|ΒΑΣ|ΒΑΣΚ|ΒΑΘΥΓΑΛ|ΒΙΟΜΗΧ|ΒΡΑΧΥΚ|ΔΙΑΤ|ΔΙΑΦ|ΕΝΟΡΓ|ΘΥΣ|ΚΑΠΝΟΒΙΟΜΗΧ|ΚΑΤΑΓΑΛ|ΚΛΙΒ|ΚΟΙΛΑΡΦ|ΛΙΒ|ΜΕΓΛΟΒΙΟΜΗΧ|ΜΙΚΡΟΒΙΟΜΗΧ|ΝΤΑΒ|ΞΗΡΟΚΛΙΒ|ΟΛΙΓΟΔΑΜ|ΟΛΟΓΑΛ|ΠΕΝΤΑΡΦ|ΠΕΡΗΦ|ΠΕΡΙΤΡ|ΠΛΑΤ|ΠΟΛΥΔΑΠ|ΠΟΛΥΜΗΧ|ΣΤΕΦ|ΤΑΒ|ΤΕΤ|ΥΠΕΡΗΦ|ΥΠΟΚΟΠ|ΧΑΜΗΛΟΔΑΠ|ΨΗΛΟΤΑΒ)$/.test(o[1]))&&(s+="ΑΝ")),null!==(o=/^(.+?)(ΗΣΕΤΕ)$/.exec(s))&&(s=o[1]),null!==(o=/^(.+?)(ΕΤΕ)$/.exec(s))&&(s=o[1],(r(s)||/(ΟΔ|ΑΙΡ|ΦΟΡ|ΤΑΘ|ΔΙΑΘ|ΣΧ|ΕΝΔ|ΕΥΡ|ΤΙΘ|ΥΠΕΡΘ|ΡΑΘ|ΕΝΘ|ΡΟΘ|ΣΘ|ΠΥΡ|ΑΙΝ|ΣΥΝΔ|ΣΥΝ|ΣΥΝΘ|ΧΩΡ|ΠΟΝ|ΒΡ|ΚΑΘ|ΕΥΘ|ΕΚΘ|ΝΕΤ|ΡΟΝ|ΑΡΚ|ΒΑΡ|ΒΟΛ|ΩΦΕΛ)$/.test(o[1])||/^(ΑΒΑΡ|ΒΕΝ|ΕΝΑΡ|ΑΒΡ|ΑΔ|ΑΘ|ΑΝ|ΑΠΛ|ΒΑΡΟΝ|ΝΤΡ|ΣΚ|ΚΟΠ|ΜΠΟΡ|ΝΙΦ|ΠΑΓ|ΠΑΡΑΚΑΛ|ΣΕΡΠ|ΣΚΕΛ|ΣΥΡΦ|ΤΟΚ|Υ|Δ|ΕΜ|ΘΑΡΡ|Θ)$/.test(o[1]))&&(s+="ΕΤ")),null!==(o=/^(.+?)(ΟΝΤΑΣ|ΩΝΤΑΣ)$/.exec(s))&&(s=o[1],/^ΑΡΧ$/.test(o[1])&&(s+="ΟΝΤ"),/ΚΡΕ$/.test(o[1])&&(s+="ΩΝΤ")),null!==(o=/^(.+?)(ΟΜΑΣΤΕ|ΙΟΜΑΣΤΕ)$/.exec(s))&&(s=o[1],/^ΟΝ$/.test(o[1])&&(s+="ΟΜΑΣΤ")),null!==(o=/^(.+?)(ΙΕΣΤΕ)$/.exec(s))&&(s=o[1],/^(Π|ΑΠ|ΣΥΜΠ|ΑΣΥΜΠ|ΑΚΑΤΑΠ|ΑΜΕΤΑΜΦ)$/.test(o[1])&&(s+="ΙΕΣΤ")),null!==(o=/^(.+?)(ΕΣΤΕ)$/.exec(s))&&(s=o[1],/^(ΑΛ|ΑΡ|ΕΚΤΕΛ|Ζ|Μ|Ξ|ΠΑΡΑΚΑΛ|ΠΡΟ|ΝΙΣ)$/.test(o[1])&&(s+="ΕΣΤ")),null!==(o=/^(.+?)(ΗΘΗΚΑ|ΗΘΗΚΕΣ|ΗΘΗΚΕ)$/.exec(s))&&(s=o[1]),null!==(o=/^(.+?)(ΗΚΑ|ΗΚΕΣ|ΗΚΕ)$/.exec(s))&&(s=o[1],(/(ΣΚΩΛ|ΣΚΟΥΛ|ΝΑΡΘ|ΣΦ|ΟΘ|ΠΙΘ)$/.test(o[1])||/^(ΔΙΑΘ|Θ|ΠΑΡΑΚΑΤΑΘ|ΠΡΟΣΘ|ΣΥΝΘ)$/.test(o[1]))&&(s+="ΗΚ")),null!==(o=/^(.+?)(ΟΥΣΑ|ΟΥΣΕΣ|ΟΥΣΕ)$/.exec(s))&&(s=o[1],(t(s)||/^(ΦΑΡΜΑΚ|ΧΑΔ|ΑΓΚ|ΑΝΑΡΡ|ΒΡΟΜ|ΕΚΛΙΠ|ΛΑΜΠΙΔ|ΛΕΧ|Μ|ΠΑΤ|Ρ|Λ|ΜΕΔ|ΜΕΣΑΖ|ΥΠΟΤΕΙΝ|ΑΜ|ΑΙΘ|ΑΝΗΚ|ΔΕΣΠΟΖ|ΕΝΔΙΑΦΕΡ)$/.test(o[1])||/(ΠΟΔΑΡ|ΒΛΕΠ|ΠΑΝΤΑΧ|ΦΡΥΔ|ΜΑΝΤΙΛ|ΜΑΛΛ|ΚΥΜΑΤ|ΛΑΧ|ΛΗΓ|ΦΑΓ|ΟΜ|ΠΡΩΤ)$/.test(o[1]))&&(s+="ΟΥΣ")),null!==(o=/^(.+?)(ΑΓΑ|ΑΓΕΣ|ΑΓΕ)$/.exec(s))&&(s=o[1],(/^(ΑΒΑΣΤ|ΠΟΛΥΦ|ΑΔΗΦ|ΠΑΜΦ|Ρ|ΑΣΠ|ΑΦ|ΑΜΑΛ|ΑΜΑΛΛΙ|ΑΝΥΣΤ|ΑΠΕΡ|ΑΣΠΑΡ|ΑΧΑΡ|ΔΕΡΒΕΝ|ΔΡΟΣΟΠ|ΞΕΦ|ΝΕΟΠ|ΝΟΜΟΤ|ΟΛΟΠ|ΟΜΟΤ|ΠΡΟΣΤ|ΠΡΟΣΩΠΟΠ|ΣΥΜΠ|ΣΥΝΤ|Τ|ΥΠΟΤ|ΧΑΡ|ΑΕΙΠ|ΑΙΜΟΣΤ|ΑΝΥΠ|ΑΠΟΤ|ΑΡΤΙΠ|ΔΙΑΤ|ΕΝ|ΕΠΙΤ|ΚΡΟΚΑΛΟΠ|ΣΙΔΗΡΟΠ|Λ|ΝΑΥ|ΟΥΛΑΜ|ΟΥΡ|Π|ΤΡ|Μ)$/.test(o[1])||/(ΟΦ|ΠΕΛ|ΧΟΡΤ|ΛΛ|ΣΦ|ΡΠ|ΦΡ|ΠΡ|ΛΟΧ|ΣΜΗΝ)$/.test(o[1])&&!/^(ΨΟΦ|ΝΑΥΛΟΧ)$/.test(o[1])||/(ΚΟΛΛ)$/.test(o[1]))&&(s+="ΑΓ")),null!==(o=/^(.+?)(ΗΣΕ|ΗΣΟΥ|ΗΣΑ)$/.exec(s))&&(s=o[1],/^(Ν|ΧΕΡΣΟΝ|ΔΩΔΕΚΑΝ|ΕΡΗΜΟΝ|ΜΕΓΑΛΟΝ|ΕΠΤΑΝ|Ι)$/.test(o[1])&&(s+="ΗΣ")),null!==(o=/^(.+?)(ΗΣΤΕ)$/.exec(s))&&(s=o[1],/^(ΑΣΒ|ΣΒ|ΑΧΡ|ΧΡ|ΑΠΛ|ΑΕΙΜΝ|ΔΥΣΧΡ|ΕΥΧΡ|ΚΟΙΝΟΧΡ|ΠΑΛΙΜΨ)$/.test(o[1])&&(s+="ΗΣΤ")),null!==(o=/^(.+?)(ΟΥΝΕ|ΗΣΟΥΝΕ|ΗΘΟΥΝΕ)$/.exec(s))&&(s=o[1],/^(Ν|Ρ|ΣΠΙ|ΣΤΡΑΒΟΜΟΥΤΣ|ΚΑΚΟΜΟΥΤΣ|ΕΞΩΝ)$/.test(o[1])&&(s+="ΟΥΝ")),null!==(o=/^(.+?)(ΟΥΜΕ|ΗΣΟΥΜΕ|ΗΘΟΥΜΕ)$/.exec(s))&&(s=o[1],/^(ΠΑΡΑΣΟΥΣ|Φ|Χ|ΩΡΙΟΠΛ|ΑΖ|ΑΛΛΟΣΟΥΣ|ΑΣΟΥΣ)$/.test(o[1])&&(s+="ΟΥΜ")),null!=(o=/^(.+?)(ΜΑΤΟΙ|ΜΑΤΟΥΣ|ΜΑΤΟ|ΜΑΤΑ|ΜΑΤΩΣ|ΜΑΤΩΝ|ΜΑΤΟΣ|ΜΑΤΕΣ|ΜΑΤΗ|ΜΑΤΗΣ|ΜΑΤΟΥ)$/.exec(s))&&(s=o[1]+"Μ",/^(ΓΡΑΜ)$/.test(o[1])?s+="Α":/^(ΓΕ|ΣΤΑ)$/.test(o[1])&&(s+="ΑΤ")),null!==(o=/^(.+?)(ΟΥΑ)$/.exec(s))&&(s=o[1]+"ΟΥ"),n.length===s.length&&null!==(o=/^(.+?)(Α|ΑΓΑΤΕ|ΑΓΑΝ|ΑΕΙ|ΑΜΑΙ|ΑΝ|ΑΣ|ΑΣΑΙ|ΑΤΑΙ|ΑΩ|Ε|ΕΙ|ΕΙΣ|ΕΙΤΕ|ΕΣΑΙ|ΕΣ|ΕΤΑΙ|Ι|ΙΕΜΑΙ|ΙΕΜΑΣΤΕ|ΙΕΤΑΙ|ΙΕΣΑΙ|ΙΕΣΑΣΤΕ|ΙΟΜΑΣΤΑΝ|ΙΟΜΟΥΝ|ΙΟΜΟΥΝΑ|ΙΟΝΤΑΝ|ΙΟΝΤΟΥΣΑΝ|ΙΟΣΑΣΤΑΝ|ΙΟΣΑΣΤΕ|ΙΟΣΟΥΝ|ΙΟΣΟΥΝΑ|ΙΟΤΑΝ|ΙΟΥΜΑ|ΙΟΥΜΑΣΤΕ|ΙΟΥΝΤΑΙ|ΙΟΥΝΤΑΝ|Η|ΗΔΕΣ|ΗΔΩΝ|ΗΘΕΙ|ΗΘΕΙΣ|ΗΘΕΙΤΕ|ΗΘΗΚΑΤΕ|ΗΘΗΚΑΝ|ΗΘΟΥΝ|ΗΘΩ|ΗΚΑΤΕ|ΗΚΑΝ|ΗΣ|ΗΣΑΝ|ΗΣΑΤΕ|ΗΣΕΙ|ΗΣΕΣ|ΗΣΟΥΝ|ΗΣΩ|Ο|ΟΙ|ΟΜΑΙ|ΟΜΑΣΤΑΝ|ΟΜΟΥΝ|ΟΜΟΥΝΑ|ΟΝΤΑΙ|ΟΝΤΑΝ|ΟΝΤΟΥΣΑΝ|ΟΣ|ΟΣΑΣΤΑΝ|ΟΣΑΣΤΕ|ΟΣΟΥΝ|ΟΣΟΥΝΑ|ΟΤΑΝ|ΟΥ|ΟΥΜΑΙ|ΟΥΜΑΣΤΕ|ΟΥΝ|ΟΥΝΤΑΙ|ΟΥΝΤΑΝ|ΟΥΣ|ΟΥΣΑΝ|ΟΥΣΑΤΕ|Υ||ΥΑ|ΥΣ|Ω|ΩΝ|ΟΙΣ)$/.exec(s))&&(s=o[1]),null!=(o=/^(.+?)(ΕΣΤΕΡ|ΕΣΤΑΤ|ΟΤΕΡ|ΟΤΑΤ|ΥΤΕΡ|ΥΤΑΤ|ΩΤΕΡ|ΩΤΑΤ)$/.exec(s))&&(/^(ΕΞ|ΕΣ|ΑΝ|ΚΑΤ|Κ|ΠΡ)$/.test(o[1])||(s=o[1]),/^(ΚΑ|Μ|ΕΛΕ|ΛΕ|ΔΕ)$/.test(o[1])&&(s+="ΥΤ")),s}var l={"ΦΑΓΙΑ":"ΦΑ","ΦΑΓΙΟΥ":"ΦΑ","ΦΑΓΙΩΝ":"ΦΑ","ΣΚΑΓΙΑ":"ΣΚΑ","ΣΚΑΓΙΟΥ":"ΣΚΑ","ΣΚΑΓΙΩΝ":"ΣΚΑ","ΣΟΓΙΟΥ":"ΣΟ","ΣΟΓΙΑ":"ΣΟ","ΣΟΓΙΩΝ":"ΣΟ","ΤΑΤΟΓΙΑ":"ΤΑΤΟ","ΤΑΤΟΓΙΟΥ":"ΤΑΤΟ","ΤΑΤΟΓΙΩΝ":"ΤΑΤΟ","ΚΡΕΑΣ":"ΚΡΕ","ΚΡΕΑΤΟΣ":"ΚΡΕ","ΚΡΕΑΤΑ":"ΚΡΕ","ΚΡΕΑΤΩΝ":"ΚΡΕ","ΠΕΡΑΣ":"ΠΕΡ","ΠΕΡΑΤΟΣ":"ΠΕΡ","ΠΕΡΑΤΑ":"ΠΕΡ","ΠΕΡΑΤΩΝ":"ΠΕΡ","ΤΕΡΑΣ":"ΤΕΡ","ΤΕΡΑΤΟΣ":"ΤΕΡ","ΤΕΡΑΤΑ":"ΤΕΡ","ΤΕΡΑΤΩΝ":"ΤΕΡ","ΦΩΣ":"ΦΩ","ΦΩΤΟΣ":"ΦΩ","ΦΩΤΑ":"ΦΩ","ΦΩΤΩΝ":"ΦΩ","ΚΑΘΕΣΤΩΣ":"ΚΑΘΕΣΤ","ΚΑΘΕΣΤΩΤΟΣ":"ΚΑΘΕΣΤ","ΚΑΘΕΣΤΩΤΑ":"ΚΑΘΕΣΤ","ΚΑΘΕΣΤΩΤΩΝ":"ΚΑΘΕΣΤ","ΓΕΓΟΝΟΣ":"ΓΕΓΟΝ","ΓΕΓΟΝΟΤΟΣ":"ΓΕΓΟΝ","ΓΕΓΟΝΟΤΑ":"ΓΕΓΟΝ","ΓΕΓΟΝΟΤΩΝ":"ΓΕΓΟΝ","ΕΥΑ":"ΕΥ"},i=["ΑΚΡΙΒΩΣ","ΑΛΑ","ΑΛΛΑ","ΑΛΛΙΩΣ","ΑΛΛΟΤΕ","ΑΜΑ","ΑΝΩ","ΑΝΑ","ΑΝΑΜΕΣΑ","ΑΝΑΜΕΤΑΞΥ","ΑΝΕΥ","ΑΝΤΙ","ΑΝΤΙΠΕΡΑ","ΑΝΤΙΟ","ΑΞΑΦΝΑ","ΑΠΟ","ΑΠΟΨΕ","ΑΡΑ","ΑΡΑΓΕ","ΑΥΡΙΟ","ΑΦΟΙ","ΑΦΟΥ","ΑΦΟΤΟΥ","ΒΡΕ","ΓΕΙΑ","ΓΙΑ","ΓΙΑΤΙ","ΓΡΑΜΜΑ","ΔΕΗ","ΔΕΝ","ΔΗΛΑΔΗ","ΔΙΧΩΣ","ΔΥΟ","ΕΑΝ","ΕΓΩ","ΕΔΩ","ΕΔΑ","ΕΙΘΕ","ΕΙΜΑΙ","ΕΙΜΑΣΤΕ","ΕΙΣΑΙ","ΕΙΣΑΣΤΕ","ΕΙΝΑΙ","ΕΙΣΤΕ","ΕΙΤΕ","ΕΚΕΙ","ΕΚΟ","ΕΛΑ","ΕΜΑΣ","ΕΜΕΙΣ","ΕΝΤΕΛΩΣ","ΕΝΤΟΣ","ΕΝΤΩΜΕΤΑΞΥ","ΕΝΩ","ΕΞΙ","ΕΞΙΣΟΥ","ΕΞΗΣ","ΕΞΩ","ΕΟΚ","ΕΠΑΝΩ","ΕΠΕΙΔΗ","ΕΠΕΙΤΑ","ΕΠΙ","ΕΠΙΣΗΣ","ΕΠΟΜΕΝΩΣ","ΕΠΤΑ","ΕΣΑΣ","ΕΣΕΙΣ","ΕΣΤΩ","ΕΣΥ","ΕΣΩ","ΕΤΣΙ","ΕΥΓΕ","ΕΦΕ","ΕΦΕΞΗΣ","ΕΧΤΕΣ","ΕΩΣ","ΗΔΗ","ΗΜΙ","ΗΠΑ","ΗΤΟΙ","ΘΕΣ","ΙΔΙΩΣ","ΙΔΗ","ΙΚΑ","ΙΣΩΣ","ΚΑΘΕ","ΚΑΘΕΤΙ","ΚΑΘΟΛΟΥ","ΚΑΘΩΣ","ΚΑΙ","ΚΑΝ","ΚΑΠΟΤΕ","ΚΑΠΟΥ","ΚΑΤΑ","ΚΑΤΙ","ΚΑΤΟΠΙΝ","ΚΑΤΩ","ΚΕΙ","ΚΙΧ","ΚΚΕ","ΚΟΛΑΝ","ΚΥΡΙΩΣ","ΚΩΣ","ΜΑΚΑΡΙ","ΜΑΛΙΣΤΑ","ΜΑΛΛΟΝ","ΜΑΙ","ΜΑΟ","ΜΑΟΥΣ","ΜΑΣ","ΜΕΘΑΥΡΙΟ","ΜΕΣ","ΜΕΣΑ","ΜΕΤΑ","ΜΕΤΑΞΥ","ΜΕΧΡΙ","ΜΗΔΕ","ΜΗΝ","ΜΗΠΩΣ","ΜΗΤΕ","ΜΙΑ","ΜΙΑΣ","ΜΙΣ","ΜΜΕ","ΜΟΛΟΝΟΤΙ","ΜΟΥ","ΜΠΑ","ΜΠΑΣ","ΜΠΟΥΦΑΝ","ΜΠΡΟΣ","ΝΑΙ","ΝΕΣ","ΝΤΑ","ΝΤΕ","ΞΑΝΑ","ΟΗΕ","ΟΚΤΩ","ΟΜΩΣ","ΟΝΕ","ΟΠΑ","ΟΠΟΥ","ΟΠΩΣ","ΟΣΟ","ΟΤΑΝ","ΟΤΕ","ΟΤΙ","ΟΥΤΕ","ΟΧΙ","ΠΑΛΙ","ΠΑΝ","ΠΑΝΟ","ΠΑΝΤΟΤΕ","ΠΑΝΤΟΥ","ΠΑΝΤΩΣ","ΠΑΝΩ","ΠΑΡΑ","ΠΕΡΑ","ΠΕΡΙ","ΠΕΡΙΠΟΥ","ΠΙΑ","ΠΙΟ","ΠΙΣΩ","ΠΛΑΙ","ΠΛΕΟΝ","ΠΛΗΝ","ΠΟΤΕ","ΠΟΥ","ΠΡΟ","ΠΡΟΣ","ΠΡΟΧΤΕΣ","ΠΡΟΧΘΕΣ","ΡΟΔΙ","ΠΩΣ","ΣΑΙ","ΣΑΣ","ΣΑΝ","ΣΕΙΣ","ΣΙΑ","ΣΚΙ","ΣΟΙ","ΣΟΥ","ΣΡΙ","ΣΥΝ","ΣΥΝΑΜΑ","ΣΧΕΔΟΝ","ΤΑΔΕ","ΤΑΞΙ","ΤΑΧΑ","ΤΕΙ","ΤΗΝ","ΤΗΣ","ΤΙΠΟΤΑ","ΤΙΠΟΤΕ","ΤΙΣ","ΤΟΝ","ΤΟΤΕ","ΤΟΥ","ΤΟΥΣ","ΤΣΑ","ΤΣΕ","ΤΣΙ","ΤΣΟΥ","ΤΩΝ","ΥΠΟ","ΥΠΟΨΗ","ΥΠΟΨΙΝ","ΥΣΤΕΡΑ","ΦΕΤΟΣ","ΦΙΣ","ΦΠΑ","ΧΑΦ","ΧΘΕΣ","ΧΤΕΣ","ΧΩΡΙΣ","ΩΣ","ΩΣΑΝ","ΩΣΟΤΟΥ","ΩΣΠΟΥ","ΩΣΤΕ","ΩΣΤΟΣΟ"],s=new RegExp("^[ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ]+$");return function(e){return"function"==typeof e.update?e.update(function(e){return n(e.toUpperCase()).toLowerCase()}):n(e.toUpperCase()).toLowerCase()}}(),e.Pipeline.registerFunction(e.el.stemmer,"stemmer-el"),e.el.stopWordFilter=e.generateStopWordFilter("αλλα αν αντι απο αυτα αυτεσ αυτη αυτο αυτοι αυτοσ αυτουσ αυτων για δε δεν εαν ειμαι ειμαστε ειναι εισαι ειστε εκεινα εκεινεσ εκεινη εκεινο εκεινοι εκεινοσ εκεινουσ εκεινων ενω επι η θα ισωσ κ και κατα κι μα με μετα μη μην να ο οι ομωσ οπωσ οσο οτι παρα ποια ποιεσ ποιο ποιοι ποιοσ ποιουσ ποιων που προσ πωσ σε στη στην στο στον τα την τησ το τον τοτε του των ωσ".split(" ")),e.Pipeline.registerFunction(e.el.stopWordFilter,"stopWordFilter-el"),e.el.normilizer=function(){var e={"Ά":"Α","ά":"α","Έ":"Ε","έ":"ε","Ή":"Η","ή":"η","Ί":"Ι","ί":"ι","Ό":"Ο","ο":"ο","Ύ":"Υ","ύ":"υ","Ώ":"Ω","ώ":"ω","Ϊ":"Ι","ϊ":"ι","Ϋ":"Υ","ϋ":"υ","ΐ":"ι","ΰ":"υ"};return function(t){if("function"==typeof t.update)return t.update(function(t){for(var r="",n=0;n=W.limit)return;W.cursor++}}}function t(){for(;!W.in_grouping(F,97,251);){if(W.cursor>=W.limit)return!0;W.cursor++}for(;!W.out_grouping(F,97,251);){if(W.cursor>=W.limit)return!0;W.cursor++}return!1}function u(){var e=W.cursor;if(q=W.limit,g=q,p=q,W.in_grouping(F,97,251)&&W.in_grouping(F,97,251)&&W.cursor=W.limit){W.cursor=q;break}W.cursor++}while(!W.in_grouping(F,97,251))}q=W.cursor,W.cursor=e,t()||(g=W.cursor,t()||(p=W.cursor))}function o(){for(var e,r;;){if(r=W.cursor,W.bra=r,!(e=W.find_among(h,4)))break;switch(W.ket=W.cursor,e){case 1:W.slice_from("i");break;case 2:W.slice_from("u");break;case 3:W.slice_from("y");break;case 4:if(W.cursor>=W.limit)return;W.cursor++}}}function c(){return q<=W.cursor}function a(){return g<=W.cursor}function l(){return p<=W.cursor}function w(){var e,r;if(W.ket=W.cursor,e=W.find_among_b(C,43)){switch(W.bra=W.cursor,e){case 1:if(!l())return!1;W.slice_del();break;case 2:if(!l())return!1;W.slice_del(),W.ket=W.cursor,W.eq_s_b(2,"ic")&&(W.bra=W.cursor,l()?W.slice_del():W.slice_from("iqU"));break;case 3:if(!l())return!1;W.slice_from("log");break;case 4:if(!l())return!1;W.slice_from("u");break;case 5:if(!l())return!1;W.slice_from("ent");break;case 6:if(!c())return!1;if(W.slice_del(),W.ket=W.cursor,e=W.find_among_b(z,6))switch(W.bra=W.cursor,e){case 1:l()&&(W.slice_del(),W.ket=W.cursor,W.eq_s_b(2,"at")&&(W.bra=W.cursor,l()&&W.slice_del()));break;case 2:l()?W.slice_del():a()&&W.slice_from("eux");break;case 3:l()&&W.slice_del();break;case 4:c()&&W.slice_from("i")}break;case 7:if(!l())return!1;if(W.slice_del(),W.ket=W.cursor,e=W.find_among_b(y,3))switch(W.bra=W.cursor,e){case 1:l()?W.slice_del():W.slice_from("abl");break;case 2:l()?W.slice_del():W.slice_from("iqU");break;case 3:l()&&W.slice_del()}break;case 8:if(!l())return!1;if(W.slice_del(),W.ket=W.cursor,W.eq_s_b(2,"at")&&(W.bra=W.cursor,l()&&(W.slice_del(),W.ket=W.cursor,W.eq_s_b(2,"ic")))){W.bra=W.cursor,l()?W.slice_del():W.slice_from("iqU");break}break;case 9:W.slice_from("eau");break;case 10:if(!a())return!1;W.slice_from("al");break;case 11:if(l())W.slice_del();else{if(!a())return!1;W.slice_from("eux")}break;case 12:if(!a()||!W.out_grouping_b(F,97,251))return!1;W.slice_del();break;case 13:return c()&&W.slice_from("ant"),!1;case 14:return c()&&W.slice_from("ent"),!1;case 15:return r=W.limit-W.cursor,W.in_grouping_b(F,97,251)&&c()&&(W.cursor=W.limit-r,W.slice_del()),!1}return!0}return!1}function f(){var e,r;if(W.cursor=q){if(s=W.limit_backward,W.limit_backward=q,W.ket=W.cursor,e=W.find_among_b(P,7))switch(W.bra=W.cursor,e){case 1:if(l()){if(i=W.limit-W.cursor,!W.eq_s_b(1,"s")&&(W.cursor=W.limit-i,!W.eq_s_b(1,"t")))break;W.slice_del()}break;case 2:W.slice_from("i");break;case 3:W.slice_del();break;case 4:W.eq_s_b(2,"gu")&&W.slice_del()}W.limit_backward=s}}function b(){var e=W.limit-W.cursor;W.find_among_b(U,5)&&(W.cursor=W.limit-e,W.ket=W.cursor,W.cursor>W.limit_backward&&(W.cursor--,W.bra=W.cursor,W.slice_del()))}function d(){for(var e,r=1;W.out_grouping_b(F,97,251);)r--;if(r<=0){if(W.ket=W.cursor,e=W.limit-W.cursor,!W.eq_s_b(1,"é")&&(W.cursor=W.limit-e,!W.eq_s_b(1,"è")))return;W.bra=W.cursor,W.slice_from("e")}}function k(){if(!w()&&(W.cursor=W.limit,!f()&&(W.cursor=W.limit,!m())))return W.cursor=W.limit,void _();W.cursor=W.limit,W.ket=W.cursor,W.eq_s_b(1,"Y")?(W.bra=W.cursor,W.slice_from("i")):(W.cursor=W.limit,W.eq_s_b(1,"ç")&&(W.bra=W.cursor,W.slice_from("c")))}var p,g,q,v=[new r("col",-1,-1),new r("par",-1,-1),new r("tap",-1,-1)],h=[new r("",-1,4),new r("I",0,1),new r("U",0,2),new r("Y",0,3)],z=[new r("iqU",-1,3),new r("abl",-1,3),new r("Ièr",-1,4),new r("ièr",-1,4),new r("eus",-1,2),new r("iv",-1,1)],y=[new r("ic",-1,2),new r("abil",-1,1),new r("iv",-1,3)],C=[new r("iqUe",-1,1),new r("atrice",-1,2),new r("ance",-1,1),new r("ence",-1,5),new r("logie",-1,3),new r("able",-1,1),new r("isme",-1,1),new r("euse",-1,11),new r("iste",-1,1),new r("ive",-1,8),new r("if",-1,8),new r("usion",-1,4),new r("ation",-1,2),new r("ution",-1,4),new r("ateur",-1,2),new r("iqUes",-1,1),new r("atrices",-1,2),new r("ances",-1,1),new r("ences",-1,5),new r("logies",-1,3),new r("ables",-1,1),new r("ismes",-1,1),new r("euses",-1,11),new r("istes",-1,1),new r("ives",-1,8),new r("ifs",-1,8),new r("usions",-1,4),new r("ations",-1,2),new r("utions",-1,4),new r("ateurs",-1,2),new r("ments",-1,15),new r("ements",30,6),new r("issements",31,12),new r("ités",-1,7),new r("ment",-1,15),new r("ement",34,6),new r("issement",35,12),new r("amment",34,13),new r("emment",34,14),new r("aux",-1,10),new r("eaux",39,9),new r("eux",-1,1),new r("ité",-1,7)],x=[new r("ira",-1,1),new r("ie",-1,1),new r("isse",-1,1),new r("issante",-1,1),new r("i",-1,1),new r("irai",4,1),new r("ir",-1,1),new r("iras",-1,1),new r("ies",-1,1),new r("îmes",-1,1),new r("isses",-1,1),new r("issantes",-1,1),new r("îtes",-1,1),new r("is",-1,1),new r("irais",13,1),new r("issais",13,1),new r("irions",-1,1),new r("issions",-1,1),new r("irons",-1,1),new r("issons",-1,1),new r("issants",-1,1),new r("it",-1,1),new r("irait",21,1),new r("issait",21,1),new r("issant",-1,1),new r("iraIent",-1,1),new r("issaIent",-1,1),new r("irent",-1,1),new r("issent",-1,1),new r("iront",-1,1),new r("ît",-1,1),new r("iriez",-1,1),new r("issiez",-1,1),new r("irez",-1,1),new r("issez",-1,1)],I=[new r("a",-1,3),new r("era",0,2),new r("asse",-1,3),new r("ante",-1,3),new r("ée",-1,2),new r("ai",-1,3),new r("erai",5,2),new r("er",-1,2),new r("as",-1,3),new r("eras",8,2),new r("âmes",-1,3),new r("asses",-1,3),new r("antes",-1,3),new r("âtes",-1,3),new r("ées",-1,2),new r("ais",-1,3),new r("erais",15,2),new r("ions",-1,1),new r("erions",17,2),new r("assions",17,3),new r("erons",-1,2),new r("ants",-1,3),new r("és",-1,2),new r("ait",-1,3),new r("erait",23,2),new r("ant",-1,3),new r("aIent",-1,3),new r("eraIent",26,2),new r("èrent",-1,2),new r("assent",-1,3),new r("eront",-1,2),new r("ât",-1,3),new r("ez",-1,2),new r("iez",32,2),new r("eriez",33,2),new r("assiez",33,3),new r("erez",32,2),new r("é",-1,2)],P=[new r("e",-1,3),new r("Ière",0,2),new r("ière",0,2),new r("ion",-1,1),new r("Ier",-1,2),new r("ier",-1,2),new r("ë",-1,4)],U=[new r("ell",-1,-1),new r("eill",-1,-1),new r("enn",-1,-1),new r("onn",-1,-1),new r("ett",-1,-1)],F=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,128,130,103,8,5],S=[1,65,20,0,0,0,0,0,0,0,0,0,0,0,0,0,128],W=new s;this.setCurrent=function(e){W.setCurrent(e)},this.getCurrent=function(){return W.getCurrent()},this.stem=function(){var e=W.cursor;return n(),W.cursor=e,u(),W.limit_backward=e,W.cursor=W.limit,k(),W.cursor=W.limit,b(),W.cursor=W.limit,d(),W.cursor=W.limit_backward,o(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}}(),e.Pipeline.registerFunction(e.fr.stemmer,"stemmer-fr"),e.fr.stopWordFilter=e.generateStopWordFilter("ai aie aient aies ait as au aura aurai auraient aurais aurait auras aurez auriez aurions aurons auront aux avaient avais avait avec avez aviez avions avons ayant ayez ayons c ce ceci celà ces cet cette d dans de des du elle en es est et eu eue eues eurent eus eusse eussent eusses eussiez eussions eut eux eûmes eût eûtes furent fus fusse fussent fusses fussiez fussions fut fûmes fût fûtes ici il ils j je l la le les leur leurs lui m ma mais me mes moi mon même n ne nos notre nous on ont ou par pas pour qu que quel quelle quelles quels qui s sa sans se sera serai seraient serais serait seras serez seriez serions serons seront ses soi soient sois soit sommes son sont soyez soyons suis sur t ta te tes toi ton tu un une vos votre vous y à étaient étais était étant étiez étions été étée étées étés êtes".split(" ")),e.Pipeline.registerFunction(e.fr.stopWordFilter,"stopWordFilter-fr")}}); -------------------------------------------------------------------------------- /assets/javascripts/lunr/min/lunr.ro.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Lunr languages, `Romanian` language 3 | * https://github.com/MihaiValentin/lunr-languages 4 | * 5 | * Copyright 2014, Mihai Valentin 6 | * http://www.mozilla.org/MPL/ 7 | */ 8 | /*! 9 | * based on 10 | * Snowball JavaScript Library v0.3 11 | * http://code.google.com/p/urim/ 12 | * http://snowball.tartarus.org/ 13 | * 14 | * Copyright 2010, Oleg Mazko 15 | * http://www.mozilla.org/MPL/ 16 | */ 17 | 18 | !function(e,i){"function"==typeof define&&define.amd?define(i):"object"==typeof exports?module.exports=i():i()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.ro=function(){this.pipeline.reset(),this.pipeline.add(e.ro.trimmer,e.ro.stopWordFilter,e.ro.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.ro.stemmer))},e.ro.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.ro.trimmer=e.trimmerSupport.generateTrimmer(e.ro.wordCharacters),e.Pipeline.registerFunction(e.ro.trimmer,"trimmer-ro"),e.ro.stemmer=function(){var i=e.stemmerSupport.Among,r=e.stemmerSupport.SnowballProgram,n=new function(){function e(e,i){L.eq_s(1,e)&&(L.ket=L.cursor,L.in_grouping(W,97,259)&&L.slice_from(i))}function n(){for(var i,r;;){if(i=L.cursor,L.in_grouping(W,97,259)&&(r=L.cursor,L.bra=r,e("u","U"),L.cursor=r,e("i","I")),L.cursor=i,L.cursor>=L.limit)break;L.cursor++}}function t(){if(L.out_grouping(W,97,259)){for(;!L.in_grouping(W,97,259);){if(L.cursor>=L.limit)return!0;L.cursor++}return!1}return!0}function a(){if(L.in_grouping(W,97,259))for(;!L.out_grouping(W,97,259);){if(L.cursor>=L.limit)return!0;L.cursor++}return!1}function o(){var e,i,r=L.cursor;if(L.in_grouping(W,97,259)){if(e=L.cursor,!t())return void(h=L.cursor);if(L.cursor=e,!a())return void(h=L.cursor)}L.cursor=r,L.out_grouping(W,97,259)&&(i=L.cursor,t()&&(L.cursor=i,L.in_grouping(W,97,259)&&L.cursor=L.limit)return!1;L.cursor++}for(;!L.out_grouping(W,97,259);){if(L.cursor>=L.limit)return!1;L.cursor++}return!0}function c(){var e=L.cursor;h=L.limit,k=h,g=h,o(),L.cursor=e,u()&&(k=L.cursor,u()&&(g=L.cursor))}function s(){for(var e;;){if(L.bra=L.cursor,e=L.find_among(z,3))switch(L.ket=L.cursor,e){case 1:L.slice_from("i");continue;case 2:L.slice_from("u");continue;case 3:if(L.cursor>=L.limit)break;L.cursor++;continue}break}}function w(){return h<=L.cursor}function m(){return k<=L.cursor}function l(){return g<=L.cursor}function f(){var e,i;if(L.ket=L.cursor,(e=L.find_among_b(C,16))&&(L.bra=L.cursor,m()))switch(e){case 1:L.slice_del();break;case 2:L.slice_from("a");break;case 3:L.slice_from("e");break;case 4:L.slice_from("i");break;case 5:i=L.limit-L.cursor,L.eq_s_b(2,"ab")||(L.cursor=L.limit-i,L.slice_from("i"));break;case 6:L.slice_from("at");break;case 7:L.slice_from("aţi")}}function p(){var e,i=L.limit-L.cursor;if(L.ket=L.cursor,(e=L.find_among_b(P,46))&&(L.bra=L.cursor,m())){switch(e){case 1:L.slice_from("abil");break;case 2:L.slice_from("ibil");break;case 3:L.slice_from("iv");break;case 4:L.slice_from("ic");break;case 5:L.slice_from("at");break;case 6:L.slice_from("it")}return _=!0,L.cursor=L.limit-i,!0}return!1}function d(){var e,i;for(_=!1;;)if(i=L.limit-L.cursor,!p()){L.cursor=L.limit-i;break}if(L.ket=L.cursor,(e=L.find_among_b(F,62))&&(L.bra=L.cursor,l())){switch(e){case 1:L.slice_del();break;case 2:L.eq_s_b(1,"ţ")&&(L.bra=L.cursor,L.slice_from("t"));break;case 3:L.slice_from("ist")}_=!0}}function b(){var e,i,r;if(L.cursor>=h){if(i=L.limit_backward,L.limit_backward=h,L.ket=L.cursor,e=L.find_among_b(q,94))switch(L.bra=L.cursor,e){case 1:if(r=L.limit-L.cursor,!L.out_grouping_b(W,97,259)&&(L.cursor=L.limit-r,!L.eq_s_b(1,"u")))break;case 2:L.slice_del()}L.limit_backward=i}}function v(){var e;L.ket=L.cursor,(e=L.find_among_b(S,5))&&(L.bra=L.cursor,w()&&1==e&&L.slice_del())}var _,g,k,h,z=[new i("",-1,3),new i("I",0,1),new i("U",0,2)],C=[new i("ea",-1,3),new i("aţia",-1,7),new i("aua",-1,2),new i("iua",-1,4),new i("aţie",-1,7),new i("ele",-1,3),new i("ile",-1,5),new i("iile",6,4),new i("iei",-1,4),new i("atei",-1,6),new i("ii",-1,4),new i("ului",-1,1),new i("ul",-1,1),new i("elor",-1,3),new i("ilor",-1,4),new i("iilor",14,4)],P=[new i("icala",-1,4),new i("iciva",-1,4),new i("ativa",-1,5),new i("itiva",-1,6),new i("icale",-1,4),new i("aţiune",-1,5),new i("iţiune",-1,6),new i("atoare",-1,5),new i("itoare",-1,6),new i("ătoare",-1,5),new i("icitate",-1,4),new i("abilitate",-1,1),new i("ibilitate",-1,2),new i("ivitate",-1,3),new i("icive",-1,4),new i("ative",-1,5),new i("itive",-1,6),new i("icali",-1,4),new i("atori",-1,5),new i("icatori",18,4),new i("itori",-1,6),new i("ători",-1,5),new i("icitati",-1,4),new i("abilitati",-1,1),new i("ivitati",-1,3),new i("icivi",-1,4),new i("ativi",-1,5),new i("itivi",-1,6),new i("icităi",-1,4),new i("abilităi",-1,1),new i("ivităi",-1,3),new i("icităţi",-1,4),new i("abilităţi",-1,1),new i("ivităţi",-1,3),new i("ical",-1,4),new i("ator",-1,5),new i("icator",35,4),new i("itor",-1,6),new i("ător",-1,5),new i("iciv",-1,4),new i("ativ",-1,5),new i("itiv",-1,6),new i("icală",-1,4),new i("icivă",-1,4),new i("ativă",-1,5),new i("itivă",-1,6)],F=[new i("ica",-1,1),new i("abila",-1,1),new i("ibila",-1,1),new i("oasa",-1,1),new i("ata",-1,1),new i("ita",-1,1),new i("anta",-1,1),new i("ista",-1,3),new i("uta",-1,1),new i("iva",-1,1),new i("ic",-1,1),new i("ice",-1,1),new i("abile",-1,1),new i("ibile",-1,1),new i("isme",-1,3),new i("iune",-1,2),new i("oase",-1,1),new i("ate",-1,1),new i("itate",17,1),new i("ite",-1,1),new i("ante",-1,1),new i("iste",-1,3),new i("ute",-1,1),new i("ive",-1,1),new i("ici",-1,1),new i("abili",-1,1),new i("ibili",-1,1),new i("iuni",-1,2),new i("atori",-1,1),new i("osi",-1,1),new i("ati",-1,1),new i("itati",30,1),new i("iti",-1,1),new i("anti",-1,1),new i("isti",-1,3),new i("uti",-1,1),new i("işti",-1,3),new i("ivi",-1,1),new i("ităi",-1,1),new i("oşi",-1,1),new i("ităţi",-1,1),new i("abil",-1,1),new i("ibil",-1,1),new i("ism",-1,3),new i("ator",-1,1),new i("os",-1,1),new i("at",-1,1),new i("it",-1,1),new i("ant",-1,1),new i("ist",-1,3),new i("ut",-1,1),new i("iv",-1,1),new i("ică",-1,1),new i("abilă",-1,1),new i("ibilă",-1,1),new i("oasă",-1,1),new i("ată",-1,1),new i("ită",-1,1),new i("antă",-1,1),new i("istă",-1,3),new i("ută",-1,1),new i("ivă",-1,1)],q=[new i("ea",-1,1),new i("ia",-1,1),new i("esc",-1,1),new i("ăsc",-1,1),new i("ind",-1,1),new i("ând",-1,1),new i("are",-1,1),new i("ere",-1,1),new i("ire",-1,1),new i("âre",-1,1),new i("se",-1,2),new i("ase",10,1),new i("sese",10,2),new i("ise",10,1),new i("use",10,1),new i("âse",10,1),new i("eşte",-1,1),new i("ăşte",-1,1),new i("eze",-1,1),new i("ai",-1,1),new i("eai",19,1),new i("iai",19,1),new i("sei",-1,2),new i("eşti",-1,1),new i("ăşti",-1,1),new i("ui",-1,1),new i("ezi",-1,1),new i("âi",-1,1),new i("aşi",-1,1),new i("seşi",-1,2),new i("aseşi",29,1),new i("seseşi",29,2),new i("iseşi",29,1),new i("useşi",29,1),new i("âseşi",29,1),new i("işi",-1,1),new i("uşi",-1,1),new i("âşi",-1,1),new i("aţi",-1,2),new i("eaţi",38,1),new i("iaţi",38,1),new i("eţi",-1,2),new i("iţi",-1,2),new i("âţi",-1,2),new i("arăţi",-1,1),new i("serăţi",-1,2),new i("aserăţi",45,1),new i("seserăţi",45,2),new i("iserăţi",45,1),new i("userăţi",45,1),new i("âserăţi",45,1),new i("irăţi",-1,1),new i("urăţi",-1,1),new i("ârăţi",-1,1),new i("am",-1,1),new i("eam",54,1),new i("iam",54,1),new i("em",-1,2),new i("asem",57,1),new i("sesem",57,2),new i("isem",57,1),new i("usem",57,1),new i("âsem",57,1),new i("im",-1,2),new i("âm",-1,2),new i("ăm",-1,2),new i("arăm",65,1),new i("serăm",65,2),new i("aserăm",67,1),new i("seserăm",67,2),new i("iserăm",67,1),new i("userăm",67,1),new i("âserăm",67,1),new i("irăm",65,1),new i("urăm",65,1),new i("ârăm",65,1),new i("au",-1,1),new i("eau",76,1),new i("iau",76,1),new i("indu",-1,1),new i("ându",-1,1),new i("ez",-1,1),new i("ească",-1,1),new i("ară",-1,1),new i("seră",-1,2),new i("aseră",84,1),new i("seseră",84,2),new i("iseră",84,1),new i("useră",84,1),new i("âseră",84,1),new i("iră",-1,1),new i("ură",-1,1),new i("âră",-1,1),new i("ează",-1,1)],S=[new i("a",-1,1),new i("e",-1,1),new i("ie",1,1),new i("i",-1,1),new i("ă",-1,1)],W=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,2,32,0,0,4],L=new r;this.setCurrent=function(e){L.setCurrent(e)},this.getCurrent=function(){return L.getCurrent()},this.stem=function(){var e=L.cursor;return n(),L.cursor=e,c(),L.limit_backward=e,L.cursor=L.limit,f(),L.cursor=L.limit,d(),L.cursor=L.limit,_||(L.cursor=L.limit,b(),L.cursor=L.limit),v(),L.cursor=L.limit_backward,s(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}}(),e.Pipeline.registerFunction(e.ro.stemmer,"stemmer-ro"),e.ro.stopWordFilter=e.generateStopWordFilter("acea aceasta această aceea acei aceia acel acela acele acelea acest acesta aceste acestea aceşti aceştia acolo acord acum ai aia aibă aici al ale alea altceva altcineva am ar are asemenea asta astea astăzi asupra au avea avem aveţi azi aş aşadar aţi bine bucur bună ca care caut ce cel ceva chiar cinci cine cineva contra cu cum cumva curând curînd când cât câte câtva câţi cînd cît cîte cîtva cîţi că căci cărei căror cărui către da dacă dar datorită dată dau de deci deja deoarece departe deşi din dinaintea dintr- dintre doi doilea două drept după dă ea ei el ele eram este eu eşti face fata fi fie fiecare fii fim fiu fiţi frumos fără graţie halbă iar ieri la le li lor lui lângă lîngă mai mea mei mele mereu meu mi mie mine mult multă mulţi mulţumesc mâine mîine mă ne nevoie nici nicăieri nimeni nimeri nimic nişte noastre noastră noi noroc nostru nouă noştri nu opt ori oricare orice oricine oricum oricând oricât oricînd oricît oriunde patra patru patrulea pe pentru peste pic poate pot prea prima primul prin puţin puţina puţină până pînă rog sa sale sau se spate spre sub sunt suntem sunteţi sută sînt sîntem sînteţi să săi său ta tale te timp tine toate toată tot totuşi toţi trei treia treilea tu tăi tău un una unde undeva unei uneia unele uneori unii unor unora unu unui unuia unul vi voastre voastră voi vostru vouă voştri vreme vreo vreun vă zece zero zi zice îi îl îmi împotriva în înainte înaintea încotro încât încît între întrucât întrucît îţi ăla ălea ăsta ăstea ăştia şapte şase şi ştiu ţi ţie".split(" ")),e.Pipeline.registerFunction(e.ro.stopWordFilter,"stopWordFilter-ro")}}); --------------------------------------------------------------------------------