IncludeCPP 3.3.11__py3-none-any.whl → 3.4.2__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.
@@ -10,6 +10,22 @@ import urllib.error
10
10
  from pathlib import Path
11
11
  from .config_parser import CppProjectConfig
12
12
 
13
+
14
+ def _is_experimental_enabled() -> bool:
15
+ """Check if experimental features (cppy, ai) are enabled."""
16
+ config_path = Path.home() / '.includecpp' / '.secret'
17
+ if config_path.exists():
18
+ try:
19
+ config = json.loads(config_path.read_text())
20
+ return config.get('experimental_features', False)
21
+ except:
22
+ pass
23
+ return False
24
+
25
+
26
+ # Check once at module load time
27
+ _EXPERIMENTAL_ENABLED = _is_experimental_enabled()
28
+
13
29
  # Unicode fallback for Windows terminals with limited encoding
14
30
  def _supports_unicode():
15
31
  """Check if terminal supports Unicode output."""
@@ -49,7 +65,7 @@ def _safe_echo(text, **kwargs):
49
65
  ('╔', '+'), ('╗', '+'), ('╚', '+'), ('╝', '+'),
50
66
  ('┌', '+'), ('┐', '+'), ('└', '+'), ('┘', '+'),
51
67
  ('═', '-'), ('─', '-'), ('║', '|'), ('│', '|'),
52
- ('✗', '[X]'), ('•', '*'),
68
+ ('✗', '[X]'), ('✓', '[OK]'), ('❌', '[X]'), ('•', '*'),
53
69
  ('→', '->'), ('▶', '>'), ('◆', '*'),
54
70
  ('\u2011', '-'), ('\u2010', '-'),
55
71
  ('\u2013', '-'), ('\u2014', '--'),
@@ -133,13 +149,13 @@ def _render_readme_with_colors(readme_text):
133
149
 
134
150
  def _show_changelog():
135
151
  """Extract and display changelog from README."""
136
- click.echo("=" * 70)
137
- click.secho("IncludeCPP Changelog", fg='cyan', bold=True)
138
- click.echo("=" * 70)
139
- click.echo()
152
+ _safe_echo("=" * 70)
153
+ _safe_echo("IncludeCPP Changelog", fg='cyan', bold=True)
154
+ _safe_echo("=" * 70)
155
+ _safe_echo("")
140
156
 
141
157
  try:
142
- click.echo(" Fetching changelog from PyPI...", nl=False)
158
+ _safe_echo(" Fetching changelog from PyPI...", nl=False)
143
159
  req = urllib.request.Request(
144
160
  "https://pypi.org/pypi/IncludeCPP/json",
145
161
  headers={"User-Agent": "IncludeCPP-CLI"}
@@ -150,12 +166,12 @@ def _show_changelog():
150
166
  data = json.loads(raw_data)
151
167
  description = data.get('info', {}).get('description', '')
152
168
  version = data.get('info', {}).get('version', 'unknown')
153
- click.secho(" OK", fg='green')
154
- click.echo()
169
+ _safe_echo(" OK", fg='green')
170
+ _safe_echo("")
155
171
 
156
172
  if description:
157
- click.secho(f"Current Version: {version}", fg='green', bold=True)
158
- click.echo()
173
+ _safe_echo(f"Current Version: {version}", fg='green', bold=True)
174
+ _safe_echo("")
159
175
 
160
176
  lines = description.split('\n')
161
177
  in_changelog = False
@@ -174,23 +190,23 @@ def _show_changelog():
174
190
  for line in changelog_lines:
175
191
  stripped = line.strip()
176
192
  if stripped.startswith('## '):
177
- click.secho(stripped[3:], fg='yellow', bold=True)
193
+ _safe_echo(stripped[3:], fg='yellow', bold=True)
178
194
  elif stripped.startswith('- '):
179
- click.echo(" " + stripped)
195
+ _safe_echo(" " + stripped)
180
196
  elif stripped:
181
- click.echo(" " + stripped)
197
+ _safe_echo(" " + stripped)
182
198
  else:
183
- click.secho("No changelog found in README.", fg='yellow')
199
+ _safe_echo("No changelog found in README.", fg='yellow')
184
200
  else:
185
- click.secho("No documentation available.", fg='yellow')
201
+ _safe_echo("No documentation available.", fg='yellow')
186
202
 
187
203
  except Exception as e:
188
- click.secho(" FAILED", fg='red')
189
- click.echo()
190
- click.secho("Could not fetch changelog: " + str(e), fg='red', err=True)
204
+ _safe_echo(" FAILED", fg='red')
205
+ _safe_echo("")
206
+ _safe_echo("Could not fetch changelog: " + str(e), fg='red', err=True)
191
207
 
192
- click.echo()
193
- click.echo("=" * 70)
208
+ _safe_echo("")
209
+ _safe_echo("=" * 70)
194
210
 
195
211
 
196
212
  @click.group(invoke_without_command=True)
@@ -4420,8 +4436,10 @@ def fix(module_name, all_modules, exclude, undo, auto_fix, verbose, use_ai):
4420
4436
  if var_decl:
4421
4437
  var_name = var_decl.group(1)
4422
4438
  # Check if variable appears again in rest of content
4439
+ # v3.4.1: Use word boundaries to avoid false positives from substring matches
4423
4440
  rest_content = '\n'.join(lines[i:])
4424
- if rest_content.count(var_name) <= 1:
4441
+ uses = len(re.findall(rf'\b{re.escape(var_name)}\b', rest_content))
4442
+ if uses <= 1:
4425
4443
  add_issue('info', 'Mistake', file_path, i, 'C003',
4426
4444
  f"Variable '{var_name}' may be unused",
4427
4445
  "Remove unused variable or mark as [[maybe_unused]]")
@@ -4816,7 +4834,8 @@ def fix(module_name, all_modules, exclude, undo, auto_fix, verbose, use_ai):
4816
4834
  click.echo(f"{'='*60}")
4817
4835
 
4818
4836
 
4819
- @cli.group(invoke_without_command=True)
4837
+ # AI group - conditionally registered based on experimental_features setting
4838
+ @click.group(invoke_without_command=True)
4820
4839
  @click.option('--info', is_flag=True, help='Show AI status and usage')
4821
4840
  @click.pass_context
4822
4841
  def ai(ctx, info):
@@ -5306,7 +5325,7 @@ def ai_edit(task, module_name, files, all_modules, exclude, think_mode, think_tw
5306
5325
  question = None # Continuation doesn't ask more questions
5307
5326
 
5308
5327
  if not changes:
5309
- verbose.status("No changes needed", success=True)
5328
+ verbose.status("No changes needed", phase='complete')
5310
5329
  verbose.end(success=True, message="No changes required")
5311
5330
  return
5312
5331
 
@@ -5520,7 +5539,7 @@ def ai_generate(task, files, think_mode, think_twice, think_three, use_websearch
5520
5539
  verbose.api_call(endpoint='chat/completions', tokens_out=len(str(changes)) // 4 if changes else len(response) // 4)
5521
5540
 
5522
5541
  if not changes:
5523
- verbose.status("No file changes needed", success=True)
5542
+ verbose.status("No file changes needed", phase='complete')
5524
5543
  # Show response if any
5525
5544
  if response:
5526
5545
  _safe_echo(f"\n{response[:2000]}")
@@ -5598,7 +5617,7 @@ def ai_generate(task, files, think_mode, think_twice, think_three, use_websearch
5598
5617
  shell=True, capture_output=True, text=True, cwd=project_root
5599
5618
  )
5600
5619
  if result.returncode == 0:
5601
- verbose.status("Plugin generated", success=True)
5620
+ verbose.status("Plugin generated", phase='complete')
5602
5621
  else:
5603
5622
  verbose.error(f"Plugin error: {result.stderr[:100]}")
5604
5623
  verbose.end(success=False, message="Plugin generation failed")
@@ -5611,7 +5630,7 @@ def ai_generate(task, files, think_mode, think_twice, think_three, use_websearch
5611
5630
  shell=True, capture_output=True, text=True, cwd=project_root
5612
5631
  )
5613
5632
  if result.returncode == 0:
5614
- verbose.status("Build successful", success=True)
5633
+ verbose.status("Build successful", phase='complete')
5615
5634
  else:
5616
5635
  verbose.error(f"Build error: {result.stderr[:100]}")
5617
5636
 
@@ -5719,7 +5738,7 @@ def ai_optimize(module_name, files, agent_task, auto_confirm, no_verbose):
5719
5738
 
5720
5739
  verbose.phase('analyzing', 'Analyzing code for optimization opportunities')
5721
5740
  for fp in files_to_optimize.keys():
5722
- verbose.status(f"Scanning {Path(fp).name}", success=True)
5741
+ verbose.status(f"Scanning {Path(fp).name}", phase='analyzing')
5723
5742
 
5724
5743
  verbose.phase('thinking', 'AI is analyzing and planning optimizations')
5725
5744
  verbose.api_call(endpoint='chat/completions', tokens_in=total_lines * 4)
@@ -5734,7 +5753,7 @@ def ai_optimize(module_name, files, agent_task, auto_confirm, no_verbose):
5734
5753
  verbose.api_call(endpoint='chat/completions', tokens_out=len(str(changes)) // 4 if changes else 0)
5735
5754
 
5736
5755
  if not changes:
5737
- verbose.status("No optimizations needed", success=True)
5756
+ verbose.status("No optimizations needed", phase='complete')
5738
5757
  verbose.end(success=True, message="Code is already optimal")
5739
5758
  return
5740
5759
 
@@ -5787,7 +5806,7 @@ def ai_optimize(module_name, files, agent_task, auto_confirm, no_verbose):
5787
5806
  click.secho("CONFIRM REQUIRED:", fg='yellow', bold=True)
5788
5807
  click.echo(f"\n{confirm}\n")
5789
5808
  if not click.confirm("Apply this change?"):
5790
- verbose.status(f"Skipped: {change['file']}", success=False)
5809
+ verbose.status(f"Skipped: {change['file']}", phase='warning')
5791
5810
  changes = [c for c in changes if c != change]
5792
5811
  if not click.confirm("Apply all changes?"):
5793
5812
  verbose.warning("Changes aborted by user")
@@ -5848,11 +5867,59 @@ def settings():
5848
5867
  click.echo(" includecpp ai key <YOUR_KEY>")
5849
5868
 
5850
5869
 
5870
+ @click.command()
5871
+ @click.option('--path', '-p', type=click.Path(), default=None,
5872
+ help='Path to project directory (default: current directory)')
5873
+ def project(path):
5874
+ """Open the Project Interface with CodeMaker.
5875
+
5876
+ Opens a professional visual mindmap tool for planning and designing
5877
+ your C++ project structure.
5878
+
5879
+ Features:
5880
+ - Visual node-based system design
5881
+ - Class, Function, Object, and Definition nodes
5882
+ - Connectable nodes with bezier curves
5883
+ - Pan/zoom navigation (middle mouse button)
5884
+ - Auto-save functionality
5885
+ - .ma map file management
5886
+
5887
+ Controls:
5888
+ - Right-click canvas: Create new nodes
5889
+ - Right-click node: Edit/Connect/Delete
5890
+ - Middle mouse + drag: Pan view
5891
+ - Middle mouse + scroll: Zoom in/out
5892
+ - ESC: Cancel connection mode
5893
+ - DELETE: Remove selected nodes
5894
+
5895
+ Requires: pip install PyQt6
5896
+ """
5897
+ from pathlib import Path as PathLib
5898
+ project_path = PathLib(path) if path else PathLib.cwd()
5899
+
5900
+ if not project_path.exists():
5901
+ click.secho(f"Path does not exist: {project_path}", fg='red', err=True)
5902
+ return
5903
+
5904
+ try:
5905
+ from ..core.project_ui import show_project, PYQT_AVAILABLE
5906
+ if not PYQT_AVAILABLE:
5907
+ click.secho("PyQt6 not installed.", fg='red', err=True)
5908
+ click.echo("Install with: pip install PyQt6")
5909
+ return
5910
+ success, msg = show_project(str(project_path))
5911
+ if not success:
5912
+ click.secho(msg, fg='red', err=True)
5913
+ except Exception as e:
5914
+ click.secho(f"Error opening project interface: {e}", fg='red', err=True)
5915
+
5916
+
5851
5917
  # ============================================================================
5852
- # CPPY - Code Conversion Tools
5918
+ # CPPY - Code Conversion Tools (Experimental)
5853
5919
  # ============================================================================
5854
5920
 
5855
- @cli.group(invoke_without_command=True)
5921
+ # CPPY group - conditionally registered based on experimental_features setting
5922
+ @click.group(invoke_without_command=True)
5856
5923
  @click.pass_context
5857
5924
  def cppy(ctx):
5858
5925
  """Code conversion tools for Python <-> C++.
@@ -6358,6 +6425,38 @@ def _convert_with_ai(content: str, module_name: str, source_file,
6358
6425
  return _convert_to_python(content, module_name, source_file, output_dir, verbose)
6359
6426
 
6360
6427
  ai_verbose.api_call(endpoint='chat/completions', tokens_out=len(response) // 4)
6428
+
6429
+ # Check if AI needs clarification on unconvertible modules
6430
+ if 'CLARIFICATION_NEEDED:' in response:
6431
+ ai_verbose.phase('clarification', 'AI needs clarification')
6432
+ clarification_match = re.search(r'CLARIFICATION_NEEDED:\n((?:- .+\n?)+)', response)
6433
+ if clarification_match:
6434
+ questions = clarification_match.group(1).strip()
6435
+ click.echo("\n" + "=" * 60)
6436
+ click.secho("AI NEEDS CLARIFICATION:", fg='yellow', bold=True)
6437
+ click.echo(questions)
6438
+ click.echo("=" * 60)
6439
+ user_input = click.prompt("Your response (or 'skip' to use defaults)")
6440
+ if user_input.lower() != 'skip':
6441
+ # Re-run AI with user's clarification
6442
+ clarified_prompt = ai_prompt + f"\n\nUSER CLARIFICATION:\n{user_input}\n\nNow convert with this clarification:"
6443
+ success, response, _ = ai_manager.generate(
6444
+ task=clarified_prompt,
6445
+ files={str(source_file): content},
6446
+ project_root=project_root,
6447
+ think=think,
6448
+ think_twice=think_twice,
6449
+ think_three=think_three,
6450
+ use_websearch=use_websearch,
6451
+ skip_tool_execution=True
6452
+ )
6453
+ if not success:
6454
+ ai_verbose.warning("Clarified conversion failed, using standard converter")
6455
+ if to_cpp:
6456
+ return _convert_to_cpp(content, module_name, source_file, output_dir, no_header, namespace, verbose)
6457
+ else:
6458
+ return _convert_to_python(content, module_name, source_file, output_dir, verbose)
6459
+
6361
6460
  ai_verbose.phase('parsing', 'Extracting converted code from AI response')
6362
6461
 
6363
6462
  # Parse AI response
@@ -6367,7 +6466,7 @@ def _convert_with_ai(content: str, module_name: str, source_file,
6367
6466
  converted_code = _extract_ai_converted_code(response, to_cpp, module_name, namespace)
6368
6467
 
6369
6468
  if not converted_code:
6370
- ai_verbose.status("Using hybrid conversion (AI + standard)", success=True)
6469
+ ai_verbose.status("Using hybrid conversion (AI + standard)", phase='complete')
6371
6470
  # Fall back to standard conversion with AI enhancements
6372
6471
  if to_cpp:
6373
6472
  converter = PythonToCppConverter()
@@ -6460,22 +6559,95 @@ def _convert_with_ai(content: str, module_name: str, source_file,
6460
6559
  return result
6461
6560
 
6462
6561
 
6562
+ def _get_includecpp_readme() -> str:
6563
+ """Load IncludeCPP README.md for AI context."""
6564
+ try:
6565
+ import importlib.resources
6566
+ import includecpp
6567
+ readme_path = Path(includecpp.__file__).parent.parent / 'README.md'
6568
+ if readme_path.exists():
6569
+ readme = readme_path.read_text(encoding='utf-8')
6570
+ # Truncate to first sections (keep it focused)
6571
+ sections = readme.split('# Changelog')[0] # Everything before changelog
6572
+ return sections[:8000] # Limit to ~8k chars
6573
+ except Exception:
6574
+ pass
6575
+ return ""
6576
+
6463
6577
  def _build_cppy_ai_prompt(source: str, mode: str, module_name: str, namespace: str, analysis: dict, no_header: bool = False) -> str:
6464
6578
  """Build AI prompt with IncludeCPP-specific instructions."""
6465
6579
 
6580
+ # Load README for AI context (dynamically, not hardcoded)
6581
+ readme_context = _get_includecpp_readme()
6582
+ includecpp_docs = ""
6583
+ if readme_context:
6584
+ includecpp_docs = f'''
6585
+ === INCLUDECPP DOCUMENTATION (from README.md) ===
6586
+ {readme_context}
6587
+ === END DOCUMENTATION ===
6588
+ '''
6589
+
6590
+ # Critical file extension rules
6591
+ file_rules = f'''
6592
+ CRITICAL FILE EXTENSION RULES:
6593
+ - C++ implementation file MUST be: {module_name}.cpp (NOT .cp, NOT .cc, NOT .cxx)
6594
+ - C++ header file MUST be: {module_name}.h (NOT .hpp, NOT .hh)
6595
+ - Python file MUST be: {module_name}.py
6596
+ FOLLOW THESE EXTENSIONS EXACTLY.
6597
+ '''
6598
+
6599
+ # Unconvertible module handling
6600
+ unconvertible_info = ""
6601
+ for item, reason, line in getattr(analysis.get('converter', None), 'unconvertible', []) if isinstance(analysis, dict) else []:
6602
+ unconvertible_info += f"- Line {line}: {item} ({reason})\n"
6603
+
6604
+ unconvertible_guidance = ""
6605
+ if unconvertible_info:
6606
+ unconvertible_guidance = f'''
6607
+ UNCONVERTIBLE MODULES DETECTED:
6608
+ {unconvertible_info}
6609
+
6610
+ For unconvertible modules (tkinter, pygame, ursina, etc.):
6611
+ 1. COMMENT OUT: Add /* UNCONVERTIBLE: tkinter */ comment
6612
+ 2. ALTERNATIVE: Suggest C++ alternatives (Qt for GUI, SDL2 for games, etc.)
6613
+ 3. SKIP: Remove the code with explanation comment
6614
+
6615
+ If you need clarification, output:
6616
+ CLARIFICATION_NEEDED:
6617
+ - <what you need clarified>
6618
+ '''
6619
+
6466
6620
  if mode == 'py_to_cpp':
6467
6621
  direction = "Python to C++"
6468
6622
  source_lang = "python"
6469
6623
  target_lang = "cpp"
6470
6624
 
6625
+ # CRITICAL: No pybind11 - IncludeCPP handles bindings automatically
6626
+ includecpp_note = '''
6627
+ **CRITICAL - IncludeCPP HANDLES BINDINGS AUTOMATICALLY:**
6628
+ - DO NOT include pybind11 headers
6629
+ - DO NOT write PYBIND11_MODULE macros
6630
+ - DO NOT use py:: namespace or py::object
6631
+ - Just write CLEAN C++ code in namespace includecpp
6632
+ - IncludeCPP will auto-generate Python bindings via: includecpp plugin <name> <file.cpp>
6633
+
6634
+ The user runs:
6635
+ 1. includecpp cppy convert file.py --cpp (generates .cpp)
6636
+ 2. includecpp plugin mymod file.cpp (auto-generates pybind11 bindings)
6637
+ 3. includecpp rebuild (compiles to Python module)
6638
+ '''
6639
+
6471
6640
  if no_header:
6472
6641
  # No header mode - put everything in .cpp
6473
6642
  specific_rules = f'''
6474
6643
  INCLUDECPP-SPECIFIC REQUIREMENTS:
6475
6644
  1. ALL code MUST be wrapped in: namespace {namespace} {{ ... }}
6476
- 2. DO NOT create a header file - put ALL code in the .cpp file only
6477
- 3. Include declarations at the top of the .cpp file (no separate .h)
6478
- 4. For Python features without C++ equivalent, use pybind11 wrappers
6645
+ 2. **CRITICAL: DO NOT create a header file** - --no-h flag is set
6646
+ 3. Put ALL declarations AND implementations in the .cpp file only
6647
+ 4. NO separate .h file should be created
6648
+ {includecpp_note}
6649
+
6650
+ {file_rules}
6479
6651
 
6480
6652
  TYPE CONVERSIONS:
6481
6653
  - int -> int
@@ -6487,8 +6659,14 @@ TYPE CONVERSIONS:
6487
6659
  - Set[T] -> std::unordered_set<T>
6488
6660
  - Optional[T] -> std::optional<T>
6489
6661
  - Callable -> std::function<R(Args...)>
6490
- - Any -> auto or py::object
6491
- '''
6662
+ - bytes -> std::vector<uint8_t>
6663
+
6664
+ GENERIC/TEMPLATE FUNCTIONS:
6665
+ - For functions that accept any type (like choices), use C++ templates:
6666
+ template<typename T> T getRandomChoice(const std::vector<T>& choices);
6667
+ - NOT: auto getRandomChoice(const std::vector<auto>& choices) // INVALID!
6668
+
6669
+ {unconvertible_guidance}'''
6492
6670
  else:
6493
6671
  # Normal mode with header
6494
6672
  specific_rules = f'''
@@ -6497,22 +6675,30 @@ INCLUDECPP-SPECIFIC REQUIREMENTS:
6497
6675
  2. Create BOTH .cpp implementation AND .h header file
6498
6676
  3. Use #include "{module_name}.h" at top of .cpp file
6499
6677
  4. Header guard: #ifndef {module_name.upper()}_H / #define / #endif
6500
- 5. For Python features without C++ equivalent, use pybind11 wrappers:
6501
- - Generators: Use callback pattern or py::iterator
6502
- - Dynamic attributes: Use py::object
6503
- - Context managers: Use RAII pattern
6504
- - Duck typing: Use templates with concepts
6678
+ {includecpp_note}
6679
+
6680
+ {file_rules}
6505
6681
 
6506
- PYBIND11 WRAPPER PATTERN (for unconvertible features):
6682
+ GENERIC/TEMPLATE FUNCTIONS:
6683
+ - For functions that accept any type, use C++ templates:
6684
+ template<typename T> T getRandomChoice(const std::vector<T>& choices);
6685
+ - NOT: auto getRandomChoice(const std::vector<auto>& choices) // INVALID!
6686
+
6687
+ TEMPLATE EXAMPLE (for generic functions):
6507
6688
  ```cpp
6508
- #include <pybind11/pybind11.h>
6509
- namespace py = pybind11;
6689
+ // In header (.h):
6690
+ template<typename T>
6691
+ T getRandomChoice(const std::vector<T>& choices);
6510
6692
 
6511
- // For Python module calls that can't be converted:
6512
- py::object call_python_func(const std::string& module, const std::string& func) {{
6513
- py::module_ mod = py::module_::import(module.c_str());
6514
- return mod.attr(func.c_str())();
6693
+ // In implementation (.cpp):
6694
+ template<typename T>
6695
+ T Manager::getRandomChoice(const std::vector<T>& choices) {{
6696
+ // implementation
6515
6697
  }}
6698
+ // Explicit instantiations for common types:
6699
+ template int Manager::getRandomChoice<int>(const std::vector<int>&);
6700
+ template double Manager::getRandomChoice<double>(const std::vector<double>&);
6701
+ template std::string Manager::getRandomChoice<std::string>(const std::vector<std::string>&);
6516
6702
  ```
6517
6703
 
6518
6704
  TYPE CONVERSIONS:
@@ -6520,13 +6706,14 @@ TYPE CONVERSIONS:
6520
6706
  - float -> double
6521
6707
  - str -> std::string
6522
6708
  - bool -> bool
6709
+ - bytes -> std::vector<uint8_t>
6523
6710
  - List[T] -> std::vector<T>
6524
6711
  - Dict[K,V] -> std::unordered_map<K,V>
6525
6712
  - Set[T] -> std::unordered_set<T>
6526
6713
  - Optional[T] -> std::optional<T>
6527
6714
  - Callable -> std::function<R(Args...)>
6528
- - Any -> auto or py::object
6529
- '''
6715
+
6716
+ {unconvertible_guidance}'''
6530
6717
  else:
6531
6718
  direction = "C++ to Python"
6532
6719
  source_lang = "cpp"
@@ -6559,37 +6746,60 @@ TYPE CONVERSIONS:
6559
6746
  # Build output format based on mode and no_header flag
6560
6747
  if mode == 'py_to_cpp':
6561
6748
  if no_header:
6562
- output_format = f'''OUTPUT FORMAT (NO HEADER - .cpp only):
6563
- 1. Output ONLY the .cpp file content:
6749
+ output_format = f'''OUTPUT FORMAT (--no-h flag is set, NO HEADER FILE):
6750
+ **CRITICAL**: Output ONLY a single .cpp file. NO .h file allowed.
6751
+
6752
+ Output the implementation file (extension MUST be .cpp):
6564
6753
  ```cpp
6565
6754
  // {module_name}.cpp
6566
- <full cpp content with all declarations and implementations>
6755
+ #include <string>
6756
+ #include <vector>
6757
+ // ... other includes ...
6758
+
6759
+ namespace {namespace} {{
6760
+
6761
+ // All declarations AND implementations go here
6762
+ // No forward declarations needed since everything is in one file
6763
+
6764
+ }} // namespace {namespace}
6567
6765
  ```
6568
6766
 
6569
- IMPORTANT: Do NOT output a header file. Put everything in .cpp.'''
6767
+ REMEMBER:
6768
+ - File extension is .cpp (NOT .cp, NOT .cc)
6769
+ - DO NOT create a .h header file
6770
+ - Put ALL code in the single .cpp file'''
6570
6771
  else:
6571
6772
  output_format = f'''OUTPUT FORMAT (with header):
6572
- 1. First output the .cpp file content:
6773
+ 1. First output the implementation file (extension MUST be .cpp):
6573
6774
  ```cpp
6574
6775
  // {module_name}.cpp
6575
- <full cpp content>
6776
+ #include "{module_name}.h"
6777
+ // ... implementations ...
6576
6778
  ```
6577
6779
 
6578
- 2. Then the header file (REQUIRED):
6780
+ 2. Then output the header file (extension MUST be .h):
6579
6781
  ```cpp
6580
6782
  // {module_name}.h
6581
- <full header content>
6582
- ```'''
6783
+ #ifndef {module_name.upper()}_H
6784
+ #define {module_name.upper()}_H
6785
+ // ... declarations ...
6786
+ #endif // {module_name.upper()}_H
6787
+ ```
6788
+
6789
+ REMEMBER:
6790
+ - Implementation file extension is .cpp (NOT .cp, NOT .cc)
6791
+ - Header file extension is .h (NOT .hpp, NOT .hh)'''
6583
6792
  else:
6584
6793
  output_format = f'''OUTPUT FORMAT:
6585
- Output the Python file:
6794
+ Output the Python file (extension MUST be .py):
6586
6795
  ```python
6587
6796
  # {module_name}.py
6588
6797
  <full python content>
6589
6798
  ```'''
6590
6799
 
6591
6800
  prompt = f'''Convert the following {direction}.
6592
-
6801
+ You are IncludeCPP's AI code converter. Follow all requirements precisely.
6802
+ {includecpp_docs}
6593
6803
  MODULE NAME: {module_name}
6594
6804
  NAMESPACE: {namespace}
6595
6805
  {specific_rules}
@@ -6602,6 +6812,10 @@ SOURCE CODE ({source_lang}):
6602
6812
 
6603
6813
  {output_format}
6604
6814
 
6815
+ If you need clarification about how to convert specific modules or patterns:
6816
+ CLARIFICATION_NEEDED:
6817
+ - <specific question about conversion approach>
6818
+
6605
6819
  At the end, list any API changes:
6606
6820
  API_CHANGES:
6607
6821
  - <change description>
@@ -6921,5 +7135,394 @@ def cppy_types():
6921
7135
  click.echo("=" * 60)
6922
7136
 
6923
7137
 
7138
+ # ============================================================================
7139
+ # EXEC - Interactive Code Execution
7140
+ # ============================================================================
7141
+
7142
+ @cli.command()
7143
+ @click.argument('lang', type=click.Choice(['py', 'cpp', 'python', 'c++']))
7144
+ @click.argument('path', required=False, type=click.Path())
7145
+ @click.option('--all', 'import_all', is_flag=True, help='Import all available modules')
7146
+ def exec(lang, path, import_all):
7147
+ """Execute code interactively for quick testing.
7148
+
7149
+ Run Python or C++ code snippets without creating files.
7150
+ Perfect for testing your IncludeCPP modules quickly.
7151
+
7152
+ \b
7153
+ Usage:
7154
+ includecpp exec py # Interactive Python
7155
+ includecpp exec cpp # Interactive C++
7156
+ includecpp exec py mymodule # Auto-import mymodule
7157
+ includecpp exec py plugins/x.cp # Auto-import from plugin
7158
+ includecpp exec py --all # Import all modules
7159
+
7160
+ \b
7161
+ Controls:
7162
+ ENTER = Add new line
7163
+ Empty line = Execute code (press ENTER twice)
7164
+ CTRL+C = Cancel
7165
+
7166
+ \b
7167
+ Examples:
7168
+ $ includecpp exec py fast_math
7169
+ >>> x = fast_math.add(1, 2)
7170
+ >>> print(x)
7171
+ >>>
7172
+ 3
7173
+ """
7174
+ import sys
7175
+ import subprocess
7176
+ import tempfile
7177
+ from pathlib import Path as PathLib
7178
+
7179
+ # Normalize language
7180
+ is_python = lang in ('py', 'python')
7181
+ lang_name = 'Python' if is_python else 'C++'
7182
+
7183
+ # Build imports/includes
7184
+ imports = []
7185
+ includes = []
7186
+
7187
+ if import_all:
7188
+ # Import all available modules
7189
+ try:
7190
+ from ..core.cpp_api import CppApi
7191
+ api = CppApi()
7192
+ modules = list(api.registry.keys())
7193
+ if is_python:
7194
+ for mod in modules:
7195
+ imports.append(f'from includecpp import {mod}')
7196
+ if modules:
7197
+ click.secho(f"Auto-importing {len(modules)} modules: {', '.join(modules)}", fg='cyan')
7198
+ else:
7199
+ for mod in modules:
7200
+ includes.append(f'#include "{mod}.h"')
7201
+ if modules:
7202
+ click.secho(f"Auto-including {len(modules)} headers", fg='cyan')
7203
+ except Exception:
7204
+ click.secho("Warning: Could not load module registry", fg='yellow')
7205
+
7206
+ elif path:
7207
+ path_obj = PathLib(path)
7208
+ module_name = None
7209
+
7210
+ # Check if it's a .cp plugin file
7211
+ if path.endswith('.cp') or '/plugins/' in path or '\\plugins\\' in path:
7212
+ # Extract module name from plugin path
7213
+ module_name = path_obj.stem
7214
+ elif path_obj.exists():
7215
+ # It's a file path
7216
+ module_name = path_obj.stem
7217
+ else:
7218
+ # Assume it's a module name directly
7219
+ module_name = path
7220
+
7221
+ if module_name:
7222
+ if is_python:
7223
+ imports.append(f'from includecpp import {module_name}')
7224
+ click.secho(f"Auto-importing: {module_name}", fg='cyan')
7225
+ else:
7226
+ includes.append(f'#include "{module_name}.h"')
7227
+ click.secho(f"Auto-including: {module_name}.h", fg='cyan')
7228
+
7229
+ # Show header
7230
+ click.echo()
7231
+ click.secho(f"=== IncludeCPP {lang_name} REPL ===", fg='cyan', bold=True)
7232
+ click.echo("Enter code line by line. Press ENTER on empty line to execute.")
7233
+ click.echo("Press CTRL+C to cancel.")
7234
+ click.echo()
7235
+
7236
+ # Show pre-loaded imports
7237
+ if imports:
7238
+ click.secho("Pre-loaded:", fg='green')
7239
+ for imp in imports:
7240
+ click.echo(f" {imp}")
7241
+ click.echo()
7242
+
7243
+ if includes:
7244
+ click.secho("Pre-loaded:", fg='green')
7245
+ for inc in includes:
7246
+ click.echo(f" {inc}")
7247
+ click.echo()
7248
+
7249
+ # Collect code lines
7250
+ lines = []
7251
+ prompt = '>>> ' if is_python else 'cpp> '
7252
+ continuation = '... ' if is_python else ' > '
7253
+
7254
+ try:
7255
+ while True:
7256
+ try:
7257
+ # Determine prompt
7258
+ current_prompt = prompt if not lines else continuation
7259
+ line = input(current_prompt)
7260
+
7261
+ # Empty line = execute
7262
+ if not line.strip():
7263
+ if lines:
7264
+ break
7265
+ continue
7266
+
7267
+ lines.append(line)
7268
+
7269
+ except EOFError:
7270
+ break
7271
+
7272
+ except KeyboardInterrupt:
7273
+ click.echo()
7274
+ click.secho("Cancelled.", fg='yellow')
7275
+ return
7276
+
7277
+ if not lines:
7278
+ click.secho("No code entered.", fg='yellow')
7279
+ return
7280
+
7281
+ # Build full code
7282
+ code_lines = imports + [''] + lines if imports else lines
7283
+
7284
+ click.echo()
7285
+ click.secho("--- Output ---", fg='green')
7286
+
7287
+ if is_python:
7288
+ # Execute Python code
7289
+ full_code = '\n'.join(code_lines)
7290
+ try:
7291
+ # Use exec with captured output
7292
+ import io
7293
+ from contextlib import redirect_stdout, redirect_stderr
7294
+
7295
+ stdout_capture = io.StringIO()
7296
+ stderr_capture = io.StringIO()
7297
+
7298
+ # Create execution context
7299
+ exec_globals = {'__name__': '__main__'}
7300
+
7301
+ with redirect_stdout(stdout_capture), redirect_stderr(stderr_capture):
7302
+ exec(full_code, exec_globals)
7303
+
7304
+ stdout_val = stdout_capture.getvalue()
7305
+ stderr_val = stderr_capture.getvalue()
7306
+
7307
+ if stdout_val:
7308
+ click.echo(stdout_val, nl=False)
7309
+ if stderr_val:
7310
+ click.secho(stderr_val, fg='red', nl=False)
7311
+
7312
+ if not stdout_val and not stderr_val:
7313
+ click.secho("(no output)", fg='bright_black')
7314
+
7315
+ except Exception as e:
7316
+ click.secho(f"Error: {e}", fg='red')
7317
+
7318
+ else:
7319
+ # Execute C++ code
7320
+ # Build a complete C++ program
7321
+ cpp_code_lines = [
7322
+ '#include <iostream>',
7323
+ '#include <vector>',
7324
+ '#include <string>',
7325
+ '#include <algorithm>',
7326
+ '#include <cmath>',
7327
+ 'using namespace std;',
7328
+ ''
7329
+ ]
7330
+ cpp_code_lines.extend(includes)
7331
+ cpp_code_lines.append('')
7332
+ cpp_code_lines.append('int main() {')
7333
+
7334
+ # Indent user code
7335
+ for line in lines:
7336
+ cpp_code_lines.append(' ' + line)
7337
+
7338
+ cpp_code_lines.append(' return 0;')
7339
+ cpp_code_lines.append('}')
7340
+
7341
+ full_code = '\n'.join(cpp_code_lines)
7342
+
7343
+ # Write to temp file and compile
7344
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.cpp', delete=False, encoding='utf-8') as f:
7345
+ f.write(full_code)
7346
+ cpp_file = f.name
7347
+
7348
+ exe_file = cpp_file.replace('.cpp', '.exe' if sys.platform == 'win32' else '')
7349
+
7350
+ try:
7351
+ # Compile
7352
+ compile_cmd = ['g++', '-std=c++17', '-o', exe_file, cpp_file]
7353
+ result = subprocess.run(compile_cmd, capture_output=True, text=True)
7354
+
7355
+ if result.returncode != 0:
7356
+ click.secho("Compilation Error:", fg='red')
7357
+ click.echo(result.stderr)
7358
+ else:
7359
+ # Run
7360
+ run_result = subprocess.run([exe_file], capture_output=True, text=True, timeout=10)
7361
+ if run_result.stdout:
7362
+ click.echo(run_result.stdout, nl=False)
7363
+ if run_result.stderr:
7364
+ click.secho(run_result.stderr, fg='red', nl=False)
7365
+ if not run_result.stdout and not run_result.stderr:
7366
+ click.secho("(no output)", fg='bright_black')
7367
+
7368
+ except subprocess.TimeoutExpired:
7369
+ click.secho("Execution timed out (10s limit)", fg='red')
7370
+ except FileNotFoundError:
7371
+ click.secho("Error: g++ not found. Install a C++ compiler.", fg='red')
7372
+ except Exception as e:
7373
+ click.secho(f"Error: {e}", fg='red')
7374
+ finally:
7375
+ # Cleanup temp files
7376
+ import os
7377
+ try:
7378
+ os.unlink(cpp_file)
7379
+ if os.path.exists(exe_file):
7380
+ os.unlink(exe_file)
7381
+ except Exception:
7382
+ pass
7383
+
7384
+ click.echo()
7385
+ click.secho("--------------", fg='green')
7386
+
7387
+
7388
+ # ============================================================================
7389
+ # CSSL - Hidden Command Group
7390
+ # ============================================================================
7391
+
7392
+ @click.group(hidden=True)
7393
+ def cssl():
7394
+ """CSSL scripting commands."""
7395
+ pass
7396
+
7397
+
7398
+ @cssl.command(name='exec')
7399
+ @click.argument('path', required=False, type=click.Path())
7400
+ @click.option('--code', '-c', type=str, help='Execute code directly')
7401
+ def cssl_exec(path, code):
7402
+ """Execute CSSL code or file."""
7403
+ from pathlib import Path as PathLib
7404
+
7405
+ try:
7406
+ from ..core.cssl_bridge import CsslLang
7407
+ except ImportError as e:
7408
+ click.secho(f"CSSL runtime not available: {e}", fg='red')
7409
+ return
7410
+
7411
+ cssl_lang = CsslLang()
7412
+
7413
+ # Determine source
7414
+ if code:
7415
+ source = code
7416
+ elif path:
7417
+ path_obj = PathLib(path)
7418
+ if not path_obj.exists():
7419
+ click.secho(f"File not found: {path}", fg='red')
7420
+ return
7421
+ source = path_obj.read_text(encoding='utf-8')
7422
+ else:
7423
+ # Interactive mode
7424
+ click.secho("=== CSSL REPL ===", fg='magenta', bold=True)
7425
+ click.echo("Enter CSSL code. Empty line to execute. CTRL+C to cancel.")
7426
+ click.echo()
7427
+
7428
+ lines = []
7429
+ prompt = 'cssl> '
7430
+
7431
+ try:
7432
+ while True:
7433
+ try:
7434
+ line = input(prompt)
7435
+ if not line.strip():
7436
+ if lines:
7437
+ break
7438
+ continue
7439
+ lines.append(line)
7440
+ except EOFError:
7441
+ break
7442
+ except KeyboardInterrupt:
7443
+ click.echo()
7444
+ click.secho("Cancelled.", fg='yellow')
7445
+ return
7446
+
7447
+ if not lines:
7448
+ click.secho("No code entered.", fg='yellow')
7449
+ return
7450
+
7451
+ source = '\n'.join(lines)
7452
+
7453
+ # Execute
7454
+ click.secho("--- Output ---", fg='green')
7455
+ try:
7456
+ result = cssl_lang.exec(source)
7457
+
7458
+ # Print output buffer
7459
+ for line in cssl_lang.get_output():
7460
+ click.echo(line)
7461
+
7462
+ if result is not None:
7463
+ click.echo(f"Result: {result}")
7464
+
7465
+ except Exception as e:
7466
+ click.secho(f"CSSL Error: {e}", fg='red')
7467
+
7468
+ click.secho("--------------", fg='green')
7469
+
7470
+
7471
+ @cssl.command(name='makemodule')
7472
+ @click.argument('path', type=click.Path(exists=True))
7473
+ @click.option('--output', '-o', type=click.Path(), help='Output path for .cssl-mod')
7474
+ def cssl_makemodule(path, output):
7475
+ """Create a .cssl-mod module from Python/C++ file."""
7476
+ from pathlib import Path as PathLib
7477
+ import pickle
7478
+ import base64
7479
+
7480
+ path_obj = PathLib(path)
7481
+ suffix = path_obj.suffix.lower()
7482
+
7483
+ if suffix not in ('.py', '.cpp', '.cp', '.h'):
7484
+ click.secho(f"Unsupported file type: {suffix}", fg='red')
7485
+ click.echo("Supported: .py, .cpp, .cp, .h")
7486
+ return
7487
+
7488
+ # Read source
7489
+ source = path_obj.read_text(encoding='utf-8')
7490
+
7491
+ # Create module data
7492
+ module_data = {
7493
+ 'name': path_obj.stem,
7494
+ 'type': 'python' if suffix == '.py' else 'cpp',
7495
+ 'source': source,
7496
+ 'version': '1.0',
7497
+ }
7498
+
7499
+ # Encode as base64 pickle
7500
+ encoded = base64.b64encode(pickle.dumps(module_data)).decode('utf-8')
7501
+
7502
+ # Write .cssl-mod file
7503
+ out_path = PathLib(output) if output else path_obj.with_suffix('.cssl-mod')
7504
+ out_path.write_text(f"CSSLMOD1\n{encoded}", encoding='utf-8')
7505
+
7506
+ click.secho(f"Created: {out_path}", fg='green')
7507
+ click.echo(f" Name: {module_data['name']}")
7508
+ click.echo(f" Type: {module_data['type']}")
7509
+
7510
+
7511
+ # Register hidden cssl command group
7512
+ cli.add_command(cssl)
7513
+
7514
+
7515
+ # ============================================================================
7516
+ # Conditional Registration of Experimental Commands
7517
+ # ============================================================================
7518
+ # AI and CPPY commands are only available when experimental features are enabled
7519
+ # Enable via: includecpp settings -> "Enable Experimental Features" checkbox
7520
+
7521
+ if _EXPERIMENTAL_ENABLED:
7522
+ cli.add_command(ai)
7523
+ cli.add_command(cppy)
7524
+ cli.add_command(project)
7525
+
7526
+
6924
7527
  if __name__ == '__main__':
6925
7528
  cli()