Initial commit
This commit is contained in:
+82
@@ -0,0 +1,82 @@
|
|||||||
|
# --- Python --------------------------------------------------------------
|
||||||
|
venv/
|
||||||
|
.env/
|
||||||
|
.venv/
|
||||||
|
env/
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*.pyo
|
||||||
|
*.pyd
|
||||||
|
*.so
|
||||||
|
*.dll
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Build / distribution
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
*.egg-info/
|
||||||
|
.eggs/
|
||||||
|
*.egg
|
||||||
|
*.whl
|
||||||
|
*.zip
|
||||||
|
*.tar.gz
|
||||||
|
*.tgz
|
||||||
|
*.rar
|
||||||
|
*.tar.bz2
|
||||||
|
*.gz
|
||||||
|
|
||||||
|
# Virtual environment activation scripts
|
||||||
|
activate*
|
||||||
|
activate.csh
|
||||||
|
activate.fish
|
||||||
|
activate.ps1
|
||||||
|
activate
|
||||||
|
pyvenv.cfg
|
||||||
|
|
||||||
|
# Coverage / testing
|
||||||
|
htmlcov/
|
||||||
|
coverage.xml
|
||||||
|
.tox/
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
|
||||||
|
# --- Node / JavaScript -----------------------------------------------
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
|
|
||||||
|
# Transpiled output (if any)
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
lib/
|
||||||
|
src/
|
||||||
|
|
||||||
|
# --- OS / IDE ---------------------------------------------------------
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*.bak
|
||||||
|
*.tmp
|
||||||
|
*.temp
|
||||||
|
*.orig
|
||||||
|
*.sublime-workspace
|
||||||
|
*.sublime-project
|
||||||
|
|
||||||
|
# --- Binaries / compiled packages --------------------------------------
|
||||||
|
lib/python3.13/site-packages/
|
||||||
|
lib64/
|
||||||
|
bin/
|
||||||
|
|
||||||
|
# --- Runtime data / large blobs ---------------------------------------
|
||||||
|
chroma_db/
|
||||||
|
chroma.sqlite3
|
||||||
|
|
||||||
|
# --- Misc ----------------------------------------------------------------
|
||||||
|
# (Leave this at the end to catch anything not matched above)
|
||||||
|
# (but **do NOT** put a blanket "*" here!)
|
||||||
+5232
File diff suppressed because one or more lines are too long
+124
@@ -0,0 +1,124 @@
|
|||||||
|
# compact_toon.py
|
||||||
|
"""
|
||||||
|
Ultra-compact Toon format translator for maximum token efficiency
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
from typing import Dict, List, Any, Optional
|
||||||
|
|
||||||
|
|
||||||
|
class CompactToon:
|
||||||
|
"""Minimal Toon format translator for maximum token savings."""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def search_results(result_string: str, query: str = "") -> str:
|
||||||
|
"""Convert search results to minimal Toon format."""
|
||||||
|
if not result_string or "No results found" in result_string:
|
||||||
|
return result_string
|
||||||
|
|
||||||
|
results = result_string.split("---")
|
||||||
|
toon_blocks = []
|
||||||
|
|
||||||
|
for result in results:
|
||||||
|
if not result.strip():
|
||||||
|
continue
|
||||||
|
toon_block = CompactToon._minimal_result(result.strip())
|
||||||
|
if toon_block:
|
||||||
|
toon_blocks.append(toon_block)
|
||||||
|
|
||||||
|
return "\n\n".join(toon_blocks)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _minimal_result(result_text: str) -> Optional[str]:
|
||||||
|
"""Create minimal Toon block for a single result."""
|
||||||
|
try:
|
||||||
|
# Fast regex extraction
|
||||||
|
file_match = re.search(r"File:\s*(.+?)\n", result_text)
|
||||||
|
line_match = re.search(r"Line:\s*(\d+)", result_text)
|
||||||
|
type_match = re.search(r"Type:\s*(.+?)\n", result_text)
|
||||||
|
score_match = re.search(r"score:\s*([\d.]+)", result_text)
|
||||||
|
|
||||||
|
if not file_match:
|
||||||
|
return None
|
||||||
|
|
||||||
|
file_path = file_match.group(1).strip()
|
||||||
|
line_num = line_match.group(1) if line_match else "1"
|
||||||
|
|
||||||
|
# Extract content efficiently
|
||||||
|
lines = result_text.split('\n')
|
||||||
|
content_lines = []
|
||||||
|
in_content = False
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
if in_content:
|
||||||
|
content_lines.append(line)
|
||||||
|
elif line.strip() and not any(line.startswith(x) for x in
|
||||||
|
['###', 'File:', 'Line:', 'Type:', 'Language:']):
|
||||||
|
in_content = True
|
||||||
|
content_lines.append(line)
|
||||||
|
|
||||||
|
content = '\n'.join(content_lines).strip()
|
||||||
|
|
||||||
|
# Build minimal Toon format
|
||||||
|
toon_lines = []
|
||||||
|
toon_lines.append(f"file://{file_path}:{line_num}")
|
||||||
|
|
||||||
|
if type_match:
|
||||||
|
toon_lines.append(f" type: {type_match.group(1).strip()}")
|
||||||
|
if score_match:
|
||||||
|
toon_lines.append(f" score: {score_match.group(1)}")
|
||||||
|
|
||||||
|
toon_lines.append(" content: |")
|
||||||
|
|
||||||
|
# Add content with minimal processing
|
||||||
|
for line in content.split('\n'):
|
||||||
|
toon_lines.append(f" {line}")
|
||||||
|
|
||||||
|
return "\n".join(toon_lines)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def references(result_string: str, symbol: str = "") -> str:
|
||||||
|
"""Convert reference results to minimal format."""
|
||||||
|
if not result_string or "No references found" in result_string:
|
||||||
|
return result_string
|
||||||
|
|
||||||
|
results = result_string.split("---")
|
||||||
|
toon_blocks = []
|
||||||
|
|
||||||
|
for result in results:
|
||||||
|
if not result.strip():
|
||||||
|
continue
|
||||||
|
toon_block = CompactToon._minimal_reference(result.strip())
|
||||||
|
if toon_block:
|
||||||
|
toon_blocks.append(toon_block)
|
||||||
|
|
||||||
|
return "\n\n".join(toon_blocks)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _minimal_reference(result_text: str) -> Optional[str]:
|
||||||
|
"""Create minimal reference block."""
|
||||||
|
try:
|
||||||
|
file_match = re.search(r"File:\s*(.+?)\n", result_text)
|
||||||
|
line_match = re.search(r"Line:\s*(\d+)", result_text)
|
||||||
|
|
||||||
|
if not file_match or not line_match:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Extract context efficiently
|
||||||
|
context_match = re.search(r"Context:\s*(.+)", result_text, re.DOTALL)
|
||||||
|
context = context_match.group(1).strip() if context_match else ""
|
||||||
|
|
||||||
|
toon_lines = []
|
||||||
|
toon_lines.append(f"file://{file_match.group(1).strip()}:{line_match.group(1)}")
|
||||||
|
toon_lines.append(" type: reference")
|
||||||
|
toon_lines.append(" content: |")
|
||||||
|
|
||||||
|
for line in context.split('\n'):
|
||||||
|
toon_lines.append(f" {line}")
|
||||||
|
|
||||||
|
return "\n".join(toon_lines)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
# enhanced_toon.py
|
||||||
|
"""
|
||||||
|
Official TOON (Token-Oriented Object Notation) formatter following the spec from github.com/toon-format/toon
|
||||||
|
Provides 30-60% token savings compared to JSON for LLM communication.
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
from typing import List, Dict, Any, Optional
|
||||||
|
|
||||||
|
|
||||||
|
class EnhancedToon:
|
||||||
|
"""
|
||||||
|
TOON formatter implementing the official specification from toon-format/toon
|
||||||
|
Optimized for uniform arrays of objects with significant token reduction.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def search_results(search_output: str, query: str) -> str:
|
||||||
|
"""
|
||||||
|
Convert search results to proper TOON format following official spec.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
search_output: Raw search results from search_codebase
|
||||||
|
query: Original search query
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Proper TOON formatted results
|
||||||
|
"""
|
||||||
|
if not search_output or "No results" in search_output:
|
||||||
|
return f"# Search: {query}\nNo results found.\n"
|
||||||
|
|
||||||
|
# If it's already an error message, return as is
|
||||||
|
if "Error reading file" in search_output:
|
||||||
|
return search_output
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Parse individual results
|
||||||
|
results = []
|
||||||
|
current_result = {}
|
||||||
|
lines = search_output.split('\n')
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
while i < len(lines):
|
||||||
|
line = lines[i].strip()
|
||||||
|
|
||||||
|
if line.startswith('### Result'):
|
||||||
|
# Save previous result if exists
|
||||||
|
if current_result and current_result.get('file'):
|
||||||
|
results.append(current_result)
|
||||||
|
|
||||||
|
# Start new result
|
||||||
|
current_result = {
|
||||||
|
'file': '',
|
||||||
|
'line': '',
|
||||||
|
'type': '',
|
||||||
|
'content': []
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract score from header (remove relevance text, keep only score)
|
||||||
|
score_match = re.search(r'score:\s*([\d.]+)', line)
|
||||||
|
if score_match:
|
||||||
|
current_result['score'] = float(score_match.group(1))
|
||||||
|
else:
|
||||||
|
current_result['score'] = 0.0
|
||||||
|
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
elif line.startswith('File:') and current_result:
|
||||||
|
# Only take the first File: line, ignore duplicates
|
||||||
|
if not current_result['file']:
|
||||||
|
current_result['file'] = line.replace('File:', '').strip()
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
elif line.startswith('Line:') and current_result:
|
||||||
|
# Only take the first Line: line, ignore duplicates
|
||||||
|
if not current_result['line']:
|
||||||
|
current_result['line'] = line.replace('Line:', '').strip()
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
elif line.startswith('Type:') and current_result:
|
||||||
|
# Only take the first Type: line, ignore duplicates
|
||||||
|
if not current_result['type']:
|
||||||
|
current_result['type'] = line.replace('Type:', '').strip()
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
elif line == '---':
|
||||||
|
# End of result
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
elif line and not line.startswith('###') and current_result:
|
||||||
|
# Skip all metadata lines (File:, Type:, Name:, Language:, Doc:, SQL:, Code:)
|
||||||
|
if not any(line.startswith(prefix) for prefix in
|
||||||
|
['File:', 'Line:', 'Type:', 'Name:', 'Language:', 'Doc:', 'SQL:', 'Code:']):
|
||||||
|
current_result['content'].append(line)
|
||||||
|
i += 1
|
||||||
|
else:
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
# Add the last result if valid
|
||||||
|
if current_result and current_result.get('file'):
|
||||||
|
results.append(current_result)
|
||||||
|
|
||||||
|
# Convert to proper TOON format
|
||||||
|
return EnhancedToon._format_search_toon(results, query)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# If parsing fails, return original output but clean it up
|
||||||
|
return EnhancedToon._clean_raw_output(search_output, query)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _clean_raw_output(raw_output: str, query: str) -> str:
|
||||||
|
"""
|
||||||
|
Fallback: Clean up raw output by removing duplicate metadata lines.
|
||||||
|
"""
|
||||||
|
lines = raw_output.split('\n')
|
||||||
|
cleaned_lines = []
|
||||||
|
skip_next_metadata = False
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
stripped = line.strip()
|
||||||
|
|
||||||
|
# Skip duplicate metadata lines that appear after the first occurrence
|
||||||
|
if any(stripped.startswith(prefix) for prefix in ['File:', 'Type:', 'Name:', 'Language:', 'Doc:']):
|
||||||
|
if skip_next_metadata:
|
||||||
|
continue
|
||||||
|
skip_next_metadata = True
|
||||||
|
elif stripped.startswith('### Result') or stripped == '---':
|
||||||
|
skip_next_metadata = False
|
||||||
|
cleaned_lines.append(line)
|
||||||
|
else:
|
||||||
|
cleaned_lines.append(line)
|
||||||
|
|
||||||
|
# Also remove relevance text, keep only score
|
||||||
|
final_lines = []
|
||||||
|
for line in cleaned_lines:
|
||||||
|
# Remove "relevance: HIGH/MEDIUM/LOW" text, keep only score
|
||||||
|
line = re.sub(r'relevance:\s*\w+,\s*', '', line)
|
||||||
|
final_lines.append(line)
|
||||||
|
|
||||||
|
return "\n".join(final_lines)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _format_search_toon(results: List[Dict], query: str) -> str:
|
||||||
|
"""
|
||||||
|
Format search results following official TOON specification.
|
||||||
|
"""
|
||||||
|
if not results:
|
||||||
|
return f"# Search: {query}\nNo results found.\n"
|
||||||
|
|
||||||
|
lines = [f"# Search: {query}", f"results[{len(results)}]{{file,line,score,content}}:"]
|
||||||
|
|
||||||
|
for i, result in enumerate(results, 1):
|
||||||
|
# Prepare content - join and clean
|
||||||
|
content = ' '.join(result.get('content', [])).strip()
|
||||||
|
content = re.sub(r'\s+', ' ', content) # Normalize whitespace
|
||||||
|
|
||||||
|
# Remove any remaining metadata patterns
|
||||||
|
content = re.sub(r'^File:.*$', '', content, flags=re.MULTILINE)
|
||||||
|
content = re.sub(r'^Type:.*$', '', content, flags=re.MULTILINE)
|
||||||
|
content = re.sub(r'^Name:.*$', '', content, flags=re.MULTILINE)
|
||||||
|
content = re.sub(r'^Language:.*$', '', content, flags=re.MULTILINE)
|
||||||
|
content = re.sub(r'^Doc:.*$', '', content, flags=re.MULTILINE)
|
||||||
|
content = re.sub(r'^SQL:.*$', '', content, flags=re.MULTILINE)
|
||||||
|
content = re.sub(r'^Code:.*$', '', content, flags=re.MULTILINE)
|
||||||
|
content = content.strip()
|
||||||
|
|
||||||
|
# Build TOON row - simple format without relevance text
|
||||||
|
row = [
|
||||||
|
result.get('file', ''),
|
||||||
|
result.get('line', ''),
|
||||||
|
f"{result.get('score', 0.0):.2f}",
|
||||||
|
content[:300] # Reasonable limit
|
||||||
|
]
|
||||||
|
|
||||||
|
# Escape fields if needed and join with comma
|
||||||
|
escaped_row = [EnhancedToon._escape_toon_field(str(field)) for field in row]
|
||||||
|
lines.append(" " + ",".join(escaped_row))
|
||||||
|
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _escape_toon_field(field: str) -> str:
|
||||||
|
"""
|
||||||
|
Escape TOON field according to specification.
|
||||||
|
Only quote fields that contain commas, quotes, or newlines.
|
||||||
|
"""
|
||||||
|
if any(char in field for char in [',', '"', '\n', '\r']):
|
||||||
|
# Escape quotes and wrap in quotes
|
||||||
|
escaped = field.replace('"', '\\"')
|
||||||
|
return f'"{escaped}"'
|
||||||
|
return field
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def reference_results(reference_output: str, symbol: str) -> str:
|
||||||
|
"""
|
||||||
|
Convert reference results to clean format.
|
||||||
|
"""
|
||||||
|
if "Error reading file" in reference_output:
|
||||||
|
return reference_output
|
||||||
|
|
||||||
|
# Clean up reference output similarly
|
||||||
|
return EnhancedToon._clean_raw_output(reference_output, f"References: {symbol}")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def file_content_results(file_output: str, path: str) -> str:
|
||||||
|
"""
|
||||||
|
Convert file content results, preserving the full content without truncation.
|
||||||
|
"""
|
||||||
|
# Remove any truncation messages and return full content
|
||||||
|
if "truncated to 100 lines" in file_output:
|
||||||
|
# This indicates the file was truncated, we want to avoid that
|
||||||
|
# For now, just return the original output but remove the truncation notice
|
||||||
|
lines = file_output.split('\n')
|
||||||
|
cleaned_lines = []
|
||||||
|
for line in lines:
|
||||||
|
if "truncated to" not in line and "Context:" not in line:
|
||||||
|
cleaned_lines.append(line)
|
||||||
|
return "\n".join(cleaned_lines)
|
||||||
|
|
||||||
|
return file_output
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
|
||||||
|
|
||||||
|
/* Greenlet object interface */
|
||||||
|
|
||||||
|
#ifndef Py_GREENLETOBJECT_H
|
||||||
|
#define Py_GREENLETOBJECT_H
|
||||||
|
|
||||||
|
|
||||||
|
#include <Python.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* This is deprecated and undocumented. It does not change. */
|
||||||
|
#define GREENLET_VERSION "1.0.0"
|
||||||
|
|
||||||
|
#ifndef GREENLET_MODULE
|
||||||
|
#define implementation_ptr_t void*
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct _greenlet {
|
||||||
|
PyObject_HEAD
|
||||||
|
PyObject* weakreflist;
|
||||||
|
PyObject* dict;
|
||||||
|
implementation_ptr_t pimpl;
|
||||||
|
} PyGreenlet;
|
||||||
|
|
||||||
|
#define PyGreenlet_Check(op) (op && PyObject_TypeCheck(op, &PyGreenlet_Type))
|
||||||
|
|
||||||
|
|
||||||
|
/* C API functions */
|
||||||
|
|
||||||
|
/* Total number of symbols that are exported */
|
||||||
|
#define PyGreenlet_API_pointers 12
|
||||||
|
|
||||||
|
#define PyGreenlet_Type_NUM 0
|
||||||
|
#define PyExc_GreenletError_NUM 1
|
||||||
|
#define PyExc_GreenletExit_NUM 2
|
||||||
|
|
||||||
|
#define PyGreenlet_New_NUM 3
|
||||||
|
#define PyGreenlet_GetCurrent_NUM 4
|
||||||
|
#define PyGreenlet_Throw_NUM 5
|
||||||
|
#define PyGreenlet_Switch_NUM 6
|
||||||
|
#define PyGreenlet_SetParent_NUM 7
|
||||||
|
|
||||||
|
#define PyGreenlet_MAIN_NUM 8
|
||||||
|
#define PyGreenlet_STARTED_NUM 9
|
||||||
|
#define PyGreenlet_ACTIVE_NUM 10
|
||||||
|
#define PyGreenlet_GET_PARENT_NUM 11
|
||||||
|
|
||||||
|
#ifndef GREENLET_MODULE
|
||||||
|
/* This section is used by modules that uses the greenlet C API */
|
||||||
|
static void** _PyGreenlet_API = NULL;
|
||||||
|
|
||||||
|
# define PyGreenlet_Type \
|
||||||
|
(*(PyTypeObject*)_PyGreenlet_API[PyGreenlet_Type_NUM])
|
||||||
|
|
||||||
|
# define PyExc_GreenletError \
|
||||||
|
((PyObject*)_PyGreenlet_API[PyExc_GreenletError_NUM])
|
||||||
|
|
||||||
|
# define PyExc_GreenletExit \
|
||||||
|
((PyObject*)_PyGreenlet_API[PyExc_GreenletExit_NUM])
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PyGreenlet_New(PyObject *args)
|
||||||
|
*
|
||||||
|
* greenlet.greenlet(run, parent=None)
|
||||||
|
*/
|
||||||
|
# define PyGreenlet_New \
|
||||||
|
(*(PyGreenlet * (*)(PyObject * run, PyGreenlet * parent)) \
|
||||||
|
_PyGreenlet_API[PyGreenlet_New_NUM])
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PyGreenlet_GetCurrent(void)
|
||||||
|
*
|
||||||
|
* greenlet.getcurrent()
|
||||||
|
*/
|
||||||
|
# define PyGreenlet_GetCurrent \
|
||||||
|
(*(PyGreenlet * (*)(void)) _PyGreenlet_API[PyGreenlet_GetCurrent_NUM])
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PyGreenlet_Throw(
|
||||||
|
* PyGreenlet *greenlet,
|
||||||
|
* PyObject *typ,
|
||||||
|
* PyObject *val,
|
||||||
|
* PyObject *tb)
|
||||||
|
*
|
||||||
|
* g.throw(...)
|
||||||
|
*/
|
||||||
|
# define PyGreenlet_Throw \
|
||||||
|
(*(PyObject * (*)(PyGreenlet * self, \
|
||||||
|
PyObject * typ, \
|
||||||
|
PyObject * val, \
|
||||||
|
PyObject * tb)) \
|
||||||
|
_PyGreenlet_API[PyGreenlet_Throw_NUM])
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PyGreenlet_Switch(PyGreenlet *greenlet, PyObject *args)
|
||||||
|
*
|
||||||
|
* g.switch(*args, **kwargs)
|
||||||
|
*/
|
||||||
|
# define PyGreenlet_Switch \
|
||||||
|
(*(PyObject * \
|
||||||
|
(*)(PyGreenlet * greenlet, PyObject * args, PyObject * kwargs)) \
|
||||||
|
_PyGreenlet_API[PyGreenlet_Switch_NUM])
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PyGreenlet_SetParent(PyObject *greenlet, PyObject *new_parent)
|
||||||
|
*
|
||||||
|
* g.parent = new_parent
|
||||||
|
*/
|
||||||
|
# define PyGreenlet_SetParent \
|
||||||
|
(*(int (*)(PyGreenlet * greenlet, PyGreenlet * nparent)) \
|
||||||
|
_PyGreenlet_API[PyGreenlet_SetParent_NUM])
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PyGreenlet_GetParent(PyObject* greenlet)
|
||||||
|
*
|
||||||
|
* return greenlet.parent;
|
||||||
|
*
|
||||||
|
* This could return NULL even if there is no exception active.
|
||||||
|
* If it does not return NULL, you are responsible for decrementing the
|
||||||
|
* reference count.
|
||||||
|
*/
|
||||||
|
# define PyGreenlet_GetParent \
|
||||||
|
(*(PyGreenlet* (*)(PyGreenlet*)) \
|
||||||
|
_PyGreenlet_API[PyGreenlet_GET_PARENT_NUM])
|
||||||
|
|
||||||
|
/*
|
||||||
|
* deprecated, undocumented alias.
|
||||||
|
*/
|
||||||
|
# define PyGreenlet_GET_PARENT PyGreenlet_GetParent
|
||||||
|
|
||||||
|
# define PyGreenlet_MAIN \
|
||||||
|
(*(int (*)(PyGreenlet*)) \
|
||||||
|
_PyGreenlet_API[PyGreenlet_MAIN_NUM])
|
||||||
|
|
||||||
|
# define PyGreenlet_STARTED \
|
||||||
|
(*(int (*)(PyGreenlet*)) \
|
||||||
|
_PyGreenlet_API[PyGreenlet_STARTED_NUM])
|
||||||
|
|
||||||
|
# define PyGreenlet_ACTIVE \
|
||||||
|
(*(int (*)(PyGreenlet*)) \
|
||||||
|
_PyGreenlet_API[PyGreenlet_ACTIVE_NUM])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Macro that imports greenlet and initializes C API */
|
||||||
|
/* NOTE: This has actually moved to ``greenlet._greenlet._C_API``, but we
|
||||||
|
keep the older definition to be sure older code that might have a copy of
|
||||||
|
the header still works. */
|
||||||
|
# define PyGreenlet_Import() \
|
||||||
|
{ \
|
||||||
|
_PyGreenlet_API = (void**)PyCapsule_Import("greenlet._C_API", 0); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* GREENLET_MODULE */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif /* !Py_GREENLETOBJECT_H */
|
||||||
+2333
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "ragmcp",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"directories": {
|
||||||
|
"lib": "lib"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"type": "commonjs",
|
||||||
|
"dependencies": {
|
||||||
|
"@typescript-eslint/typescript-estree": "^8.46.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import re
|
||||||
|
import ast
|
||||||
|
import sqlglot
|
||||||
|
from typing import Dict, List, Any
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
def extract_sql_schema(sql_code: str) -> Dict[str, List[str]]:
|
||||||
|
"""
|
||||||
|
Extracts table -> columns mapping from SQL code using sqlglot.
|
||||||
|
Returns a canonical schema.
|
||||||
|
"""
|
||||||
|
schema: Dict[str, List[str]] = {}
|
||||||
|
try:
|
||||||
|
statements = sqlglot.parse(sql_code, read="postgres")
|
||||||
|
except Exception:
|
||||||
|
return schema
|
||||||
|
|
||||||
|
for stmt in statements:
|
||||||
|
if stmt.key and stmt.key.upper() == "CREATE":
|
||||||
|
for table in stmt.find_all(sqlglot.exp.Create):
|
||||||
|
try:
|
||||||
|
tname = table.this.this
|
||||||
|
cols = []
|
||||||
|
for coldef in table.find_all(sqlglot.exp.ColumnDef):
|
||||||
|
cname = getattr(coldef.this, "name", None)
|
||||||
|
if cname:
|
||||||
|
cols.append(cname)
|
||||||
|
if tname and cols:
|
||||||
|
schema[tname] = cols
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
return schema
|
||||||
|
|
||||||
|
|
||||||
|
def extract_python_structure(code: str) -> Dict[str, List[str]]:
|
||||||
|
"""Return module structure: functions and classes."""
|
||||||
|
try:
|
||||||
|
tree = ast.parse(code)
|
||||||
|
except SyntaxError:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
funcs = [n.name for n in ast.walk(tree) if isinstance(n, ast.FunctionDef)]
|
||||||
|
classes = [n.name for n in ast.walk(tree) if isinstance(n, ast.ClassDef)]
|
||||||
|
return {"functions": funcs, "classes": classes}
|
||||||
|
|
||||||
|
|
||||||
|
def extract_go_structure(code: str) -> Dict[str, List[str]]:
|
||||||
|
"""Simple regex-based Go structure detection (lightweight)."""
|
||||||
|
funcs = re.findall(r"func\s+([A-Z]\w+)", code)
|
||||||
|
structs = re.findall(r"type\s+(\w+)\s+struct", code)
|
||||||
|
return {"functions": funcs, "structs": structs}
|
||||||
|
|
||||||
|
|
||||||
|
def extract_rust_structure(code: str) -> Dict[str, List[str]]:
|
||||||
|
"""Heuristic Rust index."""
|
||||||
|
structs = re.findall(r"struct\s+(\w+)", code)
|
||||||
|
traits = re.findall(r"trait\s+(\w+)", code)
|
||||||
|
funcs = re.findall(r"fn\s+(\w+)", code)
|
||||||
|
return {"functions": funcs, "structs": structs, "traits": traits}
|
||||||
|
|
||||||
|
|
||||||
|
def extract_svelte_structure(code: str) -> Dict[str, List[str]]:
|
||||||
|
"""Minimal Svelte export/prop finder."""
|
||||||
|
exports = re.findall(r"export\s+let\s+(\w+)", code)
|
||||||
|
funcs = re.findall(r"function\s+(\w+)", code)
|
||||||
|
return {"props": exports, "functions": funcs}
|
||||||
|
|
||||||
|
|
||||||
|
def build_quick_index(language: str, code: str, filepath: Path) -> Dict[str, Any]:
|
||||||
|
"""Dispatch to the appropriate structure extractor."""
|
||||||
|
data = {}
|
||||||
|
if language == "sql":
|
||||||
|
data = extract_sql_schema(code)
|
||||||
|
elif language == "python":
|
||||||
|
data = extract_python_structure(code)
|
||||||
|
elif language == "go":
|
||||||
|
data = extract_go_structure(code)
|
||||||
|
elif language == "rust":
|
||||||
|
data = extract_rust_structure(code)
|
||||||
|
elif language == "svelte":
|
||||||
|
data = extract_svelte_structure(code)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"text": f"Quick index for {filepath.name}:\n{data}",
|
||||||
|
"metadata": {"language": language, "file": str(filepath), "type": "index"}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# MCP server
|
||||||
|
fastmcp==0.1.0
|
||||||
|
|
||||||
|
# Ollama embeddings
|
||||||
|
langchain-ollama==0.2.0
|
||||||
|
langchain-community==0.3.31
|
||||||
|
|
||||||
|
# Vector DB
|
||||||
|
chromadb==0.4.13
|
||||||
|
|
||||||
|
# BM25 search
|
||||||
|
rank-bm25==0.2.2
|
||||||
|
|
||||||
|
# AST parsing
|
||||||
|
javalang==0.13.0
|
||||||
|
|
||||||
|
# File ignore parsing
|
||||||
|
pathspec==0.10.3
|
||||||
|
|
||||||
|
# HTTP requests (for optional reranking calls)
|
||||||
|
requests>=2.32.5,<3.0.0
|
||||||
|
|
||||||
|
# Optional: subprocess utilities
|
||||||
|
psutil==5.9.5
|
||||||
|
|
||||||
|
# --- SQL parsing stack ---
|
||||||
|
sqlglot==25.3.0 # Portable SQL AST + transpiler (fast for SELECTs, DML)
|
||||||
|
sqlparse==0.5.1 # Lightweight fallback tokenizer (simple/heuristic parsing)
|
||||||
+255
@@ -0,0 +1,255 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
HTTP server for MCP codebase RAG with REST API
|
||||||
|
"""
|
||||||
|
import uvicorn
|
||||||
|
import logging
|
||||||
|
from fastapi import FastAPI, HTTPException
|
||||||
|
from fastapi.responses import JSONResponse
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import Optional
|
||||||
|
import signal
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
|
||||||
|
from enhanced_toon import EnhancedToon
|
||||||
|
|
||||||
|
from mcp_codebase import (
|
||||||
|
startup,
|
||||||
|
search_codebase as _search_codebase,
|
||||||
|
find_code_references as _find_code_references,
|
||||||
|
read_file_lines,
|
||||||
|
rebuild_index as _rebuild_index
|
||||||
|
)
|
||||||
|
|
||||||
|
logger = logging.getLogger("rag-mcp")
|
||||||
|
|
||||||
|
def signal_handler(sig, frame):
|
||||||
|
"""Handle graceful shutdown on Ctrl+C."""
|
||||||
|
logger.info("\n=== HTTP Server Shutdown ===")
|
||||||
|
logger.info("Goodbye!\n")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# Request/Response models
|
||||||
|
class SearchRequest(BaseModel):
|
||||||
|
query: str
|
||||||
|
top_k: int = 5
|
||||||
|
rerank: bool = True
|
||||||
|
|
||||||
|
class ReferenceRequest(BaseModel):
|
||||||
|
symbol: str
|
||||||
|
top_k: int = 20
|
||||||
|
|
||||||
|
class ReadFileRequest(BaseModel):
|
||||||
|
path: str
|
||||||
|
start: int = 1
|
||||||
|
end: Optional[int] = None
|
||||||
|
|
||||||
|
class SymbolRequest(BaseModel):
|
||||||
|
symbol: str
|
||||||
|
|
||||||
|
def create_app():
|
||||||
|
"""Create FastAPI app with MCP tool endpoints"""
|
||||||
|
app = FastAPI(
|
||||||
|
title="MCP Codebase RAG Server",
|
||||||
|
description="Codebase search and analysis via MCP tools over HTTP",
|
||||||
|
version="1.0.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add CORS middleware for remote access
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"], # Configure this appropriately for production
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Health check endpoint
|
||||||
|
@app.get("/")
|
||||||
|
async def root():
|
||||||
|
return {
|
||||||
|
"status": "running",
|
||||||
|
"service": "MCP Codebase RAG Server",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"endpoints": {
|
||||||
|
"docs": "/docs",
|
||||||
|
"health": "/health",
|
||||||
|
"tools": "/tools",
|
||||||
|
"search": "POST /search",
|
||||||
|
"references": "POST /references",
|
||||||
|
"read_file": "POST /read_file",
|
||||||
|
"rebuild": "POST /rebuild"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@app.get("/health")
|
||||||
|
async def health():
|
||||||
|
"""Get server health and statistics"""
|
||||||
|
try:
|
||||||
|
# Import here to avoid circular import issues
|
||||||
|
from mcp_codebase import health_check
|
||||||
|
result = health_check()
|
||||||
|
# Parse JSON string to dict for better API response
|
||||||
|
try:
|
||||||
|
result_dict = json.loads(result)
|
||||||
|
return result_dict
|
||||||
|
except:
|
||||||
|
return {"result": result}
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Health check failed")
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
@app.get("/tools")
|
||||||
|
async def list_tools():
|
||||||
|
"""List available MCP tools"""
|
||||||
|
return {
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"name": "search_codebase",
|
||||||
|
"description": "Search the entire codebase using hybrid RAG",
|
||||||
|
"endpoint": "POST /search",
|
||||||
|
"parameters": {
|
||||||
|
"query": "string (required)",
|
||||||
|
"top_k": "int (default: 5)",
|
||||||
|
"rerank": "bool (default: true)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "find_code_references",
|
||||||
|
"description": "Find all references to a specific symbol",
|
||||||
|
"endpoint": "POST /references",
|
||||||
|
"parameters": {
|
||||||
|
"symbol": "string (required)",
|
||||||
|
"top_k": "int (default: 20)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "read_file_lines",
|
||||||
|
"description": "Read specific lines from a file with context",
|
||||||
|
"endpoint": "POST /read_file",
|
||||||
|
"parameters": {
|
||||||
|
"path": "string (required)",
|
||||||
|
"start": "int (default: 1)",
|
||||||
|
"end": "int (optional)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rebuild_index",
|
||||||
|
"description": "Force rebuild of search indexes",
|
||||||
|
"endpoint": "POST /rebuild",
|
||||||
|
"parameters": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
@app.post("/search")
|
||||||
|
async def search(request: SearchRequest):
|
||||||
|
"""Search codebase - returns enhanced Toon format"""
|
||||||
|
try:
|
||||||
|
result = _search_codebase(
|
||||||
|
query=request.query,
|
||||||
|
top_k=request.top_k,
|
||||||
|
rerank=request.rerank
|
||||||
|
)
|
||||||
|
enhanced_result = EnhancedToon.search_results(result, request.query)
|
||||||
|
return {
|
||||||
|
"result": enhanced_result,
|
||||||
|
"query": request.query,
|
||||||
|
"top_k": request.top_k
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Search failed")
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
@app.post("/references")
|
||||||
|
async def references(request: ReferenceRequest):
|
||||||
|
"""Find all references to a symbol - returns enhanced Toon format"""
|
||||||
|
try:
|
||||||
|
result = _find_code_references(
|
||||||
|
symbol=request.symbol,
|
||||||
|
top_k=request.top_k
|
||||||
|
)
|
||||||
|
enhanced_result = EnhancedToon.reference_results(result, request.symbol)
|
||||||
|
return {
|
||||||
|
"result": enhanced_result,
|
||||||
|
"symbol": request.symbol,
|
||||||
|
"top_k": request.top_k
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Reference search failed")
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
@app.post("/read_file")
|
||||||
|
async def read_file(request: ReadFileRequest):
|
||||||
|
"""Read file lines with context - returns enhanced Toon format"""
|
||||||
|
try:
|
||||||
|
result = read_file_lines(
|
||||||
|
path=request.path,
|
||||||
|
start=request.start,
|
||||||
|
end=request.end
|
||||||
|
)
|
||||||
|
enhanced_result = EnhancedToon.file_content_results(result, request.path)
|
||||||
|
return {
|
||||||
|
"result": enhanced_result,
|
||||||
|
"path": request.path,
|
||||||
|
"start_line": request.start,
|
||||||
|
"end_line": request.end
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Read file failed")
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
@app.post("/rebuild")
|
||||||
|
async def rebuild():
|
||||||
|
"""Rebuild search indexes"""
|
||||||
|
try:
|
||||||
|
result = _rebuild_index()
|
||||||
|
# For rebuild, we might not need enhanced format as it's usually a simple status message
|
||||||
|
return {"result": result}
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Rebuild failed")
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
return app
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Register signal handlers
|
||||||
|
signal.signal(signal.SIGINT, signal_handler)
|
||||||
|
signal.signal(signal.SIGTERM, signal_handler)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Initialize the indexes (this will load or build them)
|
||||||
|
startup()
|
||||||
|
|
||||||
|
logger.info("="*60)
|
||||||
|
logger.info("🚀 Starting HTTP server on: http://0.0.0.0:8000")
|
||||||
|
logger.info("📚 REST API endpoints:")
|
||||||
|
logger.info(" GET / - Service info")
|
||||||
|
logger.info(" GET /health - Server health & stats")
|
||||||
|
logger.info(" GET /tools - List available tools")
|
||||||
|
logger.info(" POST /search - Search codebase")
|
||||||
|
logger.info(" POST /references - Find symbol references")
|
||||||
|
logger.info(" POST /read_file - Read file with context")
|
||||||
|
logger.info(" POST /rebuild - Rebuild indexes")
|
||||||
|
logger.info("📖 API docs available at: http://0.0.0.0:8000/docs")
|
||||||
|
logger.info("⏹️ Press Ctrl+C to stop")
|
||||||
|
logger.info("="*60)
|
||||||
|
|
||||||
|
# Create and run app
|
||||||
|
app = create_app()
|
||||||
|
|
||||||
|
uvicorn.run(
|
||||||
|
app,
|
||||||
|
host="0.0.0.0",
|
||||||
|
port=8000,
|
||||||
|
log_level="info",
|
||||||
|
access_log=True
|
||||||
|
)
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
signal_handler(signal.SIGINT, None)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Fatal error during HTTP server startup")
|
||||||
|
sys.exit(1)
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
'\" -*- coding: us-ascii -*-
|
||||||
|
.if \n(.g .ds T< \\FC
|
||||||
|
.if \n(.g .ds T> \\F[\n[.fam]]
|
||||||
|
.de URL
|
||||||
|
\\$2 \(la\\$1\(ra\\$3
|
||||||
|
..
|
||||||
|
.if \n(.g .mso www.tmac
|
||||||
|
.TH isympy 1 2007-10-8 "" ""
|
||||||
|
.SH NAME
|
||||||
|
isympy \- interactive shell for SymPy
|
||||||
|
.SH SYNOPSIS
|
||||||
|
'nh
|
||||||
|
.fi
|
||||||
|
.ad l
|
||||||
|
\fBisympy\fR \kx
|
||||||
|
.if (\nx>(\n(.l/2)) .nr x (\n(.l/5)
|
||||||
|
'in \n(.iu+\nxu
|
||||||
|
[\fB-c\fR | \fB--console\fR] [\fB-p\fR ENCODING | \fB--pretty\fR ENCODING] [\fB-t\fR TYPE | \fB--types\fR TYPE] [\fB-o\fR ORDER | \fB--order\fR ORDER] [\fB-q\fR | \fB--quiet\fR] [\fB-d\fR | \fB--doctest\fR] [\fB-C\fR | \fB--no-cache\fR] [\fB-a\fR | \fB--auto\fR] [\fB-D\fR | \fB--debug\fR] [
|
||||||
|
-- | PYTHONOPTIONS]
|
||||||
|
'in \n(.iu-\nxu
|
||||||
|
.ad b
|
||||||
|
'hy
|
||||||
|
'nh
|
||||||
|
.fi
|
||||||
|
.ad l
|
||||||
|
\fBisympy\fR \kx
|
||||||
|
.if (\nx>(\n(.l/2)) .nr x (\n(.l/5)
|
||||||
|
'in \n(.iu+\nxu
|
||||||
|
[
|
||||||
|
{\fB-h\fR | \fB--help\fR}
|
||||||
|
|
|
||||||
|
{\fB-v\fR | \fB--version\fR}
|
||||||
|
]
|
||||||
|
'in \n(.iu-\nxu
|
||||||
|
.ad b
|
||||||
|
'hy
|
||||||
|
.SH DESCRIPTION
|
||||||
|
isympy is a Python shell for SymPy. It is just a normal python shell
|
||||||
|
(ipython shell if you have the ipython package installed) that executes
|
||||||
|
the following commands so that you don't have to:
|
||||||
|
.PP
|
||||||
|
.nf
|
||||||
|
\*(T<
|
||||||
|
>>> from __future__ import division
|
||||||
|
>>> from sympy import *
|
||||||
|
>>> x, y, z = symbols("x,y,z")
|
||||||
|
>>> k, m, n = symbols("k,m,n", integer=True)
|
||||||
|
\*(T>
|
||||||
|
.fi
|
||||||
|
.PP
|
||||||
|
So starting isympy is equivalent to starting python (or ipython) and
|
||||||
|
executing the above commands by hand. It is intended for easy and quick
|
||||||
|
experimentation with SymPy. For more complicated programs, it is recommended
|
||||||
|
to write a script and import things explicitly (using the "from sympy
|
||||||
|
import sin, log, Symbol, ..." idiom).
|
||||||
|
.SH OPTIONS
|
||||||
|
.TP
|
||||||
|
\*(T<\fB\-c \fR\*(T>\fISHELL\fR, \*(T<\fB\-\-console=\fR\*(T>\fISHELL\fR
|
||||||
|
Use the specified shell (python or ipython) as
|
||||||
|
console backend instead of the default one (ipython
|
||||||
|
if present or python otherwise).
|
||||||
|
|
||||||
|
Example: isympy -c python
|
||||||
|
|
||||||
|
\fISHELL\fR could be either
|
||||||
|
\&'ipython' or 'python'
|
||||||
|
.TP
|
||||||
|
\*(T<\fB\-p \fR\*(T>\fIENCODING\fR, \*(T<\fB\-\-pretty=\fR\*(T>\fIENCODING\fR
|
||||||
|
Setup pretty printing in SymPy. By default, the most pretty, unicode
|
||||||
|
printing is enabled (if the terminal supports it). You can use less
|
||||||
|
pretty ASCII printing instead or no pretty printing at all.
|
||||||
|
|
||||||
|
Example: isympy -p no
|
||||||
|
|
||||||
|
\fIENCODING\fR must be one of 'unicode',
|
||||||
|
\&'ascii' or 'no'.
|
||||||
|
.TP
|
||||||
|
\*(T<\fB\-t \fR\*(T>\fITYPE\fR, \*(T<\fB\-\-types=\fR\*(T>\fITYPE\fR
|
||||||
|
Setup the ground types for the polys. By default, gmpy ground types
|
||||||
|
are used if gmpy2 or gmpy is installed, otherwise it falls back to python
|
||||||
|
ground types, which are a little bit slower. You can manually
|
||||||
|
choose python ground types even if gmpy is installed (e.g., for testing purposes).
|
||||||
|
|
||||||
|
Note that sympy ground types are not supported, and should be used
|
||||||
|
only for experimental purposes.
|
||||||
|
|
||||||
|
Note that the gmpy1 ground type is primarily intended for testing; it the
|
||||||
|
use of gmpy even if gmpy2 is available.
|
||||||
|
|
||||||
|
This is the same as setting the environment variable
|
||||||
|
SYMPY_GROUND_TYPES to the given ground type (e.g.,
|
||||||
|
SYMPY_GROUND_TYPES='gmpy')
|
||||||
|
|
||||||
|
The ground types can be determined interactively from the variable
|
||||||
|
sympy.polys.domains.GROUND_TYPES inside the isympy shell itself.
|
||||||
|
|
||||||
|
Example: isympy -t python
|
||||||
|
|
||||||
|
\fITYPE\fR must be one of 'gmpy',
|
||||||
|
\&'gmpy1' or 'python'.
|
||||||
|
.TP
|
||||||
|
\*(T<\fB\-o \fR\*(T>\fIORDER\fR, \*(T<\fB\-\-order=\fR\*(T>\fIORDER\fR
|
||||||
|
Setup the ordering of terms for printing. The default is lex, which
|
||||||
|
orders terms lexicographically (e.g., x**2 + x + 1). You can choose
|
||||||
|
other orderings, such as rev-lex, which will use reverse
|
||||||
|
lexicographic ordering (e.g., 1 + x + x**2).
|
||||||
|
|
||||||
|
Note that for very large expressions, ORDER='none' may speed up
|
||||||
|
printing considerably, with the tradeoff that the order of the terms
|
||||||
|
in the printed expression will have no canonical order
|
||||||
|
|
||||||
|
Example: isympy -o rev-lax
|
||||||
|
|
||||||
|
\fIORDER\fR must be one of 'lex', 'rev-lex', 'grlex',
|
||||||
|
\&'rev-grlex', 'grevlex', 'rev-grevlex', 'old', or 'none'.
|
||||||
|
.TP
|
||||||
|
\*(T<\fB\-q\fR\*(T>, \*(T<\fB\-\-quiet\fR\*(T>
|
||||||
|
Print only Python's and SymPy's versions to stdout at startup, and nothing else.
|
||||||
|
.TP
|
||||||
|
\*(T<\fB\-d\fR\*(T>, \*(T<\fB\-\-doctest\fR\*(T>
|
||||||
|
Use the same format that should be used for doctests. This is
|
||||||
|
equivalent to '\fIisympy -c python -p no\fR'.
|
||||||
|
.TP
|
||||||
|
\*(T<\fB\-C\fR\*(T>, \*(T<\fB\-\-no\-cache\fR\*(T>
|
||||||
|
Disable the caching mechanism. Disabling the cache may slow certain
|
||||||
|
operations down considerably. This is useful for testing the cache,
|
||||||
|
or for benchmarking, as the cache can result in deceptive benchmark timings.
|
||||||
|
|
||||||
|
This is the same as setting the environment variable SYMPY_USE_CACHE
|
||||||
|
to 'no'.
|
||||||
|
.TP
|
||||||
|
\*(T<\fB\-a\fR\*(T>, \*(T<\fB\-\-auto\fR\*(T>
|
||||||
|
Automatically create missing symbols. Normally, typing a name of a
|
||||||
|
Symbol that has not been instantiated first would raise NameError,
|
||||||
|
but with this option enabled, any undefined name will be
|
||||||
|
automatically created as a Symbol. This only works in IPython 0.11.
|
||||||
|
|
||||||
|
Note that this is intended only for interactive, calculator style
|
||||||
|
usage. In a script that uses SymPy, Symbols should be instantiated
|
||||||
|
at the top, so that it's clear what they are.
|
||||||
|
|
||||||
|
This will not override any names that are already defined, which
|
||||||
|
includes the single character letters represented by the mnemonic
|
||||||
|
QCOSINE (see the "Gotchas and Pitfalls" document in the
|
||||||
|
documentation). You can delete existing names by executing "del
|
||||||
|
name" in the shell itself. You can see if a name is defined by typing
|
||||||
|
"'name' in globals()".
|
||||||
|
|
||||||
|
The Symbols that are created using this have default assumptions.
|
||||||
|
If you want to place assumptions on symbols, you should create them
|
||||||
|
using symbols() or var().
|
||||||
|
|
||||||
|
Finally, this only works in the top level namespace. So, for
|
||||||
|
example, if you define a function in isympy with an undefined
|
||||||
|
Symbol, it will not work.
|
||||||
|
.TP
|
||||||
|
\*(T<\fB\-D\fR\*(T>, \*(T<\fB\-\-debug\fR\*(T>
|
||||||
|
Enable debugging output. This is the same as setting the
|
||||||
|
environment variable SYMPY_DEBUG to 'True'. The debug status is set
|
||||||
|
in the variable SYMPY_DEBUG within isympy.
|
||||||
|
.TP
|
||||||
|
-- \fIPYTHONOPTIONS\fR
|
||||||
|
These options will be passed on to \fIipython (1)\fR shell.
|
||||||
|
Only supported when ipython is being used (standard python shell not supported).
|
||||||
|
|
||||||
|
Two dashes (--) are required to separate \fIPYTHONOPTIONS\fR
|
||||||
|
from the other isympy options.
|
||||||
|
|
||||||
|
For example, to run iSymPy without startup banner and colors:
|
||||||
|
|
||||||
|
isympy -q -c ipython -- --colors=NoColor
|
||||||
|
.TP
|
||||||
|
\*(T<\fB\-h\fR\*(T>, \*(T<\fB\-\-help\fR\*(T>
|
||||||
|
Print help output and exit.
|
||||||
|
.TP
|
||||||
|
\*(T<\fB\-v\fR\*(T>, \*(T<\fB\-\-version\fR\*(T>
|
||||||
|
Print isympy version information and exit.
|
||||||
|
.SH FILES
|
||||||
|
.TP
|
||||||
|
\*(T<\fI${HOME}/.sympy\-history\fR\*(T>
|
||||||
|
Saves the history of commands when using the python
|
||||||
|
shell as backend.
|
||||||
|
.SH BUGS
|
||||||
|
The upstreams BTS can be found at \(lahttps://github.com/sympy/sympy/issues\(ra
|
||||||
|
Please report all bugs that you find in there, this will help improve
|
||||||
|
the overall quality of SymPy.
|
||||||
|
.SH "SEE ALSO"
|
||||||
|
\fBipython\fR(1), \fBpython\fR(1)
|
||||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,194 @@
|
|||||||
|
// # Build from project root:
|
||||||
|
// go build -o tools/parse_go_ast tools/parse_go_ast.go
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GoDecl struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"` // "func", "method", "type", "struct", "interface", "var", "package"
|
||||||
|
Receiver string `json:"receiver,omitempty"` // e.g., "*User"
|
||||||
|
FullName string `json:"full_name,omitempty"`
|
||||||
|
Fields []string `json:"fields,omitempty"` // for structs
|
||||||
|
Methods []string `json:"methods,omitempty"` // for interfaces
|
||||||
|
DocComment string `json:"doc_comment,omitempty"`
|
||||||
|
StartLine int `json:"start_line"`
|
||||||
|
EndLine int `json:"end_line"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func astTypeToString(expr ast.Expr) string {
|
||||||
|
switch t := expr.(type) {
|
||||||
|
case *ast.Ident:
|
||||||
|
return t.Name
|
||||||
|
case *ast.StarExpr:
|
||||||
|
return "*" + astTypeToString(t.X)
|
||||||
|
case *ast.SelectorExpr:
|
||||||
|
return astTypeToString(t.X) + "." + t.Sel.Name
|
||||||
|
case *ast.ArrayType:
|
||||||
|
return "[]" + astTypeToString(t.Elt)
|
||||||
|
case *ast.MapType:
|
||||||
|
return "map[" + astTypeToString(t.Key) + "]" + astTypeToString(t.Value)
|
||||||
|
default:
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractDocComment(comments []*ast.CommentGroup, pos token.Pos, fset *token.FileSet) string {
|
||||||
|
if len(comments) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
line := fset.Position(pos).Line
|
||||||
|
for i := len(comments) - 1; i >= 0; i-- {
|
||||||
|
cg := comments[i]
|
||||||
|
cgLine := fset.Position(cg.End()).Line
|
||||||
|
if cgLine < line && line-cgLine <= 5 {
|
||||||
|
return strings.TrimSpace(cg.Text())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) < 2 {
|
||||||
|
fmt.Println("Usage: parse_go_ast <file.go>")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
filename := os.Args[1]
|
||||||
|
src, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("ERR: %v\n", err)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
fileNode, err := parser.ParseFile(fset, filename, src, parser.ParseComments)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("ERR: %v\n", err)
|
||||||
|
os.Exit(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
decls := []GoDecl{}
|
||||||
|
|
||||||
|
// Package comment (first comment group before package decl)
|
||||||
|
pkgComment := ""
|
||||||
|
for _, cg := range fileNode.Comments {
|
||||||
|
if cg.Pos() < fileNode.Package {
|
||||||
|
pkgComment = strings.TrimSpace(cg.Text())
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pkgComment != "" {
|
||||||
|
decls = append(decls, GoDecl{
|
||||||
|
Name: "package",
|
||||||
|
Type: "package",
|
||||||
|
DocComment: pkgComment,
|
||||||
|
StartLine: 1,
|
||||||
|
EndLine: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process declarations
|
||||||
|
for _, d := range fileNode.Decls {
|
||||||
|
switch d := d.(type) {
|
||||||
|
case *ast.FuncDecl:
|
||||||
|
name := d.Name.Name
|
||||||
|
recvType := ""
|
||||||
|
if d.Recv != nil && len(d.Recv.List) > 0 {
|
||||||
|
recvType = astTypeToString(d.Recv.List[0].Type)
|
||||||
|
}
|
||||||
|
typ := "func"
|
||||||
|
fullName := name
|
||||||
|
if recvType != "" {
|
||||||
|
typ = "method"
|
||||||
|
fullName = fmt.Sprintf("(%s).%s", recvType, name)
|
||||||
|
}
|
||||||
|
doc := extractDocComment(fileNode.Comments, d.Pos(), fset)
|
||||||
|
decls = append(decls, GoDecl{
|
||||||
|
Name: name,
|
||||||
|
Type: typ,
|
||||||
|
Receiver: recvType,
|
||||||
|
FullName: fullName,
|
||||||
|
DocComment: doc,
|
||||||
|
StartLine: fset.Position(d.Pos()).Line,
|
||||||
|
EndLine: fset.Position(d.End()).Line,
|
||||||
|
})
|
||||||
|
|
||||||
|
case *ast.GenDecl:
|
||||||
|
for _, spec := range d.Specs {
|
||||||
|
switch s := spec.(type) {
|
||||||
|
case *ast.TypeSpec:
|
||||||
|
typName := s.Name.Name
|
||||||
|
declType := "type"
|
||||||
|
var fields []string
|
||||||
|
var methods []string
|
||||||
|
|
||||||
|
switch t := s.Type.(type) {
|
||||||
|
case *ast.StructType:
|
||||||
|
declType = "struct"
|
||||||
|
for _, f := range t.Fields.List {
|
||||||
|
for _, n := range f.Names {
|
||||||
|
field := n.Name
|
||||||
|
if f.Tag != nil {
|
||||||
|
field += " " + f.Tag.Value
|
||||||
|
}
|
||||||
|
fields = append(fields, field)
|
||||||
|
}
|
||||||
|
if f.Names == nil {
|
||||||
|
// Embedded field
|
||||||
|
fields = append(fields, astTypeToString(f.Type))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case *ast.InterfaceType:
|
||||||
|
declType = "interface"
|
||||||
|
for _, m := range t.Methods.List {
|
||||||
|
if len(m.Names) > 0 {
|
||||||
|
methods = append(methods, m.Names[0].Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
doc := extractDocComment(fileNode.Comments, s.Pos(), fset)
|
||||||
|
decls = append(decls, GoDecl{
|
||||||
|
Name: typName,
|
||||||
|
Type: declType,
|
||||||
|
Fields: fields,
|
||||||
|
Methods: methods,
|
||||||
|
DocComment: doc,
|
||||||
|
StartLine: fset.Position(d.Pos()).Line,
|
||||||
|
EndLine: fset.Position(d.End()).Line,
|
||||||
|
})
|
||||||
|
|
||||||
|
case *ast.ValueSpec:
|
||||||
|
for _, name := range s.Names {
|
||||||
|
doc := extractDocComment(fileNode.Comments, name.Pos(), fset)
|
||||||
|
// Use the individual identifier's position, not the declaration's position
|
||||||
|
startLine := fset.Position(name.Pos()).Line
|
||||||
|
endLine := fset.Position(name.End()).Line
|
||||||
|
|
||||||
|
decls = append(decls, GoDecl{
|
||||||
|
Name: name.Name,
|
||||||
|
Type: "var",
|
||||||
|
DocComment: doc,
|
||||||
|
StartLine: startLine,
|
||||||
|
EndLine: endLine,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out, _ := json.Marshal(decls)
|
||||||
|
fmt.Println(string(out))
|
||||||
|
}
|
||||||
Executable
+177
@@ -0,0 +1,177 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
// Usage: node parse_ts.js file.ts
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const { parse } = require('@typescript-eslint/typescript-estree');
|
||||||
|
|
||||||
|
function getLoc(node) {
|
||||||
|
if (node.loc) {
|
||||||
|
return { start: node.loc.start.line, end: node.loc.end.line };
|
||||||
|
}
|
||||||
|
return { start: 1, end: 1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractLeadingComment(node, sourceCode) {
|
||||||
|
if (!node.range || !sourceCode) return "";
|
||||||
|
const startIdx = node.range[0];
|
||||||
|
let commentEnd = startIdx;
|
||||||
|
// Look backward for comments
|
||||||
|
let i = startIdx - 1;
|
||||||
|
let commentLines = [];
|
||||||
|
let inBlock = false;
|
||||||
|
|
||||||
|
while (i >= 0) {
|
||||||
|
const char = sourceCode[i];
|
||||||
|
if (char === '\n') break;
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
const lineStart = i + 1;
|
||||||
|
const lineAbove = sourceCode.slice(lineStart, startIdx).trim();
|
||||||
|
|
||||||
|
// Check for // comment on same line before node
|
||||||
|
if (lineAbove.startsWith('//')) {
|
||||||
|
return lineAbove.substring(2).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look further up for multi-line or JSDoc
|
||||||
|
const lines = sourceCode.substring(0, lineStart).split('\n');
|
||||||
|
for (let j = lines.length - 1; j >= Math.max(0, lines.length - 5); j--) {
|
||||||
|
const line = lines[j].trim();
|
||||||
|
if (line.startsWith('//')) {
|
||||||
|
commentLines.unshift(line.substring(2).trim());
|
||||||
|
} else if (line.endsWith('*/')) {
|
||||||
|
inBlock = true;
|
||||||
|
commentLines.unshift(line.slice(0, -2).trim());
|
||||||
|
} else if (inBlock) {
|
||||||
|
if (line.startsWith('/*') || line.startsWith('/**')) {
|
||||||
|
commentLines.unshift(line.slice(2).trim());
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
commentLines.unshift(line);
|
||||||
|
}
|
||||||
|
} else if (line === '') {
|
||||||
|
if (commentLines.length > 0) continue;
|
||||||
|
else break;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return commentLines.join('\n').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractDeclarations(ast, sourceCode) {
|
||||||
|
const decls = [];
|
||||||
|
|
||||||
|
function visit(node) {
|
||||||
|
if (!node || typeof node !== 'object') return;
|
||||||
|
|
||||||
|
if (Array.isArray(node)) {
|
||||||
|
node.forEach(n => visit(n));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let docComment = extractLeadingComment(node, sourceCode);
|
||||||
|
|
||||||
|
if (node.type === 'FunctionDeclaration' && node.id?.name) {
|
||||||
|
const loc = getLoc(node);
|
||||||
|
decls.push({
|
||||||
|
name: node.id.name,
|
||||||
|
type: 'function',
|
||||||
|
doc_comment: docComment,
|
||||||
|
start_line: loc.start,
|
||||||
|
end_line: loc.end
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (
|
||||||
|
node.type === 'VariableDeclarator' &&
|
||||||
|
node.id?.type === 'Identifier' &&
|
||||||
|
node.init?.type === 'ArrowFunctionExpression'
|
||||||
|
) {
|
||||||
|
const loc = getLoc(node);
|
||||||
|
decls.push({
|
||||||
|
name: node.id.name,
|
||||||
|
type: 'function',
|
||||||
|
doc_comment: docComment,
|
||||||
|
start_line: loc.start,
|
||||||
|
end_line: loc.end
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (node.type === 'ClassDeclaration' && node.id?.name) {
|
||||||
|
const loc = getLoc(node);
|
||||||
|
decls.push({
|
||||||
|
name: node.id.name,
|
||||||
|
type: 'class',
|
||||||
|
doc_comment: docComment,
|
||||||
|
start_line: loc.start,
|
||||||
|
end_line: loc.end
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (node.type === 'TSInterfaceDeclaration' && node.id?.name) {
|
||||||
|
const loc = getLoc(node);
|
||||||
|
decls.push({
|
||||||
|
name: node.id.name,
|
||||||
|
type: 'interface',
|
||||||
|
doc_comment: docComment,
|
||||||
|
start_line: loc.start,
|
||||||
|
end_line: loc.end
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (node.type === 'TSTypeAliasDeclaration' && node.id?.name) {
|
||||||
|
const loc = getLoc(node);
|
||||||
|
decls.push({
|
||||||
|
name: node.id.name,
|
||||||
|
type: 'type',
|
||||||
|
doc_comment: docComment,
|
||||||
|
start_line: loc.start,
|
||||||
|
end_line: loc.end
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (
|
||||||
|
node.type === 'VariableDeclarator' &&
|
||||||
|
node.id?.type === 'Identifier'
|
||||||
|
) {
|
||||||
|
const parent = node.parent;
|
||||||
|
if (
|
||||||
|
parent?.type === 'VariableDeclaration' &&
|
||||||
|
['const', 'let'].includes(parent.kind)
|
||||||
|
) {
|
||||||
|
const loc = getLoc(node);
|
||||||
|
decls.push({
|
||||||
|
name: node.id.name,
|
||||||
|
type: 'variable',
|
||||||
|
doc_comment: docComment,
|
||||||
|
start_line: loc.start,
|
||||||
|
end_line: loc.end
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.values(node).forEach(visit);
|
||||||
|
}
|
||||||
|
|
||||||
|
visit(ast);
|
||||||
|
return decls;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.argv.length < 3) {
|
||||||
|
console.error('Usage: node parse_ts.js <file.ts>');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const filePath = path.resolve(process.argv[2]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const code = fs.readFileSync(filePath, 'utf8');
|
||||||
|
const ast = parse(code, {
|
||||||
|
sourceType: 'module',
|
||||||
|
loc: true,
|
||||||
|
range: true,
|
||||||
|
comment: false // we extract manually for simplicity
|
||||||
|
});
|
||||||
|
const decls = extractDeclarations(ast, code);
|
||||||
|
console.log(JSON.stringify(decls, null, 2));
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`ERR: ${e.message}`);
|
||||||
|
process.exit(2);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user