Text Generation
PEFT
Safetensors
GGUF
Transformers
English
Spanish
harbour
fivewin
fwh
lora
sft
trl
unsloth
code-generation
xbase
clipper
conversational
Instructions to use fivetech/Harbour with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- PEFT
How to use fivetech/Harbour with PEFT:
from peft import PeftModel from transformers import AutoModelForCausalLM base_model = AutoModelForCausalLM.from_pretrained("/home/fivetech/finetune/models/Qwen3.6-35B-A3B") model = PeftModel.from_pretrained(base_model, "fivetech/Harbour") - Transformers
How to use fivetech/Harbour with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="fivetech/Harbour") messages = [ {"role": "user", "content": "Who are you?"}, ] pipe(messages)# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("fivetech/Harbour", device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use fivetech/Harbour with llama.cpp:
Install (macOS, Linux)
curl -LsSf https://llama.app/install.sh | sh # Start a local OpenAI-compatible server with a web UI: llama serve -hf fivetech/Harbour:Q4_K_M # Run inference directly in the terminal: llama cli -hf fivetech/Harbour:Q4_K_M
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf fivetech/Harbour:Q4_K_M # Run inference directly in the terminal: llama cli -hf fivetech/Harbour:Q4_K_M
Use pre-built binary
# Download pre-built binary from: # https://github.com/ggerganov/llama.cpp/releases # Start a local OpenAI-compatible server with a web UI: ./llama-server -hf fivetech/Harbour:Q4_K_M # Run inference directly in the terminal: ./llama-cli -hf fivetech/Harbour:Q4_K_M
Build from source code
git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-server llama-cli # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama-server -hf fivetech/Harbour:Q4_K_M # Run inference directly in the terminal: ./build/bin/llama-cli -hf fivetech/Harbour:Q4_K_M
Use Docker
docker model run hf.co/fivetech/Harbour:Q4_K_M
- LM Studio
- Jan
- vLLM
How to use fivetech/Harbour with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "fivetech/Harbour" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "fivetech/Harbour", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/fivetech/Harbour:Q4_K_M
- SGLang
How to use fivetech/Harbour with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "fivetech/Harbour" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "fivetech/Harbour", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "fivetech/Harbour" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "fivetech/Harbour", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' - Ollama
How to use fivetech/Harbour with Ollama:
ollama run hf.co/fivetech/Harbour:Q4_K_M
- Unsloth Studio
How to use fivetech/Harbour with Unsloth Studio:
Install Unsloth Studio (macOS, Linux, WSL)
curl -fsSL https://unsloth.ai/install.sh | sh # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for fivetech/Harbour to start chatting
Install Unsloth Studio (Windows)
irm https://unsloth.ai/install.ps1 | iex # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for fivetech/Harbour to start chatting
Using HuggingFace Spaces for Unsloth
# No setup required # Open https://huggingface.co/spaces/unsloth/studio in your browser # Search for fivetech/Harbour to start chatting
- Pi
How to use fivetech/Harbour with Pi:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf fivetech/Harbour:Q4_K_M
Configure the model in Pi
# Install Pi: npm install -g @mariozechner/pi-coding-agent # Add to ~/.pi/agent/models.json: { "providers": { "llama-cpp": { "baseUrl": "http://localhost:8080/v1", "api": "openai-completions", "apiKey": "none", "models": [ { "id": "fivetech/Harbour:Q4_K_M" } ] } } }Run Pi
# Start Pi in your project directory: pi
- Docker Model Runner
How to use fivetech/Harbour with Docker Model Runner:
docker model run hf.co/fivetech/Harbour:Q4_K_M
- Lemonade
How to use fivetech/Harbour with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull fivetech/Harbour:Q4_K_M
Run and chat with the model
lemonade run user.Harbour-Q4_K_M
List all available models
lemonade list
- Hermes Agent
How to use fivetech/Harbour with Hermes Agent:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf fivetech/Harbour:Q4_K_M
Configure Hermes
# Install Hermes: curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash hermes setup # Point Hermes at the local server: hermes config set model.provider custom hermes config set model.base_url http://127.0.0.1:8080/v1 hermes config set model.default fivetech/Harbour:Q4_K_M
Run Hermes
hermes
- Atomic Chat
- OpenClaw
How to use fivetech/Harbour with OpenClaw:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf fivetech/Harbour:Q4_K_M
Configure OpenClaw
# Install OpenClaw: npm install -g openclaw@latest # Register the local server and set it as the default model: openclaw onboard --non-interactive --mode local \ --auth-choice custom-api-key \ --custom-base-url http://127.0.0.1:8080/v1 \ --custom-model-id "fivetech/Harbour:Q4_K_M" \ --custom-provider-id llama-cpp \ --custom-compatibility openai \ --custom-text-input \ --accept-risk \ --skip-health
Run OpenClaw
openclaw agent --local --agent main --message "Hello from Hugging Face"
Upload generate_dataset.py with huggingface_hub
Browse files- generate_dataset.py +1025 -0
generate_dataset.py
ADDED
|
@@ -0,0 +1,1025 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Harbour PRG/CH Dataset Generator for Fine-tuning qwen2.5-coder:14b
|
| 4 |
+
Generates structured JSONL dataset from .prg and .ch files with descriptions.
|
| 5 |
+
CLEANED VERSION - removes boilerplate, ensures code quality.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import os
|
| 9 |
+
import re
|
| 10 |
+
import json
|
| 11 |
+
import random
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
from typing import Dict, List, Tuple, Optional
|
| 14 |
+
|
| 15 |
+
# Configuration
|
| 16 |
+
HARBOUR_ROOT = Path("/home/fivetech/harbour")
|
| 17 |
+
OUTPUT_DIR = Path("/home/fivetech/finetune")
|
| 18 |
+
MAX_CODE_LENGTH = 8000
|
| 19 |
+
MIN_CODE_LENGTH = 80 # Minimum chars of actual code
|
| 20 |
+
TRAIN_RATIO = 0.9
|
| 21 |
+
|
| 22 |
+
# Module descriptions for contrib
|
| 23 |
+
MODULE_DESCRIPTIONS = {
|
| 24 |
+
"hbhttpd": "Multithreaded HTTP/HTTPS server framework",
|
| 25 |
+
"hbwin": "Windows API wrapper functions",
|
| 26 |
+
"hbpgsql": "PostgreSQL database client library",
|
| 27 |
+
"hbmysql": "MySQL database client library",
|
| 28 |
+
"hbsqlit3": "SQLite3 database client library",
|
| 29 |
+
"hbodbc": "ODBC database connectivity",
|
| 30 |
+
"hbtip": "Internet protocol utilities (FTP, HTTP, SMTP, POP3)",
|
| 31 |
+
"hbcurl": "libcurl wrapper for HTTP/FTP/SMTP operations",
|
| 32 |
+
"hbssl": "OpenSSL wrapper for SSL/TLS encryption",
|
| 33 |
+
"hbnf": "NanForum Toolkit - legacy Clipper compatibility functions",
|
| 34 |
+
"hbct": "CA-Tools compatibility library",
|
| 35 |
+
"hbmisc": "Miscellaneous utility functions",
|
| 36 |
+
"hbgd": "Graphics drawing library (GD)",
|
| 37 |
+
"hbcairo": "Cairo graphics library wrapper",
|
| 38 |
+
"hbhpdf": "PDF generation library (libharu)",
|
| 39 |
+
"hbbmp": "BMP image handling",
|
| 40 |
+
"hbzebra": "Barcode generation library",
|
| 41 |
+
"hbexpat": "XML parsing library (Expat)",
|
| 42 |
+
"hbmxml": "XML generation library",
|
| 43 |
+
"hbnetio": "Network I/O operations",
|
| 44 |
+
"hbpipeio": "Process pipe I/O operations",
|
| 45 |
+
"hbmemio": "Memory file I/O operations",
|
| 46 |
+
"xhb": "Extended Harbour functions",
|
| 47 |
+
"hbxpp": "xBase++ compatibility functions",
|
| 48 |
+
"hbunix": "Unix-specific functions",
|
| 49 |
+
"hbtpathy": "Telepath communication library",
|
| 50 |
+
"hbblat": "Blat email sending utility",
|
| 51 |
+
"hbblink": "Blinker function extender",
|
| 52 |
+
"hbgs": "Ghostscript wrapper",
|
| 53 |
+
"hbfship": "Fships library functions",
|
| 54 |
+
"hbmzip": "ZIP file handling",
|
| 55 |
+
"hbziparc": "ZIP archive handling",
|
| 56 |
+
"hbxdiff": "File difference/patching",
|
| 57 |
+
"hblzf": "LZF compression library",
|
| 58 |
+
"hbmlzo": "LZO compression library",
|
| 59 |
+
"hbbz2": "BZ2 compression library",
|
| 60 |
+
"hbformat": "Text formatting utilities",
|
| 61 |
+
"hbfoxpro": "FoxPro file format support",
|
| 62 |
+
"hbplist": "Apple plist file format support",
|
| 63 |
+
"hbcups": "CUPS printing system wrapper",
|
| 64 |
+
"hbsms": "SMS sending via modem",
|
| 65 |
+
"hbcomm": "Serial communication library",
|
| 66 |
+
"hbfbird": "Firebird database client",
|
| 67 |
+
"hbfimage": "FreeImage library wrapper",
|
| 68 |
+
"hbdoc": "Documentation generation utilities",
|
| 69 |
+
"hbtinymt": "Tiny Mersenne Twister PRNG",
|
| 70 |
+
"hbtest": "Test framework utilities",
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def remove_all_comments(code: str) -> str:
|
| 75 |
+
"""Remove ALL comments from Harbour code: //, /* */, *, and inline comments."""
|
| 76 |
+
# Remove block comments /* ... */ (including multi-line)
|
| 77 |
+
code = re.sub(r'/\*.*?\*/', '', code, flags=re.DOTALL)
|
| 78 |
+
|
| 79 |
+
# Remove single-line comments // ...
|
| 80 |
+
code = re.sub(r'//[^\n]*', '', code)
|
| 81 |
+
|
| 82 |
+
# Remove lines that are only star-prefixed comments: * ...
|
| 83 |
+
# And standalone star lines used in block comment formatting
|
| 84 |
+
lines = code.split("\n")
|
| 85 |
+
result_lines = []
|
| 86 |
+
for line in lines:
|
| 87 |
+
stripped = line.strip()
|
| 88 |
+
# Skip lines that are ONLY comments (star-prefixed, standalone)
|
| 89 |
+
if re.match(r'^\*\s', stripped) or stripped == '*' or stripped == '*/' or stripped == '/*':
|
| 90 |
+
continue
|
| 91 |
+
result_lines.append(line)
|
| 92 |
+
code = "\n".join(result_lines)
|
| 93 |
+
|
| 94 |
+
# Remove inline comments at end of lines: code * comment
|
| 95 |
+
# Pattern: something followed by whitespace then * ... (but not in strings)
|
| 96 |
+
code = re.sub(r'([^\s"].*?)\s+\*[^"]*$', r'\1', code, flags=re.MULTILINE)
|
| 97 |
+
|
| 98 |
+
return code
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
def clean_excessive_blank_lines(code: str) -> str:
|
| 102 |
+
"""Remove excessive blank lines (more than 2 consecutive)."""
|
| 103 |
+
lines = code.split("\n")
|
| 104 |
+
result_lines = []
|
| 105 |
+
blank_count = 0
|
| 106 |
+
|
| 107 |
+
for line in lines:
|
| 108 |
+
if line.strip() == "":
|
| 109 |
+
blank_count += 1
|
| 110 |
+
if blank_count <= 2:
|
| 111 |
+
result_lines.append(line)
|
| 112 |
+
else:
|
| 113 |
+
blank_count = 0
|
| 114 |
+
result_lines.append(line)
|
| 115 |
+
|
| 116 |
+
return "\n".join(result_lines)
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def remove_disabled_code(code: str) -> str:
|
| 120 |
+
"""Remove #if 0 ... #endif blocks (disabled code)."""
|
| 121 |
+
lines = code.split("\n")
|
| 122 |
+
result_lines = []
|
| 123 |
+
in_disabled = False
|
| 124 |
+
disabled_depth = 0
|
| 125 |
+
|
| 126 |
+
for line in lines:
|
| 127 |
+
stripped = line.strip()
|
| 128 |
+
|
| 129 |
+
if stripped.upper().startswith("#IF 0") or stripped.upper().startswith("#IFDEF _HARBOUR_DISABLE"):
|
| 130 |
+
in_disabled = True
|
| 131 |
+
disabled_depth += 1
|
| 132 |
+
continue
|
| 133 |
+
|
| 134 |
+
if in_disabled:
|
| 135 |
+
if stripped.upper().startswith("#ENDIF"):
|
| 136 |
+
disabled_depth -= 1
|
| 137 |
+
if disabled_depth <= 0:
|
| 138 |
+
in_disabled = False
|
| 139 |
+
continue
|
| 140 |
+
|
| 141 |
+
result_lines.append(line)
|
| 142 |
+
|
| 143 |
+
return "\n".join(result_lines)
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
def is_ch_file(filepath: Path) -> bool:
|
| 147 |
+
"""Check if file is a .ch (Clipper header) file."""
|
| 148 |
+
return filepath.suffix.lower() == '.ch'
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
def has_real_ch_code(code: str) -> bool:
|
| 152 |
+
"""Check if .ch file contains actual preprocessor definitions."""
|
| 153 |
+
upper = code.upper()
|
| 154 |
+
|
| 155 |
+
# Must have at least one preprocessor construct
|
| 156 |
+
ch_constructs = [
|
| 157 |
+
"#XCOMMAND", "#XTRANSLATE", "#COMMAND", "#TRANSLATE",
|
| 158 |
+
"#DEFINE", "#UNDEF", "#IFDEF", "#IFNDEF", "#IF ",
|
| 159 |
+
"#INCLUDE", "#PRAGMA", "#ENDPROC",
|
| 160 |
+
]
|
| 161 |
+
if not any(construct in upper for construct in ch_constructs):
|
| 162 |
+
return False
|
| 163 |
+
|
| 164 |
+
# Count actual definition lines
|
| 165 |
+
lines = code.split("\n")
|
| 166 |
+
def_lines = 0
|
| 167 |
+
for line in lines:
|
| 168 |
+
stripped = line.strip()
|
| 169 |
+
if not stripped:
|
| 170 |
+
continue
|
| 171 |
+
if stripped.startswith("//") or stripped.startswith("/*") or stripped.startswith("*"):
|
| 172 |
+
continue
|
| 173 |
+
if stripped.startswith("#"):
|
| 174 |
+
def_lines += 1
|
| 175 |
+
|
| 176 |
+
return def_lines >= 3
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
def extract_ch_definitions(code: str) -> Dict:
|
| 180 |
+
"""Extract definitions from .ch file."""
|
| 181 |
+
defines = []
|
| 182 |
+
commands = []
|
| 183 |
+
translates = []
|
| 184 |
+
|
| 185 |
+
for line in code.split("\n"):
|
| 186 |
+
stripped = line.strip()
|
| 187 |
+
upper = stripped.upper()
|
| 188 |
+
|
| 189 |
+
if upper.startswith("#DEFINE ") or upper.startswith("#UNDEF "):
|
| 190 |
+
parts = stripped.split()
|
| 191 |
+
if len(parts) >= 2:
|
| 192 |
+
defines.append(parts[1])
|
| 193 |
+
elif upper.startswith("#XCOMMAND") or upper.startswith("#COMMAND"):
|
| 194 |
+
commands.append(stripped[:60])
|
| 195 |
+
elif upper.startswith("#XTRANSLATE") or upper.startswith("#TRANSLATE"):
|
| 196 |
+
translates.append(stripped[:60])
|
| 197 |
+
|
| 198 |
+
return {
|
| 199 |
+
"defines": defines[:10],
|
| 200 |
+
"commands": commands[:5],
|
| 201 |
+
"translates": translates[:5]
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
|
| 205 |
+
def generate_ch_description(filepath: Path, code: str) -> str:
|
| 206 |
+
"""Generate description for .ch file."""
|
| 207 |
+
rel_path = filepath.relative_to(HARBOUR_ROOT)
|
| 208 |
+
filename = filepath.stem
|
| 209 |
+
|
| 210 |
+
desc_parts = []
|
| 211 |
+
|
| 212 |
+
# Determine location context
|
| 213 |
+
if rel_path.parts[0] == "include":
|
| 214 |
+
desc_parts.append(f"Harbour header file: {rel_path}")
|
| 215 |
+
elif rel_path.parts[0] == "contrib":
|
| 216 |
+
module = rel_path.parts[1] if len(rel_path.parts) > 1 else ""
|
| 217 |
+
module_desc = MODULE_DESCRIPTIONS.get(module, "")
|
| 218 |
+
if module_desc:
|
| 219 |
+
desc_parts.append(f"Header file for contribution module '{module}' ({module_desc}): {rel_path}")
|
| 220 |
+
else:
|
| 221 |
+
desc_parts.append(f"Header file for contribution module '{module}': {rel_path}")
|
| 222 |
+
else:
|
| 223 |
+
desc_parts.append(f"Harbour header file: {rel_path}")
|
| 224 |
+
|
| 225 |
+
# Extract and describe definitions
|
| 226 |
+
defs = extract_ch_definitions(code)
|
| 227 |
+
|
| 228 |
+
if defs["defines"]:
|
| 229 |
+
if len(defs["defines"]) <= 5:
|
| 230 |
+
desc_parts.append(f"Defines constants: {', '.join(defs['defines'])}")
|
| 231 |
+
else:
|
| 232 |
+
desc_parts.append(f"Defines {len(defs['defines'])} constants including: {', '.join(defs['defines'][:5])}")
|
| 233 |
+
|
| 234 |
+
if defs["commands"]:
|
| 235 |
+
desc_parts.append(f"Declares {len(defs['commands'])} preprocessor commands")
|
| 236 |
+
|
| 237 |
+
if defs["translates"]:
|
| 238 |
+
desc_parts.append(f"Declares {len(defs['translates'])} preprocessor translations")
|
| 239 |
+
|
| 240 |
+
# Common header purposes
|
| 241 |
+
upper = code.upper()
|
| 242 |
+
if "ES_" in upper or "EG_" in upper or "ERROR" in filename.upper():
|
| 243 |
+
desc_parts.append("Defines error handling constants and codes")
|
| 244 |
+
elif "INKEY" in upper or "K_" in upper:
|
| 245 |
+
desc_parts.append("Defines keyboard input constants")
|
| 246 |
+
elif "SET" in upper and "CH" in filename.upper():
|
| 247 |
+
desc_parts.append("Defines SET command options")
|
| 248 |
+
elif "COLOR" in upper or "_SET_" in upper:
|
| 249 |
+
desc_parts.append("Defines color and display constants")
|
| 250 |
+
elif "HB_" in upper or "HBEXT" in upper:
|
| 251 |
+
desc_parts.append("Defines Harbour internal constants and macros")
|
| 252 |
+
elif "THREAD" in upper:
|
| 253 |
+
desc_parts.append("Defines threading constants and macros")
|
| 254 |
+
elif "FILE" in upper or "F_" in upper:
|
| 255 |
+
desc_parts.append("Defines file I/O constants")
|
| 256 |
+
elif "DB" in upper or "RDD" in upper:
|
| 257 |
+
desc_parts.append("Defines database/RDD constants")
|
| 258 |
+
elif "COM" in upper or "SERIAL" in upper:
|
| 259 |
+
desc_parts.append("Defines communication constants")
|
| 260 |
+
elif "GT" in upper:
|
| 261 |
+
desc_parts.append("Defines graphics terminal constants")
|
| 262 |
+
elif "BOX" in upper or "BORDER" in upper:
|
| 263 |
+
desc_parts.append("Defines box and border drawing constants")
|
| 264 |
+
elif "MEMO" in upper:
|
| 265 |
+
desc_parts.append("Defines memo field constants")
|
| 266 |
+
|
| 267 |
+
return ". ".join(desc_parts)
|
| 268 |
+
|
| 269 |
+
|
| 270 |
+
def has_real_code(code: str) -> bool:
|
| 271 |
+
"""Check if code contains actual Harbour code (not just comments/includes)."""
|
| 272 |
+
upper = code.upper()
|
| 273 |
+
|
| 274 |
+
# Must have at least one of these code constructs
|
| 275 |
+
code_constructs = [
|
| 276 |
+
"FUNCTION ", "PROCEDURE ", "CREATE CLASS", "ENDCLASS",
|
| 277 |
+
"METHOD ", "RETURN ", "LOCAL ", "MEMVAR ",
|
| 278 |
+
"THREAD STATIC", "IF ", "FOR ", "WHILE ", "DO CASE",
|
| 279 |
+
"BEGIN SEQUENCE", "SWITCH ", "REQUEST ",
|
| 280 |
+
"INIT PROCEDURE", "EXIT PROCEDURE",
|
| 281 |
+
]
|
| 282 |
+
if not any(construct in upper for construct in code_constructs):
|
| 283 |
+
return False
|
| 284 |
+
|
| 285 |
+
# Count actual code lines (non-empty, non-preprocessor, non-blank)
|
| 286 |
+
lines = code.split("\n")
|
| 287 |
+
code_lines = 0
|
| 288 |
+
for line in lines:
|
| 289 |
+
stripped = line.strip()
|
| 290 |
+
if not stripped:
|
| 291 |
+
continue
|
| 292 |
+
if stripped.startswith("#"):
|
| 293 |
+
continue
|
| 294 |
+
if stripped.startswith("//") or stripped.startswith("/*") or stripped.startswith("*"):
|
| 295 |
+
continue
|
| 296 |
+
code_lines += 1
|
| 297 |
+
|
| 298 |
+
return code_lines >= 8
|
| 299 |
+
|
| 300 |
+
|
| 301 |
+
def is_code_complete(code: str) -> bool:
|
| 302 |
+
"""Check if code is complete (proper ENDCLASS, balanced structures)."""
|
| 303 |
+
upper = code.upper()
|
| 304 |
+
|
| 305 |
+
# Check class definitions have matching ENDCLASS
|
| 306 |
+
class_count = upper.count("CREATE CLASS")
|
| 307 |
+
endclass_count = upper.count("ENDCLASS")
|
| 308 |
+
if class_count > 0 and endclass_count < class_count:
|
| 309 |
+
return False
|
| 310 |
+
|
| 311 |
+
# Check DO CASE has ENDDO CASE
|
| 312 |
+
docase_count = upper.count("DO CASE")
|
| 313 |
+
endcase_count = upper.count("ENDCASE") + upper.count("END CASE")
|
| 314 |
+
if docase_count > 0 and endcase_count < docase_count:
|
| 315 |
+
return False
|
| 316 |
+
|
| 317 |
+
# Check FOR/NEXT balance
|
| 318 |
+
for_count = len(re.findall(r'\bFOR\s+\w+', upper))
|
| 319 |
+
next_count = upper.count("\nNEXT") + (1 if upper.endswith("NEXT") else 0)
|
| 320 |
+
# Be lenient - some code uses EXIT in loops
|
| 321 |
+
|
| 322 |
+
# Check DO WHILE / ENDDO balance
|
| 323 |
+
dowhile_count = upper.count("DO WHILE")
|
| 324 |
+
enddo_count = upper.count("ENDDO") + upper.count("END DO")
|
| 325 |
+
if dowhile_count > 0 and enddo_count < dowhile_count:
|
| 326 |
+
return False
|
| 327 |
+
|
| 328 |
+
# Check BEGIN SEQUENCE / END / RECOVER balance
|
| 329 |
+
seq_count = upper.count("BEGIN SEQUENCE")
|
| 330 |
+
end_count = upper.count("\nEND\n") + upper.count("\nEND ") + (1 if upper.endswith("\nEND") or upper.endswith(" END") else 0)
|
| 331 |
+
|
| 332 |
+
# Check parentheses balance (lenient)
|
| 333 |
+
open_p = code.count('(')
|
| 334 |
+
close_p = code.count(')')
|
| 335 |
+
if abs(open_p - close_p) > 3:
|
| 336 |
+
return False
|
| 337 |
+
|
| 338 |
+
# Check BEGIN/END blocks
|
| 339 |
+
begin_count = len(re.findall(r'\bBEGIN\b', upper))
|
| 340 |
+
end_block_count = len(re.findall(r'\bEND\b', upper)) - upper.count("ENDCLASS") - upper.count("ENDCASE") - upper.count("END IF") - upper.count("ENDDO")
|
| 341 |
+
# Very lenient check - just ensure it's not wildly unbalanced
|
| 342 |
+
if begin_count > 0 and end_block_count > begin_count + 5:
|
| 343 |
+
return False
|
| 344 |
+
|
| 345 |
+
return True
|
| 346 |
+
|
| 347 |
+
|
| 348 |
+
def extract_classes_and_functions(code: str) -> Dict:
|
| 349 |
+
"""Extract class and function definitions from code."""
|
| 350 |
+
classes = []
|
| 351 |
+
functions = []
|
| 352 |
+
procedures = []
|
| 353 |
+
|
| 354 |
+
for line in code.split("\n"):
|
| 355 |
+
line_stripped = line.strip()
|
| 356 |
+
upper = line_stripped.upper()
|
| 357 |
+
|
| 358 |
+
# Class definitions
|
| 359 |
+
if upper.startswith("CREATE CLASS"):
|
| 360 |
+
parts = line_stripped.split()
|
| 361 |
+
if len(parts) >= 3:
|
| 362 |
+
class_name = parts[2]
|
| 363 |
+
classes.append(class_name)
|
| 364 |
+
|
| 365 |
+
# Function definitions
|
| 366 |
+
if upper.startswith("FUNCTION ") or (upper.startswith("STATIC FUNCTION ")):
|
| 367 |
+
parts = line_stripped.split()
|
| 368 |
+
idx = 2 if upper.startswith("STATIC") else 1
|
| 369 |
+
if len(parts) >= idx + 1:
|
| 370 |
+
func_name = parts[idx].split("(")[0]
|
| 371 |
+
functions.append(func_name)
|
| 372 |
+
|
| 373 |
+
# Procedure definitions
|
| 374 |
+
if upper.startswith("PROCEDURE ") or upper.startswith("STATIC PROCEDURE "):
|
| 375 |
+
parts = line_stripped.split()
|
| 376 |
+
idx = 2 if upper.startswith("STATIC") else 1
|
| 377 |
+
if len(parts) >= idx + 1:
|
| 378 |
+
proc_name = parts[idx].split("(")[0]
|
| 379 |
+
procedures.append(proc_name)
|
| 380 |
+
|
| 381 |
+
# INIT/EXIT procedures
|
| 382 |
+
if upper.startswith("INIT PROCEDURE") or upper.startswith("EXIT PROCEDURE"):
|
| 383 |
+
parts = line_stripped.split()
|
| 384 |
+
if len(parts) >= 3:
|
| 385 |
+
proc_name = parts[2].split("(")[0]
|
| 386 |
+
procedures.append(proc_name)
|
| 387 |
+
|
| 388 |
+
return {
|
| 389 |
+
"classes": classes,
|
| 390 |
+
"functions": functions,
|
| 391 |
+
"procedures": procedures
|
| 392 |
+
}
|
| 393 |
+
|
| 394 |
+
|
| 395 |
+
def categorize_file(filepath: Path) -> Tuple[str, str]:
|
| 396 |
+
"""Categorize a PRG file into category and subcategory."""
|
| 397 |
+
rel_path = filepath.relative_to(HARBOUR_ROOT)
|
| 398 |
+
parts = rel_path.parts
|
| 399 |
+
|
| 400 |
+
if parts[0] == "src":
|
| 401 |
+
if parts[1] == "rtl":
|
| 402 |
+
return "rtl", categorize_rtl_file(filepath)
|
| 403 |
+
elif parts[1] == "rdd":
|
| 404 |
+
return "rdd", "rdd_core"
|
| 405 |
+
elif parts[1] == "debug":
|
| 406 |
+
return "rtl", "utility"
|
| 407 |
+
else:
|
| 408 |
+
return "rtl", "utility"
|
| 409 |
+
elif parts[0] == "contrib":
|
| 410 |
+
module = parts[1] if len(parts) > 1 else "unknown"
|
| 411 |
+
return "contrib", categorize_contrib_module(module)
|
| 412 |
+
elif parts[0] == "tests":
|
| 413 |
+
return "tests", categorize_test_file(filepath)
|
| 414 |
+
elif parts[0] == "utils":
|
| 415 |
+
return "utils", categorize_utils_file(filepath)
|
| 416 |
+
elif parts[0] == "extras":
|
| 417 |
+
return "extras", categorize_extras_file(filepath)
|
| 418 |
+
else:
|
| 419 |
+
return "rtl", "utility"
|
| 420 |
+
|
| 421 |
+
|
| 422 |
+
def categorize_rtl_file(filepath: Path) -> str:
|
| 423 |
+
"""Categorize RTL files into subcategories."""
|
| 424 |
+
name = filepath.stem.lower()
|
| 425 |
+
|
| 426 |
+
if name.startswith("t") and not name.startswith("text"):
|
| 427 |
+
if any(x in name for x in ["get", "browse", "column", "editor", "scalar", "object", "class"]):
|
| 428 |
+
return "oop_class"
|
| 429 |
+
elif any(x in name for x in ["menu", "popup", "topbar"]):
|
| 430 |
+
return "ui_menu"
|
| 431 |
+
elif any(x in name for x in ["check", "radio", "push", "list", "label", "button"]):
|
| 432 |
+
return "ui_widget"
|
| 433 |
+
elif any(x in name for x in ["edit", "memo"]):
|
| 434 |
+
return "text_edit"
|
| 435 |
+
elif any(x in name for x in ["persist", "profile", "symbol"]):
|
| 436 |
+
return "oop_class"
|
| 437 |
+
else:
|
| 438 |
+
return "oop_class"
|
| 439 |
+
elif "get" in name or "read" in name:
|
| 440 |
+
return "get_system"
|
| 441 |
+
elif any(x in name for x in ["err", "alert"]):
|
| 442 |
+
return "error_handling"
|
| 443 |
+
elif any(x in name for x in ["file", "dir", "ini", "type"]):
|
| 444 |
+
return "file_io"
|
| 445 |
+
elif any(x in name for x in ["db", "memo"]):
|
| 446 |
+
return "database"
|
| 447 |
+
else:
|
| 448 |
+
return "utility"
|
| 449 |
+
|
| 450 |
+
|
| 451 |
+
def categorize_contrib_module(module: str) -> str:
|
| 452 |
+
"""Categorize contrib modules."""
|
| 453 |
+
db_modules = {"hbpgsql", "hbmysql", "hbsqlit3", "hbodbc", "hbfbird", "rddsql", "rddpg",
|
| 454 |
+
"rddmy", "rddfb", "rddads", "rddbm", "rddmisc", "sddpg", "sddmy",
|
| 455 |
+
"sddoci", "sddodbc", "sddsqlt3", "sddfb", "rddado"}
|
| 456 |
+
net_modules = {"hbtip", "hbcurl", "hbhttpd", "hbnetio", "hbcomio", "hbtcpio", "hbpipeio"}
|
| 457 |
+
sec_modules = {"hbssl", "hbmagic"}
|
| 458 |
+
gfx_modules = {"hbbmp", "hbcairo", "hbhpdf", "hbgd", "hbzebra", "hbfimage", "hbformat"}
|
| 459 |
+
fmt_modules = {"hbexpat", "hbmxml", "hbfoxpro", "hbplist", "hbmemio"}
|
| 460 |
+
plat_modules = {"hbwin", "hbunix", "hboslib", "gtalleg", "gtwvg", "gtwvw", "gtwvb"}
|
| 461 |
+
compat_modules = {"hbnf", "hbct", "xhb", "hbxpp", "hbtpathy", "hbfship"}
|
| 462 |
+
|
| 463 |
+
if module in db_modules:
|
| 464 |
+
return "database"
|
| 465 |
+
elif module in net_modules:
|
| 466 |
+
return "network"
|
| 467 |
+
elif module in sec_modules:
|
| 468 |
+
return "security"
|
| 469 |
+
elif module in gfx_modules:
|
| 470 |
+
return "graphics"
|
| 471 |
+
elif module in fmt_modules:
|
| 472 |
+
return "data_format"
|
| 473 |
+
elif module in plat_modules:
|
| 474 |
+
return "platform"
|
| 475 |
+
elif module in compat_modules:
|
| 476 |
+
return "compatibility"
|
| 477 |
+
else:
|
| 478 |
+
return "utility"
|
| 479 |
+
|
| 480 |
+
|
| 481 |
+
def categorize_test_file(filepath: Path) -> str:
|
| 482 |
+
"""Categorize test files."""
|
| 483 |
+
name = filepath.stem.lower()
|
| 484 |
+
|
| 485 |
+
if any(x in name for x in ["class", "oob", "inherit", "scope", "data"]):
|
| 486 |
+
return "oop"
|
| 487 |
+
elif any(x in name for x in ["db", "rdd", "browse"]):
|
| 488 |
+
return "database"
|
| 489 |
+
elif any(x in name for x in ["speed", "bench"]):
|
| 490 |
+
return "performance"
|
| 491 |
+
elif any(x in name for x in ["str", "math", "date", "array", "for", "while", "if", "case",
|
| 492 |
+
"static", "mem", "gt", "regex", "file", "err", "hello"]):
|
| 493 |
+
return "language_basics"
|
| 494 |
+
else:
|
| 495 |
+
return "function_api"
|
| 496 |
+
|
| 497 |
+
|
| 498 |
+
def categorize_utils_file(filepath: Path) -> str:
|
| 499 |
+
"""Categorize utility files."""
|
| 500 |
+
name = filepath.stem.lower()
|
| 501 |
+
|
| 502 |
+
if "hbmk" in name or "build" in name:
|
| 503 |
+
return "build_system"
|
| 504 |
+
elif "test" in name or "rt_" in name:
|
| 505 |
+
return "test_framework"
|
| 506 |
+
elif "i18n" in name or "lang" in name:
|
| 507 |
+
return "i18n"
|
| 508 |
+
else:
|
| 509 |
+
return "build_system"
|
| 510 |
+
|
| 511 |
+
|
| 512 |
+
def categorize_extras_file(filepath: Path) -> str:
|
| 513 |
+
"""Categorize extras files."""
|
| 514 |
+
parts = filepath.relative_to(HARBOUR_ROOT).parts
|
| 515 |
+
|
| 516 |
+
if len(parts) > 1:
|
| 517 |
+
module = parts[1].lower()
|
| 518 |
+
if "pdf" in module or "vpdf" in module:
|
| 519 |
+
return "pdf"
|
| 520 |
+
elif "xls" in module or "excel" in module:
|
| 521 |
+
return "spreadsheet"
|
| 522 |
+
elif "srv" in module or "http" in module:
|
| 523 |
+
return "server"
|
| 524 |
+
return "utility"
|
| 525 |
+
|
| 526 |
+
|
| 527 |
+
def generate_description(filepath: Path, code: str, category: str, subcategory: str) -> str:
|
| 528 |
+
"""Generate a comprehensive description for a PRG file."""
|
| 529 |
+
rel_path = filepath.relative_to(HARBOUR_ROOT)
|
| 530 |
+
module_name = rel_path.parts[1] if len(rel_path.parts) > 1 else "rtl"
|
| 531 |
+
|
| 532 |
+
# Get module description if contrib
|
| 533 |
+
module_desc = ""
|
| 534 |
+
if category == "contrib" and module_name in MODULE_DESCRIPTIONS:
|
| 535 |
+
module_desc = MODULE_DESCRIPTIONS[module_name]
|
| 536 |
+
|
| 537 |
+
# Extract code elements
|
| 538 |
+
elements = extract_classes_and_functions(code)
|
| 539 |
+
|
| 540 |
+
# Generate description based on category and content
|
| 541 |
+
desc_parts = []
|
| 542 |
+
|
| 543 |
+
# File location context
|
| 544 |
+
if category == "rtl":
|
| 545 |
+
desc_parts.append(f"Harbour Runtime Library file: {rel_path}")
|
| 546 |
+
elif category == "contrib":
|
| 547 |
+
desc_parts.append(f"Harbour contribution module '{module_name}' ({module_desc}): {rel_path}")
|
| 548 |
+
elif category == "tests":
|
| 549 |
+
desc_parts.append(f"Harbour test program: {rel_path}")
|
| 550 |
+
elif category == "utils":
|
| 551 |
+
desc_parts.append(f"Harbour utility program: {rel_path}")
|
| 552 |
+
elif category == "extras":
|
| 553 |
+
desc_parts.append(f"Harbour extra library: {rel_path}")
|
| 554 |
+
else:
|
| 555 |
+
desc_parts.append(f"Harbour source file: {rel_path}")
|
| 556 |
+
|
| 557 |
+
# Add code structure information
|
| 558 |
+
if elements["classes"]:
|
| 559 |
+
desc_parts.append(f"Defines classes: {', '.join(elements['classes'][:5])}")
|
| 560 |
+
|
| 561 |
+
if elements["functions"]:
|
| 562 |
+
if len(elements["functions"]) <= 5:
|
| 563 |
+
desc_parts.append(f"Provides functions: {', '.join(elements['functions'])}")
|
| 564 |
+
else:
|
| 565 |
+
desc_parts.append(f"Provides {len(elements['functions'])} functions including: {', '.join(elements['functions'][:5])}")
|
| 566 |
+
|
| 567 |
+
if elements["procedures"]:
|
| 568 |
+
if len(elements["procedures"]) <= 3:
|
| 569 |
+
desc_parts.append(f"Contains procedures: {', '.join(elements['procedures'])}")
|
| 570 |
+
else:
|
| 571 |
+
desc_parts.append(f"Contains {len(elements['procedures'])} procedures")
|
| 572 |
+
|
| 573 |
+
# Add subcategory context
|
| 574 |
+
subcategory_descriptions = {
|
| 575 |
+
"oop_class": "This file implements object-oriented classes using Harbour's class system",
|
| 576 |
+
"ui_widget": "This file defines UI widget classes for graphical interfaces",
|
| 577 |
+
"ui_menu": "This file implements menu system classes",
|
| 578 |
+
"text_edit": "This file provides text editing functionality",
|
| 579 |
+
"get_system": "This file implements the GET system for input field handling",
|
| 580 |
+
"scalar_type": "This file defines scalar type wrapper classes",
|
| 581 |
+
"error_handling": "This file implements error handling and reporting",
|
| 582 |
+
"file_io": "This file provides file I/O operations",
|
| 583 |
+
"database": "This file handles database operations",
|
| 584 |
+
"utility": "This file provides utility functions",
|
| 585 |
+
"rdd_core": "This file implements core Record Driver Driver functionality",
|
| 586 |
+
"rdd_driver": "This file implements a database driver",
|
| 587 |
+
"network": "This file provides network protocol implementations",
|
| 588 |
+
"security": "This file implements security and encryption functions",
|
| 589 |
+
"graphics": "This file provides graphics and image processing capabilities",
|
| 590 |
+
"data_format": "This file handles data format parsing and generation",
|
| 591 |
+
"platform": "This file provides platform-specific functionality",
|
| 592 |
+
"compatibility": "This file provides legacy compatibility functions",
|
| 593 |
+
"language_basics": "This test file exercises basic Harbour language features",
|
| 594 |
+
"function_api": "This test file tests specific function APIs",
|
| 595 |
+
"oop": "This test file tests object-oriented programming features",
|
| 596 |
+
"performance": "This test file benchmarks performance characteristics",
|
| 597 |
+
"build_system": "This file is part of the build system tooling",
|
| 598 |
+
"test_framework": "This file is part of the test framework",
|
| 599 |
+
"i18n": "This file provides internationalization support",
|
| 600 |
+
"pdf": "This file provides PDF generation capabilities",
|
| 601 |
+
"spreadsheet": "This file provides spreadsheet generation capabilities",
|
| 602 |
+
"server": "This file implements server functionality",
|
| 603 |
+
}
|
| 604 |
+
|
| 605 |
+
if subcategory in subcategory_descriptions:
|
| 606 |
+
desc_parts.append(subcategory_descriptions[subcategory])
|
| 607 |
+
|
| 608 |
+
return ". ".join(desc_parts)
|
| 609 |
+
|
| 610 |
+
|
| 611 |
+
def create_training_entry(filepath: Path, code: str, description: str) -> Dict:
|
| 612 |
+
"""Create a training entry in the instruction format."""
|
| 613 |
+
return {
|
| 614 |
+
"instruction": f"Write Harbour (xBase/Clipper) code for: {description}",
|
| 615 |
+
"input": "",
|
| 616 |
+
"output": code,
|
| 617 |
+
"metadata": {
|
| 618 |
+
"file_path": str(filepath.relative_to(HARBOUR_ROOT)),
|
| 619 |
+
"language": "harbour",
|
| 620 |
+
"description": description
|
| 621 |
+
}
|
| 622 |
+
}
|
| 623 |
+
|
| 624 |
+
|
| 625 |
+
def create_completion_entry(filepath: Path, code: str, description: str) -> Dict:
|
| 626 |
+
"""Create a completion-style training entry with diverse instructions."""
|
| 627 |
+
# Generate diverse user prompts based on code content
|
| 628 |
+
import random
|
| 629 |
+
random.seed(hash(filepath)) # Deterministic per file
|
| 630 |
+
|
| 631 |
+
elements = extract_classes_and_functions(code)
|
| 632 |
+
upper = code.upper()
|
| 633 |
+
|
| 634 |
+
# Different prompt templates based on content
|
| 635 |
+
templates = []
|
| 636 |
+
|
| 637 |
+
if elements["classes"]:
|
| 638 |
+
templates.append(f"Implement the following Harbour classes: {', '.join(elements['classes'][:3])}. {description}")
|
| 639 |
+
templates.append(f"Create Harbour OOP classes for the functionality described: {description}")
|
| 640 |
+
|
| 641 |
+
if elements["functions"]:
|
| 642 |
+
templates.append(f"Write Harbour functions: {', '.join(elements['functions'][:3])}. {description}")
|
| 643 |
+
templates.append(f"Implement these Harbour functions: {description}")
|
| 644 |
+
|
| 645 |
+
if elements["procedures"]:
|
| 646 |
+
templates.append(f"Write a Harbour program with procedures: {', '.join(elements['procedures'][:3])}. {description}")
|
| 647 |
+
|
| 648 |
+
if "#DEFINE" in upper or "#XCOMMAND" in upper:
|
| 649 |
+
templates.append(f"Create Harbour preprocessor definitions: {description}")
|
| 650 |
+
templates.append(f"Define Harbour macros and constants: {description}")
|
| 651 |
+
|
| 652 |
+
# General templates
|
| 653 |
+
templates.append(f"Write the following Harbour (xBase/Clipper) code:\n\n{description}")
|
| 654 |
+
templates.append(f"Implement this Harbour module: {description}")
|
| 655 |
+
templates.append(f"Here is a Harbour (xBase/Clipper) implementation:\n\n{description}")
|
| 656 |
+
templates.append(f"Generate Harbour code for: {description}")
|
| 657 |
+
|
| 658 |
+
# Select a random template
|
| 659 |
+
user_prompt = random.choice(templates)
|
| 660 |
+
|
| 661 |
+
return {
|
| 662 |
+
"messages": [
|
| 663 |
+
{
|
| 664 |
+
"role": "system",
|
| 665 |
+
"content": "You are an expert Harbour (xBase/Clipper) programmer. Write clean, efficient code following Harbour conventions. Use proper Hungarian notation for variable names (c=character, n=numeric, l=logical, a=array, o=object, b=codeblock)."
|
| 666 |
+
},
|
| 667 |
+
{
|
| 668 |
+
"role": "user",
|
| 669 |
+
"content": user_prompt
|
| 670 |
+
},
|
| 671 |
+
{
|
| 672 |
+
"role": "assistant",
|
| 673 |
+
"content": code
|
| 674 |
+
}
|
| 675 |
+
],
|
| 676 |
+
"metadata": {
|
| 677 |
+
"file_path": str(filepath.relative_to(HARBOUR_ROOT)),
|
| 678 |
+
"language": "harbour",
|
| 679 |
+
"description": description
|
| 680 |
+
}
|
| 681 |
+
}
|
| 682 |
+
|
| 683 |
+
|
| 684 |
+
def process_prg_file(filepath: Path) -> List[Dict]:
|
| 685 |
+
"""Process a single PRG file and generate training entries."""
|
| 686 |
+
try:
|
| 687 |
+
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
|
| 688 |
+
code = f.read()
|
| 689 |
+
except Exception as e:
|
| 690 |
+
print(f"Error reading {filepath}: {e}")
|
| 691 |
+
return []
|
| 692 |
+
|
| 693 |
+
# Skip empty files or very small files
|
| 694 |
+
if len(code.strip()) < 50:
|
| 695 |
+
return []
|
| 696 |
+
|
| 697 |
+
# Step 1: Remove ALL comments (block, single-line, star-prefixed, inline)
|
| 698 |
+
code = remove_all_comments(code)
|
| 699 |
+
|
| 700 |
+
# Step 2: Remove disabled code blocks (#if 0)
|
| 701 |
+
code = remove_disabled_code(code)
|
| 702 |
+
|
| 703 |
+
# Step 3: Clean excessive blank lines
|
| 704 |
+
code = clean_excessive_blank_lines(code)
|
| 705 |
+
|
| 706 |
+
# Step 5: Strip leading/trailing whitespace
|
| 707 |
+
code = code.strip()
|
| 708 |
+
|
| 709 |
+
# Skip if no real code remains
|
| 710 |
+
if not has_real_code(code):
|
| 711 |
+
return []
|
| 712 |
+
|
| 713 |
+
# Skip if code is too short
|
| 714 |
+
if len(code) < MIN_CODE_LENGTH:
|
| 715 |
+
return []
|
| 716 |
+
|
| 717 |
+
# Skip if code is incomplete
|
| 718 |
+
if not is_code_complete(code):
|
| 719 |
+
return []
|
| 720 |
+
|
| 721 |
+
# Truncate if too long
|
| 722 |
+
if len(code) > MAX_CODE_LENGTH:
|
| 723 |
+
lines = code.split("\n")
|
| 724 |
+
truncated_lines = []
|
| 725 |
+
current_length = 0
|
| 726 |
+
for line in lines:
|
| 727 |
+
if current_length + len(line) > MAX_CODE_LENGTH:
|
| 728 |
+
break
|
| 729 |
+
truncated_lines.append(line)
|
| 730 |
+
current_length += len(line) + 1
|
| 731 |
+
code = "\n".join(truncated_lines)
|
| 732 |
+
|
| 733 |
+
# Categorize the file
|
| 734 |
+
category, subcategory = categorize_file(filepath)
|
| 735 |
+
|
| 736 |
+
# Generate description
|
| 737 |
+
description = generate_description(filepath, code, category, subcategory)
|
| 738 |
+
|
| 739 |
+
# Create training entry (chat format only - preferred for Qwen2.5-Coder)
|
| 740 |
+
entry = create_completion_entry(filepath, code, description)
|
| 741 |
+
entry["metadata"]["category"] = category
|
| 742 |
+
entry["metadata"]["subcategory"] = subcategory
|
| 743 |
+
|
| 744 |
+
return [entry]
|
| 745 |
+
|
| 746 |
+
|
| 747 |
+
def process_ch_file(filepath: Path) -> List[Dict]:
|
| 748 |
+
"""Process a single CH file and generate training entries."""
|
| 749 |
+
try:
|
| 750 |
+
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
|
| 751 |
+
code = f.read()
|
| 752 |
+
except Exception as e:
|
| 753 |
+
print(f"Error reading {filepath}: {e}")
|
| 754 |
+
return []
|
| 755 |
+
|
| 756 |
+
# Skip empty files
|
| 757 |
+
if len(code.strip()) < 30:
|
| 758 |
+
return []
|
| 759 |
+
|
| 760 |
+
# Step 1: Remove ALL comments
|
| 761 |
+
code = remove_all_comments(code)
|
| 762 |
+
|
| 763 |
+
# Step 2: Remove disabled code blocks (#if 0)
|
| 764 |
+
code = remove_disabled_code(code)
|
| 765 |
+
|
| 766 |
+
# Step 3: Clean excessive blank lines
|
| 767 |
+
code = clean_excessive_blank_lines(code)
|
| 768 |
+
|
| 769 |
+
# Step 4: Strip leading/trailing whitespace
|
| 770 |
+
code = code.strip()
|
| 771 |
+
|
| 772 |
+
# Skip if no real code remains
|
| 773 |
+
if not has_real_ch_code(code):
|
| 774 |
+
return []
|
| 775 |
+
|
| 776 |
+
# Skip if code is too short
|
| 777 |
+
if len(code) < MIN_CODE_LENGTH:
|
| 778 |
+
return []
|
| 779 |
+
|
| 780 |
+
# Truncate if too long
|
| 781 |
+
if len(code) > MAX_CODE_LENGTH:
|
| 782 |
+
lines = code.split("\n")
|
| 783 |
+
truncated_lines = []
|
| 784 |
+
current_length = 0
|
| 785 |
+
for line in lines:
|
| 786 |
+
if current_length + len(line) > MAX_CODE_LENGTH:
|
| 787 |
+
break
|
| 788 |
+
truncated_lines.append(line)
|
| 789 |
+
current_length += len(line) + 1
|
| 790 |
+
code = "\n".join(truncated_lines)
|
| 791 |
+
|
| 792 |
+
# Generate description
|
| 793 |
+
description = generate_ch_description(filepath, code)
|
| 794 |
+
|
| 795 |
+
# Determine category
|
| 796 |
+
rel_path = filepath.relative_to(HARBOUR_ROOT)
|
| 797 |
+
parts = rel_path.parts
|
| 798 |
+
|
| 799 |
+
if parts[0] == "include":
|
| 800 |
+
category = "include"
|
| 801 |
+
elif parts[0] == "contrib":
|
| 802 |
+
category = "contrib"
|
| 803 |
+
elif parts[0] == "utils":
|
| 804 |
+
category = "utils"
|
| 805 |
+
elif parts[0] == "extras":
|
| 806 |
+
category = "extras"
|
| 807 |
+
else:
|
| 808 |
+
category = "include"
|
| 809 |
+
|
| 810 |
+
# Create training entry (chat format only)
|
| 811 |
+
entry = create_completion_entry(filepath, code, description)
|
| 812 |
+
entry["metadata"]["category"] = category
|
| 813 |
+
entry["metadata"]["subcategory"] = "header"
|
| 814 |
+
|
| 815 |
+
return [entry]
|
| 816 |
+
|
| 817 |
+
|
| 818 |
+
def main():
|
| 819 |
+
"""Main function to generate the dataset."""
|
| 820 |
+
print("=" * 60)
|
| 821 |
+
print("Harbour PRG/CH Dataset Generator (CLEANED)")
|
| 822 |
+
print("=" * 60)
|
| 823 |
+
|
| 824 |
+
# Find all PRG and CH files
|
| 825 |
+
print("\n1. Finding all Harbour source files...")
|
| 826 |
+
prg_files = list(HARBOUR_ROOT.rglob("*.prg"))
|
| 827 |
+
ch_files = list(HARBOUR_ROOT.rglob("*.ch"))
|
| 828 |
+
print(f" Found {len(prg_files)} PRG files")
|
| 829 |
+
print(f" Found {len(ch_files)} CH files")
|
| 830 |
+
|
| 831 |
+
# Process PRG files
|
| 832 |
+
print("\n2. Processing PRG files...")
|
| 833 |
+
all_entries = []
|
| 834 |
+
category_counts = {}
|
| 835 |
+
skipped_files = 0
|
| 836 |
+
|
| 837 |
+
for i, filepath in enumerate(prg_files, 1):
|
| 838 |
+
if i % 100 == 0:
|
| 839 |
+
print(f" Processing PRG file {i}/{len(prg_files)}...")
|
| 840 |
+
|
| 841 |
+
entries = process_prg_file(filepath)
|
| 842 |
+
if entries:
|
| 843 |
+
all_entries.extend(entries)
|
| 844 |
+
category = entries[0]["metadata"]["category"]
|
| 845 |
+
category_counts[category] = category_counts.get(category, 0) + 1
|
| 846 |
+
else:
|
| 847 |
+
skipped_files += 1
|
| 848 |
+
|
| 849 |
+
print(f"\n PRG: Generated {len(all_entries)} entries, skipped {skipped_files} files")
|
| 850 |
+
|
| 851 |
+
# Process CH files
|
| 852 |
+
print("\n3. Processing CH files...")
|
| 853 |
+
ch_entries = 0
|
| 854 |
+
ch_skipped = 0
|
| 855 |
+
|
| 856 |
+
for i, filepath in enumerate(ch_files, 1):
|
| 857 |
+
if i % 20 == 0:
|
| 858 |
+
print(f" Processing CH file {i}/{len(ch_files)}...")
|
| 859 |
+
|
| 860 |
+
entries = process_ch_file(filepath)
|
| 861 |
+
if entries:
|
| 862 |
+
all_entries.extend(entries)
|
| 863 |
+
ch_entries += 1
|
| 864 |
+
category = entries[0]["metadata"]["category"]
|
| 865 |
+
category_counts[category] = category_counts.get(category, 0) + 1
|
| 866 |
+
else:
|
| 867 |
+
ch_skipped += 1
|
| 868 |
+
|
| 869 |
+
print(f"\n CH: Generated {ch_entries} file entries, skipped {ch_skipped} files")
|
| 870 |
+
print(f"\n Total: {len(all_entries)} training entries")
|
| 871 |
+
|
| 872 |
+
# Print category statistics
|
| 873 |
+
print("\n3. Category statistics:")
|
| 874 |
+
for category, count in sorted(category_counts.items()):
|
| 875 |
+
print(f" {category}: {count} files")
|
| 876 |
+
|
| 877 |
+
# Shuffle entries
|
| 878 |
+
random.seed(42)
|
| 879 |
+
random.shuffle(all_entries)
|
| 880 |
+
|
| 881 |
+
# Split into train and validation
|
| 882 |
+
print("\n4. Splitting into train/validation sets...")
|
| 883 |
+
split_idx = int(len(all_entries) * TRAIN_RATIO)
|
| 884 |
+
train_entries = all_entries[:split_idx]
|
| 885 |
+
val_entries = all_entries[split_idx:]
|
| 886 |
+
|
| 887 |
+
print(f" Training set: {len(train_entries)} entries")
|
| 888 |
+
print(f" Validation set: {len(val_entries)} entries")
|
| 889 |
+
|
| 890 |
+
# Save datasets
|
| 891 |
+
print("\n5. Saving datasets...")
|
| 892 |
+
|
| 893 |
+
# Save as JSONL (instruction format)
|
| 894 |
+
train_jsonl_path = OUTPUT_DIR / "harbour_train.jsonl"
|
| 895 |
+
val_jsonl_path = OUTPUT_DIR / "harbour_val.jsonl"
|
| 896 |
+
|
| 897 |
+
with open(train_jsonl_path, 'w', encoding='utf-8') as f:
|
| 898 |
+
for entry in train_entries:
|
| 899 |
+
train_entry = {k: v for k, v in entry.items() if k != "metadata"}
|
| 900 |
+
f.write(json.dumps(train_entry, ensure_ascii=False) + "\n")
|
| 901 |
+
|
| 902 |
+
with open(val_jsonl_path, 'w', encoding='utf-8') as f:
|
| 903 |
+
for entry in val_entries:
|
| 904 |
+
val_entry = {k: v for k, v in entry.items() if k != "metadata"}
|
| 905 |
+
f.write(json.dumps(val_entry, ensure_ascii=False) + "\n")
|
| 906 |
+
|
| 907 |
+
print(f" Saved training JSONL: {train_jsonl_path}")
|
| 908 |
+
print(f" Saved validation JSONL: {val_jsonl_path}")
|
| 909 |
+
|
| 910 |
+
# Save full dataset with metadata
|
| 911 |
+
full_dataset_path = OUTPUT_DIR / "harbour_dataset_full.jsonl"
|
| 912 |
+
with open(full_dataset_path, 'w', encoding='utf-8') as f:
|
| 913 |
+
for entry in all_entries:
|
| 914 |
+
f.write(json.dumps(entry, ensure_ascii=False) + "\n")
|
| 915 |
+
|
| 916 |
+
print(f" Saved full dataset with metadata: {full_dataset_path}")
|
| 917 |
+
|
| 918 |
+
# Generate statistics file
|
| 919 |
+
total_files = len(prg_files) + len(ch_files)
|
| 920 |
+
stats = {
|
| 921 |
+
"total_prg_files": len(prg_files),
|
| 922 |
+
"total_ch_files": len(ch_files),
|
| 923 |
+
"total_files": total_files,
|
| 924 |
+
"total_entries": len(all_entries),
|
| 925 |
+
"skipped_prg": skipped_files,
|
| 926 |
+
"skipped_ch": ch_skipped,
|
| 927 |
+
"train_entries": len(train_entries),
|
| 928 |
+
"val_entries": len(val_entries),
|
| 929 |
+
"categories": category_counts,
|
| 930 |
+
"files_per_category": {}
|
| 931 |
+
}
|
| 932 |
+
|
| 933 |
+
for entry in all_entries:
|
| 934 |
+
cat = entry["metadata"]["category"]
|
| 935 |
+
subcat = entry["metadata"]["subcategory"]
|
| 936 |
+
if cat not in stats["files_per_category"]:
|
| 937 |
+
stats["files_per_category"][cat] = {}
|
| 938 |
+
stats["files_per_category"][cat][subcat] = stats["files_per_category"][cat].get(subcat, 0) + 1
|
| 939 |
+
|
| 940 |
+
stats_path = OUTPUT_DIR / "dataset_stats.json"
|
| 941 |
+
with open(stats_path, 'w', encoding='utf-8') as f:
|
| 942 |
+
json.dump(stats, f, indent=2, ensure_ascii=False)
|
| 943 |
+
|
| 944 |
+
print(f" Saved statistics: {stats_path}")
|
| 945 |
+
|
| 946 |
+
# Generate README
|
| 947 |
+
readme_content = f"""# Harbour Fine-tuning Dataset
|
| 948 |
+
|
| 949 |
+
## Overview
|
| 950 |
+
This dataset contains {len(all_entries)} training entries extracted from:
|
| 951 |
+
- {len(prg_files)} Harbour PRG (.prg) source files
|
| 952 |
+
- {len(ch_files)} Harbour Header (.ch) files
|
| 953 |
+
|
| 954 |
+
{skipped_files} PRG files and {ch_skipped} CH files were skipped due to quality issues.
|
| 955 |
+
|
| 956 |
+
## Dataset Format
|
| 957 |
+
The dataset is provided in JSONL format with the following structure:
|
| 958 |
+
|
| 959 |
+
### Instruction Format (harbour_train.jsonl / harbour_val.jsonl)
|
| 960 |
+
```json
|
| 961 |
+
{{"instruction": "...", "input": "", "output": "..."}}
|
| 962 |
+
```
|
| 963 |
+
|
| 964 |
+
### Full Dataset (harbour_dataset_full.jsonl)
|
| 965 |
+
```json
|
| 966 |
+
{{"instruction": "...", "input": "", "output": "...", "metadata": {{"file_path": "...", "language": "harbour", "category": "...", "subcategory": "..."}}}}
|
| 967 |
+
```
|
| 968 |
+
|
| 969 |
+
## Categories
|
| 970 |
+
- **include**: Header files with constants/macros ({category_counts.get('include', 0)} files)
|
| 971 |
+
- **rtl**: Harbour Runtime Library ({category_counts.get('rtl', 0)} files)
|
| 972 |
+
- **contrib**: Contribution libraries ({category_counts.get('contrib', 0)} files)
|
| 973 |
+
- **tests**: Test programs ({category_counts.get('tests', 0)} files)
|
| 974 |
+
- **utils**: Utility programs ({category_counts.get('utils', 0)} files)
|
| 975 |
+
- **extras**: Extra libraries ({category_counts.get('extras', 0)} files)
|
| 976 |
+
|
| 977 |
+
## Cleaning Applied
|
| 978 |
+
- Copyright/license headers removed
|
| 979 |
+
- Disabled code blocks (#if 0) removed
|
| 980 |
+
- Excessive trailing comments removed
|
| 981 |
+
- Excessive blank lines removed
|
| 982 |
+
- Files without actual code filtered out
|
| 983 |
+
- Incomplete code (missing ENDCLASS, etc.) filtered out
|
| 984 |
+
|
| 985 |
+
## Usage for Fine-tuning
|
| 986 |
+
```bash
|
| 987 |
+
# Using Ollama with Modelfile
|
| 988 |
+
FROM qwen2.5-coder:14b
|
| 989 |
+
|
| 990 |
+
# Training command
|
| 991 |
+
ollama create harbour-coder -f Modelfile
|
| 992 |
+
|
| 993 |
+
# Or use with other training frameworks
|
| 994 |
+
# The JSONL format is compatible with:
|
| 995 |
+
# - OpenAI fine-tuning API
|
| 996 |
+
# - Hugging Face transformers
|
| 997 |
+
# - Axolotl
|
| 998 |
+
# - LLaMA-Factory
|
| 999 |
+
```
|
| 1000 |
+
|
| 1001 |
+
## File Structure
|
| 1002 |
+
- `harbour_train.jsonl` - Training set ({len(train_entries)} entries)
|
| 1003 |
+
- `harbour_val.jsonl` - Validation set ({len(val_entries)} entries)
|
| 1004 |
+
- `harbour_dataset_full.jsonl` - Full dataset with metadata
|
| 1005 |
+
- `dataset_stats.json` - Dataset statistics
|
| 1006 |
+
- `generate_dataset.py` - This script
|
| 1007 |
+
|
| 1008 |
+
## Source
|
| 1009 |
+
The source files are from the Harbour project (https://harbour.github.io/),
|
| 1010 |
+
an open-source Clipper-compatible compiler.
|
| 1011 |
+
"""
|
| 1012 |
+
|
| 1013 |
+
readme_path = OUTPUT_DIR / "README.md"
|
| 1014 |
+
with open(readme_path, 'w', encoding='utf-8') as f:
|
| 1015 |
+
f.write(readme_content)
|
| 1016 |
+
|
| 1017 |
+
print(f" Saved README: {readme_path}")
|
| 1018 |
+
|
| 1019 |
+
print("\n" + "=" * 60)
|
| 1020 |
+
print("Dataset generation complete!")
|
| 1021 |
+
print("=" * 60)
|
| 1022 |
+
|
| 1023 |
+
|
| 1024 |
+
if __name__ == "__main__":
|
| 1025 |
+
main()
|