From eda69ab55feed3b4321665d77ff814e51eaf4ef2 Mon Sep 17 00:00:00 2001 From: tcsenpai Date: Fri, 30 Aug 2024 16:12:04 +0200 Subject: [PATCH] First commit --- .gitignore | 4 + LICENSE.md | 19 ++ README.md | 54 ++++++ env.example | 5 + pyproject.toml | 16 ++ requirements.txt | 4 + src/devlog/__init__.py | 74 ++++++++ .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 1985 bytes .../__pycache__/ghostwriter.cpython-310.pyc | Bin 0 -> 2146 bytes .../__pycache__/gitoperations.cpython-310.pyc | Bin 0 -> 3360 bytes .../__pycache__/ollamator.cpython-310.pyc | Bin 0 -> 2148 bytes src/devlog/libs/ghostwriter.py | 58 ++++++ src/devlog/libs/gitoperations.py | 106 +++++++++++ src/devlog/libs/ollamator.py | 56 ++++++ uv.lock | 168 ++++++++++++++++++ 15 files changed, 564 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 env.example create mode 100644 pyproject.toml create mode 100644 requirements.txt create mode 100644 src/devlog/__init__.py create mode 100644 src/devlog/__pycache__/__init__.cpython-310.pyc create mode 100644 src/devlog/libs/__pycache__/ghostwriter.cpython-310.pyc create mode 100644 src/devlog/libs/__pycache__/gitoperations.cpython-310.pyc create mode 100644 src/devlog/libs/__pycache__/ollamator.cpython-310.pyc create mode 100644 src/devlog/libs/ghostwriter.py create mode 100644 src/devlog/libs/gitoperations.py create mode 100644 src/devlog/libs/ollamator.py create mode 100644 uv.lock diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..01f3180 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.env +temp_repo +.venv +output diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..15bd90e --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,19 @@ + +This project is licensed under the WTFPL License. + +More details can be found here: https://spdx.org/licenses/WTFPL.html + + +DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +Version 2, December 2004 + +Copyright (C) 2024 tcsenpai + +Everyone is permitted to copy and distribute verbatim or modified copies of this license document, and changing it is allowed as long as the name is changed. + +DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..d98d913 --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +# Devlog + +Devlog is a powerful tool that automatically generates (b)log posts from your Git commit history, providing a natural language summary of your development progress. + +## Description + +Devlog retrieves all the commit messages in a repository (local or remote), groups them by customizable time periods, and generates blog posts in natural language. This tool is perfect for developers who want to maintain a development log or changelog without the manual effort of writing blog posts. + +## Features + +- Supports both local and remote Git repositories +- Customizable time periods for grouping commits +- Natural language generation for readable blog posts +- Output formats: Markdown and HTML + +## Installation and usage + +This project uses [uv](https://github.com/astral-sh/uv) to manage the environment and dependencies. +Please [install it](https://github.com/astral-sh/uv) first. + +Once you have uv installed, you can install the dependencies with following commands: + +```bash +uv venv +``` + +## Usage + +```bash +uv run src/devlog/__init__.py +``` + +Alternatively, you can skip the virtual environment and run the script directly with: + +```bash +pip install -r requirements.txt +python src/devlog/__init__.py +``` + +## Configuration + +Create a `.env` file in the root directory by copying the `env.example` file and set the following environment variables: + +```bash +OLLAMA_URL= +GIT_REPO= +GIT_TOKEN= +DEFAULT_BRANCH= +GROUP_COMMITS_DAYS= +``` + +## License + +This project is licensed under the WTFPL License. See the `LICENSE` file for more details. \ No newline at end of file diff --git a/env.example b/env.example new file mode 100644 index 0000000..426804d --- /dev/null +++ b/env.example @@ -0,0 +1,5 @@ +GIT_REPO=/your/repo/path/or/url +GIT_TOKEN=your-git-token +OLLAMA_URL=http:/localhost:11434 +DEFAULT_BRANCH=main +GROUP_COMMITS_DAYS=30 \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..c3c94ea --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,16 @@ +[project] +name = "devlog" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.10" +dependencies = [ + "gitpython>=3.1.43", + "python-dotenv>=1.0.1", + "requests>=2.32.3", + "markdown>=3.7", +] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..59344c4 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +python-dotenv +gitpython +markdown +requests diff --git a/src/devlog/__init__.py b/src/devlog/__init__.py new file mode 100644 index 0000000..067bbbe --- /dev/null +++ b/src/devlog/__init__.py @@ -0,0 +1,74 @@ +import argparse +import os +from dotenv import load_dotenv +import logging +from devlog.libs.gitoperations import GitOperations +from devlog.libs.ollamator import Ollamator +from devlog.libs.ghostwriter import write_weblog + +# Set up logging +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') +logger = logging.getLogger(__name__) + +def main(): + # Load environment variables from .env file + load_dotenv() + + parser = argparse.ArgumentParser(description="List commits from a Git repository and process text with Ollama.") + parser.add_argument("repo", nargs='?', default=None, help="Path to local repository or URL of remote repository") + parser.add_argument("--token", help="Authentication token for remote repositories") + parser.add_argument("--ollama-url", help="URL of the Ollama instance") + args = parser.parse_args() + + # Prioritize command line arguments, then .env, then default values + repo = args.repo or os.getenv('GIT_REPO') or '.' + token = args.token or os.getenv('GIT_TOKEN') + ollama_url = args.ollama_url or os.getenv('OLLAMA_URL') or 'http://localhost:11434' + + # Process Git repository + git_ops = GitOperations(repo, token) + #git_ops.list_commits() + grouped_commits = git_ops.group_commits_by_days(14) + + # Initialize OllamaProcessor (but not using it yet) + ollama_processor = Ollamator(ollama_url) + + # Process each commit message chunk + total_chunks = len(grouped_commits) + print(f"Total chunks: {total_chunks}") + for date_range, commits in grouped_commits.items(): + total_commits = len(commits) + commit_messages = "" + for commit in commits: + commit_messages += f"{commit.hexsha[:7]} - {commit.message.strip()}\n" + print(commit_messages) + print("\n") + print(f"Total commits: {total_commits}") + print(f"Commits from {date_range}:") + print("- Processing...") + + devlog = ollama_processor.process_text(f"Date range: {date_range}\n\n{commit_messages}") + + # Prepare the weblog data + weblog_data = { + 'date_range': date_range, + 'title': f"Development Update for {date_range}", + 'content': devlog + } + + # Write the weblog + write_weblog(weblog_data, 'output') + + print(devlog) + print("\n == DEVLOG POST END == \n") + + #try: + # for commit in git_ops.commits: + # ollama_processor.process_text(commit.message) + #except KeyboardInterrupt: + # logger.info("Interrupted by user") + #except Exception as e: + # logger.error(f"Error processing commit message: {e}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/devlog/__pycache__/__init__.cpython-310.pyc b/src/devlog/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bc775c01461fa25d0f40a8d7fd8015ef71ea1997 GIT binary patch literal 1985 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 X8@z@H&hfV3Y6pAZ!WOu*&f4t%l`%k7 literal 0 HcmV?d00001 diff --git a/src/devlog/libs/__pycache__/ollamator.cpython-310.pyc b/src/devlog/libs/__pycache__/ollamator.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8cf01dd69f57049edbd3ee6b640760e09381e8c7 GIT binary patch literal 2148 zcma)7&5Ij16qht(d+c~O%cfl@4XvPUz@@|q?J2aS(4?hb8)yh2z6`T7vUisGa3nd7 z%{Ua2Yv~`526m7AGkWZ?S6vH*-g_$agsgif-p>r)P^Af$_zPd^B!Mr9z(x+ zq!1IevzfEOh3)8fjzGx6cE6m-NGlo1sV>LLn6xaS zOqw!I(dAJQO_Y}7QbPmDQCT_7Rbi!7QGWHb7cI*IZi)nbwvdw&=Bbgklv+JZ)m%nd zChe}0ahd0-HMJ_{sg^mm#Wn_(wZg4E4 z(d&IS@ap^K+Kpe=HWupna%~t*)8RxFO2c@-oxf+w!UbBHX;~PxeD{{t$UEh0R-|!c z3FADqyRx}y$bqp@5vv<=`9`CgsOBEaFYnymMg|*CSigL?P>-f6wg}_V=fmUOkb?^i zLTvqDri?Ytrxey);Wl)X8WkcGA$GGfqx-nKZsXfV_tv9WO)ZKRd{s_RtFEO8w!26W zZW10OH}t(k?fDi05%b74HovZG078ZELiO7t4(w*edl=eVkw zs}aO1t`FlFMI4exf@iKl_*}Hth3^Y4c>C|IPQU}NCpN?eU+2Mh8@%b)4}0;?a_fY_ zPCGoqVnm(CELLH%eA9x36aGZ}&|is4>~nCEcMt61by1E$PcZ>pEZDuC?_hX8IDupw*0S~ zitf_>1Yf^J;sj1Tq7u;b($LJ%EMPqz`2Bb4uY-C{kkq#{Wh)E c;UFjHd3V8w4YH3x-ONz_1BP None: + """ + Create the necessary folder structure if it doesn't exist. + """ + os.makedirs(base_path, exist_ok=True) + os.makedirs(os.path.join(base_path, 'markdown'), exist_ok=True) + os.makedirs(os.path.join(base_path, 'html'), exist_ok=True) + +def write_markdown_file(content: str, file_path: str) -> None: + """ + Write the given content as a markdown file at the specified path. + """ + with open(file_path, 'w', encoding='utf-8') as f: + f.write(content) + +def write_text_file(content: str, file_path: str) -> None: + """ + Write the given content as a text file at the specified path. + """ + html = markdown.markdown(content) + text = ''.join(markdown.Markdown().convert(html).split('\n')) + with open(file_path, 'w', encoding='utf-8') as f: + f.write(text) + +def write_weblog(weblog: Dict[str, Any], output_dir: str) -> None: + """ + Write the generated weblog as markdown and text files in the appropriate folder structure. + """ + create_folder_structure(output_dir) + + date_range = weblog.get('date_range', 'unknown_date') + title = weblog.get('title', 'Untitled') + content = weblog.get('content', '') + + # Create a filename-friendly version of the title + filename = f"{date_range}_{title.lower().replace(' ', '_')}" + + # Write markdown file + md_path = os.path.join(output_dir, 'markdown', f"{filename}.md") + write_markdown_file(content, md_path) + + # Write text file + txt_path = os.path.join(output_dir, 'html', f"{filename}.html") + write_text_file(content, txt_path) + +if __name__ == "__main__": + # Test the module functionality + test_weblog = { + 'date_range': '2023-06-01_to_2023-06-07', + 'title': 'Weekly Development Update', + 'content': '# Weekly Development Update\n\nThis week, we made significant progress on...' + } + write_weblog(test_weblog, 'output') + print("Test weblog written successfully.") \ No newline at end of file diff --git a/src/devlog/libs/gitoperations.py b/src/devlog/libs/gitoperations.py new file mode 100644 index 0000000..7ff2454 --- /dev/null +++ b/src/devlog/libs/gitoperations.py @@ -0,0 +1,106 @@ +import os +import shutil +from git import Repo, GitCommandError +from git.exc import InvalidGitRepositoryError +import logging +from datetime import datetime, timedelta +from collections import defaultdict + +logger = logging.getLogger(__name__) + +class GitOperations: + def __init__(self, repo_path_or_url='.', token=None): + self.repo_path_or_url = os.getenv('REPO_PATH_OR_URL', repo_path_or_url) + self.token = os.getenv('GIT_TOKEN', token) + self.temp_dir = None + self.commits = [] + self.default_branch = os.getenv('DEFAULT_BRANCH', 'main') + + def list_commits(self): + if self.repo_path_or_url.startswith('http://') or self.repo_path_or_url.startswith('https://'): + self._clone_repo() + repo_path = self.temp_dir + else: + repo_path = self.repo_path_or_url + + try: + repo = Repo(repo_path) + + if os.path.isdir(repo_path): + logger.info(f"Accessing repository at {repo_path}") + + commits = list(repo.iter_commits(self.default_branch)) # Change 'main' to your default branch name if different + + logger.info(f"Found {len(commits)} commits") + + for commit in commits: + self.commits.append(commit) + print(f"Commit: {commit.hexsha}") + print(f"Author: {commit.author}") + print(f"Date: {commit.committed_datetime}") + print(f"Message: {commit.message}") + print("-" * 40) + + except (GitCommandError, InvalidGitRepositoryError) as e: + logger.error(f"Error: {e}") + finally: + self._cleanup() + + def _clone_repo(self): + self.temp_dir = os.path.join(os.getcwd(), 'temp_repo') + if os.path.exists(self.temp_dir): + shutil.rmtree(self.temp_dir) + logger.info(f"Cloning remote repository to {self.temp_dir}") + + if self.token: + parts = self.repo_path_or_url.split('://') + clone_url = f"{parts[0]}://{self.token}@{parts[1]}" + else: + clone_url = self.repo_path_or_url + + try: + Repo.clone_from(clone_url, self.temp_dir) + except GitCommandError as e: + logger.error(f"Failed to clone repository: {e}") + raise + + def _cleanup(self): + if self.temp_dir and os.path.exists(self.temp_dir): + logger.info(f"Cleaning up temporary directory {self.temp_dir}") + shutil.rmtree(self.temp_dir) + + def group_commits_by_days(self, days=None): + if days is None: + days = int(os.getenv('GROUP_COMMITS_DAYS', 30)) + + if not self.commits: + self.list_commits() # Ensure commits are loaded + + grouped_commits = defaultdict(list) + + # Sort commits by date (oldest first) + sorted_commits = sorted(self.commits, key=lambda c: c.committed_datetime) + + if not sorted_commits: + return [] + + # Get the date of the oldest commit + current_date = sorted_commits[0].committed_datetime.date() + end_date = current_date + timedelta(days=days) + group = [] + + for commit in sorted_commits: + commit_date = commit.committed_datetime.date() + if commit_date <= end_date: + group.append(commit) + else: + grouped_commits[f"{current_date} to {end_date}"] = group + current_date = commit_date + end_date = current_date + timedelta(days=days) + group = [commit] + + # Add the last group + if group: + grouped_commits[f"{current_date} to {end_date}"] = group + + return dict(grouped_commits) \ No newline at end of file diff --git a/src/devlog/libs/ollamator.py b/src/devlog/libs/ollamator.py new file mode 100644 index 0000000..469576c --- /dev/null +++ b/src/devlog/libs/ollamator.py @@ -0,0 +1,56 @@ +import requests +import json +import logging + +logger = logging.getLogger(__name__) + +class Ollamator: + def __init__(self, ollama_url, model="llama3"): + self.ollama_url = ollama_url + self.model = model + + def process_text(self, text): + system_prompt = """ + You are a professional social manager for a development team. + Your one and only goal is to review all the commits and their messages on the given repository and transform them into multiple coherent blog post. + You will be given a list of commits and their messages, and you will need to transform them into a single coherent blog post. + Use the date given at the beginning of the prompt to format the date in the blog post and to create a title. + """ + try: + # Combine the instruction prompt and the input text + full_prompt = f"{system_prompt}\n\n{text}" + + # Prepare the payload for the Ollama API + payload = { + "model": self.model, + "prompt": full_prompt, + "stream": False + } + + # Send the request to the Ollama instance + response = requests.post(f"{self.ollama_url}/api/generate", json=payload) + response.raise_for_status() # Raise an exception for bad status codes + + # Parse the JSON response + result = response.json() + + return result['response'] + + except requests.RequestException as e: + logger.error(f"Error communicating with Ollama instance: {e}") + return None + except json.JSONDecodeError as e: + logger.error(f"Error decoding JSON response: {e}") + return None + except KeyError as e: + logger.error(f"Unexpected response format: {e}") + return None + + def process_file(self, file_path): + try: + with open(file_path, 'r') as file: + text = file.read() + return self.process_text(text) + except IOError as e: + logger.error(f"Error reading file {file_path}: {e}") + return None \ No newline at end of file diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..3c884cc --- /dev/null +++ b/uv.lock @@ -0,0 +1,168 @@ +version = 1 +requires-python = ">=3.10" + +[[package]] +name = "certifi" +version = "2024.8.30" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/09/c1bc53dab74b1816a00d8d030de5bf98f724c52c1635e07681d312f20be8/charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", size = 104809 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/61/095a0aa1a84d1481998b534177c8566fdc50bb1233ea9a0478cd3cc075bd/charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", size = 194219 }, + { url = "https://files.pythonhosted.org/packages/cc/94/f7cf5e5134175de79ad2059edf2adce18e0685ebdb9227ff0139975d0e93/charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", size = 122521 }, + { url = "https://files.pythonhosted.org/packages/46/6a/d5c26c41c49b546860cc1acabdddf48b0b3fb2685f4f5617ac59261b44ae/charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", size = 120383 }, + { url = "https://files.pythonhosted.org/packages/b8/60/e2f67915a51be59d4539ed189eb0a2b0d292bf79270410746becb32bc2c3/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", size = 138223 }, + { url = "https://files.pythonhosted.org/packages/05/8c/eb854996d5fef5e4f33ad56927ad053d04dc820e4a3d39023f35cad72617/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", size = 148101 }, + { url = "https://files.pythonhosted.org/packages/f6/93/bb6cbeec3bf9da9b2eba458c15966658d1daa8b982c642f81c93ad9b40e1/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", size = 140699 }, + { url = "https://files.pythonhosted.org/packages/da/f1/3702ba2a7470666a62fd81c58a4c40be00670e5006a67f4d626e57f013ae/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", size = 142065 }, + { url = "https://files.pythonhosted.org/packages/3f/ba/3f5e7be00b215fa10e13d64b1f6237eb6ebea66676a41b2bcdd09fe74323/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", size = 144505 }, + { url = "https://files.pythonhosted.org/packages/33/c3/3b96a435c5109dd5b6adc8a59ba1d678b302a97938f032e3770cc84cd354/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", size = 139425 }, + { url = "https://files.pythonhosted.org/packages/43/05/3bf613e719efe68fb3a77f9c536a389f35b95d75424b96b426a47a45ef1d/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", size = 145287 }, + { url = "https://files.pythonhosted.org/packages/58/78/a0bc646900994df12e07b4ae5c713f2b3e5998f58b9d3720cce2aa45652f/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", size = 149929 }, + { url = "https://files.pythonhosted.org/packages/eb/5c/97d97248af4920bc68687d9c3b3c0f47c910e21a8ff80af4565a576bd2f0/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", size = 141605 }, + { url = "https://files.pythonhosted.org/packages/a8/31/47d018ef89f95b8aded95c589a77c072c55e94b50a41aa99c0a2008a45a4/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", size = 142646 }, + { url = "https://files.pythonhosted.org/packages/ae/d5/4fecf1d58bedb1340a50f165ba1c7ddc0400252d6832ff619c4568b36cc0/charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", size = 92846 }, + { url = "https://files.pythonhosted.org/packages/a2/a0/4af29e22cb5942488cf45630cbdd7cefd908768e69bdd90280842e4e8529/charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", size = 100343 }, + { url = "https://files.pythonhosted.org/packages/68/77/02839016f6fbbf808e8b38601df6e0e66c17bbab76dff4613f7511413597/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", size = 191647 }, + { url = "https://files.pythonhosted.org/packages/3e/33/21a875a61057165e92227466e54ee076b73af1e21fe1b31f1e292251aa1e/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", size = 121434 }, + { url = "https://files.pythonhosted.org/packages/dd/51/68b61b90b24ca35495956b718f35a9756ef7d3dd4b3c1508056fa98d1a1b/charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", size = 118979 }, + { url = "https://files.pythonhosted.org/packages/e4/a6/7ee57823d46331ddc37dd00749c95b0edec2c79b15fc0d6e6efb532e89ac/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", size = 136582 }, + { url = "https://files.pythonhosted.org/packages/74/f1/0d9fe69ac441467b737ba7f48c68241487df2f4522dd7246d9426e7c690e/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", size = 146645 }, + { url = "https://files.pythonhosted.org/packages/05/31/e1f51c76db7be1d4aef220d29fbfa5dbb4a99165d9833dcbf166753b6dc0/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", size = 139398 }, + { url = "https://files.pythonhosted.org/packages/40/26/f35951c45070edc957ba40a5b1db3cf60a9dbb1b350c2d5bef03e01e61de/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", size = 140273 }, + { url = "https://files.pythonhosted.org/packages/07/07/7e554f2bbce3295e191f7e653ff15d55309a9ca40d0362fcdab36f01063c/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", size = 142577 }, + { url = "https://files.pythonhosted.org/packages/d8/b5/eb705c313100defa57da79277d9207dc8d8e45931035862fa64b625bfead/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", size = 137747 }, + { url = "https://files.pythonhosted.org/packages/19/28/573147271fd041d351b438a5665be8223f1dd92f273713cb882ddafe214c/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", size = 143375 }, + { url = "https://files.pythonhosted.org/packages/cf/7c/f3b682fa053cc21373c9a839e6beba7705857075686a05c72e0f8c4980ca/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", size = 148474 }, + { url = "https://files.pythonhosted.org/packages/1e/49/7ab74d4ac537ece3bc3334ee08645e231f39f7d6df6347b29a74b0537103/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", size = 140232 }, + { url = "https://files.pythonhosted.org/packages/2d/dc/9dacba68c9ac0ae781d40e1a0c0058e26302ea0660e574ddf6797a0347f7/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", size = 140859 }, + { url = "https://files.pythonhosted.org/packages/6c/c2/4a583f800c0708dd22096298e49f887b49d9746d0e78bfc1d7e29816614c/charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", size = 92509 }, + { url = "https://files.pythonhosted.org/packages/57/ec/80c8d48ac8b1741d5b963797b7c0c869335619e13d4744ca2f67fc11c6fc/charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", size = 99870 }, + { url = "https://files.pythonhosted.org/packages/d1/b2/fcedc8255ec42afee97f9e6f0145c734bbe104aac28300214593eb326f1d/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", size = 192892 }, + { url = "https://files.pythonhosted.org/packages/2e/7d/2259318c202f3d17f3fe6438149b3b9e706d1070fe3fcbb28049730bb25c/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", size = 122213 }, + { url = "https://files.pythonhosted.org/packages/3a/52/9f9d17c3b54dc238de384c4cb5a2ef0e27985b42a0e5cc8e8a31d918d48d/charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", size = 119404 }, + { url = "https://files.pythonhosted.org/packages/99/b0/9c365f6d79a9f0f3c379ddb40a256a67aa69c59609608fe7feb6235896e1/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", size = 137275 }, + { url = "https://files.pythonhosted.org/packages/91/33/749df346e93d7a30cdcb90cbfdd41a06026317bfbfb62cd68307c1a3c543/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", size = 147518 }, + { url = "https://files.pythonhosted.org/packages/72/1a/641d5c9f59e6af4c7b53da463d07600a695b9824e20849cb6eea8a627761/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", size = 140182 }, + { url = "https://files.pythonhosted.org/packages/ee/fb/14d30eb4956408ee3ae09ad34299131fb383c47df355ddb428a7331cfa1e/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", size = 141869 }, + { url = "https://files.pythonhosted.org/packages/df/3e/a06b18788ca2eb6695c9b22325b6fde7dde0f1d1838b1792a0076f58fe9d/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", size = 144042 }, + { url = "https://files.pythonhosted.org/packages/45/59/3d27019d3b447a88fe7e7d004a1e04be220227760264cc41b405e863891b/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", size = 138275 }, + { url = "https://files.pythonhosted.org/packages/7b/ef/5eb105530b4da8ae37d506ccfa25057961b7b63d581def6f99165ea89c7e/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", size = 144819 }, + { url = "https://files.pythonhosted.org/packages/a2/51/e5023f937d7f307c948ed3e5c29c4b7a3e42ed2ee0b8cdf8f3a706089bf0/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", size = 149415 }, + { url = "https://files.pythonhosted.org/packages/24/9d/2e3ef673dfd5be0154b20363c5cdcc5606f35666544381bee15af3778239/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", size = 141212 }, + { url = "https://files.pythonhosted.org/packages/5b/ae/ce2c12fcac59cb3860b2e2d76dc405253a4475436b1861d95fe75bdea520/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", size = 142167 }, + { url = "https://files.pythonhosted.org/packages/ed/3a/a448bf035dce5da359daf9ae8a16b8a39623cc395a2ffb1620aa1bce62b0/charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", size = 93041 }, + { url = "https://files.pythonhosted.org/packages/b6/7c/8debebb4f90174074b827c63242c23851bdf00a532489fba57fef3416e40/charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", size = 100397 }, + { url = "https://files.pythonhosted.org/packages/28/76/e6222113b83e3622caa4bb41032d0b1bf785250607392e1b778aca0b8a7d/charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", size = 48543 }, +] + +[[package]] +name = "devlog" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "gitpython" }, + { name = "markdown" }, + { name = "python-dotenv" }, + { name = "requests" }, +] + +[package.metadata] +requires-dist = [ + { name = "gitpython", specifier = ">=3.1.43" }, + { name = "markdown", specifier = ">=3.7" }, + { name = "python-dotenv", specifier = ">=1.0.1" }, + { name = "requests", specifier = ">=2.32.3" }, +] + +[[package]] +name = "gitdb" +version = "4.0.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "smmap" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/0d/bbb5b5ee188dec84647a4664f3e11b06ade2bde568dbd489d9d64adef8ed/gitdb-4.0.11.tar.gz", hash = "sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b", size = 394469 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/5b/8f0c4a5bb9fd491c277c21eff7ccae71b47d43c4446c9d0c6cff2fe8c2c4/gitdb-4.0.11-py3-none-any.whl", hash = "sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4", size = 62721 }, +] + +[[package]] +name = "gitpython" +version = "3.1.43" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "gitdb" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b6/a1/106fd9fa2dd989b6fb36e5893961f82992cf676381707253e0bf93eb1662/GitPython-3.1.43.tar.gz", hash = "sha256:35f314a9f878467f5453cc1fee295c3e18e52f1b99f10f6cf5b1682e968a9e7c", size = 214149 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/bd/cc3a402a6439c15c3d4294333e13042b915bbeab54edc457c723931fed3f/GitPython-3.1.43-py3-none-any.whl", hash = "sha256:eec7ec56b92aad751f9912a73404bc02ba212a23adb2c7098ee668417051a1ff", size = 207337 }, +] + +[[package]] +name = "idna" +version = "3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/ac/e349c5e6d4543326c6883ee9491e3921e0d07b55fdf3cce184b40d63e72a/idna-3.8.tar.gz", hash = "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603", size = 189467 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/7e/d71db821f177828df9dea8c42ac46473366f191be53080e552e628aad991/idna-3.8-py3-none-any.whl", hash = "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac", size = 66894 }, +] + +[[package]] +name = "markdown" +version = "3.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/28/3af612670f82f4c056911fbbbb42760255801b3068c48de792d354ff4472/markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2", size = 357086 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/08/83871f3c50fc983b88547c196d11cf8c3340e37c32d2e9d6152abe2c61f7/Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803", size = 106349 }, +] + +[[package]] +name = "python-dotenv" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 }, +] + +[[package]] +name = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, +] + +[[package]] +name = "smmap" +version = "5.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/88/04/b5bf6d21dc4041000ccba7eb17dd3055feb237e7ffc2c20d3fae3af62baa/smmap-5.0.1.tar.gz", hash = "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62", size = 22291 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/a5/10f97f73544edcdef54409f1d839f6049a0d79df68adbc1ceb24d1aaca42/smmap-5.0.1-py3-none-any.whl", hash = "sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da", size = 24282 }, +] + +[[package]] +name = "urllib3" +version = "2.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/6d/fa469ae21497ddc8bc93e5877702dca7cb8f911e337aca7452b5724f1bb6/urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168", size = 292266 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/1c/89ffc63a9605b583d5df2be791a27bc1a42b7c32bab68d3c8f2f73a98cd4/urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472", size = 121444 }, +]