IncludeCPP 3.6.0__py3-none-any.whl → 3.7.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
includecpp/__init__.py CHANGED
@@ -2,7 +2,7 @@ from .core.cpp_api import CppApi
2
2
  from .core import cssl_bridge as CSSL
3
3
  import warnings
4
4
 
5
- __version__ = "3.6.0"
5
+ __version__ = "3.7.9"
6
6
  __all__ = ["CppApi", "CSSL"]
7
7
 
8
8
  # Module-level cache for C++ modules
@@ -5,6 +5,7 @@ import platform
5
5
  import os
6
6
  import sys
7
7
  import json
8
+ import re
8
9
  import urllib.request
9
10
  import urllib.error
10
11
  from pathlib import Path
@@ -1192,7 +1193,7 @@ def build(ctx, clean, keep, verbose, no_incremental, incremental, parallel, jobs
1192
1193
  @cli.command()
1193
1194
  @click.argument('module_name')
1194
1195
  def add(module_name):
1195
- """Create a new module template."""
1196
+ """Create a new module template with sample C++ code."""
1196
1197
  plugins_dir = Path("plugins")
1197
1198
  if not plugins_dir.exists():
1198
1199
  click.echo("Error: plugins/ directory not found")
@@ -1203,6 +1204,7 @@ def add(module_name):
1203
1204
  click.echo(f"Module {module_name} already exists")
1204
1205
  return
1205
1206
 
1207
+ # Create .cp config file
1206
1208
  template = f"""SOURCE(include/{module_name}.cpp) {module_name}
1207
1209
 
1208
1210
  PUBLIC(
@@ -1214,7 +1216,41 @@ PUBLIC(
1214
1216
  f.write(template)
1215
1217
 
1216
1218
  click.echo(f"Created {cp_file}")
1217
- click.echo(f"Now create include/{module_name}.cpp with your C++ code")
1219
+
1220
+ # Create include/ directory if it doesn't exist
1221
+ include_dir = Path("include")
1222
+ include_dir.mkdir(exist_ok=True)
1223
+
1224
+ # Create .cpp file with sample code
1225
+ cpp_file = include_dir / f"{module_name}.cpp"
1226
+ if not cpp_file.exists():
1227
+ cpp_template = f"""#include <string>
1228
+ #include <vector>
1229
+
1230
+ namespace includecpp {{
1231
+
1232
+ // Example function - returns a greeting
1233
+ std::string example_function(const std::string& name) {{
1234
+ return "Hello, " + name + "!";
1235
+ }}
1236
+
1237
+ // Add more functions here...
1238
+ // int add(int a, int b) {{ return a + b; }}
1239
+ // std::vector<int> range(int n) {{ ... }}
1240
+
1241
+ }} // namespace includecpp
1242
+ """
1243
+ with open(cpp_file, 'w', encoding='utf-8') as f:
1244
+ f.write(cpp_template)
1245
+
1246
+ click.echo(f"Created {cpp_file}")
1247
+ else:
1248
+ click.echo(f"Note: {cpp_file} already exists, skipped")
1249
+
1250
+ click.echo(f"\nNext steps:")
1251
+ click.echo(f" 1. Edit include/{module_name}.cpp to add your functions")
1252
+ click.echo(f" 2. Update plugins/{module_name}.cp to expose functions")
1253
+ click.echo(f" 3. Run: includecpp rebuild")
1218
1254
 
1219
1255
  @cli.command('list')
1220
1256
  def list_modules():
@@ -7479,13 +7515,12 @@ def cssl_exec(path, code):
7479
7515
  # Execute
7480
7516
  click.secho("--- Output ---", fg='green')
7481
7517
  try:
7482
- result = cssl_lang.exec(source)
7518
+ result = cssl_lang.run(source)
7483
7519
 
7484
7520
  # Output is already printed to stdout during execution via runtime.output()
7485
- # No need to print buffer again - this was causing double output
7486
-
7487
- if result is not None:
7488
- click.echo(f"Result: {result}")
7521
+ # Don't print "Result:" automatically - users should use printl() for output
7522
+ # This prevents unwanted output for function calls like: Function();
7523
+ pass
7489
7524
 
7490
7525
  except Exception as e:
7491
7526
  click.secho(f"CSSL Error: {e}", fg='red')
@@ -7534,10 +7569,29 @@ def cssl_makemodule(path, output):
7534
7569
 
7535
7570
 
7536
7571
  @cssl.command(name='doc')
7537
- def cssl_doc():
7538
- """Show CSSL documentation."""
7572
+ @click.argument('search', required=False, default=None)
7573
+ @click.option('--list', '-l', 'list_sections', is_flag=True, help='List all documentation sections')
7574
+ def cssl_doc(search, list_sections):
7575
+ """Show CSSL documentation.
7576
+
7577
+ \b
7578
+ Usage:
7579
+ includecpp cssl doc # Show full documentation
7580
+ includecpp cssl doc "open" # Search for 'open' keyword
7581
+ includecpp cssl doc "$" # Search for shared variable syntax
7582
+ includecpp cssl doc "define" # Search for define keyword
7583
+ includecpp cssl doc --list # List all sections
7584
+
7585
+ \b
7586
+ Examples:
7587
+ includecpp cssl doc "class" # Show OOP/class documentation
7588
+ includecpp cssl doc "json::" # Show JSON functions
7589
+ includecpp cssl doc "this->" # Show this-> keyword usage
7590
+ includecpp cssl doc "map" # Show Map container docs
7591
+ """
7539
7592
  from pathlib import Path as PathLib
7540
7593
  import os
7594
+ import re
7541
7595
 
7542
7596
  # Find the documentation file in the cssl package directory
7543
7597
  cssl_dir = PathLib(__file__).parent.parent / 'core' / 'cssl'
@@ -7557,8 +7611,116 @@ def cssl_doc():
7557
7611
 
7558
7612
  if doc_path.exists():
7559
7613
  content = doc_path.read_text(encoding='utf-8')
7560
- # Use pager for long content
7561
- click.echo_via_pager(content)
7614
+
7615
+ # List sections mode
7616
+ if list_sections:
7617
+ click.secho("CSSL Documentation Sections", fg='cyan', bold=True)
7618
+ click.secho("=" * 40, fg='cyan')
7619
+ sections = re.findall(r'^##\s+(.+)$', content, re.MULTILINE)
7620
+ for i, section in enumerate(sections, 1):
7621
+ click.echo(f" {i:2d}. {section}")
7622
+ click.echo()
7623
+ click.echo("Use: includecpp cssl doc \"<keyword>\" to search")
7624
+ return
7625
+
7626
+ # Search mode
7627
+ if search:
7628
+ click.secho(f"Searching for: '{search}'", fg='cyan', bold=True)
7629
+ click.secho("=" * 50, fg='cyan')
7630
+ click.echo()
7631
+
7632
+ # Split into subsections (### headers) for focused results
7633
+ subsections = re.split(r'(?=^### )', content, flags=re.MULTILINE)
7634
+
7635
+ # Also split into main sections (## headers)
7636
+ main_sections = re.split(r'(?=^## )', content, flags=re.MULTILINE)
7637
+
7638
+ # Find matching subsections (### level) - most focused
7639
+ matching_subsections = []
7640
+ for subsection in subsections:
7641
+ if search.lower() in subsection.lower():
7642
+ # Extract title
7643
+ title_match = re.match(r'^###\s+(.+)$', subsection, re.MULTILINE)
7644
+ if title_match:
7645
+ # Trim subsection to just the content until next ### or ##
7646
+ lines = subsection.split('\n')
7647
+ trimmed_lines = []
7648
+ for line in lines:
7649
+ if line.startswith('## ') and not line.startswith('### '):
7650
+ break
7651
+ trimmed_lines.append(line)
7652
+ matching_subsections.append((title_match.group(1), '\n'.join(trimmed_lines)))
7653
+
7654
+ if matching_subsections:
7655
+ click.secho(f"Found {len(matching_subsections)} matching subsection(s):", fg='green')
7656
+ click.echo()
7657
+
7658
+ # Show focused subsections (limit output)
7659
+ for title, sub_content in matching_subsections[:5]:
7660
+ click.secho(f"### {title}", fg='yellow', bold=True)
7661
+ # Highlight search term in content
7662
+ highlighted = re.sub(
7663
+ f'({re.escape(search)})',
7664
+ click.style(r'\1', fg='green', bold=True),
7665
+ sub_content,
7666
+ flags=re.IGNORECASE
7667
+ )
7668
+ # Limit lines per subsection
7669
+ lines = highlighted.split('\n')
7670
+ if len(lines) > 30:
7671
+ click.echo('\n'.join(lines[:30]))
7672
+ click.secho(f" ... ({len(lines) - 30} more lines)", fg='cyan')
7673
+ else:
7674
+ click.echo(highlighted)
7675
+ click.echo()
7676
+ click.secho("-" * 40, fg='cyan')
7677
+ click.echo()
7678
+
7679
+ if len(matching_subsections) > 5:
7680
+ click.secho(f"... and {len(matching_subsections) - 5} more subsections", fg='cyan')
7681
+ click.echo("Use --list to see all sections")
7682
+ else:
7683
+ # Fall back to main section search (## level)
7684
+ found_sections = []
7685
+ for section in main_sections:
7686
+ if search.lower() in section.lower():
7687
+ title_match = re.match(r'^##\s+(.+)$', section, re.MULTILINE)
7688
+ if title_match:
7689
+ found_sections.append((title_match.group(1), section))
7690
+
7691
+ if found_sections:
7692
+ click.secho(f"Found in {len(found_sections)} section(s):", fg='green')
7693
+ for title, _ in found_sections:
7694
+ click.echo(f" - {title}")
7695
+ click.echo()
7696
+
7697
+ # Show first matching section, trimmed
7698
+ title, section = found_sections[0]
7699
+ click.secho(f"## {title}", fg='yellow', bold=True)
7700
+ highlighted = re.sub(
7701
+ f'({re.escape(search)})',
7702
+ click.style(r'\1', fg='green', bold=True),
7703
+ section,
7704
+ flags=re.IGNORECASE
7705
+ )
7706
+ lines = highlighted.split('\n')
7707
+ if len(lines) > 40:
7708
+ click.echo('\n'.join(lines[:40]))
7709
+ click.secho(f"\n... ({len(lines) - 40} more lines in this section)", fg='cyan')
7710
+ else:
7711
+ click.echo(highlighted)
7712
+ else:
7713
+ click.secho(f"No matches found for '{search}'", fg='yellow')
7714
+ click.echo()
7715
+ click.echo("Try searching for:")
7716
+ click.echo(" - Keywords: class, function, define, open, global, shuffled")
7717
+ click.echo(" - Syntax: $, @, ::, this->, <<==, <==, #$")
7718
+ click.echo(" - Types: string, int, stack, vector, map, json")
7719
+ click.echo()
7720
+ click.echo("Or use: includecpp cssl doc --list")
7721
+ else:
7722
+ # Full documentation mode
7723
+ click.echo_via_pager(content)
7562
7724
  else:
7563
7725
  click.secho("Documentation file not found.", fg='yellow')
7564
7726
  click.echo("Looking for: CSSL_DOCUMENTATION.md")
@@ -7729,6 +7891,423 @@ def cssl_vscode():
7729
7891
  cli.add_command(cssl)
7730
7892
 
7731
7893
 
7894
+ # ============================================================================
7895
+ # VSCODE - Initialize/Update VSCode Configuration
7896
+ # ============================================================================
7897
+
7898
+ @cli.command()
7899
+ @click.option('--force', '-f', is_flag=True, help='Force overwrite existing files')
7900
+ @click.option('--stubs-only', is_flag=True, help='Only update stubs, skip extension files')
7901
+ def vscode(force, stubs_only):
7902
+ """Initialize or update VSCode configuration for IncludeCPP/CSSL.
7903
+
7904
+ Sets up .vscode folder with:
7905
+ - CSSL language support (syntax highlighting, snippets)
7906
+ - Type stubs for builtins (.pyi files)
7907
+ - Auto-generated stubs for your plugins and modules
7908
+
7909
+ \b
7910
+ Usage:
7911
+ includecpp vscode # Initialize .vscode
7912
+ includecpp vscode --force # Force overwrite existing
7913
+ includecpp vscode --stubs-only # Only update stubs
7914
+
7915
+ \b
7916
+ What it creates:
7917
+ .vscode/
7918
+ settings.json - VSCode settings for CSSL
7919
+ cssl/ - CSSL language extension
7920
+ stubs/ - Type stubs for IDE support
7921
+ cssl_builtins.pyi - CSSL builtin functions
7922
+ plugins/ - Stubs for your .cp plugins
7923
+ modules/ - Stubs for your modules
7924
+ """
7925
+ from pathlib import Path as PathLib
7926
+
7927
+ cwd = PathLib.cwd()
7928
+ vscode_dir = cwd / '.vscode'
7929
+ stubs_dir = vscode_dir / 'stubs'
7930
+ plugins_stubs_dir = stubs_dir / 'plugins'
7931
+ modules_stubs_dir = stubs_dir / 'modules'
7932
+ cssl_ext_dir = vscode_dir / 'cssl'
7933
+
7934
+ # Create directories
7935
+ vscode_dir.mkdir(exist_ok=True)
7936
+ stubs_dir.mkdir(exist_ok=True)
7937
+ plugins_stubs_dir.mkdir(exist_ok=True)
7938
+ modules_stubs_dir.mkdir(exist_ok=True)
7939
+
7940
+ click.secho("=" * 60, fg='cyan')
7941
+ click.secho("IncludeCPP VSCode Configuration", fg='cyan', bold=True)
7942
+ click.secho("=" * 60, fg='cyan')
7943
+ click.echo()
7944
+
7945
+ updated_count = 0
7946
+ created_count = 0
7947
+
7948
+ # 1. Copy CSSL extension files (unless stubs-only)
7949
+ if not stubs_only:
7950
+ click.secho("Setting up CSSL language support...", fg='yellow')
7951
+
7952
+ # Find source extension directory
7953
+ source_ext_dir = PathLib(__file__).parent.parent / 'vscode' / 'cssl'
7954
+
7955
+ if source_ext_dir.exists():
7956
+ cssl_ext_dir.mkdir(exist_ok=True)
7957
+
7958
+ # Copy extension files
7959
+ ext_files = [
7960
+ 'language-configuration.json',
7961
+ 'package.json',
7962
+ ]
7963
+
7964
+ for fname in ext_files:
7965
+ src = source_ext_dir / fname
7966
+ dst = cssl_ext_dir / fname
7967
+ if src.exists():
7968
+ if not dst.exists() or force:
7969
+ shutil.copy2(src, dst)
7970
+ created_count += 1
7971
+ click.echo(f" Created: .vscode/cssl/{fname}")
7972
+
7973
+ # Copy syntaxes folder
7974
+ syntaxes_src = source_ext_dir / 'syntaxes'
7975
+ syntaxes_dst = cssl_ext_dir / 'syntaxes'
7976
+ if syntaxes_src.exists():
7977
+ syntaxes_dst.mkdir(exist_ok=True)
7978
+ for f in syntaxes_src.glob('*.json'):
7979
+ dst = syntaxes_dst / f.name
7980
+ if not dst.exists() or force:
7981
+ shutil.copy2(f, dst)
7982
+ created_count += 1
7983
+ click.echo(f" Created: .vscode/cssl/syntaxes/{f.name}")
7984
+
7985
+ # Copy snippets folder
7986
+ snippets_src = source_ext_dir / 'snippets'
7987
+ snippets_dst = cssl_ext_dir / 'snippets'
7988
+ if snippets_src.exists():
7989
+ snippets_dst.mkdir(exist_ok=True)
7990
+ for f in snippets_src.glob('*.json'):
7991
+ dst = snippets_dst / f.name
7992
+ if not dst.exists() or force:
7993
+ shutil.copy2(f, dst)
7994
+ created_count += 1
7995
+ click.echo(f" Created: .vscode/cssl/snippets/{f.name}")
7996
+
7997
+ click.secho(" CSSL extension configured", fg='green')
7998
+ else:
7999
+ click.secho(" Warning: CSSL extension source not found", fg='yellow')
8000
+
8001
+ click.echo()
8002
+
8003
+ # 2. Copy CSSL builtins stub
8004
+ click.secho("Updating type stubs...", fg='yellow')
8005
+
8006
+ builtins_src = PathLib(__file__).parent.parent / 'core' / 'cssl' / 'cssl_builtins.pyi'
8007
+ builtins_dst = stubs_dir / 'cssl_builtins.pyi'
8008
+
8009
+ if builtins_src.exists():
8010
+ if not builtins_dst.exists() or force:
8011
+ shutil.copy2(builtins_src, builtins_dst)
8012
+ created_count += 1
8013
+ click.echo(f" Created: .vscode/stubs/cssl_builtins.pyi")
8014
+ else:
8015
+ # Check if source is newer
8016
+ if builtins_src.stat().st_mtime > builtins_dst.stat().st_mtime:
8017
+ shutil.copy2(builtins_src, builtins_dst)
8018
+ updated_count += 1
8019
+ click.echo(f" Updated: .vscode/stubs/cssl_builtins.pyi")
8020
+ else:
8021
+ click.echo(f" Up-to-date: .vscode/stubs/cssl_builtins.pyi")
8022
+ else:
8023
+ click.secho(" Warning: cssl_builtins.pyi not found in package", fg='yellow')
8024
+
8025
+ # 3. Generate stubs for user plugins (.cp files)
8026
+ click.echo()
8027
+ click.secho("Scanning for plugins...", fg='yellow')
8028
+
8029
+ plugins_dir = cwd / 'plugins'
8030
+ cp_files = []
8031
+
8032
+ # Find all .cp files
8033
+ for pattern in ['*.cp', '**/*.cp']:
8034
+ cp_files.extend(cwd.glob(pattern))
8035
+
8036
+ if plugins_dir.exists():
8037
+ cp_files.extend(plugins_dir.glob('**/*.cp'))
8038
+
8039
+ # Deduplicate
8040
+ cp_files = list(set(cp_files))
8041
+
8042
+ if cp_files:
8043
+ click.echo(f" Found {len(cp_files)} plugin(s)")
8044
+
8045
+ for cp_file in cp_files:
8046
+ stub_name = cp_file.stem + '.pyi'
8047
+ stub_path = plugins_stubs_dir / stub_name
8048
+
8049
+ # Generate stub from .cp file
8050
+ stub_content = _generate_plugin_stub(cp_file)
8051
+
8052
+ if stub_content:
8053
+ # Check if needs update
8054
+ needs_write = not stub_path.exists() or force
8055
+ if stub_path.exists() and not force:
8056
+ existing = stub_path.read_text(encoding='utf-8')
8057
+ if existing != stub_content:
8058
+ needs_write = True
8059
+ updated_count += 1
8060
+ click.echo(f" Updated: .vscode/stubs/plugins/{stub_name}")
8061
+ else:
8062
+ click.echo(f" Up-to-date: .vscode/stubs/plugins/{stub_name}")
8063
+ else:
8064
+ if needs_write:
8065
+ created_count += 1
8066
+ click.echo(f" Created: .vscode/stubs/plugins/{stub_name}")
8067
+
8068
+ if needs_write:
8069
+ stub_path.write_text(stub_content, encoding='utf-8')
8070
+ else:
8071
+ click.echo(" No .cp plugins found")
8072
+
8073
+ # 4. Generate stubs for modules in registry
8074
+ click.echo()
8075
+ click.secho("Scanning for modules...", fg='yellow')
8076
+
8077
+ try:
8078
+ from ..core.cpp_api import CppApi
8079
+ api = CppApi()
8080
+ modules = list(api.registry.keys())
8081
+
8082
+ if modules:
8083
+ click.echo(f" Found {len(modules)} registered module(s)")
8084
+
8085
+ for mod_name in modules:
8086
+ stub_name = mod_name + '.pyi'
8087
+ stub_path = modules_stubs_dir / stub_name
8088
+
8089
+ # Generate stub from module
8090
+ stub_content = _generate_module_stub(api, mod_name)
8091
+
8092
+ if stub_content:
8093
+ needs_write = not stub_path.exists() or force
8094
+ if stub_path.exists() and not force:
8095
+ existing = stub_path.read_text(encoding='utf-8')
8096
+ if existing != stub_content:
8097
+ needs_write = True
8098
+ updated_count += 1
8099
+ click.echo(f" Updated: .vscode/stubs/modules/{stub_name}")
8100
+ else:
8101
+ click.echo(f" Up-to-date: .vscode/stubs/modules/{stub_name}")
8102
+ else:
8103
+ if needs_write:
8104
+ created_count += 1
8105
+ click.echo(f" Created: .vscode/stubs/modules/{stub_name}")
8106
+
8107
+ if needs_write:
8108
+ stub_path.write_text(stub_content, encoding='utf-8')
8109
+ else:
8110
+ click.echo(" No modules registered")
8111
+
8112
+ except Exception as e:
8113
+ click.echo(f" Could not scan modules: {e}")
8114
+
8115
+ # 5. Create/update settings.json
8116
+ if not stubs_only:
8117
+ click.echo()
8118
+ click.secho("Configuring VSCode settings...", fg='yellow')
8119
+
8120
+ settings_path = vscode_dir / 'settings.json'
8121
+ settings = {}
8122
+
8123
+ if settings_path.exists():
8124
+ try:
8125
+ settings = json.loads(settings_path.read_text(encoding='utf-8'))
8126
+ except:
8127
+ pass
8128
+
8129
+ # Add CSSL settings
8130
+ cssl_settings = {
8131
+ "files.associations": {
8132
+ "*.cssl": "cssl",
8133
+ "*.cssl-pl": "cssl",
8134
+ "*.cssl-mod": "cssl",
8135
+ "*.cp": "cpp"
8136
+ },
8137
+ "python.analysis.extraPaths": [
8138
+ ".vscode/stubs"
8139
+ ],
8140
+ "python.autoComplete.extraPaths": [
8141
+ ".vscode/stubs"
8142
+ ]
8143
+ }
8144
+
8145
+ # Merge settings
8146
+ for key, value in cssl_settings.items():
8147
+ if key not in settings:
8148
+ settings[key] = value
8149
+ elif isinstance(value, dict) and isinstance(settings[key], dict):
8150
+ settings[key].update(value)
8151
+ elif isinstance(value, list) and isinstance(settings[key], list):
8152
+ for item in value:
8153
+ if item not in settings[key]:
8154
+ settings[key].append(item)
8155
+
8156
+ settings_path.write_text(json.dumps(settings, indent=4), encoding='utf-8')
8157
+ click.echo(f" Updated: .vscode/settings.json")
8158
+
8159
+ # Summary
8160
+ click.echo()
8161
+ click.secho("=" * 60, fg='cyan')
8162
+ click.secho("Summary", fg='cyan', bold=True)
8163
+ click.secho("=" * 60, fg='cyan')
8164
+ click.echo(f" Created: {created_count} file(s)")
8165
+ click.echo(f" Updated: {updated_count} file(s)")
8166
+ click.echo()
8167
+ click.secho("VSCode configuration complete!", fg='green', bold=True)
8168
+ click.echo()
8169
+ click.echo("Tips:")
8170
+ click.echo(" - Re-run 'includecpp vscode' after adding new plugins")
8171
+ click.echo(" - Use --force to overwrite all files")
8172
+ click.echo(" - Use --stubs-only to only regenerate stubs")
8173
+
8174
+
8175
+ def _generate_plugin_stub(cp_file: Path) -> str:
8176
+ """Generate a .pyi stub file from a .cp plugin file."""
8177
+ try:
8178
+ content = cp_file.read_text(encoding='utf-8')
8179
+ except:
8180
+ return ""
8181
+
8182
+ lines = [
8183
+ f'"""',
8184
+ f'Type stubs for {cp_file.name}',
8185
+ f'Auto-generated by: includecpp vscode',
8186
+ f'"""',
8187
+ f'from typing import Any, Optional, List, Dict, Union',
8188
+ f'',
8189
+ ]
8190
+
8191
+ # Parse function definitions from .cp file
8192
+ # Look for patterns like: type name(params) { or @export type name(params)
8193
+ func_pattern = re.compile(
8194
+ r'(?:@export\s+)?'
8195
+ r'(int|float|string|void|bool|auto|[A-Z]\w*)\s+'
8196
+ r'(\w+)\s*\(([^)]*)\)',
8197
+ re.MULTILINE
8198
+ )
8199
+
8200
+ found_functions = set()
8201
+
8202
+ for match in func_pattern.finditer(content):
8203
+ ret_type, func_name, params = match.groups()
8204
+
8205
+ if func_name in found_functions:
8206
+ continue
8207
+ found_functions.add(func_name)
8208
+
8209
+ # Convert C++ types to Python types
8210
+ py_ret = _cpp_to_python_type(ret_type)
8211
+ py_params = _parse_cpp_params(params)
8212
+
8213
+ lines.append(f'def {func_name}({py_params}) -> {py_ret}:')
8214
+ lines.append(f' """Function from {cp_file.name}"""')
8215
+ lines.append(f' ...')
8216
+ lines.append(f'')
8217
+
8218
+ if len(found_functions) == 0:
8219
+ lines.append(f'# No exported functions found in {cp_file.name}')
8220
+ lines.append(f'')
8221
+
8222
+ return '\n'.join(lines)
8223
+
8224
+
8225
+ def _generate_module_stub(api, module_name: str) -> str:
8226
+ """Generate a .pyi stub file from a registered module."""
8227
+ lines = [
8228
+ f'"""',
8229
+ f'Type stubs for module: {module_name}',
8230
+ f'Auto-generated by: includecpp vscode',
8231
+ f'"""',
8232
+ f'from typing import Any, Optional, List, Dict, Union',
8233
+ f'',
8234
+ ]
8235
+
8236
+ try:
8237
+ module = api.registry.get(module_name)
8238
+ if not module:
8239
+ return ""
8240
+
8241
+ # Get module functions
8242
+ funcs = getattr(module, '_functions', {})
8243
+ if not funcs and hasattr(module, '__dict__'):
8244
+ # Try to introspect
8245
+ for name, obj in module.__dict__.items():
8246
+ if callable(obj) and not name.startswith('_'):
8247
+ lines.append(f'def {name}(*args: Any, **kwargs: Any) -> Any:')
8248
+ lines.append(f' """Function from {module_name}"""')
8249
+ lines.append(f' ...')
8250
+ lines.append(f'')
8251
+
8252
+ for func_name, func_info in funcs.items():
8253
+ ret_type = func_info.get('return_type', 'Any')
8254
+ params = func_info.get('params', [])
8255
+
8256
+ py_ret = _cpp_to_python_type(ret_type)
8257
+ py_params = ', '.join([f'{p["name"]}: {_cpp_to_python_type(p.get("type", "Any"))}' for p in params]) if params else ''
8258
+
8259
+ lines.append(f'def {func_name}({py_params}) -> {py_ret}:')
8260
+ lines.append(f' """Function from {module_name}"""')
8261
+ lines.append(f' ...')
8262
+ lines.append(f'')
8263
+
8264
+ except Exception:
8265
+ lines.append(f'# Could not introspect module: {module_name}')
8266
+ lines.append(f'')
8267
+
8268
+ return '\n'.join(lines)
8269
+
8270
+
8271
+ def _cpp_to_python_type(cpp_type: str) -> str:
8272
+ """Convert C++ type to Python type annotation."""
8273
+ type_map = {
8274
+ 'void': 'None',
8275
+ 'int': 'int',
8276
+ 'float': 'float',
8277
+ 'double': 'float',
8278
+ 'string': 'str',
8279
+ 'bool': 'bool',
8280
+ 'auto': 'Any',
8281
+ 'char*': 'str',
8282
+ 'const char*': 'str',
8283
+ }
8284
+ return type_map.get(cpp_type.strip(), 'Any')
8285
+
8286
+
8287
+ def _parse_cpp_params(params_str: str) -> str:
8288
+ """Parse C++ parameter list to Python type annotations."""
8289
+ if not params_str.strip():
8290
+ return ''
8291
+
8292
+ params = []
8293
+ for param in params_str.split(','):
8294
+ param = param.strip()
8295
+ if not param:
8296
+ continue
8297
+
8298
+ # Parse "type name" or "type name = default"
8299
+ parts = param.split('=')[0].strip().split()
8300
+ if len(parts) >= 2:
8301
+ param_type = parts[-2]
8302
+ param_name = parts[-1].strip('&*')
8303
+ py_type = _cpp_to_python_type(param_type)
8304
+ params.append(f'{param_name}: {py_type}')
8305
+ elif len(parts) == 1:
8306
+ params.append(f'arg: Any')
8307
+
8308
+ return ', '.join(params)
8309
+
8310
+
7732
8311
  # ============================================================================
7733
8312
  # Conditional Registration of Experimental Commands
7734
8313
  # ============================================================================