Skip to content

Commit

Permalink
Fix a few small bugs.
Browse files Browse the repository at this point in the history
  • Loading branch information
eli64s committed Oct 27, 2024
1 parent b64204b commit 48ecec8
Show file tree
Hide file tree
Showing 11 changed files with 256 additions and 59 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ Let's take a look at some possible customizations created by readme-ai:
<!-- ROW -->
<tr>
<td colspan="2" align="center"><br>
<img src="https://raw.githubusercontent.com/eli64s/readme-ai/main/docs/docs/assets/img/headers/ascii.png" alt="ascii-readme-header-style" width="700">
<img src="https://raw.githubusercontent.com/eli64s/readme-ai/main/docs/docs/assets/img/headers/ascii-art.png" alt="ascii-readme-header-style" width="700">
<br>
<code>--header-style ascii</code>
</td>
Expand Down
File renamed without changes
1 change: 1 addition & 0 deletions docs/docs/examples/gallery.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ title: Gallery
---

Explore various README examples from different programming languages and technologies. Each example showcases a README file from a different repository and project type.

| Technology | Example Output | Repository | Description |
|------------|---------------|------------|-------------|
| Readme-ai | [readme-ai.md][default] | [readme-ai][readme-ai] | Readme-ai project |
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "readmeai"
version = "0.5.99.post3"
version = "0.5.99.post4"
description = "Automated README file generator, powered by AI."
authors = ["Eli <egsalamie@gmail.com>"]
license = "MIT"
Expand Down Expand Up @@ -46,6 +46,7 @@ structlog = "^24.4.0"
tenacity = "^8.2.2"
tiktoken = "^0.4.0"
tomli = { version = "*", python = "<3.11" }
typing-extensions = { version = "*", python = "<3.11" }

anthropic = { version = "*", optional = true }
google-generativeai = { version = "*", optional = true }
Expand Down
5 changes: 5 additions & 0 deletions readmeai/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@
from readmeai.utils.file_handler import FileHandler
from readmeai.utils.file_resource import get_resource_path

try:
from typing import Self
except ImportError:
from typing_extensions import Self

_logger = get_logger(__name__)


Expand Down
14 changes: 7 additions & 7 deletions readmeai/config/settings/prompts.toml
Original file line number Diff line number Diff line change
Expand Up @@ -132,19 +132,19 @@ Aim for a clear, engaging description that captures the essence of the project w
"""

slogan = """
Conceptualize a catchy and memorable slogan for the project: {0} ({1}).
Your response should synthesize the project's essence, values, or unique selling points into a concise and engaging phrase.
While generating the slogan, please reference the following codebase details:
Create a catchy and memorable slogan for the project: {0} ({1}).
Synthesize the project's essence, values, or unique selling points into a concise and engaging phrase.
<CONTEXT>
FILE CONTENTS: {2}
</CONTEXT>
<INSTRUCTIONS>
- Your response slogan should be 5-8 words long at most.
- Slogan should be clear, concise and memorable.
- DO NOT INCLUDE THE PROJECT NAME in the slogan.
- The slogan must be a single string with no more than 8 words.
- It should be clear, concise, and memorable.
- DO NOT INCLUDE the project name in the slogan.
- RETURN ONLY the slogan without any additional text or explanations.
</INSTRUCTIONS>
Be creative, think outside the box, and have fun. Cheers!
Be creative and have fun!
"""
6 changes: 3 additions & 3 deletions readmeai/config/settings/tool_config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
# -- Docker --------------------------------------------------------------------

[default]
install = "echo 'INSERT-INSTALL-COMMAND-HERE'"
usage = "echo 'INSERT-RUN-COMMAND-HERE'"
test = "echo 'INSERT-TEST-COMMAND-HERE'"
install = "echo 'INSERT-INSTALL-COMMAND-HERE'"
usage = "echo 'INSERT-RUN-COMMAND-HERE'"
test = "echo 'INSERT-TEST-COMMAND-HERE'"

[containers]
name = "Docker"
Expand Down
97 changes: 71 additions & 26 deletions readmeai/generators/quickstart.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from dataclasses import dataclass, field
from typing import Optional

from readmeai.config.settings import ConfigLoader
from readmeai.ingestion.models import QuickStart
Expand Down Expand Up @@ -31,6 +32,12 @@ def generate(
"""Get any relevant commands for the Quickstart instructions."""
try:
primary_language = self._get_primary_language(language_counts)

if not primary_language:
primary_language = (
f"Error detecting primary_language: {language_counts}"
)

quickstart = QuickStart(
primary_language=primary_language,
language_counts=language_counts,
Expand All @@ -46,26 +53,48 @@ def generate(
)
return QuickStart()

def _get_primary_language(self, counts: dict[str, int]) -> str | None:
def _get_primary_language(self, counts: dict[str, int]) -> Optional[str]:
"""Determine the primary language of the repository."""
if not counts:
try:
if not counts:
return None

# Filter out YAML files and empty counts
valid_counts = {
k: v
for k, v in counts.items()
if k not in ("yaml", "yml") and v > 0
}

if not valid_counts:
return None

primary_lang = max(valid_counts, key=valid_counts.get)

return self.language_names.get(
primary_lang, self.language_names.get("default")
)
except Exception as e:
_logger.error(f"Error determining primary language: {e}")
return None
counts = {k: v for k, v in counts.items() if k not in ("yaml", "yml")}
primary_lang = max(counts, key=counts.get)
return self.language_names.get(
primary_lang, self.language_names.get("default")
)

def _generate_commands(
self, quickstart: QuickStart, primary_language: str
) -> None:
"""Generate install, usage, and test commands."""
if not primary_language:
return

command_types = ["install", "usage", "test"]
tool_types = ["package_managers", "containers"]

for cmd_type in command_types:
commands: list[str] = []
for tool_type in tool_types:
tools = getattr(quickstart, tool_type)
tools = getattr(quickstart, tool_type, {})
if not tools:
continue

commands.extend(
filter(
None,
Expand All @@ -90,28 +119,44 @@ def _format_command(
file_path: str,
cmd_type: str,
tool_type: str,
) -> str | None:
) -> Optional[str]:
"""Format a command for the Quickstart instructions."""
config = (
self.tools.get(primary_language.lower(), {})
.get(tool_type, {})
.get(tool_name, {})
) or self.tools.get(tool_type, {}).get(tool_name, {})

cmd = config.get(cmd_type, self.default_commands.get(cmd_type))
if not cmd:
return None
try:
if not primary_language or not tool_name:
return None

if cmd_type == "install" and tool_type == "containers":
cmd = cmd.replace("{image_name}", self.config.config.git.full_name)
elif cmd_type in {"install", "test"}:
cmd = cmd.replace("{file}", file_path)
elif cmd_type == "usage":
cmd = cmd.replace("{executable}", self.config.config.git.name)
return f"""
**Using `{tool_name}`** &nbsp; [<img align="center" src="{config.get('shield', '')}" />]({config.get('website', '')})
lang_key = primary_language.lower()
config = (
self.tools.get(lang_key, {})
.get(tool_type, {})
.get(tool_name, {})
) or self.tools.get(tool_type, {}).get(tool_name, {})

cmd = config.get(cmd_type, self.default_commands.get(cmd_type))
if not cmd:
return None

if cmd_type == "install" and tool_type == "containers":
cmd = cmd.replace(
"{image_name}", self.config.config.git.full_name or ""
)
elif cmd_type in {"install", "test"}:
cmd = cmd.replace("{file}", file_path or "")
elif cmd_type == "usage":
cmd = cmd.replace(
"{executable}", self.config.config.git.name or ""
)

shield_url = config.get("shield", "")
website_url = config.get("website", "")

return f"""
**Using `{tool_name}`** &nbsp; [<img align="center" src="{shield_url}" />]({website_url})
```sh
{cmd}
```
"""
except Exception as e:
_logger.error(f"Error formatting command for {tool_name}: {e}")
return None
85 changes: 68 additions & 17 deletions readmeai/preprocessor/document_cleaner.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import re
import textwrap


class DocumentCleaner:
Expand All @@ -12,41 +13,91 @@ def __init__(
remove_extra_whitespaces: bool = True,
remove_trailing_whitespaces: bool = True,
normalize_indentation: bool = True,
dedent: bool = False,
):
self.remove_empty_lines = remove_empty_lines
self.remove_extra_whitespaces = remove_extra_whitespaces
self.remove_trailing_whitespaces = remove_trailing_whitespaces
self.normalize_indentation = normalize_indentation
self.dedent = dedent

def clean(self, code: str) -> str:
"""Clean the given document string."""
lines = code.splitlines()

if self.remove_empty_lines:
code = self._remove_empty_lines(code)
if self.remove_extra_whitespaces:
code = self._remove_extra_whitespaces(code)
lines = [line for line in lines if line.strip()]

if self.remove_trailing_whitespaces:
code = self._remove_trailing_whitespaces(code)
lines = [line.rstrip() for line in lines]

if self.normalize_indentation:
code = self._normalize_indentation(code)
return code.strip()
lines = self._normalize_indentation("\n".join(lines)).splitlines()

def _remove_empty_lines(self, code: str) -> str:
"""Remove empty lines and lines with only whitespace."""
return "\n".join(line for line in code.splitlines() if line.strip())
result = "\n".join(lines)

def _remove_extra_whitespaces(self, code: str) -> str:
"""Remove extra whitespaces within lines."""
return re.sub(r"\s+", " ", code)
if self.dedent:
result = textwrap.dedent(result)

def _remove_trailing_whitespaces(self, code: str) -> str:
"""Remove trailing whitespaces from each line."""
return "\n".join(line.rstrip() for line in code.splitlines())
if self.remove_extra_whitespaces:
# Only remove extra spaces within each line, preserving leading spaces
lines = result.splitlines()
lines = [
self._preserve_indent_remove_extra_spaces(line)
for line in lines
]
result = "\n".join(lines)

return result.rstrip()

def _preserve_indent_remove_extra_spaces(self, line: str) -> str:
"""Remove extra whitespaces while preserving leading indentation."""
if not line.strip():
return ""
indent = len(line) - len(line.lstrip())
return " " * indent + re.sub(r"\s+", " ", line.lstrip())

def _normalize_indentation(self, code: str) -> str:
"""Normalize indentation to spaces."""
if not code:
return code

lines = code.splitlines()
normalized_lines = []

for line in lines:
indent = len(line) - len(line.lstrip())
normalized_lines.append(" " * indent + line.lstrip())
if not line.strip():
normalized_lines.append("")
continue

# Calculate leading whitespace count, handling tabs
leading_space_count = 0
for char in line:
if char == " ":
leading_space_count += 1
elif char == "\t":
# Round up to the next multiple of 4
leading_space_count = (leading_space_count + 4) & ~3
else:
break

# Preserve the original indentation level
normalized_line = " " * leading_space_count + line.lstrip()
normalized_lines.append(normalized_line)

return "\n".join(normalized_lines)

def _remove_empty_lines(self, code: str) -> str:
"""Remove empty lines and lines with only whitespace."""
return "\n".join(line for line in code.splitlines() if line.strip())

def _remove_extra_whitespaces(self, code: str) -> str:
"""Remove extra whitespaces within lines while preserving newlines."""
lines = code.splitlines()
return "\n".join(
self._preserve_indent_remove_extra_spaces(line) for line in lines
)

def _remove_trailing_whitespaces(self, code: str) -> str:
"""Remove trailing whitespaces from each line."""
return "\n".join(line.rstrip() for line in code.splitlines())
8 changes: 4 additions & 4 deletions tests/generators/test_quickstart.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ def test_quickstart_generator_init(
for language_name in ["python", "sql", "shell", "cpp", "java"]
)
assert quickstart_generator.default_commands == {
"install": "echo 'INSERT-INSTALL-COMMAND-HERE'",
"usage": "echo 'INSERT-RUN-COMMAND-HERE'",
"test": "echo 'INSERT-TEST-COMMAND-HERE'",
"install": "echo 'INSERT-INSTALL-COMMAND-HERE'",
"usage": "echo 'INSERT-RUN-COMMAND-HERE'",
"test": "echo 'INSERT-TEST-COMMAND-HERE'",
}


Expand Down Expand Up @@ -76,7 +76,7 @@ def test_generate_quickstart_empty_args(
quickstart_generator: QuickStartGenerator,
):
quickstart = quickstart_generator.generate({}, {})
assert quickstart.primary_language is None
assert "Error detecting primary_language" in quickstart.primary_language
assert quickstart.install_commands == ""
assert quickstart.usage_commands == ""
assert quickstart.test_commands == ""
Loading

0 comments on commit 48ecec8

Please sign in to comment.