diff --git a/pyproject.toml b/pyproject.toml index 7b8b558c..cdbfb286 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "readmeai" -version = "0.5.062" +version = "0.5.063" description = "๐Ÿ‘พ Automated README file generator, powered by large language model APIs." authors = ["Eli "] license = "MIT" diff --git a/readmeai/config/enums.py b/readmeai/config/enums.py index 96f2bf00..65e8fd7f 100644 --- a/readmeai/config/enums.py +++ b/readmeai/config/enums.py @@ -1,4 +1,4 @@ -"""Enum classes for the readme-ai CLI.""" +"""Enums for the CLI options, used to customize the README file.""" from enum import Enum diff --git a/readmeai/config/settings/config.toml b/readmeai/config/settings/config.toml index ac01af2f..0c808571 100644 --- a/readmeai/config/settings/config.toml +++ b/readmeai/config/settings/config.toml @@ -68,12 +68,12 @@ quick_links = """ ## ๐Ÿ”— Quick Links > - [๐Ÿ“ Overview](#-overview) -> - [๐Ÿ“ฆ Features](#-features) +> - [๐Ÿ”ฎ Features](#-features) > - [๐Ÿ“‚ Repository Structure](#-repository-structure) > - [๐Ÿงฉ Modules](#-modules) > - [๐Ÿš€ Getting Started](#-getting-started) -> - [โš™๏ธ Install](#๏ธ-install) -> - [โ–บ Using {repo_name}](#-using-{repo_name}) +> - [๐Ÿ“ฆ Install](#๏ธ-install) +> - [๐Ÿ’ป Using {repo_name}](#-using-{repo_name}) > - [๐Ÿงช Tests](#-tests) > - [๐Ÿ›  Project Roadmap](#-project-roadmap) > - [๐Ÿค Contributing](#-contributing) @@ -88,12 +88,12 @@ toc = """\

Table of Contents

- [๐Ÿ“ Overview](#-overview) -- [๐Ÿ“ฆ Features](#-features) +- [๐Ÿ”ฎ Features](#-features) - [๐Ÿ“‚ Repository Structure](#-repository-structure) - [๐Ÿงฉ Modules](#-modules) - [๐Ÿš€ Getting Started](#-getting-started) - - [โš™๏ธ Install](#๏ธ-install) - - [โ–บ Using {repo_name}](#-using-{repo_name}) + - [๐Ÿ“ฆ Install](#๏ธ-install) + - [๐Ÿ’ป Using {repo_name}](#-using-{repo_name}) - [๐Ÿงช Tests](#-tests) - [๐Ÿ›  Project Roadmap](#-project-roadmap) - [๐Ÿค Contributing](#-contributing) @@ -114,7 +114,7 @@ overview = """ # Features Template features = """ -## ๐Ÿ“ฆ Features +## ๐Ÿ”ฎ Features {0} @@ -145,13 +145,13 @@ quickstart = """ ## ๐Ÿš€ Getting Started -***Requirements*** +### โš™๏ธ Prerequisites -Ensure you have the following dependencies installed on your system: +Ensure you have the following installed on your local machine: * {prerequisites} -### โš™๏ธ Install +### ๐Ÿ“ฆ Install 1. Clone the {repo_name} repository: @@ -171,7 +171,7 @@ cd {repo_name} {install_command} ``` -### โ–บ Using `{repo_name}` +### ๐Ÿ’ป Using `{repo_name}` Use the following command to run {repo_name}: diff --git a/readmeai/config/settings/markdown.toml b/readmeai/config/settings/markdown.toml index 3d02bca7..3570443e 100644 --- a/readmeai/config/settings/markdown.toml +++ b/readmeai/config/settings/markdown.toml @@ -41,12 +41,12 @@ quick_links = """ ## ๐Ÿ”— Quick Links > - [๐Ÿ“ Overview](#-overview) -> - [๐Ÿ“ฆ Features](#-features) +> - [๐Ÿ”ฎ Features](#-features) > - [๐Ÿ“‚ Repository Structure](#-repository-structure) > - [๐Ÿงฉ Modules](#-modules) > - [๐Ÿš€ Getting Started](#-getting-started) -> - [โš™๏ธ Install](#๏ธ-install) -> - [โ–บ Using {repo_name}](#-using-{repo_name}) +> - [๐Ÿ“ฆ Install](#๏ธ-install) +> - [๐Ÿ’ป Using {repo_name}](#-using-{repo_name}) > - [๐Ÿงช Tests](#-tests) > - [๐Ÿ›  Project Roadmap](#-project-roadmap) > - [๐Ÿค Contributing](#-contributing) @@ -61,12 +61,12 @@ toc = """\

Table of Contents

- [๐Ÿ“ Overview](#-overview) -- [๐Ÿ“ฆ Features](#-features) +- [๐Ÿ”ฎ Features](#-features) - [๐Ÿ“‚ Repository Structure](#-repository-structure) - [๐Ÿงฉ Modules](#-modules) - [๐Ÿš€ Getting Started](#-getting-started) - - [โš™๏ธ Install](#๏ธ-install) - - [โ–บ Using {repo_name}](#-using-{repo_name}) + - [๐Ÿ“ฆ Install](#๏ธ-install) + - [๐Ÿ’ป Using {repo_name}](#-using-{repo_name}) - [๐Ÿงช Tests](#-tests) - [๐Ÿ›  Project Roadmap](#-project-roadmap) - [๐Ÿค Contributing](#-contributing) @@ -87,7 +87,7 @@ overview = """ # Features Template features = """ -## ๐Ÿ“ฆ Features +## ๐Ÿ”ฎ Features {0} @@ -118,13 +118,13 @@ quickstart = """ ## ๐Ÿš€ Getting Started -***Requirements*** +### โš™๏ธ Prerequisites -Ensure you have the following dependencies installed on your system: +Ensure you have the following installed on your local machine: * {prerequisites} -### โš™๏ธ Install +### ๐Ÿ“ฆ Install 1. Clone the {repo_name} repository: @@ -144,7 +144,7 @@ cd {repo_name} {install_command} ``` -### โ–บ Using `{repo_name}` +### ๐Ÿ’ป Using `{repo_name}` Use the following command to run {repo_name}: @@ -209,7 +209,7 @@ Contributions are welcome! Here are several ways you can contribute:
Contributor Graph
-

+

diff --git a/readmeai/config/settings/prompts.toml b/readmeai/config/settings/prompts.toml index 5ea5ab35..28ed0346 100644 --- a/readmeai/config/settings/prompts.toml +++ b/readmeai/config/settings/prompts.toml @@ -68,7 +68,7 @@ File path: {1} File contents: {2} ================================================================================ Additionally, avoid using words like `This file`, `This code`, etc. in your response. \ -Use concise language and ensure the summary response is no more than 50 tokens. +Use concise language and ensure the summary response is no more than 60 tokens. """ overview = """Analyze the codebase named {0} ({1}) and provide a robust, yet succinct overview of the project. \ diff --git a/readmeai/core/models.py b/readmeai/core/models.py index 9ff16d5d..d68ee673 100644 --- a/readmeai/core/models.py +++ b/readmeai/core/models.py @@ -153,7 +153,7 @@ async def batch_request( additional_responses = await self._batch_prompts(additional_prompts) synthesize_features_table_prompt = await set_feature_context( - additional_responses[0], + additional_responses[0] ) synthesize_features_table_response = await self._batch_prompts( synthesize_features_table_prompt @@ -259,13 +259,10 @@ async def _handle_response_code_summary( prompt = self.prompts["prompts"]["file_summary"].format( self.config.md.tree, file_path, file_content ) - tokens = update_max_tokens(self.tokens, prompt) - _, summary_or_error = await self._handle_response( file_path, prompt, tokens ) - summary_text.append((file_path, summary_or_error)) return summary_text diff --git a/readmeai/core/preprocess.py b/readmeai/core/preprocess.py index 44b6e1cc..70ee3432 100644 --- a/readmeai/core/preprocess.py +++ b/readmeai/core/preprocess.py @@ -11,7 +11,7 @@ from readmeai.parsers.factory import parser_handler from readmeai.utils.logger import Logger -GITHUB_ACTIONS_PATH = ".github/workflows" +_github_actions_path = ".github/workflows" @dataclass @@ -113,7 +113,7 @@ def get_dependencies(self, contents: List[FileContext]) -> List[str]: file_data.file_name ] = file_data.dependencies - if GITHUB_ACTIONS_PATH in str(file_data.file_path): + if _github_actions_path in str(file_data.file_path): dependencies.add("github actions") return list(dependencies), dependency_dict @@ -143,7 +143,7 @@ def _process_file_path( Processes an individual file path and returns FileContext. """ relative_path = file_path.relative_to(repo_path) - if GITHUB_ACTIONS_PATH in str(relative_path): + if _github_actions_path in str(relative_path): return FileContext( file_path=relative_path, file_name="github actions", @@ -205,8 +205,8 @@ def preprocessor( config = config_loader.config repo_processor = RepositoryProcessor(config_loader) repo_context = repo_processor.generate_contents(temp_dir) - # repo_context = repo_processor.tokenize_content(repo_context) repo_context = repo_processor.language_mapper(repo_context) + # repo_context = repo_processor.tokenize_content(repo_context) dependencies, dependency_dict = repo_processor.get_dependencies( repo_context @@ -220,4 +220,4 @@ def preprocessor( config_loader, dependencies, raw_files, temp_dir ).md_tree - return dependencies, raw_files, dependency_dict + return dependencies, raw_files diff --git a/readmeai/core/utils.py b/readmeai/core/utils.py index af109530..5ee49fbd 100644 --- a/readmeai/core/utils.py +++ b/readmeai/core/utils.py @@ -52,18 +52,18 @@ def set_model_engine(config: Settings) -> None: config.llm.model = config.llm.model config.llm.offline = False _logger.info("Running CLI with OpenAI API llm engine...") - return config + elif vertex_env: config.llm.api = ModelOptions.VERTEX.name config.llm.model = "gemini-pro" config.llm.offline = False _logger.info("Running CLI with Google Vertex AI llm engine...") - return config + elif config.llm.api == ModelOptions.OLLAMA.name: config.llm.model = "ollama" config.llm.offline = False _logger.info("Running CLI with Ollama llm engine...") - return config + else: if config.llm.api == ModelOptions.OFFLINE.name: message = "Offline mode enabled by user via CLI." @@ -71,7 +71,7 @@ def set_model_engine(config: Settings) -> None: message = ( "\n\n\t\t...No LLM API settings exist in environment..." ) - return _set_offline(config, message) + _set_offline(config, message) elif llm_engine is not None: _logger.info(f"LLM API CLI input received: {llm_engine}") @@ -82,9 +82,9 @@ def set_model_engine(config: Settings) -> None: config.llm.model = config.llm.model config.llm.offline = False _logger.info("OpenAI settings found in environment!") - return config + else: - return _set_offline( + _set_offline( config, "\n\t\t...OpenAI settings NOT FOUND in environment...", ) @@ -94,7 +94,6 @@ def set_model_engine(config: Settings) -> None: config.llm.model = "ollama" config.llm.offline = False _logger.info("Ollama settings found in environment!") - return config elif llm_engine == ModelOptions.VERTEX.name: if ( @@ -104,10 +103,9 @@ def set_model_engine(config: Settings) -> None: config.llm.model = "gemini-pro" config.llm.offline = False _logger.info("Vertex AI settings found in environment!") - return config else: - return _set_offline( + _set_offline( config, "\n\t\t...Vertex AI settings NOT FOUND in environment...", ) @@ -117,7 +115,7 @@ def set_model_engine(config: Settings) -> None: message = "\n\t\t...Offline mode enabled by user..." else: message = "Invalid LLM API service provided!" - return _set_offline(config, message) + _set_offline(config, message) def _scan_environ(keys: list[str]) -> bool: @@ -141,4 +139,3 @@ def _set_offline(config: Settings, log_msg: str) -> Settings: ) config.llm.api = ModelOptions.OFFLINE.name config.llm.offline = True - return config diff --git a/readmeai/readmeai.py b/readmeai/readmeai.py index 6812633d..5118bf88 100644 --- a/readmeai/readmeai.py +++ b/readmeai/readmeai.py @@ -11,11 +11,8 @@ from typing import Optional from readmeai.cli.options import prompt_for_image -from readmeai.config.enums import ImageOptions -from readmeai.config.settings import ( - ConfigLoader, - GitSettings, -) +from readmeai.config.enums import GitService, ImageOptions, ModelOptions +from readmeai.config.settings import ConfigLoader, GitSettings from readmeai.core.preprocess import preprocessor from readmeai.core.utils import set_model_engine from readmeai.exceptions import ReadmeGeneratorError @@ -42,40 +39,36 @@ def readme_agent( repository: str, temperature: Optional[float], # template: Optional[str], - tree_depth: Optional[int], + tree_depth: Optional[int] = 3, ) -> None: - """Main method of the readme-ai CLI application.""" + """README.md file generation agent.""" try: config_loader = ConfigLoader() config = config_loader.config - config.git = GitSettings(repository=repository) try: + config.git = GitSettings(repository=repository) config.git.host_domain = config.git.repository.host except Exception: - config.git.host_domain = "LOCAL" - - config.llm.api = api - config.llm.model = model - config.llm.temperature = temperature - config.llm.tokens_max = max_tokens + _logger.warning("Detected local repository...") + config.git.host_domain = GitService.LOCAL.name + config.output_file = output or config.output_file + config.llm.api = api or ModelOptions.OFFLINE.name + config.llm.model = model or config.llm.model + config.llm.temperature = temperature or config.llm.temperature + config.llm.tokens_max = max_tokens or config.llm.tokens_max config.md.alignment = alignment or config.md.alignment - config.md.badge_color = badge_color - config.md.badge_style = badge_style + config.md.badge_color = badge_color or config.md.badge_color + config.md.badge_style = badge_style or config.md.badge_style + config.md.tree_depth = tree_depth config.md.emojis = emojis - config.md.tree_depth = tree_depth or 3 config.md.image = ( image if image not in [ImageOptions.CUSTOM.name, ImageOptions.LLM.name] else prompt_for_image(image) ) - config.output_file = output or config.output_file - - _logger.info(f"Repository settings validated: {config.git}") - _logger.info(f"LLM API settings validated: {config.llm}") - set_model_engine(config) asyncio.run(readme_generator(config_loader)) @@ -86,6 +79,8 @@ def readme_agent( async def readme_generator(config_loader: ConfigLoader) -> None: """Orchestrates the README file generation process.""" config = config_loader.config + _logger.info(f"Repository settings validated: {config.git}") + _logger.info(f"LLM API settings validated: {config.llm}") try: with tempfile.TemporaryDirectory() as temp_dir: @@ -93,10 +88,10 @@ async def readme_generator(config_loader: ConfigLoader) -> None: ( dependencies, raw_files, - dependencies_dict, ) = preprocessor(config_loader, temp_dir) _logger.info(f"Dependencies extracted: {dependencies}") + _logger.info(f"Total files analyzed: {len(raw_files)}") async with model_handler(config, config_loader).use_api() as llm: responses = await llm.batch_request(dependencies, raw_files) @@ -107,16 +102,15 @@ async def readme_generator(config_loader: ConfigLoader) -> None: slogan, features_synthesized, ) = responses - config.md.features = config.md.features.format(features) - config.md.overview = config.md.overview.format(overview) - config.md.slogan = slogan + config.md.features = config.md.features.format(features) + config.md.overview = config.md.overview.format(overview) + config.md.slogan = slogan md_content = build_markdown( config_loader, dependencies, summaries, temp_dir ) FileHandler().write(Path(config.output_file), md_content) - _logger.info(f"README file generated @ {config.output_file}") _logger.info(f"Share it with us @ {config.discussions} !") diff --git a/readmeai/utils/formatter.py b/readmeai/utils/formatter.py index 1c84b22e..1a5a4b28 100644 --- a/readmeai/utils/formatter.py +++ b/readmeai/utils/formatter.py @@ -12,6 +12,10 @@ def clean_text(text: str) -> str: # Remove any text before and including "**:" text = re.sub(r"\*\*:\s*", "", text, flags=re.DOTALL) + # Remove single and double quotes that are missing their closing counterpart + text = re.sub(r"['\"](.*?)$", r"\1", text) + text = re.sub(r"^(.*?)['\"]", r"\1", text) + # Remove specific pattern and rephrase text = re.sub( r"\*\*Code Summary:\*\*\s*(.*?)\s*provides functions to", diff --git a/tests/core/test_models.py b/tests/core/test_models.py index fc820fc0..6df45a0a 100644 --- a/tests/core/test_models.py +++ b/tests/core/test_models.py @@ -113,6 +113,7 @@ async def test_batch_request(handler, mock_dependencies, mock_summaries): """Test the handling of the response from the OpenAI API.""" # Arrange handler.http_client = MagicMock() + handler._handle_response = AsyncMock() # Act test_response = await handler.batch_request( mock_dependencies, mock_summaries diff --git a/tests/core/test_utils.py b/tests/core/test_utils.py index baaa5a53..a615c2ce 100644 --- a/tests/core/test_utils.py +++ b/tests/core/test_utils.py @@ -54,12 +54,12 @@ def test_set_model_engine_vertex(mock_config, monkeypatch): assert os.getenv(SecretKeys.VERTEXAI_PROJECT.name) == "test-project" +@patch.dict("os.environ", {}, clear=True) def test_set_offline_mode_directly(mock_config): """Test the _set_offline_mode function directly for setting offline mode.""" - message = "Testing offline mode directly." - result = _set_offline(mock_config, message) - assert result.llm.api == ModelOptions.OFFLINE.name - assert result.llm.offline is True + mock_config.llm.api = ModelOptions.OFFLINE.name + _set_offline(mock_config, "Run in offline mode...") + assert mock_config.llm.offline is True @patch.dict("os.environ", {}, clear=True) diff --git a/tests/test_readmeai.py b/tests/test_readmeai.py index a1489715..237b7b37 100644 --- a/tests/test_readmeai.py +++ b/tests/test_readmeai.py @@ -30,7 +30,7 @@ async def test_readme_generator( mock_preprocessor.return_value = ( mock_dependencies, mock_summaries, - {"dependency1": "command1", "dependency2": "command2"}, + # {"dependency1": "command1", "dependency2": "command2"}, ) mock_config.git.repository = tmp_path mock_model_handler.return_value.use_api.return_value.__aenter__.return_value.batch_request.return_value = ( @@ -77,4 +77,4 @@ async def test_readme_generator_exception_handling(mock_configs): """Test the readme_generator function exception handling.""" with pytest.raises(ReadmeGeneratorError) as exc: await readme_generator(mock_configs) - assert isinstance(exc.value, ReadmeGeneratorError) + assert isinstance(exc.value, ReadmeGeneratorError) diff --git a/tests/utils/test_formatter.py b/tests/utils/test_formatter.py index ebfb9822..96680316 100644 --- a/tests/utils/test_formatter.py +++ b/tests/utils/test_formatter.py @@ -56,6 +56,16 @@ def test_format_response_no_table(): ("'hello world'", "Hello world"), ("Clear, Concise, Captivating**", "Clear, Concise, Captivating"), ("main.py** : hello world!", "Hello world!"), + ("AI-Driven, Streamlined Success'", "AI-Driven, Streamlined Success"), + ( + "**AI-Driven, Streamlined Success**", + "AI-Driven, Streamlined Success", + ), + ("**AI-Driven, Streamlined Success", "AI-Driven, Streamlined Success"), + ( + "'\AI-Driven, Streamlined Success!", + "AI-Driven, Streamlined Success!", + ), ], ) def test_clean_text(input_text, expected):