├── .github └── workflows │ └── python-publish.yml ├── .gitignore ├── LICENSE ├── README.md ├── painy ├── __init__.py ├── chat.py ├── comment.py ├── enums.py ├── errors │ ├── GitDiffException.py │ ├── NoChangesException.py │ ├── NoSettingException.py │ └── __init__.py ├── git.py ├── main.py ├── managers.py ├── settings │ ├── config.json │ ├── p_extensions.txt │ ├── prompt_comment_base.txt │ ├── prompt_commit_style.txt │ └── rules.json └── utils.py ├── requirements.txt └── setup.py /.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://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: Upload Python Package 10 | 11 | on: 12 | release: 13 | types: [published] 14 | 15 | permissions: 16 | contents: read 17 | 18 | jobs: 19 | deploy: 20 | 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - uses: actions/checkout@v3 25 | - name: Set up Python 26 | uses: actions/setup-python@v3 27 | with: 28 | python-version: '3.x' 29 | - name: Install dependencies 30 | run: | 31 | python -m pip install --upgrade pip 32 | pip install build 33 | - name: Build package 34 | run: python -m build 35 | - name: Publish package 36 | uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 37 | with: 38 | user: __token__ 39 | password: ${{ secrets.PYPI_API_TOKEN }} 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Raevsky Rudolf 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 | # Painy 2 | 3 | Comments for this repository commits are written with Painy itself. 4 | 5 | *Star the repository to support the project.* 6 | 7 | ## Features 8 | 9 | - Painy is a simple tool which allows you to automatically generate commit messages with the help of OpenAI models. It automatically check the staged changes in your repository and generate a commit message based on the changes. 10 | 11 | - Works well with Jupyter Notebooks (`.ipynb` files) by using `nbdime` for diffing. 12 | 13 | - Painy is capable of reviewing past commits and creating new ones that follow the same style. 14 | 15 | - You can add your own rules/requests as additional prompts for model. For example, set maximum number of characters/sentences in the commit message. 16 | 17 | - Interactive mode. 18 | 19 | ## Prerequisites 20 | 21 | - OpenAI API key. You can get one [here](https://platform.openai.com). 22 | 23 | - Python 3.7.1 or higher and `pip` package manager. 24 | 25 | ## Installation 26 | 27 | - Install the package via pip: 28 | 29 | ```bash 30 | pip install painy 31 | ``` 32 | 33 | - Set the environment variable `OPENAI_API_KEY` to your OpenAI API key. 34 | 35 | - By default, Painy will use the `gpt-3.5-turbo` (ChatGPT), if you want to use a different model, set the environment variable `OPENAI_MODEL_NAME` to the model you want to use. You must have access to the model you want to use. 36 | 37 | - Max context length depends on the model you use. Try to use the tool on not too big changes, ideally on a single file. 38 | 39 | ## Usage 40 | 41 | - (Recommended) Go to the folder with your repository and run the following command: 42 | 43 | - Add something to stage: 44 | 45 | ```bash 46 | git add 47 | ``` 48 | 49 | - Then use Painy: 50 | 51 | ```bash 52 | painy comment 53 | ``` 54 | 55 | - Or with the environment variable `OPENAI_API_KEY` set inplace: 56 | 57 | ```bash 58 | OPENAI_API_KEY= painy comment 59 | ``` 60 | 61 | This will generate a commit message based on the staged changes in your repository. 62 | 63 | - Commit staged changes with a generated commit message: 64 | 65 | ```bash 66 | painy commit 67 | ``` 68 | 69 | - Interactive mode: 70 | 71 | ```bash 72 | painy -i 73 | ``` 74 | 75 | This will generate a commit message based on the staged changes in your repository and will ask you if you want to regenerate the commit message. 76 | 77 | - If you want to generate a commit message for all the changes in your repository, run the following command (this files must be registered in git before in order to have a diff to compare): 78 | 79 | ```bash 80 | painy --check-all 81 | ``` 82 | 83 | - By default the `use_commit_history_style` is set to `True`. If you want to disable it, run the following command: 84 | 85 | ```bash 86 | painy config --set use-commit-history-style False 87 | ``` 88 | 89 | - To get the actual value of config option run the following command: 90 | 91 | ```bash 92 | painy config --get use-commit-history-style 93 | ``` 94 | 95 | - List of options: 96 | - `use_commit_history_style` - whether to use the style of the former commits in the repository. By default it is set to `false`. (Experimental) 97 | - `max_num_commits_style` - the maximum number of last commits to use for the style. By default it is set to `5`. 98 | - `max_characters` (used as a property in one of default rules) - the desired maximum number of characters in the commit message. By default it is set to `100`. 99 | 100 | - To get the list of rules run the following command: 101 | 102 | ```bash 103 | painy rules 104 | ``` 105 | 106 | - To add a new rule run the following command: 107 | 108 | ```bash 109 | painy rules --add "Your own rule" 110 | ``` 111 | 112 | - To remove i-th rule from the list run the following command: 113 | 114 | ```bash 115 | painy rules --remove 116 | ``` 117 | -------------------------------------------------------------------------------- /painy/__init__.py: -------------------------------------------------------------------------------- 1 | from rich.console import Console 2 | console = Console() -------------------------------------------------------------------------------- /painy/chat.py: -------------------------------------------------------------------------------- 1 | from .git import * 2 | from .enums import * 3 | import os 4 | from langchain_community.chat_models import ChatOpenAI 5 | from langchain_core.runnables import RunnableLambda, RunnablePassthrough 6 | from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder 7 | from langchain.chains import ConversationChain 8 | from langchain_core.messages import SystemMessage 9 | from operator import itemgetter 10 | from langchain.memory import ConversationBufferMemory 11 | 12 | 13 | MODEL_NAME = os.getenv(key="OPENAI_MODEL_NAME", default="gpt-3.5-turbo") 14 | 15 | 16 | class Model: 17 | def __init__(self, purpose_prompt: str, model_name: str = MODEL_NAME): 18 | self.model_name = MODEL_NAME 19 | 20 | self.llm = ChatOpenAI( 21 | temperature=0.1, 22 | model_name=self.model_name 23 | ) 24 | 25 | self.memory = ConversationBufferMemory(return_messages=True) 26 | self.memory.chat_memory.add_message( 27 | SystemMessage(content=purpose_prompt)) 28 | 29 | self.chain = ConversationChain( 30 | llm=self.llm, 31 | verbose=False, 32 | memory=self.memory 33 | ) 34 | 35 | def get_response(self, prompt: str) -> str: 36 | inputs = {"input": prompt} 37 | response = self.chain.run(inputs) 38 | 39 | return response 40 | -------------------------------------------------------------------------------- /painy/comment.py: -------------------------------------------------------------------------------- 1 | from painy.git import * 2 | from painy.chat import Model 3 | from painy import console 4 | from painy.utils import print_commit_message, load_json, load_txt 5 | from painy.errors import NoSettingException 6 | from painy.managers import ConfigManager, RulesManager 7 | 8 | 9 | def get_rules(config: dict): 10 | rules_loaded = load_json("settings/rules.json")['rules'] 11 | rules_processed = process_rules(rules_loaded, config) 12 | 13 | rules_str = '\n\nFollow this rules/restrictions when writing commit messages:\n' 14 | rules_str += '\n'.join([f'- {rule.strip()}' for rule in rules_processed]) 15 | 16 | return rules_str 17 | 18 | 19 | def get_prompt() -> str: 20 | prompt = load_txt('settings/prompt_comment_base.txt') 21 | config_manager = ConfigManager() 22 | rules_manager = RulesManager() 23 | rules = rules_manager.get_rules(config_manager.config_dict) 24 | 25 | prompt += '\n\nYou MUST OBEY FOLLOWING rules/restrictions when writing commit messages:\n' 26 | prompt += '\n'.join([f'- {rule.strip()}' for rule in rules]) 27 | 28 | if config_manager.get_option('use_commit_history_style'): 29 | max_num_commits = config_manager.get_option('max_num_commits_style') 30 | max_num_commits = max_num_commits if max_num_commits is not None else 20 31 | commit_history = get_commit_messsage_history()[:max_num_commits] 32 | 33 | style_prompt = load_txt('settings/prompt_commit_style.txt') 34 | prompt += '\n\n' + style_prompt + '\n' 35 | prompt += '\n'.join([f'- {rule.strip()}' for rule in commit_history]) 36 | 37 | return prompt 38 | 39 | 40 | def get_commmit_message(diff_str: str, interactive: bool = False): 41 | with console.status(status="Generating commit message...", spinner='aesthetic'): 42 | comment_prompt = get_prompt() 43 | model = Model(purpose_prompt=comment_prompt) 44 | response = model.get_response(prompt=diff_str) 45 | 46 | return response 47 | 48 | 49 | def comment_interactive(msg: str, diff_str: str) -> str: 50 | """ 51 | Interactive mode for generating commit messages. 52 | 53 | Args: 54 | msg (str): Already (first time) generated commit message 55 | 56 | Returns: 57 | str: Final commit message 58 | """ 59 | while True: 60 | console.rule(characters="=", style="cyan") 61 | option = console.input("Try another one? [green]y[/green]/[red]n[/red]: ") 62 | 63 | if option.lower() in ["y", "yes"]: 64 | msg = get_commmit_message(diff_str) 65 | print_commit_message(console, msg) 66 | else: 67 | return msg -------------------------------------------------------------------------------- /painy/enums.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class Action(Enum): 5 | COMMENT = "comment" 6 | COMMIT = "commit" 7 | CONFIG = "config" 8 | RULES = "rules" 9 | -------------------------------------------------------------------------------- /painy/errors/GitDiffException.py: -------------------------------------------------------------------------------- 1 | class GitDiffException(Exception): 2 | def __init__(self, message: str = "Error. Are you in a git repository?"): 3 | self.message = message 4 | super().__init__(self.message) 5 | def __str__(self): 6 | return self.message -------------------------------------------------------------------------------- /painy/errors/NoChangesException.py: -------------------------------------------------------------------------------- 1 | class NoChangesException(Exception): 2 | def __init__(self, message: str = "No changes found."): 3 | self.message = message 4 | super().__init__(self.message) 5 | def __str__(self): 6 | return self.message -------------------------------------------------------------------------------- /painy/errors/NoSettingException.py: -------------------------------------------------------------------------------- 1 | class NoSettingException(Exception): 2 | def __init__(self, message: str = "No setting found."): 3 | self.message = message 4 | super().__init__(self.message) 5 | def __str__(self): 6 | return self.message -------------------------------------------------------------------------------- /painy/errors/__init__.py: -------------------------------------------------------------------------------- 1 | from .GitDiffException import GitDiffException 2 | from .NoChangesException import NoChangesException 3 | from .NoSettingException import NoSettingException -------------------------------------------------------------------------------- /painy/git.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | import subprocess 3 | import re 4 | from painy.utils import get_package_path, load_valid_extensions 5 | from painy.errors import * 6 | 7 | 8 | def get_changed_files(staged=False) -> List[str]: 9 | try: 10 | cmd = ["git", "diff", "--name-only"] 11 | 12 | if staged: 13 | cmd += ["--staged"] 14 | 15 | output = subprocess.check_output(cmd, stderr=subprocess.STDOUT) 16 | 17 | changed_files = output.decode("utf-8").strip().split("\n") 18 | except subprocess.CalledProcessError: 19 | raise GitDiffException() 20 | 21 | valid_extensions = load_valid_extensions() 22 | 23 | changed_files = [file for file in changed_files if file != ''] 24 | changed_files = [file for file in changed_files if f".{file.split('.')[-1]}" in valid_extensions] 25 | 26 | return changed_files 27 | 28 | def get_ipynb_changes_staged(file_path: str) -> str: 29 | assert file_path.endswith(".ipynb") 30 | 31 | cmd = ["nbdiff", file_path] 32 | output = subprocess.check_output(cmd, stderr=subprocess.STDOUT) 33 | changes = output.decode("utf-8").strip() + "\n" 34 | 35 | # Remove additional info 36 | regs = [ 37 | "##.* added \/nbformat:[\n \S]*?\n\n", 38 | "##.* added \/nbformat_minor:[\n \S]*?\n\n", 39 | "##.* added \/metadata:[\n \S]*?\n\n"] 40 | 41 | for reg in regs: 42 | changes = re.sub(reg, "", changes) 43 | 44 | return changes 45 | 46 | def get_file_changes(file_path: str) -> str: 47 | if file_path.endswith(".ipynb"): 48 | changes = get_ipynb_changes_staged(file_path) 49 | else: 50 | output = subprocess.check_output(["git", "diff", "HEAD", "--", file_path]) 51 | changes = output.decode("utf-8") 52 | return changes 53 | 54 | def get_changes_staged() -> str: 55 | output = subprocess.check_output(["git", "diff", "--staged"], stderr=subprocess.STDOUT) 56 | changes = output.decode("utf-8").strip() 57 | return changes 58 | 59 | def get_diff_str(changed_files: List[str]) -> str: 60 | if len(changed_files) == 0: 61 | raise NoChangesException() 62 | 63 | diffs = [] 64 | 65 | for file in changed_files: 66 | diff = get_file_changes(file) 67 | diffs.append(diff) 68 | 69 | return "\n".join(diffs) 70 | 71 | def get_commit_messsage_history() -> List[str]: 72 | output = subprocess.check_output(["git", "log", "--pretty=format:\"%s\""], stderr=subprocess.STDOUT) 73 | messages = output.decode("utf-8").strip().split("\n") 74 | messages = [message.strip("\"") for message in messages] 75 | 76 | return messages 77 | 78 | def commit(commit_message: str) -> None: 79 | """ 80 | Commits the staged changes with the given commit message. 81 | Used to commit the auto-generated commit message. 82 | 83 | Args: 84 | commit_message (str): The commit message to use. 85 | """ 86 | subprocess.check_output(["git", "commit", "-m", commit_message], stderr=subprocess.STDOUT) -------------------------------------------------------------------------------- /painy/main.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import openai 3 | import os 4 | from painy import console 5 | from painy.comment import get_commmit_message, comment_interactive 6 | from painy.enums import Action 7 | from painy.git import commit, get_changed_files, get_diff_str 8 | from painy.utils import print_commit_message 9 | from painy.managers import ConfigManager, RulesManager 10 | 11 | 12 | def main(): 13 | api_key = os.getenv("OPENAI_API_KEY") 14 | 15 | if api_key is None: 16 | console.print("[red]Error: OPENAI_API_KEY not set[/red]") 17 | exit(1) 18 | 19 | openai.api_key = api_key 20 | 21 | parser = argparse.ArgumentParser( 22 | prog="Painy", 23 | description="A tool to help you write better commit messages." 24 | ) 25 | 26 | parser.add_argument("action", choices=["comment", "commit", "config", "rules"], help="The action to perform") 27 | parser.add_argument("--check-all", action="store_true", help="Check all files previously registered in git, not just staged ones") 28 | parser.add_argument("-i", "--interactive", action="store_true", help="Run in interactive mode") 29 | parser.add_argument("-s", "--set", nargs=2, help="Set a config value. Example: painy config --set use_commit_history_style True") 30 | parser.add_argument("-g", "--get", help="Get a config value. Example: painy config --get use_commit_history_style") 31 | parser.add_argument("-a", "--add", help="Add a rule. Example: painy rules --add ''") 32 | parser.add_argument("-r", "--remove", help="Remove a i-th rule from the list. Example: painy rules --remove ") 33 | 34 | args = parser.parse_args() 35 | 36 | action = args.action if args.action is not None else Action.COMMENT.value 37 | 38 | staged = not args.check_all 39 | interactive = args.interactive 40 | 41 | if action == Action.CONFIG.value: 42 | if args.set is not None and len(args.set) == 2: 43 | key, value = args.set 44 | console.print(f"[green]Setting config value:[/green] {key} = {value}") 45 | 46 | config_manager = ConfigManager() 47 | config_manager.set_option(key, value) 48 | elif args.get is not None: 49 | key = args.get 50 | console.print(f"[green]Getting config value:[/green] {key}") 51 | 52 | config_manager = ConfigManager() 53 | value = config_manager.get_option(key) 54 | 55 | console.print(f"[green]Value:[/green] {value}") 56 | 57 | return 58 | 59 | if action == Action.RULES.value: 60 | rules_manager = RulesManager() 61 | config_manager = ConfigManager() 62 | 63 | if args.add is not None: 64 | rule = args.add 65 | console.print(f"[green]Adding rule:[/green] {rule}") 66 | 67 | rules_manager.add_rule(rule) 68 | elif args.remove is not None: 69 | i = int(args.remove) 70 | if i <= 0: 71 | console.print("[red]Error: i must be greater than 0[/red]") 72 | return 73 | 74 | console.print(f"[green]Removing rule:[/green] {i}") 75 | try: 76 | rules_manager.remove_rule(i - 1) 77 | except IndexError: 78 | length = rules_manager.get_length() 79 | console.print(f"[red]Error: i must be less than or equal to {length}[/red]") 80 | else: 81 | rules = rules_manager.get_rules(config_manager.config_dict) 82 | 83 | rules_str = "" 84 | for i, rule in enumerate(rules): 85 | rules_str += f"{i + 1}. {rule}\n" 86 | 87 | console.print(f"[green]Rules:[/green]\n{rules_str}") 88 | 89 | return 90 | 91 | try: 92 | changed_files = get_changed_files(staged) 93 | console.print(f"[green]Changed files:[/green] {', '.join(changed_files)}") 94 | 95 | diff_str = get_diff_str(changed_files) 96 | except Exception as e: 97 | console.print(f"[red]{e}[/red]") 98 | return 99 | 100 | if action == Action.COMMENT.value: 101 | msg = get_commmit_message(diff_str) 102 | print_commit_message(console, msg) 103 | 104 | if interactive: 105 | msg = comment_interactive(msg, diff_str) 106 | 107 | elif action == Action.COMMIT.value: 108 | msg = get_commmit_message(diff_str) 109 | print_commit_message(console, msg) 110 | 111 | if interactive: 112 | msg = comment_interactive(msg, diff_str) 113 | 114 | option = console.input("Do you want to commit with this message? [green]y[/green]/[red]n[/red]: ") 115 | 116 | if option.lower() in ["y", "yes"]: 117 | commit(msg) 118 | else: 119 | print_commit_message(console, msg) 120 | commit(msg) 121 | 122 | 123 | if __name__ == "__main__": 124 | main() -------------------------------------------------------------------------------- /painy/managers.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | from painy.utils import load_json, save_json 3 | from painy.errors import NoSettingException 4 | import re 5 | 6 | 7 | class ConfigManager: 8 | def __init__(self, config_path='settings/config.json'): 9 | self.config_path = config_path 10 | self.config_dict = load_json(self.config_path) 11 | 12 | def set_option(self, option: str, value) -> None: 13 | self.config_dict[option] = value 14 | self.save_config() 15 | 16 | def get_option(self, option: str): 17 | if option not in self.config_dict: 18 | return None 19 | value = self.config_dict[option] 20 | return ConfigManager.process(value) 21 | 22 | def save_config(self) -> None: 23 | save_json(self.config_path, self.config_dict) 24 | 25 | @staticmethod 26 | def process(value): 27 | if type(value) == str: 28 | if value.lower() == 'true': 29 | return True 30 | elif value.lower() == 'false': 31 | return False 32 | elif value.isdigit(): 33 | return int(value) 34 | 35 | return value 36 | 37 | 38 | class RulesManager: 39 | def __init__(self, rules_path='settings/rules.json'): 40 | self.rules_path = rules_path 41 | self.rules_dict = load_json(self.rules_path) 42 | 43 | def add_rule(self, rule: str) -> None: 44 | self.rules_dict['rules'] = self.rules_dict['rules'] + [rule] 45 | self.save_rules() 46 | 47 | def remove_rule(self, i) -> None: 48 | self.rules_dict['rules'].pop(i) 49 | self.save_rules() 50 | 51 | def get_rules(self, config: dict = None) -> List[str]: 52 | rules_loaded = self.rules_dict['rules'] 53 | if config is None: 54 | return rules_loaded 55 | return RulesManager.process_rules(rules_loaded, config) 56 | 57 | def get_length(self) -> int: 58 | return len(self.rules_dict['rules']) 59 | 60 | def remove_all_rules(self) -> None: 61 | self.rules_dict['rules'] = [] 62 | self.save_rules() 63 | 64 | def save_rules(self) -> None: 65 | save_json(self.rules_path, self.rules_dict) 66 | 67 | @staticmethod 68 | def process_rules(rules: list, config: dict) -> List[str]: 69 | pattern = r"(\<.+\>)" 70 | 71 | processed = [] 72 | for rule in rules: 73 | var_matches = re.findall(pattern, rule) 74 | if not var_matches: 75 | processed.append(rule) 76 | for match in var_matches: 77 | var = match.strip('<>') 78 | 79 | if var in config: 80 | rule = rule.replace(match, str(config[var])) 81 | processed.append(rule) 82 | else: 83 | raise NoSettingException(f"Setting {var} not found in config.json") 84 | 85 | return processed -------------------------------------------------------------------------------- /painy/settings/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "use_commit_history_style": false, 3 | "max_num_commits_style": 5, 4 | "max_characters": 100 5 | } -------------------------------------------------------------------------------- /painy/settings/p_extensions.txt: -------------------------------------------------------------------------------- 1 | .abap 2 | .asc 3 | .ash 4 | .ampl 5 | .mod 6 | .g4 7 | .apib 8 | .apl 9 | .dyalog 10 | .asp 11 | .asax 12 | .ascx 13 | .ashx 14 | .asmx 15 | .aspx 16 | .axd 17 | .dats 18 | .hats 19 | .sats 20 | .as 21 | .adb 22 | .ada 23 | .ads 24 | .agda 25 | .als 26 | .apacheconf 27 | .vhost 28 | .cls 29 | .applescript 30 | .scpt 31 | .arc 32 | .ino 33 | .asciidoc 34 | .adoc 35 | .asc 36 | .aj 37 | .asm 38 | .a51 39 | .inc 40 | .nasm 41 | .aug 42 | .ahk 43 | .ahkl 44 | .au3 45 | .awk 46 | .auk 47 | .gawk 48 | .mawk 49 | .nawk 50 | .bat 51 | .cmd 52 | .befunge 53 | .bison 54 | .bb 55 | .bb 56 | .decls 57 | .bmx 58 | .bsv 59 | .boo 60 | .b 61 | .bf 62 | .brs 63 | .bro 64 | .c 65 | .cats 66 | .h 67 | .idc 68 | .w 69 | .cs 70 | .cake 71 | .cshtml 72 | .csx 73 | .cpp 74 | .c++ 75 | .cc 76 | .cp 77 | .cxx 78 | .h 79 | .h++ 80 | .hh 81 | .hpp 82 | .hxx 83 | .inc 84 | .inl 85 | .ipp 86 | .tcc 87 | .tpp 88 | .c-objdump 89 | .chs 90 | .clp 91 | .cmake 92 | .cmake.in 93 | .cob 94 | .cbl 95 | .ccp 96 | .cobol 97 | .cpy 98 | .css 99 | .csv 100 | .capnp 101 | .mss 102 | .ceylon 103 | .chpl 104 | .ch 105 | .ck 106 | .cirru 107 | .clw 108 | .icl 109 | .dcl 110 | .click 111 | .clj 112 | .boot 113 | .cl2 114 | .cljc 115 | .cljs 116 | .cljs.hl 117 | .cljscm 118 | .cljx 119 | .hic 120 | .coffee 121 | ._coffee 122 | .cake 123 | .cjsx 124 | .cson 125 | .iced 126 | .cfm 127 | .cfml 128 | .cfc 129 | .lisp 130 | .asd 131 | .cl 132 | .l 133 | .lsp 134 | .ny 135 | .podsl 136 | .sexp 137 | .cp 138 | .cps 139 | .cl 140 | .coq 141 | .v 142 | .cppobjdump 143 | .c++-objdump 144 | .c++objdump 145 | .cpp-objdump 146 | .cxx-objdump 147 | .creole 148 | .cr 149 | .feature 150 | .cu 151 | .cuh 152 | .cy 153 | .pyx 154 | .pxd 155 | .pxi 156 | .d 157 | .di 158 | .d-objdump 159 | .com 160 | .dm 161 | .zone 162 | .arpa 163 | .d 164 | .darcspatch 165 | .dpatch 166 | .dart 167 | .diff 168 | .patch 169 | .dockerfile 170 | .djs 171 | .dylan 172 | .dyl 173 | .intr 174 | .lid 175 | .E 176 | .ecl 177 | .eclxml 178 | .ecl 179 | .sch 180 | .brd 181 | .epj 182 | .e 183 | .ex 184 | .exs 185 | .elm 186 | .el 187 | .emacs 188 | .emacs.desktop 189 | .em 190 | .emberscript 191 | .erl 192 | .es 193 | .escript 194 | .hrl 195 | .xrl 196 | .yrl 197 | .fs 198 | .fsi 199 | .fsx 200 | .fx 201 | .flux 202 | .f90 203 | .f 204 | .f03 205 | .f08 206 | .f77 207 | .f95 208 | .for 209 | .fpp 210 | .factor 211 | .fy 212 | .fancypack 213 | .fan 214 | .fs 215 | .for 216 | .eam.fs 217 | .fth 218 | .4th 219 | .f 220 | .for 221 | .forth 222 | .fr 223 | .frt 224 | .fs 225 | .ftl 226 | .fr 227 | .g 228 | .gco 229 | .gcode 230 | .gms 231 | .g 232 | .gap 233 | .gd 234 | .gi 235 | .tst 236 | .s 237 | .ms 238 | .gd 239 | .glsl 240 | .fp 241 | .frag 242 | .frg 243 | .fs 244 | .fsh 245 | .fshader 246 | .geo 247 | .geom 248 | .glslv 249 | .gshader 250 | .shader 251 | .vert 252 | .vrx 253 | .vsh 254 | .vshader 255 | .gml 256 | .kid 257 | .ebuild 258 | .eclass 259 | .po 260 | .pot 261 | .glf 262 | .gp 263 | .gnu 264 | .gnuplot 265 | .plot 266 | .plt 267 | .go 268 | .golo 269 | .gs 270 | .gst 271 | .gsx 272 | .vark 273 | .grace 274 | .gradle 275 | .gf 276 | .gml 277 | .graphql 278 | .dot 279 | .gv 280 | .man 281 | .1 282 | .1in 283 | .1m 284 | .1x 285 | .2 286 | .3 287 | .3in 288 | .3m 289 | .3qt 290 | .3x 291 | .4 292 | .5 293 | .6 294 | .7 295 | .8 296 | .9 297 | .l 298 | .me 299 | .ms 300 | .n 301 | .rno 302 | .roff 303 | .groovy 304 | .grt 305 | .gtpl 306 | .gvy 307 | .gsp 308 | .hcl 309 | .tf 310 | .hlsl 311 | .fx 312 | .fxh 313 | .hlsli 314 | .html 315 | .htm 316 | .html.hl 317 | .inc 318 | .st 319 | .xht 320 | .xhtml 321 | .mustache 322 | .jinja 323 | .eex 324 | .erb 325 | .erb.deface 326 | .phtml 327 | .http 328 | .hh 329 | .php 330 | .haml 331 | .haml.deface 332 | .handlebars 333 | .hbs 334 | .hb 335 | .hs 336 | .hsc 337 | .hx 338 | .hxsl 339 | .hy 340 | .bf 341 | .pro 342 | .dlm 343 | .ipf 344 | .ini 345 | .cfg 346 | .prefs 347 | .pro 348 | .properties 349 | .irclog 350 | .weechatlog 351 | .idr 352 | .lidr 353 | .ni 354 | .i7x 355 | .iss 356 | .io 357 | .ik 358 | .thy 359 | .ijs 360 | .flex 361 | .jflex 362 | .json 363 | .geojson 364 | .lock 365 | .topojson 366 | .json5 367 | .jsonld 368 | .jq 369 | .jsx 370 | .jade 371 | .j 372 | .java 373 | .jsp 374 | .js 375 | ._js 376 | .bones 377 | .es 378 | .es6 379 | .frag 380 | .gs 381 | .jake 382 | .jsb 383 | .jscad 384 | .jsfl 385 | .jsm 386 | .jss 387 | .njs 388 | .pac 389 | .sjs 390 | .ssjs 391 | .sublime-build 392 | .sublime-commands 393 | .sublime-completions 394 | .sublime-keymap 395 | .sublime-macro 396 | .sublime-menu 397 | .sublime-mousemap 398 | .sublime-project 399 | .sublime-settings 400 | .sublime-theme 401 | .sublime-workspace 402 | .sublime_metrics 403 | .sublime_session 404 | .xsjs 405 | .xsjslib 406 | .jl 407 | .ipynb 408 | .krl 409 | .sch 410 | .brd 411 | .kicad_pcb 412 | .kit 413 | .kt 414 | .ktm 415 | .kts 416 | .lfe 417 | .ll 418 | .lol 419 | .lsl 420 | .lslp 421 | .lvproj 422 | .lasso 423 | .las 424 | .lasso8 425 | .lasso9 426 | .ldml 427 | .latte 428 | .lean 429 | .hlean 430 | .less 431 | .l 432 | .lex 433 | .ly 434 | .ily 435 | .b 436 | .m 437 | .ld 438 | .lds 439 | .mod 440 | .liquid 441 | .lagda 442 | .litcoffee 443 | .lhs 444 | .ls 445 | ._ls 446 | .xm 447 | .x 448 | .xi 449 | .lgt 450 | .logtalk 451 | .lookml 452 | .ls 453 | .lua 454 | .fcgi 455 | .nse 456 | .pd_lua 457 | .rbxs 458 | .wlua 459 | .mumps 460 | .m 461 | .m4 462 | .m4 463 | .ms 464 | .mcr 465 | .mtml 466 | .muf 467 | .m 468 | .mak 469 | .d 470 | .mk 471 | .mkfile 472 | .mako 473 | .mao 474 | .md 475 | .markdown 476 | .mkd 477 | .mkdn 478 | .mkdown 479 | .ron 480 | .mask 481 | .mathematica 482 | .cdf 483 | .m 484 | .ma 485 | .mt 486 | .nb 487 | .nbp 488 | .wl 489 | .wlt 490 | .matlab 491 | .m 492 | .maxpat 493 | .maxhelp 494 | .maxproj 495 | .mxt 496 | .pat 497 | .mediawiki 498 | .wiki 499 | .m 500 | .moo 501 | .metal 502 | .minid 503 | .druby 504 | .duby 505 | .mir 506 | .mirah 507 | .mo 508 | .mod 509 | .mms 510 | .mmk 511 | .monkey 512 | .moo 513 | .moon 514 | .myt 515 | .ncl 516 | .nl 517 | .nsi 518 | .nsh 519 | .n 520 | .axs 521 | .axi 522 | .axs.erb 523 | .axi.erb 524 | .nlogo 525 | .nl 526 | .lisp 527 | .lsp 528 | .nginxconf 529 | .vhost 530 | .nim 531 | .nimrod 532 | .ninja 533 | .nit 534 | .nix 535 | .nu 536 | .numpy 537 | .numpyw 538 | .numsc 539 | .ml 540 | .eliom 541 | .eliomi 542 | .ml4 543 | .mli 544 | .mll 545 | .mly 546 | .objdump 547 | .m 548 | .h 549 | .mm 550 | .j 551 | .sj 552 | .omgrofl 553 | .opa 554 | .opal 555 | .cl 556 | .opencl 557 | .p 558 | .cls 559 | .scad 560 | .org 561 | .ox 562 | .oxh 563 | .oxo 564 | .oxygene 565 | .oz 566 | .pwn 567 | .inc 568 | .php 569 | .aw 570 | .ctp 571 | .fcgi 572 | .inc 573 | .php3 574 | .php4 575 | .php5 576 | .phps 577 | .phpt 578 | .pls 579 | .pck 580 | .pkb 581 | .pks 582 | .plb 583 | .plsql 584 | .sql 585 | .sql 586 | .pov 587 | .inc 588 | .pan 589 | .psc 590 | .parrot 591 | .pasm 592 | .pir 593 | .pas 594 | .dfm 595 | .dpr 596 | .inc 597 | .lpr 598 | .pp 599 | .pl 600 | .al 601 | .cgi 602 | .fcgi 603 | .perl 604 | .ph 605 | .plx 606 | .pm 607 | .pod 608 | .psgi 609 | .t 610 | .6pl 611 | .6pm 612 | .nqp 613 | .p6 614 | .p6l 615 | .p6m 616 | .pl 617 | .pl6 618 | .pm 619 | .pm6 620 | .t 621 | .pkl 622 | .l 623 | .pig 624 | .pike 625 | .pmod 626 | .pod 627 | .pogo 628 | .pony 629 | .ps 630 | .eps 631 | .ps1 632 | .psd1 633 | .psm1 634 | .pde 635 | .pl 636 | .pro 637 | .prolog 638 | .yap 639 | .spin 640 | .proto 641 | .asc 642 | .pub 643 | .pp 644 | .pd 645 | .pb 646 | .pbi 647 | .purs 648 | .py 649 | .bzl 650 | .cgi 651 | .fcgi 652 | .gyp 653 | .lmi 654 | .pyde 655 | .pyp 656 | .pyt 657 | .pyw 658 | .rpy 659 | .tac 660 | .wsgi 661 | .xpy 662 | .pytb 663 | .qml 664 | .qbs 665 | .pro 666 | .pri 667 | .r 668 | .rd 669 | .rsx 670 | .raml 671 | .rdoc 672 | .rbbas 673 | .rbfrm 674 | .rbmnu 675 | .rbres 676 | .rbtbar 677 | .rbuistate 678 | .rhtml 679 | .rmd 680 | .rkt 681 | .rktd 682 | .rktl 683 | .scrbl 684 | .rl 685 | .raw 686 | .reb 687 | .r 688 | .r2 689 | .r3 690 | .rebol 691 | .red 692 | .reds 693 | .cw 694 | .rpy 695 | .rs 696 | .rsh 697 | .robot 698 | .rg 699 | .rb 700 | .builder 701 | .fcgi 702 | .gemspec 703 | .god 704 | .irbrc 705 | .jbuilder 706 | .mspec 707 | .pluginspec 708 | .podspec 709 | .rabl 710 | .rake 711 | .rbuild 712 | .rbw 713 | .rbx 714 | .ru 715 | .ruby 716 | .thor 717 | .watchr 718 | .rs 719 | .rs.in 720 | .sas 721 | .scss 722 | .smt2 723 | .smt 724 | .sparql 725 | .rq 726 | .sqf 727 | .hqf 728 | .sql 729 | .cql 730 | .ddl 731 | .inc 732 | .prc 733 | .tab 734 | .udf 735 | .viw 736 | .sql 737 | .db2 738 | .ston 739 | .svg 740 | .sage 741 | .sagews 742 | .sls 743 | .sass 744 | .scala 745 | .sbt 746 | .sc 747 | .scaml 748 | .scm 749 | .sld 750 | .sls 751 | .sps 752 | .ss 753 | .sci 754 | .sce 755 | .tst 756 | .self 757 | .sh 758 | .bash 759 | .bats 760 | .cgi 761 | .command 762 | .fcgi 763 | .ksh 764 | .sh.in 765 | .tmux 766 | .tool 767 | .zsh 768 | .sh-session 769 | .shen 770 | .sl 771 | .slim 772 | .smali 773 | .st 774 | .cs 775 | .tpl 776 | .sp 777 | .inc 778 | .sma 779 | .nut 780 | .stan 781 | .ML 782 | .fun 783 | .sig 784 | .sml 785 | .do 786 | .ado 787 | .doh 788 | .ihlp 789 | .mata 790 | .matah 791 | .sthlp 792 | .styl 793 | .sc 794 | .scd 795 | .swift 796 | .sv 797 | .svh 798 | .vh 799 | .toml 800 | .txl 801 | .tcl 802 | .adp 803 | .tm 804 | .tcsh 805 | .csh 806 | .tex 807 | .aux 808 | .bbx 809 | .bib 810 | .cbx 811 | .cls 812 | .dtx 813 | .ins 814 | .lbx 815 | .ltx 816 | .mkii 817 | .mkiv 818 | .mkvi 819 | .sty 820 | .toc 821 | .tea 822 | .t 823 | .txt 824 | .fr 825 | .nb 826 | .ncl 827 | .no 828 | .textile 829 | .thrift 830 | .t 831 | .tu 832 | .ttl 833 | .twig 834 | .ts 835 | .tsx 836 | .upc 837 | .anim 838 | .asset 839 | .mat 840 | .meta 841 | .prefab 842 | .unity 843 | .uno 844 | .uc 845 | .ur 846 | .urs 847 | .vcl 848 | .vhdl 849 | .vhd 850 | .vhf 851 | .vhi 852 | .vho 853 | .vhs 854 | .vht 855 | .vhw 856 | .vala 857 | .vapi 858 | .v 859 | .veo 860 | .vim 861 | .vb 862 | .bas 863 | .cls 864 | .frm 865 | .frx 866 | .vba 867 | .vbhtml 868 | .vbs 869 | .volt 870 | .vue 871 | .owl 872 | .webidl 873 | .x10 874 | .xc 875 | .xml 876 | .ant 877 | .axml 878 | .ccxml 879 | .clixml 880 | .cproject 881 | .csl 882 | .csproj 883 | .ct 884 | .dita 885 | .ditamap 886 | .ditaval 887 | .dll.config 888 | .dotsettings 889 | .filters 890 | .fsproj 891 | .fxml 892 | .glade 893 | .gml 894 | .grxml 895 | .iml 896 | .ivy 897 | .jelly 898 | .jsproj 899 | .kml 900 | .launch 901 | .mdpolicy 902 | .mm 903 | .mod 904 | .mxml 905 | .nproj 906 | .nuspec 907 | .odd 908 | .osm 909 | .plist 910 | .pluginspec 911 | .props 912 | .ps1xml 913 | .psc1 914 | .pt 915 | .rdf 916 | .rss 917 | .scxml 918 | .srdf 919 | .storyboard 920 | .stTheme 921 | .sublime-snippet 922 | .targets 923 | .tmCommand 924 | .tml 925 | .tmLanguage 926 | .tmPreferences 927 | .tmSnippet 928 | .tmTheme 929 | .ts 930 | .tsx 931 | .ui 932 | .urdf 933 | .ux 934 | .vbproj 935 | .vcxproj 936 | .vssettings 937 | .vxml 938 | .wsdl 939 | .wsf 940 | .wxi 941 | .wxl 942 | .wxs 943 | .x3d 944 | .xacro 945 | .xaml 946 | .xib 947 | .xlf 948 | .xliff 949 | .xmi 950 | .xml.dist 951 | .xproj 952 | .xsd 953 | .xul 954 | .zcml 955 | .xsp-config 956 | .xsp.metadata 957 | .xpl 958 | .xproc 959 | .xquery 960 | .xq 961 | .xql 962 | .xqm 963 | .xqy 964 | .xs 965 | .xslt 966 | .xsl 967 | .xojo_code 968 | .xojo_menu 969 | .xojo_report 970 | .xojo_script 971 | .xojo_toolbar 972 | .xojo_window 973 | .xtend 974 | .yml 975 | .reek 976 | .rviz 977 | .sublime-syntax 978 | .syntax 979 | .yaml 980 | .yaml-tmlanguage 981 | .yang 982 | .y 983 | .yacc 984 | .yy 985 | .zep 986 | .zimpl 987 | .zmpl 988 | .zpl 989 | .desktop 990 | .desktop.in 991 | .ec 992 | .eh 993 | .edn 994 | .fish 995 | .mu 996 | .nc 997 | .ooc 998 | .rst 999 | .rest 1000 | .rest.txt 1001 | .rst.txt 1002 | .wisp 1003 | .prg 1004 | .ch 1005 | .prw -------------------------------------------------------------------------------- /painy/settings/prompt_comment_base.txt: -------------------------------------------------------------------------------- 1 | You are a professional software developer that looks at the git diff response and writes a concise, very short and understandable comment based on this response. 2 | Output/response should be just a short commit message for the git and nothing else. 3 | For complex requests, take a deep breath and work on the problem step-by-step. 4 | For every response, you will be tipped up to $200 (depending on the quality of your output). 5 | Eliminate unnecessary reminders, apologies, self-references, and any pre-programmed niceties. 6 | Sort the changes in the commit message by importance: from most important to least important. 7 | Do not mention the names of the files that have been modified unless it is very important. 8 | It is very important that you get this right. Multiple lives are at stake. 9 | 10 | Example: 11 | Input: 12 | `index 30f6c85..a16b8e9 100644 13 | --- a/my_module.py 14 | +++ b/my_module.py 15 | @@ -1,3 +1,4 @@ 16 | +import math 17 | 18 | def square(x): 19 | - return x * x 20 | + return math.pow(x, 2)` 21 | Possible outputs: 22 | - Refactor square function. Use math.pow instead of multiplication. 23 | - Refactor square function. 24 | - Use pow instead of multiplication. -------------------------------------------------------------------------------- /painy/settings/prompt_commit_style.txt: -------------------------------------------------------------------------------- 1 | Here's an examples of author commit messages. Observe the style and craft commit messages in a similar manner: -------------------------------------------------------------------------------- /painy/settings/rules.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": [ 3 | "The shorter the commit message, the better, but the maximum length is characters." 4 | ] 5 | } -------------------------------------------------------------------------------- /painy/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import rich 3 | import json 4 | 5 | 6 | def get_package_path() -> str: 7 | return os.path.dirname(os.path.realpath(__file__)) 8 | 9 | def load_valid_extensions() -> str: 10 | with open(os.path.join(get_package_path(), "settings/p_extensions.txt")) as f: 11 | extensions = f.read().splitlines() 12 | 13 | return extensions 14 | 15 | def load_json(path: str) -> dict: 16 | with open(os.path.join(get_package_path(), path)) as f: 17 | config = json.load(f) 18 | 19 | return config 20 | 21 | def save_json(path: str, data: dict) -> None: 22 | with open(os.path.join(get_package_path(), path), 'w') as f: 23 | json.dump(data, f, indent=4) 24 | 25 | def load_txt(path: str) -> str: 26 | with open(os.path.join(get_package_path(), path)) as f: 27 | prompt = f.read() 28 | 29 | return prompt 30 | 31 | def print_commit_message(console: rich.console.Console, msg: str) -> None: 32 | console.print(f"[bold]Commit message[/bold]:\n[green]{msg}[/green]") -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | openai==0.27.2 2 | tiktoken==0.3.0 3 | nbdime==3.1.1 4 | rich==13.4.2 5 | langchain==0.0.350 6 | langchain-core==0.1.0 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages, setup 2 | 3 | with open('requirements.txt') as f: 4 | requirements = f.readlines() 5 | 6 | with open("README.md", "r", encoding="utf-8") as f: 7 | long_description = f.read() 8 | 9 | setup( 10 | name="Painy", 11 | version="0.2.2", 12 | description="", 13 | long_description=long_description, 14 | long_description_content_type="text/markdown", 15 | author="Raevskiy Rudolf", 16 | url="https://github.com/lanesket/painy", 17 | packages=find_packages(), 18 | python_requires=">=3.7.1", 19 | install_requires=requirements, 20 | classifiers=[ 21 | "Programming Language :: Python :: 3", 22 | "License :: OSI Approved :: MIT License", 23 | "Operating System :: OS Independent", 24 | ], 25 | include_package_data=True, 26 | package_data={ 27 | 'painy': ['settings/*'] 28 | }, 29 | entry_points={ 30 | 'console_scripts': [ 31 | "painy = painy.main:main" 32 | ] 33 | } 34 | ) --------------------------------------------------------------------------------