Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:29:15 +08:00
commit be476a3fea
76 changed files with 12812 additions and 0 deletions

View File

@@ -0,0 +1,114 @@
# Ontological Documentation Scripts
This directory contains utility scripts for automated concept extraction and diagram generation.
## Scripts
### extract_concepts.py
Analyzes codebases to extract domain concepts, entities, and relationships for building ontological documentation.
**Features:**
- Supports Python and JavaScript/TypeScript
- Extracts classes, functions, inheritance relationships, and imports
- Builds ontological structure with relationships (is_a, part_of, depends_on, associates_with)
- Generates JSON ontology representation
- Creates Mermaid diagrams
**Usage:**
```bash
# Analyze a single file
python extract_concepts.py /path/to/file.py
# Analyze a directory
python extract_concepts.py /path/to/codebase
# Save output to file
python extract_concepts.py /path/to/codebase > ontology.json
```
**Output:**
- JSON ontology structure
- Mermaid diagram representation
### generate_ontology_diagram.py
Generates visual representations of ontologies in various formats.
**Features:**
- Supports multiple diagram formats (Mermaid, PlantUML, GraphViz DOT, JSON-LD)
- Customizable relationship symbols
- Semantic web compatibility (JSON-LD output)
- Styled diagrams with concept type differentiation
**Usage:**
```bash
# Generate all formats
python generate_ontology_diagram.py ontology.json
# Generate specific format
python generate_ontology_diagram.py ontology.json --format mermaid
# Specify output directory
python generate_ontology_diagram.py ontology.json --output ./diagrams
# Available formats: mermaid, plantuml, dot, json-ld, all
```
**Output Examples:**
Mermaid:
```bash
python generate_ontology_diagram.py ontology.json --format mermaid
# Creates: ontology_mermaid.md
```
PlantUML:
```bash
python generate_ontology_diagram.py ontology.json --format plantuml
# Creates: ontology_plantuml.puml
```
GraphViz DOT:
```bash
python generate_ontology_diagram.py ontology.json --format dot
# Creates: ontology.dot
```
JSON-LD:
```bash
python generate_ontology_diagram.py ontology.json --format json-ld
# Creates: ontology_jsonld.json
```
## Requirements
Both scripts use only Python standard library modules:
- `ast` - Python AST parsing
- `re` - Regular expressions
- `json` - JSON processing
- `pathlib` - Path handling
- `argparse` - Command-line argument parsing
No additional dependencies required!
## Example Workflow
```bash
# 1. Extract concepts from codebase
python extract_concepts.py /path/to/codebase > ontology.json
# 2. Generate diagrams
python generate_ontology_diagram.py ontology.json --output ./docs/diagrams
# 3. Review generated documentation
ls ./docs/diagrams/
# ontology_mermaid.md
# ontology_plantuml.puml
# ontology.dot
# ontology_jsonld.json
```
## Integration with Skill
These scripts support the main [SKILL.md](../SKILL.md) by providing automated tools for concept extraction and visualization. Use them to bootstrap your ontological documentation process.

View File

@@ -0,0 +1,259 @@
#!/usr/bin/env python3
"""
Concept Extraction Script for Ontological Documentation
This script analyzes codebases to extract domain concepts, entities, and relationships
for building ontological documentation. It supports multiple programming languages
and can identify inheritance hierarchies, composition patterns, and semantic relationships.
"""
import ast
import re
import json
import sys
from pathlib import Path
from typing import Dict, List, Set, Tuple, Any
from collections import defaultdict
class ConceptExtractor:
"""Extracts ontological concepts from source code."""
def __init__(self):
self.concepts = defaultdict(dict)
self.relationships = defaultdict(list)
self.taxonomies = defaultdict(list)
def extract_from_python(self, file_path: Path) -> Dict[str, Any]:
"""Extract concepts from Python source code."""
try:
with open(file_path, 'r', encoding='utf-8') as f:
tree = ast.parse(f.read())
visitor = ClassVisitor()
visitor.visit(tree)
return {
'classes': visitor.classes,
'inheritance': visitor.inheritance,
'imports': visitor.imports,
'functions': visitor.functions
}
except Exception as e:
return {'error': str(e)}
def extract_from_javascript(self, file_path: Path) -> Dict[str, Any]:
"""Extract concepts from JavaScript/TypeScript source code."""
concepts = {
'classes': [],
'interfaces': [],
'functions': [],
'imports': []
}
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Extract class declarations
class_pattern = r'(?:class|interface)\s+(\w+)(?:\s+extends\s+(\w+))?'
for match in re.finditer(class_pattern, content):
class_name = match.group(1)
parent_class = match.group(2)
concepts['classes'].append({
'name': class_name,
'parent': parent_class,
'type': 'class' if 'class' in match.group(0) else 'interface'
})
# Extract function declarations
func_pattern = r'(?:function\s+(\w+)|const\s+(\w+)\s*=\s*(?:\([^)]*\)\s*)?=>|(\w+)\s*:\s*\([^)]*\)\s*=>)'
for match in re.finditer(func_pattern, content):
func_name = match.group(1) or match.group(2) or match.group(3)
if func_name:
concepts['functions'].append({'name': func_name})
# Extract imports
import_pattern = r'import\s+.*?from\s+["\']([^"\']+)["\']'
for match in re.finditer(import_pattern, content):
concepts['imports'].append({'source': match.group(1)})
except Exception as e:
concepts['error'] = str(e)
return concepts
def build_ontology(self, extracted_data: List[Dict[str, Any]]) -> Dict[str, Any]:
"""Build ontological structure from extracted data."""
ontology = {
'concepts': {},
'relationships': {
'is_a': [], # inheritance
'part_of': [], # composition
'depends_on': [], # dependencies
'associates_with': [] # loose associations
},
'taxonomies': {}
}
all_classes = []
all_functions = []
all_imports = []
# Collect all entities
for data in extracted_data:
if 'classes' in data:
all_classes.extend(data['classes'])
if 'functions' in data:
all_functions.extend(data['functions'])
if 'imports' in data:
all_imports.extend(data['imports'])
# Build concepts
for cls in all_classes:
if isinstance(cls, dict):
concept_name = cls.get('name', str(cls))
ontology['concepts'][concept_name] = {
'type': 'class',
'properties': cls
}
# Build inheritance relationships
parent = cls.get('parent')
if parent:
ontology['relationships']['is_a'].append({
'subject': concept_name,
'object': parent
})
for func in all_functions:
if isinstance(func, dict):
func_name = func.get('name', str(func))
ontology['concepts'][func_name] = {
'type': 'function',
'properties': func
}
return ontology
def generate_mermaid_diagram(self, ontology: Dict[str, Any]) -> str:
"""Generate Mermaid diagram from ontology."""
lines = ["graph TD"]
# Add concepts as nodes
for concept_name, concept_data in ontology['concepts'].items():
concept_type = concept_data.get('type', 'concept')
if concept_type == 'class':
lines.append(f" {concept_name}[({concept_name})]")
else:
lines.append(f" {concept_name}[{concept_name}]")
# Add relationships
for rel_type, relationships in ontology['relationships'].items():
for rel in relationships:
subject = rel['subject']
obj = rel['object']
if rel_type == 'is_a':
lines.append(f" {subject} --|> {obj}")
elif rel_type == 'part_of':
lines.append(f" {subject} --* {obj}")
elif rel_type == 'depends_on':
lines.append(f" {subject} -.-> {obj}")
else:
lines.append(f" {subject} --- {obj}")
return "\n".join(lines)
class ClassVisitor(ast.NodeVisitor):
"""AST visitor for Python class analysis."""
def __init__(self):
self.classes = []
self.inheritance = []
self.imports = []
self.functions = []
def visit_ClassDef(self, node):
class_info = {
'name': node.name,
'bases': [base.id for base in node.bases if hasattr(base, 'id')],
'methods': [],
'line_number': node.lineno
}
for item in node.body:
if isinstance(item, ast.FunctionDef):
class_info['methods'].append({
'name': item.name,
'args': [arg.arg for arg in item.args.args],
'line_number': item.lineno
})
self.classes.append(class_info)
# Track inheritance
for base in node.bases:
if hasattr(base, 'id'):
self.inheritance.append({
'child': node.name,
'parent': base.id
})
self.generic_visit(node)
def visit_Import(self, node):
for alias in node.names:
self.imports.append({
'module': alias.name,
'alias': alias.asname
})
self.generic_visit(node)
def visit_FunctionDef(self, node):
func_info = {
'name': node.name,
'args': [arg.arg for arg in node.args.args],
'line_number': node.lineno
}
self.functions.append(func_info)
self.generic_visit(node)
def main():
"""Main function to run concept extraction."""
if len(sys.argv) < 2:
print("Usage: python extract_concepts.py <file_or_directory_path>")
sys.exit(1)
path = Path(sys.argv[1])
extractor = ConceptExtractor()
extracted_data = []
if path.is_file():
if path.suffix == '.py':
data = extractor.extract_from_python(path)
extracted_data.append(data)
elif path.suffix in ['.js', '.ts', '.jsx', '.tsx']:
data = extractor.extract_from_javascript(path)
extracted_data.append(data)
elif path.is_dir():
for file_path in path.rglob('*'):
if file_path.is_file():
if file_path.suffix == '.py':
data = extractor.extract_from_python(file_path)
extracted_data.append(data)
elif file_path.suffix in ['.js', '.ts', '.jsx', '.tsx']:
data = extractor.extract_from_javascript(file_path)
extracted_data.append(data)
ontology = extractor.build_ontology(extracted_data)
# Output as JSON
print(json.dumps(ontology, indent=2))
# Also generate Mermaid diagram
diagram = extractor.generate_mermaid_diagram(ontology)
print("\n--- Mermaid Diagram ---")
print(diagram)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,288 @@
#!/usr/bin/env python3
"""
Ontology Diagram Generator
This script generates visual representations of ontologies in various formats
including Mermaid, PlantUML, GraphViz DOT, and JSON-LD for semantic web applications.
"""
import json
import sys
import argparse
import re
from pathlib import Path
from typing import Dict, List, Any, Optional
class OntologyDiagramGenerator:
"""Generates diagrams for ontological documentation."""
def __init__(self):
self.relationship_symbols = {
'is_a': '--|>', # Inheritance
'part_of': '--*', # Composition
'depends_on': '-.->', # Dependency
'associates_with': '---', # Association
'instance_of': '..>' # Instantiation
}
def generate_mermaid(self, ontology: Dict[str, Any]) -> str:
"""Generate Mermaid diagram from ontology."""
lines = ["graph TD"]
lines.append(" %% Ontology Diagram")
lines.append(" %% Generated from ontological documentation")
# Add styling
lines.extend([
" classDef concept fill:#e1f5fe,stroke:#01579b,stroke-width:2px",
" classDef class fill:#f3e5f5,stroke:#4a148c,stroke-width:2px",
" classDef interface fill:#e8f5e8,stroke:#1b5e20,stroke-width:2px",
" classDef function fill:#fff3e0,stroke:#e65100,stroke-width:2px"
])
# Add concepts as nodes
concept_classes = {}
for concept_name, concept_data in ontology['concepts'].items():
concept_type = concept_data.get('type', 'concept')
if concept_type == 'class':
lines.append(f" {self._safe_name(concept_name)}[({concept_name})]")
concept_classes[concept_name] = 'class'
elif concept_type == 'interface':
lines.append(f" {self._safe_name(concept_name)}[{{interface}}{concept_name}]")
concept_classes[concept_name] = 'interface'
elif concept_type == 'function':
lines.append(f" {self._safe_name(concept_name)}[{concept_name}()]")
concept_classes[concept_name] = 'function'
else:
lines.append(f" {self._safe_name(concept_name)}[{concept_name}]")
concept_classes[concept_name] = 'concept'
# Add relationships
for rel_type, relationships in ontology['relationships'].items():
symbol = self.relationship_symbols.get(rel_type, '---')
for rel in relationships:
subject = self._safe_name(rel['subject'])
obj = self._safe_name(rel['object'])
label = rel.get('label', rel_type.replace('_', ' ').title())
lines.append(f" {subject} {symbol} {obj}")
# Apply classes
for concept_name, css_class in concept_classes.items():
lines.append(f" class {self._safe_name(concept_name)} {css_class}")
return "\n".join(lines)
def generate_plantuml(self, ontology: Dict[str, Any]) -> str:
"""Generate PlantUML diagram from ontology."""
lines = ["@startuml Ontology"]
lines.append("!theme plain")
lines.append("skinparam classAttributeIconSize 0")
# Add concepts as classes
for concept_name, concept_data in ontology['concepts'].items():
concept_type = concept_data.get('type', 'concept')
if concept_type == 'class':
lines.append(f"class {concept_name} {{}}")
elif concept_type == 'interface':
lines.append(f"interface {concept_name} {{}}")
elif concept_type == 'function':
lines.append(f"object {concept_name} {{}}")
else:
lines.append(f"abstract {concept_name} {{}}")
lines.append("") # Empty line for separation
# Add relationships
for rel_type, relationships in ontology['relationships'].items():
for rel in relationships:
subject = rel['subject']
obj = rel['object']
if rel_type == 'is_a':
lines.append(f"{subject} <|-- {obj}")
elif rel_type == 'part_of':
lines.append(f"{subject} *-- {obj}")
elif rel_type == 'depends_on':
lines.append(f"{subject} ..> {obj}")
else:
lines.append(f"{subject} -- {obj}")
lines.append("@enduml")
return "\n".join(lines)
def generate_dot(self, ontology: Dict[str, Any]) -> str:
"""Generate GraphViz DOT diagram from ontology."""
lines = ["digraph Ontology {"]
lines.append(' rankdir=TB;')
lines.append(' node [shape=box, style=filled];')
lines.append(' edge [fontsize=10];')
# Add concepts as nodes
for concept_name, concept_data in ontology['concepts'].items():
concept_type = concept_data.get('type', 'concept')
# Set colors based on type
if concept_type == 'class':
color = "lightpurple"
elif concept_type == 'interface':
color = "lightgreen"
elif concept_type == 'function':
color = "lightorange"
else:
color = "lightblue"
lines.append(f' "{concept_name}" [label="{concept_name}", fillcolor="{color}"];')
lines.append("") # Empty line for separation
# Add relationships
for rel_type, relationships in ontology['relationships'].items():
for rel in relationships:
subject = rel['subject']
obj = rel['object']
label = rel.get('label', rel_type.replace('_', ' ').title())
# Set arrow styles based on relationship type
if rel_type == 'is_a':
arrow = 'empty'
elif rel_type == 'part_of':
arrow = 'diamond'
elif rel_type == 'depends_on':
arrow = 'dashed'
else:
arrow = 'normal'
lines.append(f' "{subject}" -> "{obj}" [label="{label}", arrowhead={arrow}];')
lines.append("}")
return "\n".join(lines)
def generate_json_ld(self, ontology: Dict[str, Any]) -> Dict[str, Any]:
"""Generate JSON-LD representation of ontology."""
context = {
"@context": {
"rdfs": "http://www.w3.org/2000/01/rdf-schema#",
"owl": "http://www.w3.org/2002/07/owl#",
"Concept": "rdfs:Class",
"subClassOf": {
"@id": "rdfs:subClassOf",
"@type": "@id"
},
"partOf": {
"@id": "http://example.org/ontology#partOf",
"@type": "@id"
},
"dependsOn": {
"@id": "http://example.org/ontology#dependsOn",
"@type": "@id"
}
},
"@graph": []
}
# Add concepts
for concept_name, concept_data in ontology['concepts'].items():
concept_entry = {
"@id": f"http://example.org/ontology#{concept_name}",
"@type": ["Concept"]
}
concept_type = concept_data.get('type', 'concept')
if concept_type == 'class':
concept_entry["@type"].append("owl:Class")
elif concept_type == 'interface':
concept_entry["@type"].append("owl:Class")
context["@graph"].append(concept_entry)
# Add relationships
for rel_type, relationships in ontology['relationships'].items():
for rel in relationships:
subject = rel['subject']
obj = rel['object']
if rel_type == 'is_a':
context["@graph"].append({
"@id": f"http://example.org/ontology#{subject}",
"subClassOf": f"http://example.org/ontology#{obj}"
})
elif rel_type == 'part_of':
context["@graph"].append({
"@id": f"http://example.org/ontology#{subject}",
"partOf": f"http://example.org/ontology#{obj}"
})
elif rel_type == 'depends_on':
context["@graph"].append({
"@id": f"http://example.org/ontology#{subject}",
"dependsOn": f"http://example.org/ontology#{obj}"
})
return context
def _safe_name(self, name: str) -> str:
"""Convert name to safe identifier for diagram formats."""
# Replace special characters and spaces with underscores
return re.sub(r'[^a-zA-Z0-9_]', '_', name)
def load_ontology(file_path: Path) -> Dict[str, Any]:
"""Load ontology from JSON file."""
with open(file_path, 'r', encoding='utf-8') as f:
return json.load(f)
def main():
"""Main function to generate diagrams."""
parser = argparse.ArgumentParser(description='Generate ontology diagrams')
parser.add_argument('ontology_file', help='Path to ontology JSON file')
parser.add_argument('--format', choices=['mermaid', 'plantuml', 'dot', 'json-ld', 'all'],
default='all', help='Diagram format to generate')
parser.add_argument('--output', help='Output directory (default: current directory)')
args = parser.parse_args()
ontology_path = Path(args.ontology_file)
if not ontology_path.exists():
print(f"Error: Ontology file '{ontology_path}' not found")
sys.exit(1)
ontology = load_ontology(ontology_path)
generator = OntologyDiagramGenerator()
output_dir = Path(args.output) if args.output else Path('.')
output_dir.mkdir(exist_ok=True)
formats_to_generate = ['mermaid', 'plantuml', 'dot', 'json-ld'] if args.format == 'all' else [args.format]
for format_type in formats_to_generate:
if format_type == 'mermaid':
diagram = generator.generate_mermaid(ontology)
output_file = output_dir / f"{ontology_path.stem}_mermaid.md"
with open(output_file, 'w', encoding='utf-8') as f:
f.write(f"# {ontology_path.stem} Ontology Diagram\n\n")
f.write("```mermaid\n")
f.write(diagram)
f.write("\n```")
print(f"Generated Mermaid diagram: {output_file}")
elif format_type == 'plantuml':
diagram = generator.generate_plantuml(ontology)
output_file = output_dir / f"{ontology_path.stem}_plantuml.puml"
with open(output_file, 'w', encoding='utf-8') as f:
f.write(diagram)
print(f"Generated PlantUML diagram: {output_file}")
elif format_type == 'dot':
diagram = generator.generate_dot(ontology)
output_file = output_dir / f"{ontology_path.stem}.dot"
with open(output_file, 'w', encoding='utf-8') as f:
f.write(diagram)
print(f"Generated GraphViz DOT diagram: {output_file}")
elif format_type == 'json-ld':
json_ld = generator.generate_json_ld(ontology)
output_file = output_dir / f"{ontology_path.stem}_jsonld.json"
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(json_ld, f, indent=2)
print(f"Generated JSON-LD: {output_file}")
if __name__ == "__main__":
main()