Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions code_utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ uv sync --extra dev
# Run the code extractor
uv run code-util extract

# Run compiler checks on extracted code
uv run code-util check
# Run compiler checks on extracted code, (can only be done on lang at a time)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: typo

uv run code-util check -l programming-language

# Get help in the cli
uv run code-util --help
Expand All @@ -34,14 +34,21 @@ uv run code-util --help
## Structure

- `main.py` - Entrypoint for the CLI.
- `code_finder.py` - CodeFinder class
- `code_checker.py` - CodeChecker class.
- `docker/` - docker files are different scripts and config the container uses.
- `code_finder/` - module for code relating to the CodeFinder class a.k.a the functionality to scan through markdown documents.
- `code_checker/` - module for code relating to the CodeChecker class a.k.a the functionality to build containers and run commands in them.
- `code_checker/docker/` - docker files, scripts and config that the containers use.
- `temp/` - Generated code snippets (gitignored).
- `files_with_code.txt` - List of files containing code (gitignored).

## Dependancies

- [Click](https://click.palletsprojects.com/en/stable/) - Framework for building CLIs
- [Click](https://click.palletsprojects.com/en/stable/) - Framework for building CLIs.
- [Docker SDK For Python](https://docker-py.readthedocs.io/en/stable/) - Library for interacting with the Docker API.
- [Pytest](https://docs.pytest.org/en/stable/index.html) - Python Unit testing Framework.
- [Pyfakefs](https://pytest-pyfakefs.readthedocs.io/en/latest/) - Python utility for faking a filesystem during testing.

## Notes

- Hard coded to only deal with python, javascript(typescript) and C#
- Javascript container uses a private npm package. Please set PAT_TOKEN and CODAT_EMAIL env vars in order to build.

7 changes: 7 additions & 0 deletions code_utils/code_checker/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""Code checker package for validating code snippets from Codat documentation."""

from .code_checker import CodeChecker
from .docker_operator import DockerOperator
from .codat_code_checker_config_models import CodeCheckerConfig, LanguageDockerConfig, DEFAULT_CONFIG

__all__ = ['CodeChecker', 'DockerOperator', 'CodeCheckerConfig', 'LanguageDockerConfig', 'DEFAULT_CONFIG']
58 changes: 58 additions & 0 deletions code_utils/code_checker/codat_code_checker_config_models.py

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: what a long file name 😆

Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from dataclasses import dataclass
from typing import Dict
from pathlib import Path


@dataclass
class LanguageDockerConfig:
"""Configuration for a specific programming language's Docker setup."""
docker_directory: str
validation_command: str
working_directory: str = ""

def get_docker_path(self, base_dir: Path) -> Path:
"""Get the full path to the Docker directory for this language."""
return base_dir / self.docker_directory


@dataclass
class CodeCheckerConfig:
"""Complete configuration for the CodeChecker Docker utility."""
python: LanguageDockerConfig
javascript: LanguageDockerConfig
csharp: LanguageDockerConfig
container_name: str = "code-snippets:latest"

def get_language_config(self, language: str) -> LanguageDockerConfig:
"""Get Docker configuration for a specific language."""
language_map = {
'python': self.python,
'javascript': self.javascript,
'csharp': self.csharp
}
return language_map.get(language.lower())

def get_all_languages(self) -> list[str]:
"""Get list of all supported language names."""
return ['python', 'javascript', 'csharp']


# Default configuration instance based on current CodeChecker implementation
DEFAULT_CONFIG = CodeCheckerConfig(
python=LanguageDockerConfig(
docker_directory="docker/python",
validation_command="bash -c 'cd snippets && pyright .'",
working_directory="python/snippets"
),
javascript=LanguageDockerConfig(
docker_directory="docker/javascript",
validation_command="tsc --noEmit",
working_directory="javascript"
),
csharp=LanguageDockerConfig(
docker_directory="docker/csharp",
validation_command="bash -c 'cd /workspace/code-snippets/csharp && ./validate-csharp-snippets.sh'",
working_directory="/workspace/code-snippets/csharp"
),
container_name="code-snippets"
)
128 changes: 128 additions & 0 deletions code_utils/code_checker/code_checker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
"""
Code Checker for validating code snippets for a specified programming language.
This module builds and runs a Docker container to validate complete code snippets.
"""

import sys
from pathlib import Path
from typing import Dict, Optional

from .codat_code_checker_config_models import CodeCheckerConfig, DEFAULT_CONFIG
from .docker_operator import DockerOperator


class CodeChecker:
"""
A class for validating code snippets by building and running validation commands
in a Docker container for a specified programming language environment.
"""

def __init__(self, target_language: str, config: Optional[CodeCheckerConfig] = None, base_dir: Optional[Path] = None, docker_operator: Optional[DockerOperator] = None):
"""
Initialize the CodeChecker with dependency injection.

Args:
target_language: Specific language to check (required).
config: Configuration object for Docker settings. Defaults to DEFAULT_CONFIG.
base_dir: Base directory path. Defaults to the directory containing this file.
docker_operator: DockerOperator instance. If None, creates DockerOperator(config, base_dir).
"""
self.config = config or DEFAULT_CONFIG
self.base_dir = base_dir or Path(__file__).parent
self.docker_wrapper = docker_operator or DockerOperator(self.config, self.base_dir)
self.target_language = target_language.lower()

def _build_docker_images(self) -> Dict[str, Dict[str, any]]:
"""
Build Docker image for the target language.

Returns:
Dictionary with build results for the target language
"""
build_results = {}

print(f"🔨 Building Docker images for: {self.target_language}...")
print("=" * 60)

success, message = self.docker_wrapper.build_language_image(self.target_language)
build_results[self.target_language] = {
'success': success,
'message': message
}

if not success:
print(f"❌ Failed to build {self.target_language} image: {message}")

print("=" * 60)

return build_results

def _validate_language_snippets(self, language: str) -> Dict[str, any]:
"""
Validate code snippets for a specific language using the DockerOperator.

Args:
language: The programming language to validate

Returns:
Dictionary with validation results
"""
success, output = self.docker_wrapper.validate_language_snippets(language)
return {
'success': success,
'output': output
}

def check_complete_snippets(self) -> Dict[str, Dict[str, any]]:
"""
Build Docker image and validate complete code snippets for the target language.

Returns:
Dictionary with validation results for the target language:
{
'build': {language: {'success': bool, 'message': str}},
'validation': {language: {'success': bool, 'output': str}}
}
"""
print(f"🚀 Starting code snippet validation for: {self.target_language}...")
print("=" * 60)

# Step 1: Build Docker images for target languages
build_results = self._build_docker_images()

# Check if all builds succeeded
all_builds_successful = all(result['success'] for result in build_results.values())

if not all_builds_successful:
failed_builds = [lang for lang, result in build_results.items() if not result['success']]
print(f"❌ Docker build failed for: {', '.join(failed_builds)}")
return {
'build': build_results,
'validation': {}
}

print("=" * 60)

# Step 2: Validate snippets for target languages
validation_results = {}

validation_results[self.target_language] = self._validate_language_snippets(self.target_language)

# Summary
print("=" * 60)
print("📊 Validation Summary:")

validation_result = validation_results[self.target_language]

if validation_result['success']:
print(f"🎉 {self.target_language.title()} validation passed!")
else:
print(f"❌ {self.target_language.title()} validation failed!")

return {
'build': build_results,
'validation': validation_results
}



64 changes: 64 additions & 0 deletions code_utils/code_checker/docker/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Exclude unnecessary files from Docker build context
**/__pycache__/
**/*.pyc
**/*.pyo
**/*.pyd
**/.Python
**/env/
**/venv/
**/.venv/
**/pip-log.txt
**/pip-delete-this-directory.txt

# Node modules and TypeScript compilation outputs
**/node_modules/
**/npm-debug.log*
**/yarn-debug.log*
**/yarn-error.log*
**/.npm
**/.yarn-integrity
**/dist/
**/build/

# .NET build outputs
**/bin/
**/obj/
**/*.user
**/*.suo
**/*.cache
**/packages/

# Version control
**/.git/
**/.gitignore
**/.gitattributes

# IDE and editor files
**/.vscode/
**/.idea/
**/*.swp
**/*.swo
*~

# OS generated files
**/.DS_Store
**/.DS_Store?
**/._*
**/.Spotlight-V100
**/.Trashes
**/ehthumbs.db
**/Thumbs.db

# Documentation and temp files
**/temp/incomplete/
**/*.md
**/*.txt
**/output.txt
**/link-results.json

# Only include the complete code snippets and dependency files
!temp/*/complete/
!docker/requirements.txt
!docker/package.json
!docker/tsconfig.json
!docker/CodatSnippets.csproj
21 changes: 21 additions & 0 deletions code_utils/code_checker/docker/csharp/CodatSnippets.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Codat.Platform" Version="*" />
<PackageReference Include="Codat.BankFeeds" Version="*" />
<PackageReference Include="Codat.Lending" Version="*" />
<PackageReference Include="Codat.Sync.Commerce" Version="*" />
<PackageReference Include="Codat.Sync.Expenses" Version="*" />
<PackageReference Include="Codat.Sync.Payables" Version="*" />
<PackageReference Include="Codat.Sync.Payroll" Version="*" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>

</Project>
59 changes: 59 additions & 0 deletions code_utils/code_checker/docker/csharp/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# C#/.NET-specific Dockerfile for Codat code snippets validation
FROM ubuntu:22.04

# Set environment variables
ENV DEBIAN_FRONTEND=noninteractive
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
ENV DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1

# Update package list and install common dependencies
RUN apt-get update && apt-get install -y \
curl \
wget \
apt-transport-https \
software-properties-common \
gnupg \
lsb-release \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*

# Install .NET 8.0 SDK
RUN wget https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb \
&& dpkg -i packages-microsoft-prod.deb \
&& rm packages-microsoft-prod.deb \
&& apt-get update \
&& apt-get install -y dotnet-sdk-8.0 \
&& rm -rf /var/lib/apt/lists/*

# Create workspace directory
WORKDIR /workspace/code-snippets/csharp

# Copy C# project file and restore packages
COPY code_checker/docker/csharp/CodatSnippets.csproj ./CodatSnippets.csproj
RUN dotnet restore

# Create directory for code snippets and copy them from temp directory
RUN mkdir -p ./snippets/

# Copy C# code snippets from temp directory (similar to other languages)
COPY temp/csharp/complete/ ./snippets/

# Copy C# validation script and fix line endings
COPY code_checker/docker/csharp/validate-csharp-snippets.sh ./validate-csharp-snippets.sh
RUN sed -i 's/\r$//' validate-csharp-snippets.sh && chmod +x validate-csharp-snippets.sh

# Verify .NET installation and show packages
RUN echo "=== C#/.NET Environment Information ===" && \
echo ".NET version:" && dotnet --version && \
echo "" && \
echo "=== C# Codat Packages ===" && \
dotnet list package | grep -i codat || echo "Codat packages will be available after restore" && \
echo "" && \
echo "=== C# Snippets Count ===" && \
find ./snippets -name "*.cs" 2>/dev/null | wc -l | xargs echo "C# snippets found:" || echo "No snippets directory found yet"

# Set working directory
WORKDIR /workspace/code-snippets/csharp

# Default command
CMD ["/bin/bash"]
Loading
Loading