├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── critical-bug.md │ └── feature_request.md ├── pull_request_template.md └── workflows │ ├── python-package.yml │ └── python-publish.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs ├── README.md ├── assets │ └── banner.png ├── docs │ ├── blog.md │ ├── cli.md │ └── index.md ├── mkdocs.yml └── polyglot-result.png ├── polyglot ├── __init__.py ├── __main__.py ├── arguments │ ├── __init__.py │ ├── arguments.py │ └── position.py ├── core │ ├── __init__.py │ ├── beautify.py │ ├── display.py │ ├── extension.py │ ├── ignore.py │ ├── path.py │ ├── polyglot.py │ ├── project.py │ ├── result.py │ └── tree.py ├── exceptions │ ├── __init__.py │ ├── custom.py │ └── exceptions.py ├── ext │ ├── __init__.py │ ├── dir.py │ ├── env.py │ ├── extensions.py │ └── json.py └── path.py ├── requirements.txt ├── setup.py ├── test.py └── tests ├── __init__.py ├── context.py ├── test_arguments.py ├── test_file.py └── test_polyglot.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: pranavbaburaj 5 | 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 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 | **Desktop (please complete the following information):** 27 | 28 | - OS: [e.g. iOS] 29 | - Browser [e.g. chrome, safari] 30 | - Version [e.g. 22] 31 | 32 | **Smartphone (please complete the following information):** 33 | 34 | - Device: [e.g. iPhone6] 35 | - OS: [e.g. iOS8.1] 36 | - Browser [e.g. stock browser, safari] 37 | - Version [e.g. 22] 38 | 39 | **Additional context** 40 | Add any other context about the problem here. 41 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/critical-bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Critical Bug 3 | about: Issues that should be fixed at the earliest 4 | title: Critical Bug 5 | labels: bug 6 | assignees: pranavbaburaj 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 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/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Related Issue 2 | 5 | 6 | Closes: #[issue number that will be closed through this PR] 7 | 8 | 9 | ## Describe the changes you've made 10 | 13 | 14 | 15 | ## Checklist: 16 | 17 | 21 | 22 | - [ ] My code follows the style guidelines of this project. 23 | - [ ] I have performed a self-review of my own code. 24 | - [ ] I have commented my code, particularly in hard-to-understand areas. 25 | - [ ] I have made corresponding changes to the documentation. 26 | - [ ] My changes generate no new warnings. 27 | 28 | ## Screenshots 29 | 30 | | Original | Updated | 31 | | :---------------------: | :------------------------: | 32 | | **original screenshot** | updated screenshot | -------------------------------------------------------------------------------- /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Python package 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | python-version: [3.7, 3.8, 3.9] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Set up Python ${{ matrix.python-version }} 24 | uses: actions/setup-python@v2 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | - name: Install dependencies 28 | run: | 29 | python -m pip install --upgrade pip 30 | python -m pip install flake8 pytest 31 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 32 | - name: Lint with flake8 33 | run: | 34 | # stop the build if there are Python syntax errors or undefined names 35 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 36 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 37 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 38 | 39 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | name: Upload Python Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | deploy: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up Python 18 | uses: actions/setup-python@v2 19 | with: 20 | python-version: '3.x' 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install setuptools wheel twine 25 | - name: Build and publish 26 | env: 27 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 28 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 29 | run: | 30 | python setup.py sdist bdist_wheel 31 | twine upload dist/* 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | testing_json_storage.json 10 | 11 | ignore.polyglot 12 | 13 | .vscode/ 14 | 15 | .vscode/ 16 | language.yml 17 | language.yaml 18 | 19 | # Distribution / packaging 20 | .Python 21 | build/ 22 | develop-eggs/ 23 | dist/ 24 | downloads/ 25 | eggs/ 26 | .eggs/ 27 | lib/ 28 | lib64/ 29 | parts/ 30 | sdist/ 31 | var/ 32 | wheels/ 33 | pip-wheel-metadata/ 34 | share/python-wheels/ 35 | *.egg-info/ 36 | .installed.cfg 37 | *.egg 38 | MANIFEST 39 | 40 | # PyInstaller 41 | # Usually these files are written by a python script from a template 42 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 43 | *.manifest 44 | *.spec 45 | 46 | # Installer logs 47 | pip-log.txt 48 | pip-delete-this-directory.txt 49 | 50 | # Unit test / coverage reports 51 | htmlcov/ 52 | .tox/ 53 | .nox/ 54 | .coverage 55 | .coverage.* 56 | .cache 57 | nosetests.xml 58 | coverage.xml 59 | *.cover 60 | *.py,cover 61 | .hypothesis/ 62 | .pytest_cache/ 63 | 64 | # Translations 65 | *.mo 66 | *.pot 67 | 68 | # Django stuff: 69 | *.log 70 | local_settings.py 71 | db.sqlite3 72 | db.sqlite3-journal 73 | 74 | # Flask stuff: 75 | instance/ 76 | .webassets-cache 77 | 78 | # Scrapy stuff: 79 | .scrapy 80 | 81 | # Sphinx documentation 82 | docs/_build/ 83 | 84 | # PyBuilder 85 | target/ 86 | 87 | # Jupyter Notebook 88 | .ipynb_checkpoints 89 | 90 | # IPython 91 | profile_default/ 92 | ipython_config.py 93 | 94 | # pyenv 95 | .python-version 96 | 97 | # pipenv 98 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 99 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 100 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 101 | # install all needed dependencies. 102 | #Pipfile.lock 103 | 104 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 105 | __pypackages__/ 106 | 107 | # Celery stuff 108 | celerybeat-schedule 109 | celerybeat.pid 110 | 111 | # SageMath parsed files 112 | *.sage.py 113 | 114 | # Environments 115 | .env 116 | .venv 117 | env/ 118 | venv/ 119 | ENV/ 120 | env.bak/ 121 | venv.bak/ 122 | 123 | # Spyder project settings 124 | .spyderproject 125 | .spyproject 126 | 127 | # Rope project settings 128 | .ropeproject 129 | 130 | # mkdocs documentation 131 | /site 132 | 133 | # mypy 134 | .mypy_cache/ 135 | .dmypy.json 136 | dmypy.json 137 | 138 | # Pyre type checker 139 | .pyre/ 140 | 141 | manifest.json -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | - Demonstrating empathy and kindness toward other people 21 | - Being respectful of differing opinions, viewpoints, and experiences 22 | - Giving and gracefully accepting constructive feedback 23 | - Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | - Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | - The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | - Trolling, insulting or derogatory comments, and personal or political attacks 33 | - Public or private harassment 34 | - Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | - Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | . 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Clone the repo 2 | Start by cloning the repo 3 | ```ps1 4 | git clone https://github.com/pranavbaburaj/polyglot.git 5 | ``` 6 | 7 | ## Requirements 8 | - `Python` - Download and install python from the [official download page](https://www.python.org/downloads/) 9 | - `PIP` - PIP is the python package manager. Read more about it [here](https://pip.pypa.io/en/stable/user_guide/) 10 | 11 | ## Install the requirements 12 | ```ps1 13 | pip install -r requirements.txt 14 | ``` 15 | 16 | ## Formatting the code 17 | Once you have finished adding the changes, make sure to format your code with formatter. It is recommended to use [`black`](https://github.com/psf/black) 18 | 19 | ```ps1 20 | <# 21 | Install the black code formatter 22 | using pip 23 | #> 24 | pip install black 25 | 26 | <# 27 | Format the code using black 28 | #> 29 | black . 30 | ``` 31 | 32 | ## Create a pull request 33 | You can read more about pull requests [here](https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request) 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Pranav Baburaj 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

5 | 6 |

Polyglot

7 | 8 |

9 | Find the percentage of programming languages used in your project 10 |
11 | 📖 Documentation 12 | · 13 | Report a Bug 14 | · 15 | Request Feature 16 |

17 |
18 |

19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |

27 | 28 | 29 |
30 | 31 |

32 | 33 | 34 | ## About The Project 35 | 36 | Find the percentage of programming languages used in your project 37 | 38 | 39 | 40 | ## ⚡ Getting Started 41 | 42 | In order to get started, please make sure that you have [`python`](https://python.org) and `pip` installed on your computer. 43 | 44 | 45 | ### ⬇️ Installation 46 | 47 | - Install pip packages 48 | 49 | ```sh 50 | # install the python-polyglot package using pip 51 | 52 | pip3 install python-polyglot 53 | # or 54 | pip install python-polyglot 55 | ``` 56 | 57 | ## 🎉 Usage 58 | 59 | Once Polyglot is all setup and good to go, implementing is easy as pie. 60 | 61 | ### 🔰 Initial Setup 62 | 63 | You can initialize Polyglot with the example below: 64 | 65 | ```python 66 | from polyglot.core import Polyglot 67 | 68 | dirname = "path/to/directory" 69 | 70 | polyglot = Polyglot(dirname) 71 | polyglot.show(display=True) 72 | ``` 73 | 74 | Read the complete polyglot documentation [here](https://github.com/pranavbaburaj/polyglot/blob/main/docs/README.md) 75 | 76 |
77 | 78 | 79 | > Read more about the documentation [here](https://github.com/pranavbaburaj/polyglot/blob/main/docs/README.md) 80 | 81 | 109 | 110 | 111 | 112 | ## Contributing 113 | 114 | Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. 115 | 116 | 1. [Fork](https://github.com/pranavbaburaj/polyglot/fork) the Project 117 | 2. Create your Feature Branch (`git checkout -b feature`) 118 | 3. Commit your Changes (`git commit -m 'Add some features'`) 119 | 4. Push to the Branch (`git push origin feature`) 120 | 5. Open a Pull Request 121 | 122 | 123 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Docs 2 | The documentation is divided into two parts. 3 | - [The Library documentation](./docs/index.md) 4 | - [The CLI documentation](./docs/cli.md) -------------------------------------------------------------------------------- /docs/assets/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lainq/polyglot/0a9b889bf02445f7b810e05587551ad0defc13b4/docs/assets/banner.png -------------------------------------------------------------------------------- /docs/docs/blog.md: -------------------------------------------------------------------------------- 1 | I've recently been working on a side project called `polyglot`. Polyglot is a python module that finds the percentage of different programming languages used in your project. 2 | 3 | {% github pranavbaburaj/polyglot no-readme %} 4 | You can check it out on Github and also drop a star. 5 | 6 | ## Get Started 7 | In order to get started, you will need to have python and pip installed on your system. 8 | 9 | - Check the versions of `python` and `pip` 10 | ``` 11 | python -v 12 | pip -v 13 | ``` 14 | - Install `python-polyglot` using `pip` 15 | 16 | To install `python-polyglot` in your system, use 17 | ``` 18 | pip install python-polyglot 19 | ``` 20 | 21 | ## How to use it 22 | Once Polyglot is all set up and good to go, implementing is easy as pie. 23 | ```python 24 | from polyglot.core import Polyglot 25 | 26 | # dot(.) represents the current working directory 27 | dirname = "." or "path/to/dir" 28 | 29 | poly = Polyglot(".") 30 | poly.show() 31 | 32 | ``` 33 | This prints out something similar 34 | ``` 35 | +-------------------------+-------+ 36 | | Language | files | 37 | +-------------------------+-------+ 38 | | Ignore List | 5.88 | 39 | | GCC Machine Description | 11.76 | 40 | | Unknown | 5.88 | 41 | | Text | 5.88 | 42 | | Python | 64.71 | 43 | | JSON | 5.88 | 44 | +-------------------------+-------+ 45 | 46 | 47 | +-------------------------+-------+ 48 | | Language | lines | 49 | +-------------------------+-------+ 50 | | Ignore List | 17.22 | 51 | | GCC Machine Description | 22.24 | 52 | | Unknown | 2.83 | 53 | | Text | 0.26 | 54 | | Python | 57.07 | 55 | | JSON | 0.39 | 56 | +-------------------------+-------+ 57 | ``` 58 | 59 | ### `Ignores` 60 | The `ignore` option is used to ignore specific files in the directory tree. For instance, if you don't want the `JSON` files to appear in the table, you can add the `.json` extension to a `polyglot-ignore` file and pass it as a parameter while creating the polyglot instance. 61 | 62 | - `Polyglot Ignores` 63 | Polyglot ignores are used to ignore 64 | specific files in the directory tree. They 65 | should have a `.polyglot` file extension. 66 | Polyglot Ignores as similar to gitignores 67 | and are easy to write with almost the same 68 | syntax. 69 | 70 | - `Writing a Polyglot ignore.` 71 | Create a `test.polyglot` file and add the 72 | files to ignore 73 | ```rb 74 | # for a specific file extension 75 | .json 76 | 77 | # for a specific folder 78 | dist/ 79 | 80 | # for a specific file 81 | dub.sdl 82 | LICENSE 83 | 84 | # for specific folders in the directory 85 | ~.tox 86 | ``` 87 | Once you have an ignore file, use it with polyglot like this 88 | ```python 89 | poly = Polyglot(dirname, ignore="test.polyglot") 90 | ``` 91 | 92 | ### `Arguments` 93 | ```python 94 | from polyglot.arugments import Arguments 95 | ``` 96 | The Polyglot Arguments is used to parse a list of arguments(`sys.argv[1:]` by default) and perform actions related to Polyglot. 97 | 98 | - You can either pass in arguments manually 99 | ```python 100 | args = Arguments(arguments=[ 101 | "--show=True", "--dir=.", "--o=out.json", "--ignore=test.polyglot" 102 | ], return_value=False) 103 | ``` 104 | or leave it blank to parse the command line arguments passed in along with the file 105 | ```python 106 | args = Arguments() 107 | ``` 108 | 109 | 110 | - Start the argument parser 111 | ```python 112 | args.parse() 113 | ``` 114 | 115 | The command-line parser has four main options, 116 | `--dir`(default:`current directory`) - The directory path 117 | `--show`(default:`True`) - Whether to display the table or not 118 | `--o`(default:`None`) - Outputs the data as JSON in the file 119 | `--ignore`(default:`None`) - The ignore file 120 | 121 | An example usage 122 | ```] 123 | python -B .py --dir=. --show=False 124 | ``` 125 | 126 |
127 | 128 | Please star the project on GitHub if you like it. And thank you for scrolling. 129 | -------------------------------------------------------------------------------- /docs/docs/cli.md: -------------------------------------------------------------------------------- 1 | # CLI 2 | The polyglot module comes along with a command line application. The cli can be accessed using `polyglot` or `pgt` 3 | 4 | ## `Stats` 5 | ```ps1 6 | polyglot stats 7 | ``` 8 | #### Parameters 9 | - `--dir`:The directory to check the stats 10 | - Default : Current directory 11 | - `--ignore`: Files to ignore 12 | - Defult: By default the cli searches for files with a `.polyglot`extension 13 | - `--detect`: The language detection file 14 | - `--fmt`: `l` for comparison based on loc, `f` for files. 15 | - `--output`: Store the stats output into a file. Only json files and toml files are allowed. 16 | 17 | ```ps1 18 | polyglot stats --dir=/home/Documents --ignore --fmt=l --output=output.toml 19 | ``` 20 | 21 | ## `Tree` 22 | ```ps1 23 | polyglot tree 24 | ``` 25 | 26 | #### Parameters 27 | - `--dir`: The directory. Set to the current directory by default. 28 | 29 | ## `dir` or `ls` 30 | ```ps1 31 | polyglot ls 32 | polyglot dir 33 | ``` 34 | 35 | ## `up` 36 | Update your cli 37 | ``` 38 | polyglot up 39 | ``` 40 | 41 | 42 | -------------------------------------------------------------------------------- /docs/docs/index.md: -------------------------------------------------------------------------------- 1 | # Polyglot 2 | 3 |
4 | 5 | ## `Polyglot` 6 | 7 | The `polyglot.core.Polyglot` is the main class of polyglot the polyglot module. 8 | 9 | ```python 10 | from polyglot.core import Polyglot 11 | ``` 12 | 13 | Initialize a polyglot instance 14 | 15 | ```python 16 | polyglot = Polyglot("path/to/directory", ignore="example.polyglot") 17 | ``` 18 | 19 | Polyglot takes in to parameters, the path to the directory and the ingore file 20 | 21 | - directory_name(**required**) - `string` The path of the directory 22 | - ignore - The ignore filename 23 |
24 | The ignore file should have a `.polyglot` file extension and has a syntax similar to a `.gitignore` file. 25 | - `.` for file extensions 26 | - `/` for folders 27 | - `` for files 28 | ```rb 29 | # for a specific file extension 30 | .json 31 | 32 | # for a specific folder 33 | dist/ 34 | 35 | # for a specific file 36 | dub.sdl 37 | LICENSE 38 | 39 | # for specific folders in the directory 40 | ~.tox 41 | ``` 42 | 43 | 44 | and use the file with the polyglot object 45 | ```python 46 | poly = Polyglot(".", "example.polyglot") 47 | ``` 48 | 49 | Getting information from the polyglot object 50 | 51 | ```python 52 | polyglot.show(language_detection_file="language.yml", display=True) 53 | ``` 54 | 55 | The `show` method takes in two parameters, the language detection file as well as the disply option 56 | 57 | - language_detection_file(**optional**) - `string` The yaml file containing information about all the languages. By default, the `language_detection_file` is set to `None`. and the file is downloaded from the internet. 58 | - display(**optional**) - `bool` Whether to output the table on the console or not. The show method returns a dict containing information about the files. 59 | 60 | ## `Tree` 61 | The `polyglot.core.tree` module helps us to generate a tree of the current directory 62 | ```py 63 | from polyglot.core.tree import Tree 64 | 65 | tree = Tree("path/to/directory").generate() 66 | ``` 67 | 68 | ```ps1 69 | ├── colors.ts 70 | ├── gists 71 | │ ├── gist.ts 72 | │ └── new.ts 73 | ├── interface.ts 74 | ├── json 75 | │ └── colors.json 76 | ├── repos.ts 77 | └── user 78 | └── user.ts 79 | ``` 80 | 81 | ## `Arguments` 82 | 83 | The `polyglot.arguments.Arguments` helps you parse a set of arguments and execute functions accordingly. 84 | 85 | ```python 86 | from polyglot.arugments import Arguments 87 | ``` 88 | 89 | passing in arguments 90 | 91 | ```python 92 | args = Arguments(arguments=[], return_value=False) 93 | args.parse() 94 | ``` 95 | 96 | - arguments(**optional**) - `list` The set of arguments. By default, the arguments in set to `sys.argv[1:]`. 97 | - return_value(**optional**) - `bool` Whether to return anything or not 98 | 99 | ## `Project` 100 | The `polyglot.core.project.Project` is used to generate folders and files. 101 | 102 | To create a `Project` object 103 | ```python 104 | from polyglot.core.project import Project, ProjectFiles 105 | 106 | # `.` for the current directory 107 | project = Project("project-name", ProjectFiles( 108 | files=["file1", "dir1/file1"], 109 | folders=["dir2", "some-unknown-folder"] 110 | )) 111 | ``` 112 | To generate the directories 113 | ```py 114 | project.create(clean=False) 115 | ``` 116 | The `create` function takes in the `clean` parameter which determines whether to clean the project directory if it already exists. The default value is set to `False` 117 | 118 | ## `polyglot.ext` 119 | 120 | ### `Env` 121 | The `polyglot.ext.env.Env` helps to load variables defined in a `.env` into the process environment variables 122 | ```py 123 | from polyglot.ext.env import Env 124 | env = Env() 125 | ``` 126 | 127 | ### `directory` or `ls` 128 | The `polyglot.ext.dir` outputs something similar to an `ls` command in linux. 129 | ```python 130 | from polyglot.ext.dir import directory, ls 131 | 132 | directory("path/to/folder") 133 | # or 134 | ls("path/to/folder") 135 | 136 | ``` 137 | -------------------------------------------------------------------------------- /docs/mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Polyglot 2 | nav : 3 | - Home : index.md 4 | - Blog : blog.md 5 | theme: material 6 | -------------------------------------------------------------------------------- /docs/polyglot-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lainq/polyglot/0a9b889bf02445f7b810e05587551ad0defc13b4/docs/polyglot-result.png -------------------------------------------------------------------------------- /polyglot/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2021 Pranav Baburaj 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | """ 25 | 26 | from .core import Polyglot 27 | 28 | __version__ = "4.2.9" 29 | -------------------------------------------------------------------------------- /polyglot/__main__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import json 4 | import pathlib 5 | import pip 6 | 7 | from clint.textui import colored 8 | 9 | from polyglot.core.polyglot import Polyglot 10 | from polyglot.core.project import Project, ProjectFiles 11 | from polyglot.core.tree import Tree 12 | from polyglot.ext.dir import ls 13 | 14 | COMMANDS = ["stats", "project", "tree", "dir", "ls", "help", "up"] 15 | DESCRIPTIONS = [ 16 | "--dir= --ignore= --detect= --fmt= --output=", 17 | "--manifest=", 18 | "--dir=", 19 | "--dir=", 20 | "--dir=", 21 | "", 22 | "", 23 | ] 24 | 25 | 26 | class EventLogger(object): 27 | @staticmethod 28 | def info(message): 29 | print(colored.cyan(f"INFO:{message}")) 30 | 31 | @staticmethod 32 | def warning(message): 33 | print(colored.yellow(f"WARNING:{message}")) 34 | 35 | @staticmethod 36 | def error(message): 37 | print(colored.red(f"ERROR:{message}")) 38 | 39 | 40 | class CommandLineException(object): 41 | def __init__(self, message, suggestion=None, fatal=True): 42 | self.message = message 43 | self.suggestion = suggestion 44 | self.is_fatal = fatal 45 | 46 | self.create_exception() 47 | 48 | def create_exception(self): 49 | """Create an exception and exit the program if 50 | the exception is fatal 51 | """ 52 | print(colored.red(f"ERROR:{self.message}")) 53 | if self.suggestion: 54 | print(colored.yellow(self.suggestion.strip())) 55 | if self.is_fatal: 56 | sys.exit(1) 57 | 58 | 59 | class ArgumentParser(object): 60 | class _ArgumentParserResults(object): 61 | def __init__(self, command, parameters): 62 | self.command = command 63 | self.parameters = parameters 64 | 65 | def __init__(self, arguments): 66 | self.arguments = arguments 67 | 68 | def create_argument_parser(self): 69 | """ 70 | Parse the arguments into different commands 71 | and parameters. The first element in the 72 | argv list is considered to be the command. 73 | 74 | All the following elements should start with -- 75 | and are considered as parameters 76 | """ 77 | command, parameters = None, {} 78 | for index, current in enumerate(self.arguments): 79 | if index == 0: 80 | command = current 81 | continue 82 | 83 | if not current.startswith("--"): 84 | exception = CommandLineException( 85 | f"Invalid parameter {current}", 86 | f"Parameters should start with double hiphens ", 87 | ) 88 | 89 | statement = current.split("=") 90 | parameter_key, value = statement[0], -2 91 | if len(statement) > 1: 92 | value = "=".join(map(str, statement[1:])) 93 | parameters.setdefault(parameter_key, value) 94 | return self._ArgumentParserResults(command, parameters) 95 | 96 | 97 | class HelpMessage(object): 98 | def __init__(self, commands, descriptions): 99 | assert isinstance(commands, list) 100 | self.commands = commands 101 | self.descriptions = descriptions 102 | 103 | self.create_help_string() 104 | 105 | def create_help_string(self): 106 | statements = ["usage: polyglot =", ""] 107 | 108 | for index, command in enumerate(self.commands): 109 | statements.append( 110 | f"{command}{self.spaces(command, COMMANDS)} -> {self.descriptions[index]}" 111 | ) 112 | print("\n".join(statements)) 113 | 114 | def spaces(self, command, commands): 115 | largest = max( 116 | [len(commands[current_index]) for current_index in range(len(commands))] 117 | ) 118 | return "".join([" " for index in range(largest - len(command))]) 119 | 120 | 121 | class LanguageStats(object): 122 | def __init__(self, parameters): 123 | self.directory = parameters.get("--dir") or os.getcwd() 124 | self.ignore = parameters.get("--ignore") or -1 125 | self.language_file = ( 126 | None 127 | if (parameters.get("--detect") or None) == -2 128 | else parameters.get("--detect") 129 | ) 130 | self.fmt = parameters.get("--fmt") 131 | self.output = parameters.get("--output") or None 132 | 133 | if self.fmt not in ["l", "f", "L", "F"]: 134 | self.fmt = None 135 | else: 136 | self.fmt = self.fmt.lower() 137 | 138 | if self.ignore == -2: 139 | self.ignore = self.__find_ignore_file() 140 | else: 141 | self.ignore = None 142 | 143 | try: 144 | polyglot = Polyglot(self.directory, self.ignore) 145 | polyglot.show(self.language_file, True, self.fmt, self.output) 146 | except Exception as exception: 147 | EventLogger.error(exception.__str__()) 148 | sys.exit(1) 149 | 150 | def __find_ignore_file(self): 151 | if not os.path.isdir(self.directory): 152 | _ = CommandLineException(f"{self.directory} is not a directory") 153 | return None 154 | files = list( 155 | filter( 156 | lambda current_element: current_element.endswith(".polyglot"), 157 | os.listdir(self.directory), 158 | ) 159 | ) 160 | if len(files) == 0: 161 | EventLogger.error(f"Could not find an ignore file in {self.directory}") 162 | return None 163 | 164 | if len(files) > 1: 165 | EventLogger.warning(f"Found {len(files)} ignore files") 166 | 167 | ignore_filename = files[0] 168 | EventLogger.info(f"{ignore_filename} is taken as the ignore file") 169 | return ignore_filename 170 | 171 | 172 | class ListDirectories(object): 173 | def __init__(self, directory, only_dirs): 174 | self.directory = directory 175 | self.dirs = only_dirs 176 | 177 | self.list_directory_content() 178 | 179 | def list_directory_content(self): 180 | for filename in self.content: 181 | current_path = os.path.join(self.directory, filename) 182 | size = f"[size:{os.stat(current_path).st_size} bytes]" 183 | color = colored.green if os.path.isfile(current_path) else colored.blue 184 | print(f"{color(filename)} -> {colored.yellow(size)}") 185 | 186 | @property 187 | def content(self): 188 | return list(filter(self.file_filter_function, os.listdir(self.directory))) 189 | 190 | def file_filter_function(self, filename): 191 | if self.dirs: 192 | return os.path.isdir(filename) 193 | return True 194 | 195 | 196 | def search_for_manifest(manifest_filename): 197 | filename = os.path.join(os.getcwd(), manifest_filename) 198 | if not os.path.isfile(filename): 199 | _ = CommandLineException(f"{manifest_filename} does not exist") 200 | try: 201 | with open(filename, "r") as file_reader: 202 | return json.load(file_reader) 203 | except Exception as exception: 204 | CommandLineException(exception.__str__()) 205 | 206 | 207 | def command_executor(results): 208 | command, params = results.command, results.parameters 209 | command_directory = params.get("--dir") or os.getcwd() 210 | if command_directory == -2: 211 | command_directory = os.getcwd() 212 | if not os.path.isdir(command_directory): 213 | EventLogger.error(f"{command_directory} is not a directory") 214 | return None 215 | if command == "stats": 216 | _ = LanguageStats(params) 217 | elif command == "project": 218 | manifest_file = params.get("--manifest") or "manifest.json" 219 | if manifest_file == -2: 220 | manifest_file = "manifest.json" 221 | manifest_data = search_for_manifest(manifest_file) 222 | 223 | name, files, folders = ( 224 | manifest_data.get("name") or ".", 225 | manifest_data.get("files") or {}, 226 | manifest_data.get("directories") or manifest_data.get("folders") or [], 227 | ) 228 | try: 229 | project = Project(name, ProjectFiles(files, folders)) 230 | project.create() 231 | except Exception as ProjectException: 232 | EventLogger.error(ProjectException.__str__()) 233 | sys.exit(1) 234 | elif command == "tree": 235 | directory = params.get("--dir") or os.getcwd() 236 | if directory == -2: 237 | directory = os.getcwd() 238 | if not os.path.isdir(directory): 239 | EventLogger.error(f"{directory} is not a directory") 240 | return None 241 | 242 | try: 243 | tree = Tree(directory) 244 | tree.generate() 245 | except Exception as tree_exception: 246 | EventLogger.error(tree_exception.__str__()) 247 | sys.exit(1) 248 | elif command == "dir": 249 | ls(command_directory) 250 | elif command == "ls": 251 | dirs = ListDirectories(command_directory, params.get("--only-dirs")) 252 | elif command == "help": 253 | help = HelpMessage(COMMANDS, DESCRIPTIONS) 254 | elif command == "up": 255 | pip.main(["install", "python-polyglot", "--upgrade"]) 256 | 257 | 258 | class Properties(object): 259 | def __init__(self, path): 260 | assert os.path.exists(path), f"{path} does not exist" 261 | self.path = self.find_file_path(path) 262 | self.properties_command() 263 | 264 | def find_file_path(self, path): 265 | if path == ".": 266 | return os.getcwd() 267 | elif path == "..": 268 | return os.path.dirname(path) 269 | 270 | return path 271 | 272 | def properties_command(self): 273 | print( 274 | colored.green(os.path.basename(self.path), bold=True), 275 | colored.yellow(f"[{self.file_type}]"), 276 | ) 277 | self.draw_seperator() 278 | 279 | properties = self.properties 280 | for property in properties: 281 | print( 282 | colored.cyan(f"{property} -> "), 283 | colored.yellow(properties.get(property)), 284 | ) 285 | 286 | def draw_seperator(self): 287 | length = len(self.basename) + (len(self.file_type) + 3) 288 | for index in range(length): 289 | print(colored.yellow("-"), end=("\n" if index + 1 == length else "")) 290 | 291 | @property 292 | def properties(self): 293 | return { 294 | "type": self.file_type, 295 | "extension": self.file_extension, 296 | "parent": self.find_file_path(pathlib.Path(self.path).parent.__str__()), 297 | "size": os.stat(self.path).st_size, 298 | } 299 | 300 | @property 301 | def file_extension(self): 302 | if os.path.isdir(self.path): 303 | return "" 304 | split_path = self.basename.split(".") 305 | if len(split_path[0]) == 0 or split_path[0] == self.basename: 306 | return "" 307 | return split_path[-1] 308 | 309 | @property 310 | def basename(self): 311 | return os.path.basename(self.path) 312 | 313 | @property 314 | def file_type(self): 315 | return "DIR" if os.path.isdir(self.path) else "FILE" 316 | 317 | 318 | def main(): 319 | arguments = sys.argv[1:] 320 | if len(arguments) == 0: 321 | return 1 322 | argument_parser = ArgumentParser(arguments) 323 | results = argument_parser.create_argument_parser() 324 | if not results.command: 325 | return 1 326 | 327 | if results.command.strip().__len__() == 0: 328 | return 1 329 | 330 | if results.command not in COMMANDS: 331 | try: 332 | Properties(results.command) 333 | except AssertionError as exception: 334 | EventLogger.error(exception.__str__()) 335 | sys.exit() 336 | command_executor(results) 337 | 338 | 339 | if __name__ == "__main__": 340 | exit_status = main() 341 | if exit_status == 1: 342 | HelpMessage(COMMANDS, DESCRIPTIONS) 343 | -------------------------------------------------------------------------------- /polyglot/arguments/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2021 Pranav Baburaj 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | """ 25 | 26 | from .arguments import Arguments 27 | -------------------------------------------------------------------------------- /polyglot/arguments/arguments.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import json 4 | 5 | from polyglot.arguments.position import Position 6 | from polyglot.exceptions.custom import PolyglotException 7 | from polyglot.core.polyglot import Polyglot 8 | 9 | 10 | class Arguments(object): 11 | """ 12 | Pass a set of arguments which is parsed and 13 | later executed 14 | 15 | Attributes- 16 | arguments -- The arguments to parse 17 | return_value -- Whether to return any value 18 | position -- The lexer postion 19 | 20 | """ 21 | 22 | def __init__(self, arguments=None, return_value=False): 23 | self.arguments = sys.argv[1:] if not arguments else arguments 24 | self.return_value = return_value 25 | 26 | self.position = Position(0) 27 | 28 | def parse(self): 29 | """ 30 | Parse the arguments and validate 31 | them . Also execute the functions 32 | """ 33 | assert isinstance(self.arguments, list) 34 | valid_flags = ["--dir", "--o", "--show", "--ignore"] 35 | 36 | parameters = {"dir": os.getcwd(), "o": None, "show": str(True), "ignore": ""} 37 | 38 | current_character = self.position.current_character(self.arguments) 39 | while current_character is not None: 40 | if not self.is_valid_flag(valid_flags, current_character): 41 | exception = PolyglotException( 42 | f"{current_character} is not recogonised as a valid parmeter", 43 | f"Try again with valid parameters", 44 | fatal=False, 45 | ) 46 | return None 47 | character_key = current_character[2:] 48 | if "=" not in character_key: 49 | exception = PolyglotException( 50 | f"User equal-to(=) to add value to parameters", 51 | f"Try again with valid values", 52 | fatal=False, 53 | ) 54 | return None 55 | 56 | if character_key.count("=") > 1: 57 | exception = PolyglotException( 58 | f"More than one assignments for the same parameter", 59 | f"Try again with valid values", 60 | fatal=False, 61 | ) 62 | return None 63 | 64 | data = character_key.split("=") 65 | key, value = data[0], data[1] 66 | 67 | parameters[key] = value 68 | 69 | self.position.increment() 70 | current_character = self.position.current_character(self.arguments) 71 | 72 | return_data = self.validate_parameters(parameters) 73 | if return_data == None: 74 | return return_data 75 | else: 76 | polyglot = Polyglot(parameters["dir"], ignore=parameters["ignore"]) 77 | data = polyglot.show(display=parameters["show"]) 78 | 79 | if parameters["o"]: 80 | with open(parameters["o"], "w") as writer: 81 | writer.write(json.dumps(data)) 82 | 83 | if self.return_value: 84 | return data 85 | 86 | def is_valid_flag(self, valid_cases, current_character): 87 | for valid_case_index in range(len(valid_cases)): 88 | if current_character.startswith(valid_cases[valid_case_index]): 89 | return True 90 | 91 | return False 92 | 93 | def validate_parameters(self, parameters): 94 | if parameters["show"] not in [str(True), str(False)]: 95 | exception = PolyglotException( 96 | "Invalid value for paramter show", "Try again", fatal=False 97 | ) 98 | return None 99 | 100 | parameters["show"] = bool(parameters["show"]) 101 | parameters["ignore"] = parameters["ignore"].split(",") 102 | return parameters 103 | -------------------------------------------------------------------------------- /polyglot/arguments/position.py: -------------------------------------------------------------------------------- 1 | class Position(object): 2 | def __init__(self, initial_position): 3 | assert isinstance(initial_position, int), "Expected an integer" 4 | self.position = initial_position 5 | 6 | def increment(self, increment_by=1): 7 | self.position += 1 8 | return self.position 9 | 10 | def decrement(self, decrement_by=1): 11 | self.position -= 1 12 | return self.position 13 | 14 | def current_character(self, data, increment_value=False): 15 | assert isinstance(data, list), "Data expected to be a string" 16 | if len(data) == self.position: 17 | return None 18 | 19 | return_value = data[self.position] 20 | if increment_value: 21 | self.increment() 22 | 23 | return return_value 24 | -------------------------------------------------------------------------------- /polyglot/core/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2021 Pranav Baburaj 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | """ 25 | 26 | from .polyglot import Polyglot 27 | -------------------------------------------------------------------------------- /polyglot/core/beautify.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | from clint.textui import colored 4 | from collections.abc import Iterable 5 | 6 | from polyglot.path import DirectoryError 7 | 8 | 9 | class _ExtensionsionType: 10 | required_type = Iterable 11 | 12 | def __init__(self, placeholder): 13 | self.value = placeholder 14 | self.check_value_type(self.value) 15 | 16 | def check_value_type(self, value): 17 | """ 18 | Make sure that the key value is an 19 | iterable, either a tuple, list, string 20 | or dictionary 21 | 22 | Args: 23 | value (any): The value to check the type of 24 | 25 | Raises: 26 | TypeError: Raises a TypeError when the value is not 27 | an iterable 28 | """ 29 | if not isinstance(value, self.required_type): 30 | raise TypeError(colored.red(f"{value} not an iterable")) 31 | 32 | def __repr__(self): 33 | return self.value 34 | 35 | 36 | class ExtensionMap(object): 37 | def __init__(self, extensions={}): 38 | assert isinstance(extensions, dict), "Extensions expected to be a dict" 39 | self.extensions = {} 40 | self.__add_all_extensions(extensions) 41 | 42 | def __add_all_extensions(self, extensions): 43 | """ 44 | Add all the extensions from the dict passed 45 | in as a parameter 46 | 47 | Args: 48 | extensions (dict): The dict to copy 49 | """ 50 | for key in extensions: 51 | _ExtensionsionType(extensions[key]) 52 | self.extensions = extensions 53 | 54 | def add(self, folder, extensions): 55 | """ 56 | Add a new folder to the extension map with its 57 | associated extension array 58 | 59 | Args: 60 | folder (string): The name of the folder 61 | extensions (Iterable): The extensions to search for 62 | 63 | Raises: 64 | KeyError: Raises an error when the key already 65 | exists in the map 66 | """ 67 | if self.extensions.get(folder): 68 | raise KeyError(f"{folder} already exists in the map") 69 | 70 | _ExtensionsionType(extensions) 71 | self.extensions.setdefault(folder, extensions) 72 | 73 | def remove(self, folder): 74 | if folder not in self.extensions: 75 | raise KeyError(f"{folder} does not exist") 76 | 77 | del self.extensions[folder] 78 | 79 | def get(self): 80 | return self.extensions 81 | 82 | def __repr__(self): 83 | return str(self.extensions) 84 | 85 | def __str__(self): 86 | return self.__repr__() 87 | 88 | def __len__(self): 89 | return len(self.extensions) 90 | 91 | 92 | class _Prompt(object): 93 | def __init__(self, prompt, options, require=True): 94 | self.prompt = prompt 95 | self.options = options 96 | self.require = require 97 | 98 | def create_prompt(self): 99 | data = None 100 | while data not in self.options: 101 | data = input(f"{self.prompt} (y/n) ") 102 | return data == "y" 103 | 104 | 105 | class _Logs(object): 106 | def __init__(self, log): 107 | self.log_messages = log 108 | self.counter = 0 109 | 110 | def log(self, message, critical=False): 111 | color = colored.red if critical else colored.green 112 | if self.log_messages: 113 | self.counter += 1 114 | print(color(f"LOG[{self.counter}] {message}")) 115 | 116 | 117 | class Beautify(object): 118 | def __init__(self, directory, extensions, prompt=True, log=True): 119 | assert isinstance( 120 | extensions, ExtensionMap 121 | ), "extensions expected to be an extension map" 122 | 123 | self.directory = self.__is_directory(directory) 124 | self.extensions = extensions 125 | self.log = _Logs(log) 126 | 127 | self.clean_directory(prompt) 128 | 129 | def clean_directory(self, prompt): 130 | if prompt: 131 | data = _Prompt( 132 | "Do you want to clear the directory", ["y", "n"] 133 | ).create_prompt() 134 | if data: 135 | self.__clean() 136 | 137 | return None 138 | 139 | self.__clean() 140 | 141 | def __clean(self): 142 | replace_files = {} 143 | data = self.extensions.get() 144 | for filename in os.listdir(self.directory): 145 | folder = self.__get_extension_folder(filename, data) 146 | if not folder: 147 | continue 148 | 149 | path = os.path.join(self.directory, folder) 150 | move_location = os.path.join(path, filename) 151 | 152 | if not os.path.exists(path) or not os.path.isdir(path): 153 | os.mkdir(path) 154 | 155 | shutil.move(os.path.join(self.directory, filename), move_location) 156 | self.log.log( 157 | f"Moved Successfully [{os.path.join(self.directory, filename)} => {move_location}]" 158 | ) 159 | 160 | def __get_extension_folder(self, extension, data): 161 | for foldername in data: 162 | extension_list = data[foldername] 163 | for file_extension in extension_list: 164 | if extension.endswith(file_extension): 165 | return foldername 166 | return None 167 | 168 | def __is_directory(self, directory): 169 | is_directory = os.path.isdir(directory) 170 | if not is_directory: 171 | raise DirectoryError(f"{directory} not a directory") 172 | 173 | return directory 174 | -------------------------------------------------------------------------------- /polyglot/core/display.py: -------------------------------------------------------------------------------- 1 | from prettytable import PrettyTable 2 | from clint.textui import colored 3 | 4 | 5 | class Display(object): 6 | def __init__(self, display_text): 7 | assert isinstance(display_text, dict), "Expected a dict" 8 | self.text = display_text 9 | 10 | if not "files" in self.text or not "lines" in self.text: 11 | raise NameError("Cannot find required keys - lines, files") 12 | 13 | self.display_output() 14 | 15 | def display_output(self): 16 | """ 17 | Verify the dict elements to be dicts 18 | and then continue to print out 19 | the dict data in tabular form using 20 | prettytable. 21 | """ 22 | self.verify_text() 23 | 24 | for display_text_type in self.text: 25 | if bool(self.text[display_text_type]): 26 | print("\n") 27 | table = PrettyTable() 28 | table.field_names = [ 29 | "Language", 30 | display_text_type.capitalize(), 31 | "Total", 32 | "Blank", 33 | ] 34 | 35 | for data in self.text[display_text_type]: 36 | current_data = self.text[display_text_type][data] 37 | current_row = [ 38 | data, 39 | current_data.get("data"), 40 | current_data.get("total"), 41 | current_data.get("blank"), 42 | ] 43 | 44 | table.add_rows([current_row]) 45 | print(colored.yellow(table)) 46 | 47 | def verify_text(self): 48 | assert isinstance(self.text["files"], dict), "Files expected to be a dict" 49 | assert isinstance(self.text["lines"], dict), "lines expected to be a dict" 50 | -------------------------------------------------------------------------------- /polyglot/core/extension.py: -------------------------------------------------------------------------------- 1 | import os 2 | import requests 3 | import yaml 4 | 5 | from polyglot.core.path import LanguageJSON 6 | 7 | LANGUAGE_FILE = "https://raw.githubusercontent.com/github/linguist/master/lib/linguist/languages.yml" 8 | 9 | 10 | def validate_argument_types(values, types, message): 11 | assert len(values) == len(types), "Values and types should have the same length" 12 | for index in range(len(values)): 13 | assert isinstance(values[index], types[index]), str(message) 14 | return True 15 | 16 | 17 | def install_files(read_url, write_file_dir, filename, extension): 18 | assert isinstance(read_url, str), "Read url expected to be a string" 19 | assert isinstance(write_file_dir, str), "Write path expected to be a string" 20 | 21 | filename = os.path.join(write_file_dir, f"{filename}.{extension}") 22 | try: 23 | with open(filename, "wb") as file_writer: 24 | file_writer.write(requests.get(read_url, allow_redirects=True).content) 25 | except Exception as exception: 26 | raise Exception 27 | 28 | return filename 29 | 30 | 31 | class Extensions(object): 32 | def __init__(self, language_file, display, files): 33 | 34 | self.language_detection_file = language_file 35 | self.display_output = display 36 | self.filenames = files 37 | 38 | self.languages = {} 39 | 40 | self.content = self.remove_unwanted_keys( 41 | self.__create_language_file(self.language_detection_file)[0] 42 | ) 43 | 44 | def get_extension_data(self): 45 | return self.__split_files(self.filenames, self.content) 46 | 47 | def __split_files(self, files, content): 48 | """ 49 | Loop through each file in the files array 50 | and determine the language with the help of 51 | the language extension 52 | """ 53 | for filename in files: 54 | language = self.__find_language_name(filename, content) 55 | if language not in self.languages: 56 | self.languages[language] = [] 57 | 58 | self.languages[language].append(filename) 59 | return self.languages 60 | 61 | def __find_language_name(self, filename, content): 62 | extension = f".{filename.split('.')[-1]}" 63 | for language_key in content: 64 | if "extensions" not in content[language_key]: 65 | continue 66 | 67 | if extension in content[language_key]["extensions"]: 68 | return language_key 69 | 70 | return "Unknown file" 71 | 72 | def remove_unwanted_keys(self, file_content): 73 | """ 74 | Remove all the unwanted keys from the 75 | file_content dictionary and only keep 76 | the 'extensions' key 77 | """ 78 | for language in dict(file_content): 79 | assert isinstance(file_content[language], dict), "Expected a dict" 80 | for key in dict(file_content[language]): 81 | if key != "extensions": 82 | del file_content[language][key] 83 | return file_content 84 | 85 | def __create_language_file(self, language_file): 86 | """ 87 | If language file is mentioned, and the file is a string 88 | return the filecontent and the number of lines 89 | 90 | Else, install the language file from the internet 91 | and return the file_content along with the number of lines 92 | """ 93 | if language_file is not None and isinstance(language_file, str): 94 | if ( 95 | not language_file.endswith(".yml") 96 | and not language_file.endswith(".json") 97 | and not language_file.endswith(".yaml") 98 | ): 99 | raise Exception("Language file expected to be a yaml or json file") 100 | 101 | if language_file.endswith(".json"): 102 | filename = LanguageJSON(language_file).convert_to_yaml() 103 | return Extensions.read_file_data(filename, True) 104 | else: 105 | return Extensions.read_file_data(language_file, True) 106 | 107 | return Extensions.read_file_data( 108 | install_files(LANGUAGE_FILE, os.getcwd(), "language", "yml"), True 109 | ) 110 | 111 | @staticmethod 112 | def read_file_data(filename, is_yaml=False): 113 | """ 114 | Read the specified filename and if the file 115 | is a yaml file,parse the yaml string 116 | using the yaml library 117 | """ 118 | with open(filename, "r") as file_reader: 119 | file_content = file_reader.read() 120 | line_number_count = len(file_content.split("\n")) 121 | 122 | if not is_yaml: 123 | return file_content, line_number_count 124 | 125 | return yaml.safe_load(file_content), line_number_count 126 | -------------------------------------------------------------------------------- /polyglot/core/ignore.py: -------------------------------------------------------------------------------- 1 | import os 2 | from clint.textui import colored 3 | 4 | 5 | class IgnoreFileError(FileNotFoundError): 6 | def __init__(self, message_data): 7 | self.error_message = colored.red(message_data.strip()) 8 | super().__init__(self.error_message) 9 | 10 | 11 | class PolyglotExtensionError(Exception): 12 | def __init__(self, message_data): 13 | self.error_message = colored.red(message_data.strip()) 14 | super().__init__(self.error_message) 15 | 16 | 17 | class Ignore(object): 18 | def __init__(self, ignore_list_filename): 19 | assert isinstance(ignore_list_filename, str) 20 | 21 | self.ignore_files = [] 22 | self.ignore_list_filename = ignore_list_filename 23 | self.ignore_data = self.read_file(self.ignore_list_filename) 24 | 25 | self.files = None 26 | 27 | def create_ignore_files(self, files, directory): 28 | """ 29 | Update the ignore files list 30 | based on information from the polyglot ignore 31 | file 32 | """ 33 | self.files = files 34 | for ignore_data_line in self.ignore_data: 35 | if ignore_data_line.startswith("."): 36 | self.__find_file_extension(ignore_data_line) 37 | elif ignore_data_line.endswith("/"): 38 | self.find_dir_files(ignore_data_line[:-1]) 39 | elif ignore_data_line.startswith("~"): 40 | self.add_root_dirs(ignore_data_line[1:], directory) 41 | else: 42 | self.add_files(ignore_data_line) 43 | 44 | self.add_root_dirs(".git", directory) 45 | return self.ignore_files 46 | 47 | def add_root_dirs(self, root, dirname): 48 | root_directory = os.path.join(dirname, root) 49 | if not self.files: 50 | return None 51 | 52 | for filename in self.files: 53 | if filename.startswith(root_directory): 54 | self.ignore_files.append(filename) 55 | 56 | def find_dir_files(self, directory_name): 57 | """ 58 | Get all the files with the directory_name 59 | as the parent directory 60 | """ 61 | if not self.files: 62 | return None 63 | for filename in self.files: 64 | dirname = os.path.basename(os.path.dirname(filename)) 65 | if dirname == directory_name: 66 | self.ignore_files.append(filename) 67 | 68 | def add_files(self, data_line): 69 | """ 70 | Added other files with the basename as 71 | the data_line 72 | """ 73 | if not self.files: 74 | return None 75 | for filename in self.files: 76 | if os.path.basename(filename) == data_line: 77 | self.ignore_files.append(filename) 78 | 79 | def __find_file_extension(self, extension): 80 | """ 81 | Add all the files with the specific 82 | file extension 83 | """ 84 | if not self.files: 85 | return None 86 | for filename in self.files: 87 | if filename.endswith(extension): 88 | self.ignore_files.append(filename) 89 | 90 | @staticmethod 91 | def __find_all_files(ignore_files, ignore_extensions, files): 92 | for filename_index in range(len(files)): 93 | current_filename = files[filename_index] 94 | file_extension = current_filename.split(".")[-1] 95 | if current_filename.enswith(file_extension): 96 | ignore_files.append(current_filename) 97 | return ignore_files 98 | 99 | def read_file(self, read_file_name, ignore_text=True): 100 | """ 101 | Valiate a file and read the file 102 | """ 103 | if not os.path.exists(read_file_name) or not os.path.isfile(read_file_name): 104 | raise IgnoreFileError(f"{read_file_name} is not a valid file") 105 | 106 | if ignore_text and not read_file_name.endswith(".polyglot"): 107 | raise PolyglotExtensionError( 108 | f"Ignore files require to have a .polyglot file extension" 109 | ) 110 | 111 | with open(read_file_name, "r") as file_reader: 112 | file_data = file_reader.read().split("\n") 113 | 114 | if not ignore_text: 115 | return file_data 116 | 117 | return Ignore.remove_specific_list_element( 118 | [ 119 | (filename if len(filename.strip()) > 0 else None) 120 | for filename in file_data 121 | ], 122 | [None], 123 | ) 124 | 125 | @staticmethod 126 | def remove_specific_list_element(list_data, remove_element): 127 | """ 128 | Remove a specific list of elements from another 129 | list and return the new list 130 | """ 131 | assert isinstance(list_data, list) 132 | return_array = [] 133 | for element in list_data: 134 | if element not in remove_element: 135 | return_array.append(element) 136 | 137 | return return_array 138 | -------------------------------------------------------------------------------- /polyglot/core/path.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import yaml 4 | from clint.textui import colored 5 | 6 | 7 | class LanguageException(FileNotFoundError): 8 | def __init__(self, text): 9 | self.message = colored.red(text) 10 | super().__init__(self.message) 11 | 12 | 13 | class LanguageJSON(object): 14 | def __init__(self, json_path, rename=None): 15 | self.path = json_path 16 | self.rename = self.create_new_name(rename) 17 | 18 | def create_new_name(self, rename): 19 | if not isinstance(rename, str): 20 | filename = self.path.split(".")[0] 21 | return f"{filename}.yml" 22 | 23 | return rename 24 | 25 | def convert_to_yaml(self): 26 | if not os.path.exists(self.path): 27 | raise LanguageException(f"Cannot find {self.path}") 28 | with open(self.path, "r", encoding="utf8") as file_reader: 29 | data = json.loads(file_reader.read()) 30 | 31 | with open(self.path, "w", encoding="utf8") as yaml_writer: 32 | yaml.dump(data, yaml_writer, allow_unicode=True) 33 | 34 | os.rename(self.path, self.rename) 35 | return self.rename 36 | -------------------------------------------------------------------------------- /polyglot/core/polyglot.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import yaml 4 | import toml 5 | 6 | from polyglot.core.extension import Extensions 7 | from polyglot.core.result import Result 8 | from polyglot.core.display import Display 9 | from polyglot.core.ignore import Ignore 10 | 11 | from polyglot.exceptions.exceptions import PolyglotFileNotFoundError 12 | 13 | 14 | class Polyglot(object): 15 | """ 16 | The main polyglot class. An instance of this class 17 | is created by the user to use the core functions of 18 | the module 19 | 20 | Attributes: 21 | ignore -- The files to ignore 22 | directory -- The abspath of the directory to check 23 | files -- All the files inside of the directory 24 | 25 | """ 26 | 27 | def __init__(self, directory_name: str, ignore=None): 28 | assert ignore == None or isinstance( 29 | ignore, str 30 | ), "Expected to be a string or None" 31 | self.ignore = ignore 32 | self.directory = Polyglot.find_directory_path(directory_name) 33 | self.files = self.find_directory_files(self.directory) 34 | if self.ignore: 35 | self.files = Ignore.remove_specific_list_element( 36 | self.files, 37 | Ignore(self.ignore).create_ignore_files(self.files, self.directory), 38 | ) 39 | 40 | @staticmethod 41 | def find_directory_path(directory_path: str): 42 | """ 43 | Determine the directory path based on 44 | the parameter. If the path is a dot(.) return the 45 | current working directory, else return 46 | the path if the path is a directory, else 47 | throw an error 48 | """ 49 | assert isinstance(directory_path, str), "Path expected to be a string" 50 | if directory_path == ".": 51 | return os.getcwd() 52 | 53 | if os.path.isdir(directory_path): 54 | return directory_path 55 | 56 | raise PolyglotFileNotFoundError(f"{directory_path} does not exist") 57 | 58 | def __find_hidden_files(self, hidden, filepath): 59 | """ 60 | Make sure that the root of the file 61 | is not hidden 62 | """ 63 | hidden_root = [str(filepath).startswith(hidden_file) for hidden_file in hidden] 64 | return True in hidden_root 65 | 66 | def find_directory_files(self, directory): 67 | """ 68 | Find all the files by walking through 69 | the directory tree 70 | """ 71 | filenames = [] 72 | hidden_directories = [] 73 | for (root, dirs, files) in os.walk(directory, topdown=True): 74 | if not self.__find_hidden_files(hidden_directories, root): 75 | for filename in files: 76 | if filename.startswith(os.path.join(self.directory, ".git")): 77 | continue 78 | filenames.append(os.path.join(root, filename)) 79 | 80 | return filenames 81 | 82 | def show(self, language_detection_file=None, display=True, fmt=None, output=None): 83 | DEFAULT_LANGUAGE_DETECTION_FILE = "language.yml" 84 | if language_detection_file is None: 85 | for filename in os.listdir(os.getcwd()): 86 | if filename == DEFAULT_LANGUAGE_DETECTION_FILE and os.path.isfile( 87 | filename 88 | ): 89 | language_detection_file = os.path.join(os.getcwd(), filename) 90 | break 91 | 92 | extensions = Extensions(language_detection_file, display, self.files) 93 | data = extensions.get_extension_data() 94 | 95 | result = Result(data).show_file_information() 96 | if display and fmt is None: 97 | display_text = Display(result) 98 | elif display and fmt is not None: 99 | if fmt.lower() == "l": 100 | result["files"] = {} 101 | display_text = Display(result) 102 | elif fmt.lower() == "f": 103 | result["lines"] = {} 104 | display_text = Display(result) 105 | 106 | if isinstance(output, str): 107 | with open(output, mode="w", encoding="utf8") as output_logger: 108 | if output.endswith(".yml") or output.endswith(".yaml"): 109 | yaml.dump(result, output_logger, allow_unicode=True) 110 | elif output.endswith(".toml"): 111 | output_logger.write(toml.dumps(result)) 112 | else: 113 | output_logger.write(json.dumps(result, indent=4)) 114 | 115 | return result 116 | -------------------------------------------------------------------------------- /polyglot/core/project.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from polyglot.core import Polyglot 4 | 5 | 6 | class ProjectFiles(object): 7 | def __init__(self, files, folders): 8 | assert isinstance(files, dict), "Files expected to be a dict" 9 | self.files = files 10 | self.folders = folders 11 | 12 | 13 | class Project(object): 14 | def __init__(self, project_name, project_files, polyglot=False): 15 | assert isinstance( 16 | project_files, ProjectFiles 17 | ), "Parameter expected to be of type ProjectFiles" 18 | self.name = project_name 19 | self.files = project_files 20 | self.polyglot = polyglot 21 | 22 | if self.polyglot: 23 | self.files.files["ignore.polyglot"] = "language.yml" 24 | 25 | def create(self, clean=False): 26 | directory = self.__directory_path(self.name) 27 | if os.path.exists(directory) and os.path.isdir(directory): 28 | directory_length = len(os.listdir(directory)) 29 | if directory_length > 0: 30 | if not clean: 31 | raise FileExistsError(f"{directory} already exists") 32 | return None 33 | 34 | os.rmdir(directory) 35 | os.mkdir(directory) 36 | else: 37 | os.mkdir(directory) 38 | 39 | self.__create_project_files(directory) 40 | 41 | def __create_project_files(self, directory): 42 | for filename in self.files.files: 43 | self.write_file_data( 44 | os.path.join(directory, filename), self.files.files.get(filename) 45 | ) 46 | 47 | for folder in self.files.folders: 48 | if not os.path.isdir(folder): 49 | os.mkdir(os.path.join(directory, folder)) 50 | 51 | if self.polyglot: 52 | polyglot = Polyglot(directory, "ignore.polyglot") 53 | polyglot.show( 54 | display=False, output=os.path.join(directory, "polyglot.json") 55 | ) 56 | 57 | def write_file_data(self, filename, data=""): 58 | with open(filename, "w") as file_writer: 59 | file_writer.write(data) 60 | 61 | def __directory_path(self, project_name): 62 | assert isinstance(project_name, str), "Project name expected to be a string" 63 | if project_name == ".": 64 | return os.getcwd() 65 | 66 | return os.path.join(os.getcwd(), project_name) 67 | -------------------------------------------------------------------------------- /polyglot/core/result.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from polyglot.core.ignore import Ignore 4 | 5 | 6 | class Result(object): 7 | def __init__(self, file_information): 8 | assert isinstance(file_information, dict), "Expected a dict" 9 | 10 | self.file_information = file_information 11 | self.data = {"files": {}, "lines": {}} 12 | 13 | def show_file_information(self): 14 | files = self.__find_by_files(self.file_information) 15 | lines = self.__find_by_lines(self.file_information) 16 | 17 | return self.data 18 | 19 | def __find_by_files(self, data): 20 | length = sum([len(data[key]) for key in data]) 21 | for file_type in data: 22 | self.data["files"][file_type] = { 23 | "data": f"{round((len(data[file_type]) / length) * 100, 2)} %", 24 | "total": len(data[file_type]), 25 | "blank": len( 26 | Ignore.remove_specific_list_element( 27 | [ 28 | os.path.getsize(filename) == 0 29 | for filename in data[file_type] 30 | ], 31 | [False], 32 | ) 33 | ), 34 | } 35 | 36 | def __find_by_lines(self, data): 37 | lines = {} 38 | empty = {} 39 | for file_type in data: 40 | file_line_count = 0 41 | empty_line_count = 0 42 | for filename in data[file_type]: 43 | if not os.path.exists(filename): 44 | continue 45 | 46 | with open(filename, "r", errors="ignore") as line_counter: 47 | file_data = line_counter.read().split("\n") 48 | 49 | file_line_count += len(file_data) 50 | for line in file_data: 51 | if len(line.strip()) == 0: 52 | empty_line_count += 1 53 | 54 | lines[file_type] = file_line_count 55 | empty[file_type] = empty_line_count 56 | total_lines = sum([lines[key] for key in lines]) 57 | for line_key in data: 58 | self.data["lines"][line_key] = { 59 | "data": f"{round((lines[line_key] / total_lines) * 100, 2)} %", 60 | "total": lines.get(line_key), 61 | "blank": empty.get(line_key), 62 | } 63 | -------------------------------------------------------------------------------- /polyglot/core/tree.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pathlib 3 | import itertools 4 | import clint 5 | 6 | 7 | class UnknownPathError(FileNotFoundError): 8 | def __init__(self, error_message): 9 | self.message = clint.textui.colored.red(error_message) 10 | 11 | super.__init__(self.message) 12 | 13 | 14 | class Tree(object): 15 | space = " " 16 | branch = "│ " 17 | tree = "├── " 18 | last = "└── " 19 | 20 | def __init__(self, directory): 21 | self.directory = ( 22 | directory 23 | if self.__verify_directory_path( 24 | os.getcwd() if directory == "." else directory 25 | ) 26 | else None 27 | ) 28 | 29 | def __verify_directory_path(self, directory): 30 | if not os.path.exists(directory) or not os.path.isdir(directory): 31 | return False 32 | 33 | return True 34 | 35 | def generate(self, level=-1, limit_to_directories=False, length_limit=None): 36 | if not self.directory: 37 | raise UnknownPathError(f"Cannot find {self.directory}") 38 | 39 | path = pathlib.Path(self.directory) 40 | 41 | def inner(dir_path: pathlib.Path, prefix: str = "", level=-1): 42 | if not level: 43 | return 44 | if limit_to_directories: 45 | contents = [d for d in dir_path.iterdir() if d.is_dir()] 46 | else: 47 | contents = list(dir_path.iterdir()) 48 | pointers = [self.tree] * (len(contents) - 1) + [self.last] 49 | for pointer, path in zip(pointers, contents): 50 | root = os.path.abspath(os.path.dirname(path.absolute())) 51 | try: 52 | if path.is_dir(): 53 | if ".git" in str(path.absolute()): 54 | continue 55 | yield prefix + pointer + path.name 56 | extension = self.branch if pointer == self.tree else self.space 57 | yield from inner(path, prefix=prefix + extension, level=level - 1) 58 | elif not limit_to_directories: 59 | yield prefix + pointer + path.name 60 | except PermissionError: 61 | continue 62 | try: 63 | iterator = inner(path, level=level) 64 | for line in itertools.islice(iterator, length_limit): 65 | print(clint.textui.colored.cyan(line)) 66 | if next(iterator, None): 67 | print( 68 | clint.textui.colored.red( 69 | f"... length_limit, {length_limit}, reached, counted:" 70 | ) 71 | ) 72 | except KeyboardInterrupt: 73 | return None 74 | -------------------------------------------------------------------------------- /polyglot/exceptions/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2021 Pranav Baburaj 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | """ 25 | from .custom import PolyglotException 26 | from .exceptions import * 27 | -------------------------------------------------------------------------------- /polyglot/exceptions/custom.py: -------------------------------------------------------------------------------- 1 | import sys as sys 2 | import logging as logging 3 | import time as time 4 | from clint.textui import colored 5 | 6 | 7 | def stop_current_application(exit_reason=None, set_timeout=0): 8 | assert isinstance(set_timeout, int), "Expected an integer" 9 | if exit_reason is not None: 10 | logging.error(f"Exiting application [{exit_reason}]") 11 | 12 | time.sleep(set_timeout) 13 | sys.exit() 14 | 15 | 16 | class PolyglotException(object): 17 | def __init__(self, error_message, suggestion=None, timeout=None, fatal=True): 18 | assert self.is_valid_timeout(timeout), "Timeout expected to be an integer" 19 | assert isinstance(fatal, bool), "Fatal expected a boolean value" 20 | 21 | self.error_message = str(error_message) 22 | self.error_is_fatal = fatal 23 | 24 | self.suggestion = suggestion 25 | self.create_exception_message(timeout) 26 | 27 | def create_exception_message(self, timeout=None): 28 | if timeout is not None: 29 | time.sleep(timeout) 30 | 31 | self.suggestion = self.create_suggestion_message(self.suggestion) 32 | self.throw_exception(self.error_message, self.suggestion) 33 | 34 | def throw_exception(self, error, suggestion): 35 | throw_exception_data = [ 36 | colored.red(f"ERROR: {error}"), 37 | colored.green(suggestion), 38 | ] 39 | for element in throw_exception_data: 40 | print(element) 41 | 42 | if self.error_is_fatal: 43 | stop_current_application() 44 | 45 | def is_valid_timeout(self, timeout): 46 | return isinstance(timeout, int) or timeout == None 47 | 48 | def create_suggestion_message(self, suggestion): 49 | if suggestion == None: 50 | return None 51 | 52 | return f"HELP: {suggestion}" 53 | 54 | def __len__(self): 55 | return len(str(self.error_message)) 56 | 57 | def __str__(self): 58 | return str(self.error_message) 59 | 60 | def __int__(self): 61 | return self.__len__() 62 | 63 | def __bool__(self): 64 | return bool(self.error_is_fatal) 65 | -------------------------------------------------------------------------------- /polyglot/exceptions/exceptions.py: -------------------------------------------------------------------------------- 1 | from clint.textui import colored 2 | 3 | 4 | class PolyglotFileNotFoundError(FileNotFoundError): 5 | def __init__(self, message): 6 | self.message = colored.red(message) 7 | 8 | super().__init__(self.message) 9 | 10 | def str(self): 11 | return self.message 12 | -------------------------------------------------------------------------------- /polyglot/ext/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2021 Pranav Baburaj 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | """ 25 | -------------------------------------------------------------------------------- /polyglot/ext/dir.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import prettytable 4 | 5 | 6 | class _Directory(object): 7 | def __init__(self, path=os.getcwd(), display=True): 8 | self.path = path 9 | self.show = display 10 | 11 | def create(self): 12 | data = self.__create_data() 13 | table = prettytable.PrettyTable() 14 | table.field_names = ["Modified at", "label", "size", "path"] 15 | for key in data: 16 | table.add_row( 17 | [ 18 | data[key].get("modified"), 19 | data[key].get("label"), 20 | data[key].get("size"), 21 | key, 22 | ] 23 | ) 24 | 25 | if self.show: 26 | print(table) 27 | return data 28 | 29 | def __create_data(self): 30 | data = {} 31 | files = os.listdir(self.path) 32 | data["."] = self.__generate_data(self.path) 33 | data[".."] = self.__generate_data(os.path.dirname(self.path)) 34 | 35 | for file_index in range(len(files)): 36 | filename = files[file_index] 37 | data.setdefault( 38 | os.path.basename(filename), 39 | self.__generate_data(os.path.join(self.path, filename)), 40 | ) 41 | 42 | return data 43 | 44 | def __generate_data(self, path): 45 | return { 46 | "modified": time.ctime(os.path.getmtime(path)), 47 | "label": "" if os.path.isdir(path) else "", 48 | "size": self.__get_file_length(path) if os.path.isfile(path) else "", 49 | } 50 | 51 | def __get_file_length(self, path): 52 | try: 53 | with open(path, "rb") as file_reader: 54 | return len(file_reader.read()) 55 | except Exception as exception: 56 | return "" 57 | 58 | 59 | def directory(path=os.getcwd(), display=True): 60 | final_directory_path = path 61 | if final_directory_path == ".": 62 | final_directory_path = os.getcwd() 63 | elif final_directory_path == "..": 64 | final_directory_path = os.path.dirname(os.getcwd()) 65 | 66 | return _Directory(final_directory_path, display).create() 67 | 68 | 69 | def ls(path=os.getcwd(), display=True): 70 | data = directory(path, display) 71 | return data 72 | -------------------------------------------------------------------------------- /polyglot/ext/env.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from clint.textui import colored 4 | 5 | 6 | def find_token_type(value): 7 | if value.isdigit(): 8 | return (int, float) 9 | 10 | return str 11 | 12 | 13 | class EnvAssignmentError(Exception): 14 | def __init__(self, error, line_number): 15 | self.message = error.strip() 16 | self.line = line_number 17 | 18 | error_message = colored.red(f"{self.message} at line {self.line}") 19 | super().__init__(error_message) 20 | 21 | 22 | class InvalidVariableName(EnvAssignmentError): 23 | def __init__(self, message, line): 24 | super().__init__(message, line) 25 | 26 | 27 | class Tokens(object): 28 | COMMENT_TOKEN = "#" 29 | 30 | 31 | class EnvironmentVariable(object): 32 | def __init__(self, variable, value, token_type, line_number): 33 | self.line = line_number 34 | 35 | self.variable = self.__check_variable_name(variable) 36 | self.value = value 37 | self.token_type = token_type 38 | 39 | def __check_variable_name(self, name): 40 | if len(name) == 0: 41 | raise InvalidVariableName(f"Invalid variable name {name}", self.line) 42 | first = name[0] 43 | if first.isdigit(): 44 | raise InvalidVariableName( 45 | f"Variable name {name} starts with a number", self.line 46 | ) 47 | 48 | return name 49 | 50 | 51 | class EnvParserPosition(object): 52 | def __init__(self, position=0): 53 | self.position = position 54 | 55 | def increment(self, increment_by=1): 56 | self.position += increment_by 57 | 58 | def decrement(self, decrement_by=1): 59 | self.position += -decrement_by 60 | 61 | def current_character(self, data): 62 | if len(data) == self.position: 63 | return None 64 | 65 | return data[self.position] 66 | 67 | 68 | class EnvParser(object): 69 | tokens = [] 70 | 71 | def __init__(self, source, line): 72 | self.source = source.strip() 73 | self.line_number = line + 1 74 | self.position = EnvParserPosition(0) 75 | self.character = self.position.current_character(self.source) 76 | 77 | def create_parser_tokens(self): 78 | if len(self.source) == 0 or self.source.startswith(Tokens.COMMENT_TOKEN): 79 | return [] 80 | 81 | assignment_counts = self.source.count("=") 82 | if assignment_counts > 1 or assignment_counts == 0: 83 | raise EnvAssignmentError("Multiple or no assignments ", self.line_number) 84 | 85 | name, value = self.source.split("=") 86 | token_type = find_token_type(value) 87 | existing_variables = list( 88 | filter(lambda list_element: list_element.variable == name, self.tokens) 89 | ) 90 | if not len(existing_variables) == 0: 91 | raise InvalidVariableName( 92 | f"Duplicate variable name {name}", self.line_number 93 | ) 94 | token = EnvironmentVariable(name, value, token_type, self.line_number) 95 | self.tokens.append(token) 96 | 97 | return self.tokens 98 | 99 | def update(self): 100 | self.position.increment(1) 101 | self.character = self.position.current_character(self.source) 102 | 103 | 104 | 105 | class Env(object): 106 | default_filename = os.path.join(os.getcwd(), ".env") 107 | 108 | def __init__(self, env=None, load=True, file=None): 109 | assert isinstance(env, str) or env == None, "Unexpected type of parameter env" 110 | self.env = env or os.path.join(os.path.dirname(file), ".env") or self.defualt_filename 111 | self.load_to_process = load 112 | 113 | def load(self): 114 | data = self.__read(self.env).split("\n") 115 | tokens = [] 116 | for line_number in range(len(data)): 117 | parser = EnvParser(data[line_number], line_number) 118 | token_data = parser.create_parser_tokens() 119 | for token_element in token_data: 120 | tokens.append(token_element) 121 | 122 | for token_element in tokens: 123 | os.environ.setdefault(token_element.variable, token_element.value) 124 | 125 | def __read(self, filename): 126 | if not os.path.exists(filename) and os.path.isfile(filename): 127 | raise FileNotFoundError(f"{filename} does not exist") 128 | 129 | with open(filename, mode="r") as env_file_reader: 130 | return env_file_reader.read() 131 | -------------------------------------------------------------------------------- /polyglot/ext/extensions.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | class _FilterResults(object): 5 | def __init__(self, result, count=None): 6 | self.result = result 7 | self.count = count 8 | 9 | def __repr__(self): 10 | return f"Result:{self.result}" 11 | 12 | 13 | def __filter_extension_handler(file, extension): 14 | file_extension = f".{file.split('.')[-1:][0]}" 15 | return file.endswith(extension) 16 | 17 | 18 | def filter_by_extension(extension, directory=os.getcwd(), count=True): 19 | files = list( 20 | filter( 21 | lambda file: __filter_extension_handler(file, extension), 22 | os.listdir(directory), 23 | ) 24 | ) 25 | results = _FilterResults(files) 26 | if count: 27 | results.count = len(files) 28 | return results 29 | -------------------------------------------------------------------------------- /polyglot/ext/json.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import uuid 4 | 5 | class UseAscii(object): 6 | @staticmethod 7 | def string_to_ascii_string(string, separator="."): 8 | assert "__str__" in dir(string) 9 | string_form, return_value = str(string), "" 10 | for current_character in string_form: 11 | return_value += str(UseAscii.get_ascii_value(current_character)) + separator 12 | return return_value 13 | 14 | @staticmethod 15 | def get_ascii_value(character): 16 | try: 17 | return ord(character) 18 | except Exception as exception: 19 | return -1 20 | 21 | @staticmethod 22 | def ascii_string_to_string(ascii_string, separator="."): 23 | return_value = "" 24 | for character in list(filter(lambda element: element.strip(),ascii_string.split(separator))): 25 | if len(character) == 0:continue 26 | value = int(character) 27 | if value == -1:continue 28 | return_value += chr(value) 29 | return return_value 30 | 31 | class JsonStore(object): 32 | def __init__(self, filename, database_name, typeof_data="any"): 33 | self.file = filename 34 | self.name = database_name.strip() 35 | self.__path = os.path.join(os.path.dirname(self.file), f"{self.name}.json") 36 | 37 | assert len(self.name) > 0, "Name should have atleast one character" 38 | if not (type(typeof_data).__name__ == "type" or typeof_data == "any"): 39 | raise TypeError(f"Invalid type : {type(typeof_data).__name__}") 40 | 41 | self.typeof_data = typeof_data 42 | self.__store = self.initialize_database() 43 | 44 | self.__store = self.__store if isinstance(self.__store, dict) else {} 45 | 46 | self.commit() 47 | 48 | @property 49 | def keys(self): 50 | return list(self.__store.keys()) 51 | 52 | def __create_short_uuid(self, check_for_duplicate=[], length=5): 53 | short_uuid = lambda: str(uuid.uuid4())[:length] 54 | value = short_uuid() 55 | while value in check_for_duplicate: 56 | value = short_uuid() 57 | return value 58 | 59 | def initialize_database(self): 60 | if not os.path.isfile(self.__path): 61 | with open(self.__path, "w") as file_writer: 62 | file_writer.write(json.dumps({})) 63 | return {} 64 | return self.__get_store_content() 65 | 66 | def __get_store_content(self): 67 | with open(self.__path, "r") as file_reader: 68 | try: 69 | return json.loads(file_reader.read()) 70 | except Exception as exception: 71 | with open(self.__path, "w") as file_writer: 72 | file_writer.write(json.dumps({})) 73 | return {} 74 | 75 | def add(self, data): 76 | if not self.__validate_data_type(data): 77 | raise TypeError(f"Parameter") 78 | key = self.__create_short_uuid(check_for_duplicate=self.keys) 79 | self.__store[key] = ( 80 | UseAscii.string_to_ascii_string(data) if isinstance(data, str) else data 81 | ) 82 | self.commit() 83 | 84 | @property 85 | def __expected_parameter_type(self): 86 | if self.typeof_data == "any": 87 | return "any" 88 | 89 | return self.typeof_data.__name__ 90 | 91 | def filter_by_value(self, value): 92 | values = list(self.__store.values()) 93 | keys = self.keys 94 | matches = [ 95 | keys[index] if val == value else None for index, val in enumerate(values) 96 | ] 97 | return list(filter(lambda element: element is not None, matches)) 98 | 99 | def get(self, key=None): 100 | if len(self.keys) == 0 and key == None: 101 | return None 102 | return_value = self.__store[key or self.keys[0]] 103 | if not isinstance(return_value, str): 104 | return return_value 105 | return UseAscii.ascii_string_to_string(return_value) 106 | 107 | def commit(self): 108 | with open(self.__path, "w") as file_writer: 109 | file_writer.write(json.dumps(self.__store)) 110 | 111 | def __validate_data_type(self, data): 112 | if self.typeof_data == "any": 113 | return True 114 | return isinstance(data, self.typeof_data) 115 | -------------------------------------------------------------------------------- /polyglot/path.py: -------------------------------------------------------------------------------- 1 | import os 2 | from clint.textui import colored 3 | from collections.abc import Iterable 4 | 5 | 6 | class DirectoryError(Exception): 7 | def __init__(self, error_message): 8 | self.error_message = colored.red(error_message) 9 | 10 | super().__init__(self.error_message) 11 | 12 | 13 | class FileContentFilter(object): 14 | def __init__(self, files=None, folders=None): 15 | self.__validate_parameter_types(files=files, folders=folders) 16 | 17 | self.files = files 18 | self.folders = folders 19 | 20 | def __validate_parameter_types(self, **kwargs): 21 | valid_types = bool 22 | for parameter_key, value in kwargs.items(): 23 | if not type(value) == valid_types: 24 | if value == None: 25 | continue 26 | raise TypeError(f"{parameter_key} expected to be of type bool or None") 27 | return True 28 | 29 | 30 | class Log(object): 31 | def __init__(self, message, critical=False): 32 | self.message = colored.red(message) if critical else colored.cyan(message) 33 | self.create_message_log(self.message) 34 | 35 | def create_message_log(self, message, end="\n"): 36 | print(message, end=end) 37 | 38 | 39 | class _Stat(object): 40 | def __init__(self, path): 41 | self.path = path 42 | 43 | self.parent = os.path.dirname(path) 44 | self.basename = os.path.basename(path) 45 | self.directory = os.path.isdir(path) 46 | self.file = os.path.isfile(path) 47 | self.absolute = os.path.abspath(path) 48 | 49 | def __repr__(self): 50 | return str( 51 | { 52 | "parent": self.parent, 53 | "basename": self.basename, 54 | "directory": self.directory, 55 | "file": self.file, 56 | "abs": self.absolute, 57 | } 58 | ) 59 | 60 | def __str__(self): 61 | return self.__repr__() 62 | 63 | 64 | class PolyglotPath(object): 65 | def __init__(self, path=None): 66 | self.directory = self.__find_directory_path(path) 67 | 68 | def __find_directory_path(self, path): 69 | if path == "." or path == None: 70 | return os.getcwd() 71 | 72 | return path 73 | 74 | @property 75 | def basename(self): 76 | return os.path.basename(self.directory) 77 | 78 | def listdir(self): 79 | return os.listdir(self.directory) 80 | 81 | @property 82 | def content(self): 83 | return self.listdir() 84 | 85 | @property 86 | def is_directory(self): 87 | return os.path.isdir(self.directory) 88 | 89 | def touch(self, create_files=[], log=True): 90 | assert isinstance( 91 | create_files, Iterable 92 | ), "Parameter expected to be an iterable" 93 | 94 | for index, filename in enumerate(create_files): 95 | with open( 96 | os.path.join(self.directory, filename), "w" 97 | ) as create_file_writer: 98 | create_file_writer.write("") 99 | 100 | log = Log(f"{index+1} Created {filename}") 101 | 102 | def mkdirs(self, directories, log=True, overwrite=False): 103 | assert isinstance(directories, Iterable) 104 | 105 | for index, dirname in enumerate(directories): 106 | if os.path.exists(dirname) and os.path.isdir(dirname): 107 | Log(f"{index+1} Failed to create {dirname}", critical=True) 108 | else: 109 | os.mkdir(dirname) 110 | Log(f"{index+1}. Created {dirname}") 111 | 112 | def join(self, *args): 113 | path = self.directory 114 | for joinpath in args: 115 | path = os.path.join(path, joinpath) 116 | 117 | return path 118 | 119 | @property 120 | def parent(self): 121 | return os.path.dirname(self.directory) 122 | 123 | @property 124 | def stat(self): 125 | return _Stat(self.directory) 126 | 127 | def __repr__(self): 128 | return str(self.directory) 129 | 130 | def __str__(self): 131 | return self.__repr__() 132 | 133 | def __len__(self): 134 | if not os.path.isdir(self.directory): 135 | return -1 136 | 137 | return len(os.listdir(self.directory)) 138 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | pyyaml 3 | prettytable 4 | clint 5 | toml -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open("README.md", "r", encoding="utf-8") as fh: 4 | long_description = fh.read() 5 | 6 | DEPENDENCIES = ["requests", "pyyaml", "prettytable", "clint", "toml"] 7 | 8 | 9 | setuptools.setup( 10 | name="python-polyglot", # Replace with your own username 11 | version="4.2.9", 12 | author="P Pranav Baburaj", 13 | author_email="code-roller@googlegroups.com", 14 | description="Find the percentage of programming languages used in your project", 15 | long_description=long_description, 16 | long_description_content_type="text/markdown", 17 | url="https://github.com/pranavbaburaj/polyglot", 18 | packages=setuptools.find_packages( 19 | exclude=["tests", "*.tests", "*.tests.*", "tests.*"] 20 | ), 21 | install_requires=DEPENDENCIES, 22 | classifiers=[ 23 | "Programming Language :: Python :: 3", 24 | "License :: OSI Approved :: MIT License", 25 | "Operating System :: OS Independent", 26 | ], 27 | entry_points={ 28 | "console_scripts": [ 29 | "pgt = polyglot.__main__:main", 30 | "polyglot = polyglot.__main__:main", 31 | ] 32 | }, 33 | python_requires=">=3.6", 34 | ) 35 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | from polyglot.core import Polyglot 2 | 3 | poly = Polyglot("E://clones//v//").show() -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2021 Pranav Baburaj 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | -------------------------------------------------------------------------------- /tests/context.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | 4 | sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) 5 | 6 | import polyglot 7 | 8 | from polyglot.core import * 9 | from polyglot.exceptions import * 10 | from polyglot.arguments import * 11 | from polyglot.path import PolyglotPath 12 | -------------------------------------------------------------------------------- /tests/test_arguments.py: -------------------------------------------------------------------------------- 1 | from context import Arguments 2 | 3 | import unittest 4 | 5 | 6 | class TestCases(unittest.TestCase): 7 | """Advanced test cases.""" 8 | 9 | def test_application(self): 10 | data = Arguments( 11 | arguments=[ 12 | "--dir=.", 13 | "--o=dara.json", 14 | "--show=True", 15 | "--ignore=data.json,file.json,test.json", 16 | ] 17 | ) 18 | 19 | print(data.parse()) 20 | self.assertIsNone(None) 21 | 22 | 23 | if __name__ == "__main__": 24 | unittest.main() 25 | -------------------------------------------------------------------------------- /tests/test_file.py: -------------------------------------------------------------------------------- 1 | from context import PolyglotPath 2 | 3 | import unittest 4 | 5 | 6 | class TestCases(unittest.TestCase): 7 | """Advanced test cases.""" 8 | 9 | def test_application(self): 10 | d = PolyglotPath(".") 11 | print(d.listdir()) 12 | self.assertIsNone(None) 13 | 14 | 15 | if __name__ == "__main__": 16 | unittest.main() 17 | -------------------------------------------------------------------------------- /tests/test_polyglot.py: -------------------------------------------------------------------------------- 1 | from context import polyglot 2 | 3 | import unittest 4 | 5 | 6 | class TestCases(unittest.TestCase): 7 | """Advanced test cases.""" 8 | 9 | def test_application(self): 10 | self.assertIsNone(None) 11 | 12 | 13 | if __name__ == "__main__": 14 | unittest.main() 15 | --------------------------------------------------------------------------------