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", dtype="auto") - llama-cpp-python
How to use fivetech/Harbour with llama-cpp-python:
# !pip install llama-cpp-python from llama_cpp import Llama llm = Llama.from_pretrained( repo_id="fivetech/Harbour", filename="Qwen3.6-35B-A3B-LoRA-Q4_K_M.gguf", )
llm.create_chat_completion( messages = [ { "role": "user", "content": "What is the capital of France?" } ] ) - 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
- Hermes Agent new
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 new
- OpenClaw new
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"
- 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
| #!/usr/bin/env python3 | |
| """ | |
| Harbour PRG/CH Dataset Generator for Fine-tuning qwen2.5-coder:14b | |
| Generates structured JSONL dataset from .prg and .ch files with descriptions. | |
| CLEANED VERSION - removes boilerplate, ensures code quality. | |
| """ | |
| import os | |
| import re | |
| import json | |
| import random | |
| from pathlib import Path | |
| from typing import Dict, List, Tuple, Optional | |
| # Configuration | |
| HARBOUR_ROOT = Path("/home/fivetech/harbour") | |
| OUTPUT_DIR = Path("/home/fivetech/finetune") | |
| MAX_CODE_LENGTH = 8000 | |
| MIN_CODE_LENGTH = 80 # Minimum chars of actual code | |
| TRAIN_RATIO = 0.9 | |
| # Module descriptions for contrib | |
| MODULE_DESCRIPTIONS = { | |
| "hbhttpd": "Multithreaded HTTP/HTTPS server framework", | |
| "hbwin": "Windows API wrapper functions", | |
| "hbpgsql": "PostgreSQL database client library", | |
| "hbmysql": "MySQL database client library", | |
| "hbsqlit3": "SQLite3 database client library", | |
| "hbodbc": "ODBC database connectivity", | |
| "hbtip": "Internet protocol utilities (FTP, HTTP, SMTP, POP3)", | |
| "hbcurl": "libcurl wrapper for HTTP/FTP/SMTP operations", | |
| "hbssl": "OpenSSL wrapper for SSL/TLS encryption", | |
| "hbnf": "NanForum Toolkit - legacy Clipper compatibility functions", | |
| "hbct": "CA-Tools compatibility library", | |
| "hbmisc": "Miscellaneous utility functions", | |
| "hbgd": "Graphics drawing library (GD)", | |
| "hbcairo": "Cairo graphics library wrapper", | |
| "hbhpdf": "PDF generation library (libharu)", | |
| "hbbmp": "BMP image handling", | |
| "hbzebra": "Barcode generation library", | |
| "hbexpat": "XML parsing library (Expat)", | |
| "hbmxml": "XML generation library", | |
| "hbnetio": "Network I/O operations", | |
| "hbpipeio": "Process pipe I/O operations", | |
| "hbmemio": "Memory file I/O operations", | |
| "xhb": "Extended Harbour functions", | |
| "hbxpp": "xBase++ compatibility functions", | |
| "hbunix": "Unix-specific functions", | |
| "hbtpathy": "Telepath communication library", | |
| "hbblat": "Blat email sending utility", | |
| "hbblink": "Blinker function extender", | |
| "hbgs": "Ghostscript wrapper", | |
| "hbfship": "Fships library functions", | |
| "hbmzip": "ZIP file handling", | |
| "hbziparc": "ZIP archive handling", | |
| "hbxdiff": "File difference/patching", | |
| "hblzf": "LZF compression library", | |
| "hbmlzo": "LZO compression library", | |
| "hbbz2": "BZ2 compression library", | |
| "hbformat": "Text formatting utilities", | |
| "hbfoxpro": "FoxPro file format support", | |
| "hbplist": "Apple plist file format support", | |
| "hbcups": "CUPS printing system wrapper", | |
| "hbsms": "SMS sending via modem", | |
| "hbcomm": "Serial communication library", | |
| "hbfbird": "Firebird database client", | |
| "hbfimage": "FreeImage library wrapper", | |
| "hbdoc": "Documentation generation utilities", | |
| "hbtinymt": "Tiny Mersenne Twister PRNG", | |
| "hbtest": "Test framework utilities", | |
| } | |
| def remove_all_comments(code: str) -> str: | |
| """Remove ALL comments from Harbour code: //, /* */, *, and inline comments.""" | |
| # Remove block comments /* ... */ (including multi-line) | |
| code = re.sub(r'/\*.*?\*/', '', code, flags=re.DOTALL) | |
| # Remove single-line comments // ... | |
| code = re.sub(r'//[^\n]*', '', code) | |
| # Remove lines that are only star-prefixed comments: * ... | |
| # And standalone star lines used in block comment formatting | |
| lines = code.split("\n") | |
| result_lines = [] | |
| for line in lines: | |
| stripped = line.strip() | |
| # Skip lines that are ONLY comments (star-prefixed, standalone) | |
| if re.match(r'^\*\s', stripped) or stripped == '*' or stripped == '*/' or stripped == '/*': | |
| continue | |
| result_lines.append(line) | |
| code = "\n".join(result_lines) | |
| # Remove inline comments at end of lines: code * comment | |
| # Pattern: something followed by whitespace then * ... (but not in strings) | |
| code = re.sub(r'([^\s"].*?)\s+\*[^"]*$', r'\1', code, flags=re.MULTILINE) | |
| return code | |
| def clean_excessive_blank_lines(code: str) -> str: | |
| """Remove excessive blank lines (more than 2 consecutive).""" | |
| lines = code.split("\n") | |
| result_lines = [] | |
| blank_count = 0 | |
| for line in lines: | |
| if line.strip() == "": | |
| blank_count += 1 | |
| if blank_count <= 2: | |
| result_lines.append(line) | |
| else: | |
| blank_count = 0 | |
| result_lines.append(line) | |
| return "\n".join(result_lines) | |
| def remove_disabled_code(code: str) -> str: | |
| """Remove #if 0 ... #endif blocks (disabled code).""" | |
| lines = code.split("\n") | |
| result_lines = [] | |
| in_disabled = False | |
| disabled_depth = 0 | |
| for line in lines: | |
| stripped = line.strip() | |
| if stripped.upper().startswith("#IF 0") or stripped.upper().startswith("#IFDEF _HARBOUR_DISABLE"): | |
| in_disabled = True | |
| disabled_depth += 1 | |
| continue | |
| if in_disabled: | |
| if stripped.upper().startswith("#ENDIF"): | |
| disabled_depth -= 1 | |
| if disabled_depth <= 0: | |
| in_disabled = False | |
| continue | |
| result_lines.append(line) | |
| return "\n".join(result_lines) | |
| def is_ch_file(filepath: Path) -> bool: | |
| """Check if file is a .ch (Clipper header) file.""" | |
| return filepath.suffix.lower() == '.ch' | |
| def has_real_ch_code(code: str) -> bool: | |
| """Check if .ch file contains actual preprocessor definitions.""" | |
| upper = code.upper() | |
| # Must have at least one preprocessor construct | |
| ch_constructs = [ | |
| "#XCOMMAND", "#XTRANSLATE", "#COMMAND", "#TRANSLATE", | |
| "#DEFINE", "#UNDEF", "#IFDEF", "#IFNDEF", "#IF ", | |
| "#INCLUDE", "#PRAGMA", "#ENDPROC", | |
| ] | |
| if not any(construct in upper for construct in ch_constructs): | |
| return False | |
| # Count actual definition lines | |
| lines = code.split("\n") | |
| def_lines = 0 | |
| for line in lines: | |
| stripped = line.strip() | |
| if not stripped: | |
| continue | |
| if stripped.startswith("//") or stripped.startswith("/*") or stripped.startswith("*"): | |
| continue | |
| if stripped.startswith("#"): | |
| def_lines += 1 | |
| return def_lines >= 3 | |
| def extract_ch_definitions(code: str) -> Dict: | |
| """Extract definitions from .ch file.""" | |
| defines = [] | |
| commands = [] | |
| translates = [] | |
| for line in code.split("\n"): | |
| stripped = line.strip() | |
| upper = stripped.upper() | |
| if upper.startswith("#DEFINE ") or upper.startswith("#UNDEF "): | |
| parts = stripped.split() | |
| if len(parts) >= 2: | |
| defines.append(parts[1]) | |
| elif upper.startswith("#XCOMMAND") or upper.startswith("#COMMAND"): | |
| commands.append(stripped[:60]) | |
| elif upper.startswith("#XTRANSLATE") or upper.startswith("#TRANSLATE"): | |
| translates.append(stripped[:60]) | |
| return { | |
| "defines": defines[:10], | |
| "commands": commands[:5], | |
| "translates": translates[:5] | |
| } | |
| def generate_ch_description(filepath: Path, code: str) -> str: | |
| """Generate description for .ch file.""" | |
| rel_path = filepath.relative_to(HARBOUR_ROOT) | |
| filename = filepath.stem | |
| desc_parts = [] | |
| # Determine location context | |
| if rel_path.parts[0] == "include": | |
| desc_parts.append(f"Harbour header file: {rel_path}") | |
| elif rel_path.parts[0] == "contrib": | |
| module = rel_path.parts[1] if len(rel_path.parts) > 1 else "" | |
| module_desc = MODULE_DESCRIPTIONS.get(module, "") | |
| if module_desc: | |
| desc_parts.append(f"Header file for contribution module '{module}' ({module_desc}): {rel_path}") | |
| else: | |
| desc_parts.append(f"Header file for contribution module '{module}': {rel_path}") | |
| else: | |
| desc_parts.append(f"Harbour header file: {rel_path}") | |
| # Extract and describe definitions | |
| defs = extract_ch_definitions(code) | |
| if defs["defines"]: | |
| if len(defs["defines"]) <= 5: | |
| desc_parts.append(f"Defines constants: {', '.join(defs['defines'])}") | |
| else: | |
| desc_parts.append(f"Defines {len(defs['defines'])} constants including: {', '.join(defs['defines'][:5])}") | |
| if defs["commands"]: | |
| desc_parts.append(f"Declares {len(defs['commands'])} preprocessor commands") | |
| if defs["translates"]: | |
| desc_parts.append(f"Declares {len(defs['translates'])} preprocessor translations") | |
| # Common header purposes | |
| upper = code.upper() | |
| if "ES_" in upper or "EG_" in upper or "ERROR" in filename.upper(): | |
| desc_parts.append("Defines error handling constants and codes") | |
| elif "INKEY" in upper or "K_" in upper: | |
| desc_parts.append("Defines keyboard input constants") | |
| elif "SET" in upper and "CH" in filename.upper(): | |
| desc_parts.append("Defines SET command options") | |
| elif "COLOR" in upper or "_SET_" in upper: | |
| desc_parts.append("Defines color and display constants") | |
| elif "HB_" in upper or "HBEXT" in upper: | |
| desc_parts.append("Defines Harbour internal constants and macros") | |
| elif "THREAD" in upper: | |
| desc_parts.append("Defines threading constants and macros") | |
| elif "FILE" in upper or "F_" in upper: | |
| desc_parts.append("Defines file I/O constants") | |
| elif "DB" in upper or "RDD" in upper: | |
| desc_parts.append("Defines database/RDD constants") | |
| elif "COM" in upper or "SERIAL" in upper: | |
| desc_parts.append("Defines communication constants") | |
| elif "GT" in upper: | |
| desc_parts.append("Defines graphics terminal constants") | |
| elif "BOX" in upper or "BORDER" in upper: | |
| desc_parts.append("Defines box and border drawing constants") | |
| elif "MEMO" in upper: | |
| desc_parts.append("Defines memo field constants") | |
| return ". ".join(desc_parts) | |
| def has_real_code(code: str) -> bool: | |
| """Check if code contains actual Harbour code (not just comments/includes).""" | |
| upper = code.upper() | |
| # Must have at least one of these code constructs | |
| code_constructs = [ | |
| "FUNCTION ", "PROCEDURE ", "CREATE CLASS", "ENDCLASS", | |
| "METHOD ", "RETURN ", "LOCAL ", "MEMVAR ", | |
| "THREAD STATIC", "IF ", "FOR ", "WHILE ", "DO CASE", | |
| "BEGIN SEQUENCE", "SWITCH ", "REQUEST ", | |
| "INIT PROCEDURE", "EXIT PROCEDURE", | |
| ] | |
| if not any(construct in upper for construct in code_constructs): | |
| return False | |
| # Count actual code lines (non-empty, non-preprocessor, non-blank) | |
| lines = code.split("\n") | |
| code_lines = 0 | |
| for line in lines: | |
| stripped = line.strip() | |
| if not stripped: | |
| continue | |
| if stripped.startswith("#"): | |
| continue | |
| if stripped.startswith("//") or stripped.startswith("/*") or stripped.startswith("*"): | |
| continue | |
| code_lines += 1 | |
| return code_lines >= 8 | |
| def is_code_complete(code: str) -> bool: | |
| """Check if code is complete (proper ENDCLASS, balanced structures).""" | |
| upper = code.upper() | |
| # Check class definitions have matching ENDCLASS | |
| class_count = upper.count("CREATE CLASS") | |
| endclass_count = upper.count("ENDCLASS") | |
| if class_count > 0 and endclass_count < class_count: | |
| return False | |
| # Check DO CASE has ENDDO CASE | |
| docase_count = upper.count("DO CASE") | |
| endcase_count = upper.count("ENDCASE") + upper.count("END CASE") | |
| if docase_count > 0 and endcase_count < docase_count: | |
| return False | |
| # Check FOR/NEXT balance | |
| for_count = len(re.findall(r'\bFOR\s+\w+', upper)) | |
| next_count = upper.count("\nNEXT") + (1 if upper.endswith("NEXT") else 0) | |
| # Be lenient - some code uses EXIT in loops | |
| # Check DO WHILE / ENDDO balance | |
| dowhile_count = upper.count("DO WHILE") | |
| enddo_count = upper.count("ENDDO") + upper.count("END DO") | |
| if dowhile_count > 0 and enddo_count < dowhile_count: | |
| return False | |
| # Check BEGIN SEQUENCE / END / RECOVER balance | |
| seq_count = upper.count("BEGIN SEQUENCE") | |
| end_count = upper.count("\nEND\n") + upper.count("\nEND ") + (1 if upper.endswith("\nEND") or upper.endswith(" END") else 0) | |
| # Check parentheses balance (lenient) | |
| open_p = code.count('(') | |
| close_p = code.count(')') | |
| if abs(open_p - close_p) > 3: | |
| return False | |
| # Check BEGIN/END blocks | |
| begin_count = len(re.findall(r'\bBEGIN\b', upper)) | |
| end_block_count = len(re.findall(r'\bEND\b', upper)) - upper.count("ENDCLASS") - upper.count("ENDCASE") - upper.count("END IF") - upper.count("ENDDO") | |
| # Very lenient check - just ensure it's not wildly unbalanced | |
| if begin_count > 0 and end_block_count > begin_count + 5: | |
| return False | |
| return True | |
| def extract_classes_and_functions(code: str) -> Dict: | |
| """Extract class and function definitions from code.""" | |
| classes = [] | |
| functions = [] | |
| procedures = [] | |
| for line in code.split("\n"): | |
| line_stripped = line.strip() | |
| upper = line_stripped.upper() | |
| # Class definitions | |
| if upper.startswith("CREATE CLASS"): | |
| parts = line_stripped.split() | |
| if len(parts) >= 3: | |
| class_name = parts[2] | |
| classes.append(class_name) | |
| # Function definitions | |
| if upper.startswith("FUNCTION ") or (upper.startswith("STATIC FUNCTION ")): | |
| parts = line_stripped.split() | |
| idx = 2 if upper.startswith("STATIC") else 1 | |
| if len(parts) >= idx + 1: | |
| func_name = parts[idx].split("(")[0] | |
| functions.append(func_name) | |
| # Procedure definitions | |
| if upper.startswith("PROCEDURE ") or upper.startswith("STATIC PROCEDURE "): | |
| parts = line_stripped.split() | |
| idx = 2 if upper.startswith("STATIC") else 1 | |
| if len(parts) >= idx + 1: | |
| proc_name = parts[idx].split("(")[0] | |
| procedures.append(proc_name) | |
| # INIT/EXIT procedures | |
| if upper.startswith("INIT PROCEDURE") or upper.startswith("EXIT PROCEDURE"): | |
| parts = line_stripped.split() | |
| if len(parts) >= 3: | |
| proc_name = parts[2].split("(")[0] | |
| procedures.append(proc_name) | |
| return { | |
| "classes": classes, | |
| "functions": functions, | |
| "procedures": procedures | |
| } | |
| def categorize_file(filepath: Path) -> Tuple[str, str]: | |
| """Categorize a PRG file into category and subcategory.""" | |
| rel_path = filepath.relative_to(HARBOUR_ROOT) | |
| parts = rel_path.parts | |
| if parts[0] == "src": | |
| if parts[1] == "rtl": | |
| return "rtl", categorize_rtl_file(filepath) | |
| elif parts[1] == "rdd": | |
| return "rdd", "rdd_core" | |
| elif parts[1] == "debug": | |
| return "rtl", "utility" | |
| else: | |
| return "rtl", "utility" | |
| elif parts[0] == "contrib": | |
| module = parts[1] if len(parts) > 1 else "unknown" | |
| return "contrib", categorize_contrib_module(module) | |
| elif parts[0] == "tests": | |
| return "tests", categorize_test_file(filepath) | |
| elif parts[0] == "utils": | |
| return "utils", categorize_utils_file(filepath) | |
| elif parts[0] == "extras": | |
| return "extras", categorize_extras_file(filepath) | |
| else: | |
| return "rtl", "utility" | |
| def categorize_rtl_file(filepath: Path) -> str: | |
| """Categorize RTL files into subcategories.""" | |
| name = filepath.stem.lower() | |
| if name.startswith("t") and not name.startswith("text"): | |
| if any(x in name for x in ["get", "browse", "column", "editor", "scalar", "object", "class"]): | |
| return "oop_class" | |
| elif any(x in name for x in ["menu", "popup", "topbar"]): | |
| return "ui_menu" | |
| elif any(x in name for x in ["check", "radio", "push", "list", "label", "button"]): | |
| return "ui_widget" | |
| elif any(x in name for x in ["edit", "memo"]): | |
| return "text_edit" | |
| elif any(x in name for x in ["persist", "profile", "symbol"]): | |
| return "oop_class" | |
| else: | |
| return "oop_class" | |
| elif "get" in name or "read" in name: | |
| return "get_system" | |
| elif any(x in name for x in ["err", "alert"]): | |
| return "error_handling" | |
| elif any(x in name for x in ["file", "dir", "ini", "type"]): | |
| return "file_io" | |
| elif any(x in name for x in ["db", "memo"]): | |
| return "database" | |
| else: | |
| return "utility" | |
| def categorize_contrib_module(module: str) -> str: | |
| """Categorize contrib modules.""" | |
| db_modules = {"hbpgsql", "hbmysql", "hbsqlit3", "hbodbc", "hbfbird", "rddsql", "rddpg", | |
| "rddmy", "rddfb", "rddads", "rddbm", "rddmisc", "sddpg", "sddmy", | |
| "sddoci", "sddodbc", "sddsqlt3", "sddfb", "rddado"} | |
| net_modules = {"hbtip", "hbcurl", "hbhttpd", "hbnetio", "hbcomio", "hbtcpio", "hbpipeio"} | |
| sec_modules = {"hbssl", "hbmagic"} | |
| gfx_modules = {"hbbmp", "hbcairo", "hbhpdf", "hbgd", "hbzebra", "hbfimage", "hbformat"} | |
| fmt_modules = {"hbexpat", "hbmxml", "hbfoxpro", "hbplist", "hbmemio"} | |
| plat_modules = {"hbwin", "hbunix", "hboslib", "gtalleg", "gtwvg", "gtwvw", "gtwvb"} | |
| compat_modules = {"hbnf", "hbct", "xhb", "hbxpp", "hbtpathy", "hbfship"} | |
| if module in db_modules: | |
| return "database" | |
| elif module in net_modules: | |
| return "network" | |
| elif module in sec_modules: | |
| return "security" | |
| elif module in gfx_modules: | |
| return "graphics" | |
| elif module in fmt_modules: | |
| return "data_format" | |
| elif module in plat_modules: | |
| return "platform" | |
| elif module in compat_modules: | |
| return "compatibility" | |
| else: | |
| return "utility" | |
| def categorize_test_file(filepath: Path) -> str: | |
| """Categorize test files.""" | |
| name = filepath.stem.lower() | |
| if any(x in name for x in ["class", "oob", "inherit", "scope", "data"]): | |
| return "oop" | |
| elif any(x in name for x in ["db", "rdd", "browse"]): | |
| return "database" | |
| elif any(x in name for x in ["speed", "bench"]): | |
| return "performance" | |
| elif any(x in name for x in ["str", "math", "date", "array", "for", "while", "if", "case", | |
| "static", "mem", "gt", "regex", "file", "err", "hello"]): | |
| return "language_basics" | |
| else: | |
| return "function_api" | |
| def categorize_utils_file(filepath: Path) -> str: | |
| """Categorize utility files.""" | |
| name = filepath.stem.lower() | |
| if "hbmk" in name or "build" in name: | |
| return "build_system" | |
| elif "test" in name or "rt_" in name: | |
| return "test_framework" | |
| elif "i18n" in name or "lang" in name: | |
| return "i18n" | |
| else: | |
| return "build_system" | |
| def categorize_extras_file(filepath: Path) -> str: | |
| """Categorize extras files.""" | |
| parts = filepath.relative_to(HARBOUR_ROOT).parts | |
| if len(parts) > 1: | |
| module = parts[1].lower() | |
| if "pdf" in module or "vpdf" in module: | |
| return "pdf" | |
| elif "xls" in module or "excel" in module: | |
| return "spreadsheet" | |
| elif "srv" in module or "http" in module: | |
| return "server" | |
| return "utility" | |
| def generate_description(filepath: Path, code: str, category: str, subcategory: str) -> str: | |
| """Generate a comprehensive description for a PRG file.""" | |
| rel_path = filepath.relative_to(HARBOUR_ROOT) | |
| module_name = rel_path.parts[1] if len(rel_path.parts) > 1 else "rtl" | |
| # Get module description if contrib | |
| module_desc = "" | |
| if category == "contrib" and module_name in MODULE_DESCRIPTIONS: | |
| module_desc = MODULE_DESCRIPTIONS[module_name] | |
| # Extract code elements | |
| elements = extract_classes_and_functions(code) | |
| # Generate description based on category and content | |
| desc_parts = [] | |
| # File location context | |
| if category == "rtl": | |
| desc_parts.append(f"Harbour Runtime Library file: {rel_path}") | |
| elif category == "contrib": | |
| desc_parts.append(f"Harbour contribution module '{module_name}' ({module_desc}): {rel_path}") | |
| elif category == "tests": | |
| desc_parts.append(f"Harbour test program: {rel_path}") | |
| elif category == "utils": | |
| desc_parts.append(f"Harbour utility program: {rel_path}") | |
| elif category == "extras": | |
| desc_parts.append(f"Harbour extra library: {rel_path}") | |
| else: | |
| desc_parts.append(f"Harbour source file: {rel_path}") | |
| # Add code structure information | |
| if elements["classes"]: | |
| desc_parts.append(f"Defines classes: {', '.join(elements['classes'][:5])}") | |
| if elements["functions"]: | |
| if len(elements["functions"]) <= 5: | |
| desc_parts.append(f"Provides functions: {', '.join(elements['functions'])}") | |
| else: | |
| desc_parts.append(f"Provides {len(elements['functions'])} functions including: {', '.join(elements['functions'][:5])}") | |
| if elements["procedures"]: | |
| if len(elements["procedures"]) <= 3: | |
| desc_parts.append(f"Contains procedures: {', '.join(elements['procedures'])}") | |
| else: | |
| desc_parts.append(f"Contains {len(elements['procedures'])} procedures") | |
| # Add subcategory context | |
| subcategory_descriptions = { | |
| "oop_class": "This file implements object-oriented classes using Harbour's class system", | |
| "ui_widget": "This file defines UI widget classes for graphical interfaces", | |
| "ui_menu": "This file implements menu system classes", | |
| "text_edit": "This file provides text editing functionality", | |
| "get_system": "This file implements the GET system for input field handling", | |
| "scalar_type": "This file defines scalar type wrapper classes", | |
| "error_handling": "This file implements error handling and reporting", | |
| "file_io": "This file provides file I/O operations", | |
| "database": "This file handles database operations", | |
| "utility": "This file provides utility functions", | |
| "rdd_core": "This file implements core Record Driver Driver functionality", | |
| "rdd_driver": "This file implements a database driver", | |
| "network": "This file provides network protocol implementations", | |
| "security": "This file implements security and encryption functions", | |
| "graphics": "This file provides graphics and image processing capabilities", | |
| "data_format": "This file handles data format parsing and generation", | |
| "platform": "This file provides platform-specific functionality", | |
| "compatibility": "This file provides legacy compatibility functions", | |
| "language_basics": "This test file exercises basic Harbour language features", | |
| "function_api": "This test file tests specific function APIs", | |
| "oop": "This test file tests object-oriented programming features", | |
| "performance": "This test file benchmarks performance characteristics", | |
| "build_system": "This file is part of the build system tooling", | |
| "test_framework": "This file is part of the test framework", | |
| "i18n": "This file provides internationalization support", | |
| "pdf": "This file provides PDF generation capabilities", | |
| "spreadsheet": "This file provides spreadsheet generation capabilities", | |
| "server": "This file implements server functionality", | |
| } | |
| if subcategory in subcategory_descriptions: | |
| desc_parts.append(subcategory_descriptions[subcategory]) | |
| return ". ".join(desc_parts) | |
| def create_training_entry(filepath: Path, code: str, description: str) -> Dict: | |
| """Create a training entry in the instruction format.""" | |
| return { | |
| "instruction": f"Write Harbour (xBase/Clipper) code for: {description}", | |
| "input": "", | |
| "output": code, | |
| "metadata": { | |
| "file_path": str(filepath.relative_to(HARBOUR_ROOT)), | |
| "language": "harbour", | |
| "description": description | |
| } | |
| } | |
| def create_completion_entry(filepath: Path, code: str, description: str) -> Dict: | |
| """Create a completion-style training entry with diverse instructions.""" | |
| # Generate diverse user prompts based on code content | |
| import random | |
| random.seed(hash(filepath)) # Deterministic per file | |
| elements = extract_classes_and_functions(code) | |
| upper = code.upper() | |
| # Different prompt templates based on content | |
| templates = [] | |
| if elements["classes"]: | |
| templates.append(f"Implement the following Harbour classes: {', '.join(elements['classes'][:3])}. {description}") | |
| templates.append(f"Create Harbour OOP classes for the functionality described: {description}") | |
| if elements["functions"]: | |
| templates.append(f"Write Harbour functions: {', '.join(elements['functions'][:3])}. {description}") | |
| templates.append(f"Implement these Harbour functions: {description}") | |
| if elements["procedures"]: | |
| templates.append(f"Write a Harbour program with procedures: {', '.join(elements['procedures'][:3])}. {description}") | |
| if "#DEFINE" in upper or "#XCOMMAND" in upper: | |
| templates.append(f"Create Harbour preprocessor definitions: {description}") | |
| templates.append(f"Define Harbour macros and constants: {description}") | |
| # General templates | |
| templates.append(f"Write the following Harbour (xBase/Clipper) code:\n\n{description}") | |
| templates.append(f"Implement this Harbour module: {description}") | |
| templates.append(f"Here is a Harbour (xBase/Clipper) implementation:\n\n{description}") | |
| templates.append(f"Generate Harbour code for: {description}") | |
| # Select a random template | |
| user_prompt = random.choice(templates) | |
| return { | |
| "messages": [ | |
| { | |
| "role": "system", | |
| "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)." | |
| }, | |
| { | |
| "role": "user", | |
| "content": user_prompt | |
| }, | |
| { | |
| "role": "assistant", | |
| "content": code | |
| } | |
| ], | |
| "metadata": { | |
| "file_path": str(filepath.relative_to(HARBOUR_ROOT)), | |
| "language": "harbour", | |
| "description": description | |
| } | |
| } | |
| def process_prg_file(filepath: Path) -> List[Dict]: | |
| """Process a single PRG file and generate training entries.""" | |
| try: | |
| with open(filepath, 'r', encoding='utf-8', errors='ignore') as f: | |
| code = f.read() | |
| except Exception as e: | |
| print(f"Error reading {filepath}: {e}") | |
| return [] | |
| # Skip empty files or very small files | |
| if len(code.strip()) < 50: | |
| return [] | |
| # Step 1: Remove ALL comments (block, single-line, star-prefixed, inline) | |
| code = remove_all_comments(code) | |
| # Step 2: Remove disabled code blocks (#if 0) | |
| code = remove_disabled_code(code) | |
| # Step 3: Clean excessive blank lines | |
| code = clean_excessive_blank_lines(code) | |
| # Step 5: Strip leading/trailing whitespace | |
| code = code.strip() | |
| # Skip if no real code remains | |
| if not has_real_code(code): | |
| return [] | |
| # Skip if code is too short | |
| if len(code) < MIN_CODE_LENGTH: | |
| return [] | |
| # Skip if code is incomplete | |
| if not is_code_complete(code): | |
| return [] | |
| # Truncate if too long | |
| if len(code) > MAX_CODE_LENGTH: | |
| lines = code.split("\n") | |
| truncated_lines = [] | |
| current_length = 0 | |
| for line in lines: | |
| if current_length + len(line) > MAX_CODE_LENGTH: | |
| break | |
| truncated_lines.append(line) | |
| current_length += len(line) + 1 | |
| code = "\n".join(truncated_lines) | |
| # Categorize the file | |
| category, subcategory = categorize_file(filepath) | |
| # Generate description | |
| description = generate_description(filepath, code, category, subcategory) | |
| # Create training entry (chat format only - preferred for Qwen2.5-Coder) | |
| entry = create_completion_entry(filepath, code, description) | |
| entry["metadata"]["category"] = category | |
| entry["metadata"]["subcategory"] = subcategory | |
| return [entry] | |
| def process_ch_file(filepath: Path) -> List[Dict]: | |
| """Process a single CH file and generate training entries.""" | |
| try: | |
| with open(filepath, 'r', encoding='utf-8', errors='ignore') as f: | |
| code = f.read() | |
| except Exception as e: | |
| print(f"Error reading {filepath}: {e}") | |
| return [] | |
| # Skip empty files | |
| if len(code.strip()) < 30: | |
| return [] | |
| # Step 1: Remove ALL comments | |
| code = remove_all_comments(code) | |
| # Step 2: Remove disabled code blocks (#if 0) | |
| code = remove_disabled_code(code) | |
| # Step 3: Clean excessive blank lines | |
| code = clean_excessive_blank_lines(code) | |
| # Step 4: Strip leading/trailing whitespace | |
| code = code.strip() | |
| # Skip if no real code remains | |
| if not has_real_ch_code(code): | |
| return [] | |
| # Skip if code is too short | |
| if len(code) < MIN_CODE_LENGTH: | |
| return [] | |
| # Truncate if too long | |
| if len(code) > MAX_CODE_LENGTH: | |
| lines = code.split("\n") | |
| truncated_lines = [] | |
| current_length = 0 | |
| for line in lines: | |
| if current_length + len(line) > MAX_CODE_LENGTH: | |
| break | |
| truncated_lines.append(line) | |
| current_length += len(line) + 1 | |
| code = "\n".join(truncated_lines) | |
| # Generate description | |
| description = generate_ch_description(filepath, code) | |
| # Determine category | |
| rel_path = filepath.relative_to(HARBOUR_ROOT) | |
| parts = rel_path.parts | |
| if parts[0] == "include": | |
| category = "include" | |
| elif parts[0] == "contrib": | |
| category = "contrib" | |
| elif parts[0] == "utils": | |
| category = "utils" | |
| elif parts[0] == "extras": | |
| category = "extras" | |
| else: | |
| category = "include" | |
| # Create training entry (chat format only) | |
| entry = create_completion_entry(filepath, code, description) | |
| entry["metadata"]["category"] = category | |
| entry["metadata"]["subcategory"] = "header" | |
| return [entry] | |
| def main(): | |
| """Main function to generate the dataset.""" | |
| print("=" * 60) | |
| print("Harbour PRG/CH Dataset Generator (CLEANED)") | |
| print("=" * 60) | |
| # Find all PRG and CH files | |
| print("\n1. Finding all Harbour source files...") | |
| prg_files = list(HARBOUR_ROOT.rglob("*.prg")) | |
| ch_files = list(HARBOUR_ROOT.rglob("*.ch")) | |
| print(f" Found {len(prg_files)} PRG files") | |
| print(f" Found {len(ch_files)} CH files") | |
| # Process PRG files | |
| print("\n2. Processing PRG files...") | |
| all_entries = [] | |
| category_counts = {} | |
| skipped_files = 0 | |
| for i, filepath in enumerate(prg_files, 1): | |
| if i % 100 == 0: | |
| print(f" Processing PRG file {i}/{len(prg_files)}...") | |
| entries = process_prg_file(filepath) | |
| if entries: | |
| all_entries.extend(entries) | |
| category = entries[0]["metadata"]["category"] | |
| category_counts[category] = category_counts.get(category, 0) + 1 | |
| else: | |
| skipped_files += 1 | |
| print(f"\n PRG: Generated {len(all_entries)} entries, skipped {skipped_files} files") | |
| # Process CH files | |
| print("\n3. Processing CH files...") | |
| ch_entries = 0 | |
| ch_skipped = 0 | |
| for i, filepath in enumerate(ch_files, 1): | |
| if i % 20 == 0: | |
| print(f" Processing CH file {i}/{len(ch_files)}...") | |
| entries = process_ch_file(filepath) | |
| if entries: | |
| all_entries.extend(entries) | |
| ch_entries += 1 | |
| category = entries[0]["metadata"]["category"] | |
| category_counts[category] = category_counts.get(category, 0) + 1 | |
| else: | |
| ch_skipped += 1 | |
| print(f"\n CH: Generated {ch_entries} file entries, skipped {ch_skipped} files") | |
| print(f"\n Total: {len(all_entries)} training entries") | |
| # Print category statistics | |
| print("\n3. Category statistics:") | |
| for category, count in sorted(category_counts.items()): | |
| print(f" {category}: {count} files") | |
| # Shuffle entries | |
| random.seed(42) | |
| random.shuffle(all_entries) | |
| # Split into train and validation | |
| print("\n4. Splitting into train/validation sets...") | |
| split_idx = int(len(all_entries) * TRAIN_RATIO) | |
| train_entries = all_entries[:split_idx] | |
| val_entries = all_entries[split_idx:] | |
| print(f" Training set: {len(train_entries)} entries") | |
| print(f" Validation set: {len(val_entries)} entries") | |
| # Save datasets | |
| print("\n5. Saving datasets...") | |
| # Save as JSONL (instruction format) | |
| train_jsonl_path = OUTPUT_DIR / "harbour_train.jsonl" | |
| val_jsonl_path = OUTPUT_DIR / "harbour_val.jsonl" | |
| with open(train_jsonl_path, 'w', encoding='utf-8') as f: | |
| for entry in train_entries: | |
| train_entry = {k: v for k, v in entry.items() if k != "metadata"} | |
| f.write(json.dumps(train_entry, ensure_ascii=False) + "\n") | |
| with open(val_jsonl_path, 'w', encoding='utf-8') as f: | |
| for entry in val_entries: | |
| val_entry = {k: v for k, v in entry.items() if k != "metadata"} | |
| f.write(json.dumps(val_entry, ensure_ascii=False) + "\n") | |
| print(f" Saved training JSONL: {train_jsonl_path}") | |
| print(f" Saved validation JSONL: {val_jsonl_path}") | |
| # Save full dataset with metadata | |
| full_dataset_path = OUTPUT_DIR / "harbour_dataset_full.jsonl" | |
| with open(full_dataset_path, 'w', encoding='utf-8') as f: | |
| for entry in all_entries: | |
| f.write(json.dumps(entry, ensure_ascii=False) + "\n") | |
| print(f" Saved full dataset with metadata: {full_dataset_path}") | |
| # Generate statistics file | |
| total_files = len(prg_files) + len(ch_files) | |
| stats = { | |
| "total_prg_files": len(prg_files), | |
| "total_ch_files": len(ch_files), | |
| "total_files": total_files, | |
| "total_entries": len(all_entries), | |
| "skipped_prg": skipped_files, | |
| "skipped_ch": ch_skipped, | |
| "train_entries": len(train_entries), | |
| "val_entries": len(val_entries), | |
| "categories": category_counts, | |
| "files_per_category": {} | |
| } | |
| for entry in all_entries: | |
| cat = entry["metadata"]["category"] | |
| subcat = entry["metadata"]["subcategory"] | |
| if cat not in stats["files_per_category"]: | |
| stats["files_per_category"][cat] = {} | |
| stats["files_per_category"][cat][subcat] = stats["files_per_category"][cat].get(subcat, 0) + 1 | |
| stats_path = OUTPUT_DIR / "dataset_stats.json" | |
| with open(stats_path, 'w', encoding='utf-8') as f: | |
| json.dump(stats, f, indent=2, ensure_ascii=False) | |
| print(f" Saved statistics: {stats_path}") | |
| # Generate README | |
| readme_content = f"""# Harbour Fine-tuning Dataset | |
| ## Overview | |
| This dataset contains {len(all_entries)} training entries extracted from: | |
| - {len(prg_files)} Harbour PRG (.prg) source files | |
| - {len(ch_files)} Harbour Header (.ch) files | |
| {skipped_files} PRG files and {ch_skipped} CH files were skipped due to quality issues. | |
| ## Dataset Format | |
| The dataset is provided in JSONL format with the following structure: | |
| ### Instruction Format (harbour_train.jsonl / harbour_val.jsonl) | |
| ```json | |
| {{"instruction": "...", "input": "", "output": "..."}} | |
| ``` | |
| ### Full Dataset (harbour_dataset_full.jsonl) | |
| ```json | |
| {{"instruction": "...", "input": "", "output": "...", "metadata": {{"file_path": "...", "language": "harbour", "category": "...", "subcategory": "..."}}}} | |
| ``` | |
| ## Categories | |
| - **include**: Header files with constants/macros ({category_counts.get('include', 0)} files) | |
| - **rtl**: Harbour Runtime Library ({category_counts.get('rtl', 0)} files) | |
| - **contrib**: Contribution libraries ({category_counts.get('contrib', 0)} files) | |
| - **tests**: Test programs ({category_counts.get('tests', 0)} files) | |
| - **utils**: Utility programs ({category_counts.get('utils', 0)} files) | |
| - **extras**: Extra libraries ({category_counts.get('extras', 0)} files) | |
| ## Cleaning Applied | |
| - Copyright/license headers removed | |
| - Disabled code blocks (#if 0) removed | |
| - Excessive trailing comments removed | |
| - Excessive blank lines removed | |
| - Files without actual code filtered out | |
| - Incomplete code (missing ENDCLASS, etc.) filtered out | |
| ## Usage for Fine-tuning | |
| ```bash | |
| # Using Ollama with Modelfile | |
| FROM qwen2.5-coder:14b | |
| # Training command | |
| ollama create harbour-coder -f Modelfile | |
| # Or use with other training frameworks | |
| # The JSONL format is compatible with: | |
| # - OpenAI fine-tuning API | |
| # - Hugging Face transformers | |
| # - Axolotl | |
| # - LLaMA-Factory | |
| ``` | |
| ## File Structure | |
| - `harbour_train.jsonl` - Training set ({len(train_entries)} entries) | |
| - `harbour_val.jsonl` - Validation set ({len(val_entries)} entries) | |
| - `harbour_dataset_full.jsonl` - Full dataset with metadata | |
| - `dataset_stats.json` - Dataset statistics | |
| - `generate_dataset.py` - This script | |
| ## Source | |
| The source files are from the Harbour project (https://harbour.github.io/), | |
| an open-source Clipper-compatible compiler. | |
| """ | |
| readme_path = OUTPUT_DIR / "README.md" | |
| with open(readme_path, 'w', encoding='utf-8') as f: | |
| f.write(readme_content) | |
| print(f" Saved README: {readme_path}") | |
| print("\n" + "=" * 60) | |
| print("Dataset generation complete!") | |
| print("=" * 60) | |
| if __name__ == "__main__": | |
| main() | |