IncludeCPP 2.5.2__tar.gz → 2.7.2__tar.gz

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.
Files changed (36) hide show
  1. {includecpp-2.5.2 → includecpp-2.7.2}/IncludeCPP.egg-info/PKG-INFO +16 -5
  2. {includecpp-2.5.2 → includecpp-2.7.2}/IncludeCPP.egg-info/SOURCES.txt +4 -0
  3. {includecpp-2.5.2 → includecpp-2.7.2}/PKG-INFO +16 -5
  4. {includecpp-2.5.2 → includecpp-2.7.2}/README.md +15 -4
  5. includecpp-2.7.2/includecpp/__init__.py +58 -0
  6. {includecpp-2.5.2 → includecpp-2.7.2}/includecpp/__init__.pyi +17 -0
  7. {includecpp-2.5.2 → includecpp-2.7.2}/includecpp/cli/commands.py +196 -37
  8. {includecpp-2.5.2 → includecpp-2.7.2}/includecpp/core/build_manager.py +147 -8
  9. includecpp-2.7.2/includecpp/core/error_catalog.py +756 -0
  10. {includecpp-2.5.2 → includecpp-2.7.2}/includecpp/core/error_formatter.py +45 -18
  11. includecpp-2.7.2/includecpp/py.typed +0 -0
  12. {includecpp-2.5.2 → includecpp-2.7.2}/pyproject.toml +6 -2
  13. {includecpp-2.5.2 → includecpp-2.7.2}/setup.py +5 -1
  14. includecpp-2.5.2/includecpp/__init__.py +0 -4
  15. {includecpp-2.5.2 → includecpp-2.7.2}/IncludeCPP.egg-info/dependency_links.txt +0 -0
  16. {includecpp-2.5.2 → includecpp-2.7.2}/IncludeCPP.egg-info/entry_points.txt +0 -0
  17. {includecpp-2.5.2 → includecpp-2.7.2}/IncludeCPP.egg-info/requires.txt +0 -0
  18. {includecpp-2.5.2 → includecpp-2.7.2}/IncludeCPP.egg-info/top_level.txt +0 -0
  19. {includecpp-2.5.2 → includecpp-2.7.2}/LICENSE +0 -0
  20. {includecpp-2.5.2 → includecpp-2.7.2}/MANIFEST.in +0 -0
  21. {includecpp-2.5.2 → includecpp-2.7.2}/includecpp/__main__.py +0 -0
  22. {includecpp-2.5.2 → includecpp-2.7.2}/includecpp/cli/__init__.py +0 -0
  23. {includecpp-2.5.2 → includecpp-2.7.2}/includecpp/cli/config_parser.py +0 -0
  24. {includecpp-2.5.2 → includecpp-2.7.2}/includecpp/core/__init__.py +0 -0
  25. {includecpp-2.5.2 → includecpp-2.7.2}/includecpp/core/cpp_api.py +0 -0
  26. {includecpp-2.5.2 → includecpp-2.7.2}/includecpp/core/cpp_api.pyi +0 -0
  27. {includecpp-2.5.2 → includecpp-2.7.2}/includecpp/core/exceptions.py +0 -0
  28. {includecpp-2.5.2 → includecpp-2.7.2}/includecpp/core/path_discovery.py +0 -0
  29. {includecpp-2.5.2 → includecpp-2.7.2}/includecpp/generator/__init__.py +0 -0
  30. {includecpp-2.5.2 → includecpp-2.7.2}/includecpp/generator/parser.cpp +0 -0
  31. {includecpp-2.5.2 → includecpp-2.7.2}/includecpp/generator/parser.h +0 -0
  32. {includecpp-2.5.2 → includecpp-2.7.2}/includecpp/generator/type_resolver.cpp +0 -0
  33. {includecpp-2.5.2 → includecpp-2.7.2}/includecpp/generator/type_resolver.h +0 -0
  34. {includecpp-2.5.2 → includecpp-2.7.2}/includecpp/templates/cpp.proj.template +0 -0
  35. {includecpp-2.5.2 → includecpp-2.7.2}/requirements.txt +0 -0
  36. {includecpp-2.5.2 → includecpp-2.7.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: IncludeCPP
3
- Version: 2.5.2
3
+ Version: 2.7.2
4
4
  Summary: Professional C++ Python bindings with type-generic templates, pystubs and native threading
5
5
  Home-page: https://github.com/includecpp/includecpp
6
6
  Author: IncludeCPP Team
@@ -116,17 +116,19 @@ includecpp rebuild [OPTIONS]
116
116
  ```
117
117
 
118
118
  **Options:**
119
- - `--clean` - Force clean rebuild (ignore incremental cache)
120
- - `--full` - Complete rebuild including generator and all caches
119
+ - `--clean` - Force clean rebuild (delete all cached files)
120
+ - `--keep` - Keep existing generator (skip automatic generator rebuild)
121
121
  - `--verbose` - Detailed build output
122
122
  - `--no-incremental` - Disable incremental builds
123
123
  - `--parallel/--no-parallel` - Enable/disable parallel compilation (default: enabled)
124
124
  - `-j, --jobs <N>` - Max parallel jobs (default: 4)
125
125
  - `-m, --modules <name>` - Rebuild specific modules only
126
126
 
127
+ **Note:** The generator is automatically rebuilt on each run to ensure it's up-to-date. Use `--keep` to skip this if you know the generator is current.
128
+
127
129
  **Examples:**
128
130
  ```bash
129
- includecpp rebuild --full --verbose
131
+ includecpp rebuild --verbose
130
132
  includecpp rebuild -m crypto -m networking
131
133
  ```
132
134
 
@@ -337,6 +339,15 @@ SHA256-based change detection. Only rebuilds modified modules.
337
339
 
338
340
  ## Python API
339
341
 
342
+ ### Simple Import (v2.6+)
343
+
344
+ ```python
345
+ from includecpp import fast_list
346
+ result = fast_list.fast_sort([3, 1, 2])
347
+ ```
348
+
349
+ ### Classic API
350
+
340
351
  ```python
341
352
  from includecpp import CppApi
342
353
 
@@ -368,5 +379,5 @@ api.exists("module_name") # Check if module exists
368
379
  ---
369
380
 
370
381
  **License:** MIT
371
- **Version:** 2.5.2
382
+ **Version:** 2.6.1
372
383
  **Repository:** https://github.com/liliassg/IncludeCPP
@@ -7,6 +7,7 @@ setup.py
7
7
  ./includecpp/__init__.py
8
8
  ./includecpp/__init__.pyi
9
9
  ./includecpp/__main__.py
10
+ ./includecpp/py.typed
10
11
  ./includecpp/cli/__init__.py
11
12
  ./includecpp/cli/commands.py
12
13
  ./includecpp/cli/config_parser.py
@@ -14,6 +15,7 @@ setup.py
14
15
  ./includecpp/core/build_manager.py
15
16
  ./includecpp/core/cpp_api.py
16
17
  ./includecpp/core/cpp_api.pyi
18
+ ./includecpp/core/error_catalog.py
17
19
  ./includecpp/core/error_formatter.py
18
20
  ./includecpp/core/exceptions.py
19
21
  ./includecpp/core/path_discovery.py
@@ -32,6 +34,7 @@ IncludeCPP.egg-info/top_level.txt
32
34
  includecpp/__init__.py
33
35
  includecpp/__init__.pyi
34
36
  includecpp/__main__.py
37
+ includecpp/py.typed
35
38
  includecpp/cli/__init__.py
36
39
  includecpp/cli/commands.py
37
40
  includecpp/cli/config_parser.py
@@ -39,6 +42,7 @@ includecpp/core/__init__.py
39
42
  includecpp/core/build_manager.py
40
43
  includecpp/core/cpp_api.py
41
44
  includecpp/core/cpp_api.pyi
45
+ includecpp/core/error_catalog.py
42
46
  includecpp/core/error_formatter.py
43
47
  includecpp/core/exceptions.py
44
48
  includecpp/core/path_discovery.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: IncludeCPP
3
- Version: 2.5.2
3
+ Version: 2.7.2
4
4
  Summary: Professional C++ Python bindings with type-generic templates, pystubs and native threading
5
5
  Home-page: https://github.com/includecpp/includecpp
6
6
  Author: IncludeCPP Team
@@ -116,17 +116,19 @@ includecpp rebuild [OPTIONS]
116
116
  ```
117
117
 
118
118
  **Options:**
119
- - `--clean` - Force clean rebuild (ignore incremental cache)
120
- - `--full` - Complete rebuild including generator and all caches
119
+ - `--clean` - Force clean rebuild (delete all cached files)
120
+ - `--keep` - Keep existing generator (skip automatic generator rebuild)
121
121
  - `--verbose` - Detailed build output
122
122
  - `--no-incremental` - Disable incremental builds
123
123
  - `--parallel/--no-parallel` - Enable/disable parallel compilation (default: enabled)
124
124
  - `-j, --jobs <N>` - Max parallel jobs (default: 4)
125
125
  - `-m, --modules <name>` - Rebuild specific modules only
126
126
 
127
+ **Note:** The generator is automatically rebuilt on each run to ensure it's up-to-date. Use `--keep` to skip this if you know the generator is current.
128
+
127
129
  **Examples:**
128
130
  ```bash
129
- includecpp rebuild --full --verbose
131
+ includecpp rebuild --verbose
130
132
  includecpp rebuild -m crypto -m networking
131
133
  ```
132
134
 
@@ -337,6 +339,15 @@ SHA256-based change detection. Only rebuilds modified modules.
337
339
 
338
340
  ## Python API
339
341
 
342
+ ### Simple Import (v2.6+)
343
+
344
+ ```python
345
+ from includecpp import fast_list
346
+ result = fast_list.fast_sort([3, 1, 2])
347
+ ```
348
+
349
+ ### Classic API
350
+
340
351
  ```python
341
352
  from includecpp import CppApi
342
353
 
@@ -368,5 +379,5 @@ api.exists("module_name") # Check if module exists
368
379
  ---
369
380
 
370
381
  **License:** MIT
371
- **Version:** 2.5.2
382
+ **Version:** 2.6.1
372
383
  **Repository:** https://github.com/liliassg/IncludeCPP
@@ -80,17 +80,19 @@ includecpp rebuild [OPTIONS]
80
80
  ```
81
81
 
82
82
  **Options:**
83
- - `--clean` - Force clean rebuild (ignore incremental cache)
84
- - `--full` - Complete rebuild including generator and all caches
83
+ - `--clean` - Force clean rebuild (delete all cached files)
84
+ - `--keep` - Keep existing generator (skip automatic generator rebuild)
85
85
  - `--verbose` - Detailed build output
86
86
  - `--no-incremental` - Disable incremental builds
87
87
  - `--parallel/--no-parallel` - Enable/disable parallel compilation (default: enabled)
88
88
  - `-j, --jobs <N>` - Max parallel jobs (default: 4)
89
89
  - `-m, --modules <name>` - Rebuild specific modules only
90
90
 
91
+ **Note:** The generator is automatically rebuilt on each run to ensure it's up-to-date. Use `--keep` to skip this if you know the generator is current.
92
+
91
93
  **Examples:**
92
94
  ```bash
93
- includecpp rebuild --full --verbose
95
+ includecpp rebuild --verbose
94
96
  includecpp rebuild -m crypto -m networking
95
97
  ```
96
98
 
@@ -301,6 +303,15 @@ SHA256-based change detection. Only rebuilds modified modules.
301
303
 
302
304
  ## Python API
303
305
 
306
+ ### Simple Import (v2.6+)
307
+
308
+ ```python
309
+ from includecpp import fast_list
310
+ result = fast_list.fast_sort([3, 1, 2])
311
+ ```
312
+
313
+ ### Classic API
314
+
304
315
  ```python
305
316
  from includecpp import CppApi
306
317
 
@@ -332,5 +343,5 @@ api.exists("module_name") # Check if module exists
332
343
  ---
333
344
 
334
345
  **License:** MIT
335
- **Version:** 2.5.2
346
+ **Version:** 2.6.1
336
347
  **Repository:** https://github.com/liliassg/IncludeCPP
@@ -0,0 +1,58 @@
1
+ from .core.cpp_api import CppApi
2
+ import warnings
3
+
4
+ __version__ = "2.7.2"
5
+ __all__ = ["CppApi"]
6
+
7
+ # Module-level cache for C++ modules
8
+ _api_instance = None
9
+ _loaded_modules = {}
10
+
11
+ def _get_api():
12
+ """Get or create singleton CppApi instance."""
13
+ global _api_instance
14
+ if _api_instance is None:
15
+ _api_instance = CppApi()
16
+ return _api_instance
17
+
18
+ def __getattr__(name: str):
19
+ """Enable: from includecpp import fast_list
20
+
21
+ This hook is called when Python cannot find an attribute in this module.
22
+ It allows dynamic C++ module loading via the import system.
23
+ """
24
+ if name.startswith('_'):
25
+ raise AttributeError(f"module 'includecpp' has no attribute '{name}'")
26
+
27
+ if name in _loaded_modules:
28
+ return _loaded_modules[name]
29
+
30
+ api = _get_api()
31
+
32
+ if name not in api.registry:
33
+ available = list(api.registry.keys())
34
+ raise AttributeError(
35
+ f"Module '{name}' not found. "
36
+ f"Available: {available}. "
37
+ f"Run 'includecpp rebuild' first."
38
+ )
39
+
40
+ if api.need_update(name):
41
+ warnings.warn(
42
+ f"Module '{name}' source files changed. "
43
+ f"Run 'includecpp rebuild' to update.",
44
+ UserWarning
45
+ )
46
+
47
+ module = api.include(name)
48
+ _loaded_modules[name] = module
49
+ return module
50
+
51
+ def __dir__():
52
+ """List available attributes including C++ modules."""
53
+ base = ['CppApi', '__version__']
54
+ try:
55
+ api = _get_api()
56
+ return sorted(set(base + list(api.registry.keys())))
57
+ except Exception:
58
+ return base
@@ -2,9 +2,26 @@
2
2
 
3
3
  from typing import Any, Dict, Optional, List, Literal, overload
4
4
  from pathlib import Path
5
+ from types import ModuleType
6
+
7
+ # Import generated module wrappers for VSCode autocomplete
8
+ # These are created by 'includecpp rebuild' and provide module-specific type hints
9
+ try:
10
+ from .core.cpp_api_extensions import *
11
+ except ImportError:
12
+ pass # Generated during rebuild
5
13
 
6
14
  __version__: str
7
15
 
16
+ # Dynamic module access via: from includecpp import <module_name>
17
+ # Auto-generated module declarations
18
+ # These allow: from includecpp import <module_name>
19
+ # (Run 'includecpp rebuild' to generate declarations for your modules)
20
+
21
+ def __dir__() -> List[str]:
22
+ """List available modules including dynamically loaded C++ modules."""
23
+ ...
24
+
8
25
  class ModuleWrapper:
9
26
  """Wrapper for C++ modules with getInfo() and dynamic attributes."""
10
27
 
@@ -31,7 +31,7 @@ _SEC_TOP = "┌─────────────────────
31
31
  _SEC_BOTTOM = "└─────────────────────────────────────────────────────────────┘" if _UNICODE else "+-------------------------------------------------------------+"
32
32
  _SEC_SIDE = "│" if _UNICODE else "|"
33
33
  _HLINE = "═" if _UNICODE else "-"
34
- _CHECK = "" if _UNICODE else "[OK]"
34
+ _CHECK = "" # Removed check marks per user request
35
35
  _CROSS = "✗" if _UNICODE else "[X]"
36
36
  _BULLET = "•" if _UNICODE else "*"
37
37
  _ARROW = "→" if _UNICODE else "->"
@@ -49,7 +49,7 @@ def _safe_echo(text, **kwargs):
49
49
  ('╔', '+'), ('╗', '+'), ('╚', '+'), ('╝', '+'),
50
50
  ('┌', '+'), ('┐', '+'), ('└', '+'), ('┘', '+'),
51
51
  ('═', '-'), ('─', '-'), ('║', '|'), ('│', '|'),
52
- ('✓', '[OK]'), ('✗', '[X]'), ('•', '*'),
52
+ ('✗', '[X]'), ('•', '*'),
53
53
  ('→', '->'), ('▶', '>'), ('◆', '*')
54
54
  ]
55
55
  for uni, ascii_char in replacements:
@@ -494,8 +494,11 @@ def _show_build_info_report(build_dir: Path, compiler: str):
494
494
  f = first_info['functions'][0]
495
495
  first_func = f.get('name', f) if isinstance(f, dict) else f
496
496
 
497
- click.echo(f" from {first_mod} import {first_func or 'your_function'}")
498
- click.echo(f" result = {first_func or 'your_function'}(...)")
497
+ click.echo(f" from includecpp import CppApi")
498
+ click.echo(f" api = CppApi()")
499
+ click.echo(f" {first_mod} = api.include(\"{first_mod}\")")
500
+ if first_func:
501
+ click.echo(f" result = {first_mod}.{first_func}(...)")
499
502
 
500
503
  click.echo("")
501
504
  _safe_echo(_HLINE * 64, fg='cyan')
@@ -531,9 +534,61 @@ def _check_for_updates_silent():
531
534
  pass # Silently fail - don't interrupt user's workflow
532
535
  return None
533
536
 
537
+
538
+ def _parse_cp_sources(cp_path):
539
+ """Parse SOURCE() and HEADER() paths from an existing .cp file.
540
+
541
+ Returns:
542
+ Tuple of (source_files, header_files) as lists of Path objects
543
+ """
544
+ import re
545
+ from pathlib import Path
546
+
547
+ source_files = []
548
+ header_files = []
549
+
550
+ try:
551
+ content = cp_path.read_text()
552
+ project_root = cp_path.parent.parent # plugins/ -> project root
553
+
554
+ # Find SOURCE(...) declarations
555
+ source_match = re.search(r'SOURCE\s*\(\s*([^)]+)\s*\)', content)
556
+ if source_match:
557
+ sources_str = source_match.group(1)
558
+ # Handle multiple files: SOURCE(file1.cpp file2.cpp) or SOURCE(file1.cpp, file2.cpp)
559
+ sources = re.split(r'[,\s]+', sources_str.strip())
560
+ for src in sources:
561
+ src = src.strip()
562
+ if src:
563
+ p = Path(src)
564
+ if not p.is_absolute():
565
+ p = project_root / src
566
+ if p.exists():
567
+ source_files.append(p)
568
+
569
+ # Find HEADER(...) declarations
570
+ header_match = re.search(r'HEADER\s*\(\s*([^)]+)\s*\)', content)
571
+ if header_match:
572
+ headers_str = header_match.group(1)
573
+ headers = re.split(r'[,\s]+', headers_str.strip())
574
+ for hdr in headers:
575
+ hdr = hdr.strip()
576
+ if hdr:
577
+ p = Path(hdr)
578
+ if not p.is_absolute():
579
+ p = project_root / hdr
580
+ if p.exists():
581
+ header_files.append(p)
582
+
583
+ except Exception as e:
584
+ pass # Return empty lists on error
585
+
586
+ return (source_files, header_files)
587
+
588
+
534
589
  @cli.command()
535
590
  @click.option('--clean', is_flag=True, help='Force clean rebuild (ignore incremental)')
536
- @click.option('--full', is_flag=True, help='Complete rebuild including plugin_gen.exe and all caches')
591
+ @click.option('--keep', is_flag=True, help='Keep existing plugin_gen.exe (skip generator rebuild)')
537
592
  @click.option('--verbose', is_flag=True, help='Verbose output')
538
593
  @click.option('--no-incremental', is_flag=True, help='Disable incremental builds')
539
594
  @click.option('--incremental', is_flag=True, help='Explicitly enable incremental builds (default: auto)')
@@ -542,14 +597,14 @@ def _check_for_updates_silent():
542
597
  @click.option('--modules', '-m', multiple=True, help='Specific modules to rebuild')
543
598
  @click.option('--this', 'build_paths', multiple=True, type=click.Path(exists=True),
544
599
  help='Build only from specific paths (can specify multiple)')
545
- @click.option('--fast', 'fast_plugin', type=str, default=None,
546
- help='Fast incremental rebuild of single plugin (e.g., algorithms.cp)')
600
+ @click.option('--fast', 'fast_mode', is_flag=True,
601
+ help='Fast incremental rebuild mode (use with module name, e.g., rebuild --fast gamekit)')
547
602
  @click.option('--all', 'fast_all', is_flag=True, help='With --fast: rebuild all plugins incrementally')
548
603
  @click.option('--info', is_flag=True, help='Show detailed analysis report of the last build')
549
604
  @click.argument('module_args', nargs=-1)
550
- def rebuild(clean, full, verbose, no_incremental, incremental, parallel, jobs, modules,
551
- build_paths, fast_plugin, fast_all, info, module_args):
552
- """v2.0: Rebuild C++ modules with incremental and parallel support."""
605
+ def rebuild(clean, keep, verbose, no_incremental, incremental, parallel, jobs, modules,
606
+ build_paths, fast_mode, fast_all, info, module_args):
607
+ """Rebuild C++ modules with automatic generator updates."""
553
608
  from ..core.build_manager import BuildManager
554
609
  from ..core.error_formatter import BuildErrorFormatter, BuildSuccessFormatter
555
610
  import time
@@ -599,14 +654,17 @@ def rebuild(clean, full, verbose, no_incremental, incremental, parallel, jobs, m
599
654
  _show_build_info_report(build_dir, compiler)
600
655
  return
601
656
 
602
- # Full rebuild: delete everything including generator
603
- if full and build_dir.exists():
604
- click.echo("Full rebuild: Deleting entire build directory including plugin_gen.exe...")
605
- shutil.rmtree(build_dir)
606
- clean = True # Force clean mode
657
+ # v2.5.4: By default, always delete the generator to ensure it's up-to-date
658
+ # Use --keep to skip generator rebuild
659
+ if build_dir.exists():
660
+ gen_path = build_dir / "bin" / ".appc"
661
+ if not keep and gen_path.exists():
662
+ if verbose:
663
+ click.echo("Removing old generator to force rebuild...")
664
+ shutil.rmtree(gen_path)
607
665
 
608
- # Clean if requested
609
- elif clean and build_dir.exists():
666
+ # Clean if requested - delete entire build directory
667
+ if clean and build_dir.exists():
610
668
  click.echo("Cleaning build directory...")
611
669
  shutil.rmtree(build_dir)
612
670
 
@@ -623,14 +681,28 @@ def rebuild(clean, full, verbose, no_incremental, incremental, parallel, jobs, m
623
681
  click.secho("Warning: --clean overrides --incremental", fg='yellow')
624
682
  incremental = False
625
683
 
626
- if fast_plugin and build_paths:
684
+ if fast_mode and build_paths:
627
685
  click.secho("Error: Cannot use both --fast and --this", fg='red', err=True)
628
686
  raise click.Abort()
629
687
 
630
- if fast_all and not fast_plugin:
631
- click.secho("Error: --all requires --fast <plugin>", fg='red', err=True)
688
+ # Validate --all requires --fast
689
+ if fast_all and not fast_mode:
690
+ click.secho("Error: --all requires --fast", fg='red', err=True)
632
691
  raise click.Abort()
633
692
 
693
+ # Determine fast plugin from modules (positional args or -m option)
694
+ fast_plugin = None
695
+ if fast_mode and not fast_all:
696
+ if modules:
697
+ fast_plugin = modules[0] # Use first module from -m option
698
+ elif module_args:
699
+ fast_plugin = module_args[0] # Use first positional argument
700
+ else:
701
+ click.secho("Error: --fast requires a module name", fg='red', err=True)
702
+ click.echo("Usage: includecpp rebuild --fast <module_name>")
703
+ click.echo(" or: includecpp rebuild -m <module_name> --fast")
704
+ raise click.Abort()
705
+
634
706
  final_incremental = True
635
707
  if incremental:
636
708
  final_incremental = True
@@ -694,7 +766,7 @@ def rebuild(clean, full, verbose, no_incremental, incremental, parallel, jobs, m
694
766
  if verbose:
695
767
  click.echo(f"Affected modules ({len(modules)}): {', '.join(modules)}")
696
768
 
697
- if fast_all:
769
+ if fast_mode and fast_all:
698
770
  if verbose:
699
771
  click.echo("Fast rebuild mode: ALL plugins")
700
772
 
@@ -766,12 +838,12 @@ def rebuild(clean, full, verbose, no_incremental, incremental, parallel, jobs, m
766
838
  # Print success message
767
839
  click.echo("")
768
840
  _safe_echo(_BOX_TOP, fg='green')
769
- _safe_echo(f"{_BOX_SIDE} {_CHECK} BUILD SUCCESSFUL", fg='green')
841
+ _safe_echo(f"{_BOX_SIDE} BUILD SUCCESSFUL", fg='green')
770
842
  _safe_echo(_BOX_BOTTOM, fg='green')
771
843
  click.echo("")
772
844
  click.echo(f"Modules built ({len(built_modules)}):")
773
845
  for mod in built_modules:
774
- _safe_echo(f" {_CHECK} {mod}", fg='green')
846
+ _safe_echo(f" {mod}", fg='green')
775
847
  click.echo("")
776
848
  click.echo(f"Build time: {build_time:.2f}s")
777
849
  if stats:
@@ -782,8 +854,20 @@ def rebuild(clean, full, verbose, no_incremental, incremental, parallel, jobs, m
782
854
  if 'total_structs' in stats:
783
855
  click.echo(f" Structs exported: {stats['total_structs']}")
784
856
  click.echo("")
785
- click.echo("Your modules are ready to use:")
786
- click.secho(" from module_name import function_name", fg='cyan')
857
+ # Show usage with real module name from build
858
+ if built_modules:
859
+ first_mod = built_modules[0]
860
+ first_func = None
861
+ if first_mod in modules and modules[first_mod].get('functions'):
862
+ func_info = modules[first_mod]['functions'][0]
863
+ first_func = func_info.get('name', func_info) if isinstance(func_info, dict) else func_info
864
+
865
+ click.echo("Usage:")
866
+ click.echo(" from includecpp import CppApi")
867
+ click.echo(" api = CppApi()")
868
+ click.echo(f" {first_mod} = api.include(\"{first_mod}\")")
869
+ if first_func:
870
+ click.echo(f" result = {first_mod}.{first_func}(...)")
787
871
  click.echo("")
788
872
  else:
789
873
  click.echo("")
@@ -803,13 +887,9 @@ def rebuild(clean, full, verbose, no_incremental, incremental, parallel, jobs, m
803
887
  except Exception as e:
804
888
  build_time = time.time() - start_time
805
889
  error_msg = str(e)
890
+ module_name = target_modules[0] if target_modules else ""
806
891
 
807
- # v2.4.1: Use analyze_error for intelligent error formatting
808
- formatted_error = BuildErrorFormatter.analyze_error(error_msg, target_modules[0] if target_modules else "")
809
-
810
- click.echo("")
811
- click.secho(formatted_error, fg='red', err=True)
812
-
892
+ # v2.7: Print verbose details FIRST (for those who read everything)
813
893
  if verbose:
814
894
  click.echo("")
815
895
  _safe_echo(_HLINE * 60, fg='yellow')
@@ -817,9 +897,43 @@ def rebuild(clean, full, verbose, no_incremental, incremental, parallel, jobs, m
817
897
  _safe_echo(_HLINE * 60, fg='yellow')
818
898
  import traceback
819
899
  traceback.print_exc()
900
+ click.echo("")
901
+
902
+ # v2.7: Print detailed error analysis
903
+ formatted_error = BuildErrorFormatter.analyze_error(error_msg, module_name)
904
+ click.echo("")
905
+ click.secho(formatted_error, fg='red', err=True)
906
+
907
+ # v2.7: Print short helpful message LAST (for lazy users who only read the end)
908
+ final_msg = BuildErrorFormatter.get_final_message(error_msg, module_name)
909
+ click.echo("")
910
+ click.secho(final_msg, fg='red', bold=True, err=True)
820
911
 
821
912
  raise click.Abort()
822
913
 
914
+ @cli.command()
915
+ @click.option('--clean', is_flag=True, help='Force clean rebuild')
916
+ @click.option('--keep', is_flag=True, help='Keep existing plugin_gen.exe')
917
+ @click.option('-v', '--verbose', is_flag=True, help='Show detailed output')
918
+ @click.option('--no-incremental', is_flag=True, help='Disable incremental builds')
919
+ @click.option('--incremental', is_flag=True, help='Force incremental build')
920
+ @click.option('--parallel', is_flag=True, help='Build modules in parallel')
921
+ @click.option('-j', '--jobs', type=int, default=None, help='Number of parallel jobs')
922
+ @click.option('-m', '--modules', multiple=True, help='Specific modules to build')
923
+ @click.option('--this', 'build_paths', multiple=True, type=click.Path(exists=True), help='Build from path')
924
+ @click.option('--fast', 'fast_mode', is_flag=True, help='Fast incremental build')
925
+ @click.option('--all', 'fast_all', is_flag=True, help='With --fast: rebuild all plugins')
926
+ @click.option('--info', is_flag=True, help='Show build analysis report')
927
+ @click.argument('module_args', nargs=-1)
928
+ @click.pass_context
929
+ def build(ctx, clean, keep, verbose, no_incremental, incremental, parallel, jobs, modules,
930
+ build_paths, fast_mode, fast_all, info, module_args):
931
+ """Build C++ modules (alias for rebuild)."""
932
+ ctx.invoke(rebuild, clean=clean, keep=keep, verbose=verbose, no_incremental=no_incremental,
933
+ incremental=incremental, parallel=parallel, jobs=jobs, modules=modules,
934
+ build_paths=build_paths, fast_mode=fast_mode, fast_all=fast_all, info=info,
935
+ module_args=module_args)
936
+
823
937
  @cli.command()
824
938
  @click.argument('module_name')
825
939
  def add(module_name):
@@ -847,8 +961,8 @@ PUBLIC(
847
961
  click.echo(f"Created {cp_file}")
848
962
  click.echo(f"Now create include/{module_name}.cpp with your C++ code")
849
963
 
850
- @cli.command()
851
- def list():
964
+ @cli.command('list')
965
+ def list_modules():
852
966
  """List all available C++ modules."""
853
967
  from ..core.build_manager import BuildManager
854
968
 
@@ -1707,12 +1821,22 @@ def reboot():
1707
1821
 
1708
1822
  @cli.command()
1709
1823
  @click.argument('plugin_name')
1710
- @click.argument('files', nargs=-1, required=True)
1824
+ @click.argument('files', nargs=-1, required=False)
1711
1825
  @click.option('--private', '-p', multiple=True, help='Private functions to exclude from public API')
1712
1826
  def plugin(plugin_name, files, private):
1827
+ """Generate or regenerate a .cp plugin definition from C++ source files.
1828
+
1829
+ If no files are provided and an existing .cp file exists, SOURCE() and HEADER()
1830
+ paths are read from it to auto-regenerate the plugin definition.
1831
+ """
1713
1832
  import re
1714
1833
  from pathlib import Path
1715
1834
 
1835
+ # Normalize plugin_name: strip .cp extension and path prefixes
1836
+ if plugin_name.endswith('.cp'):
1837
+ plugin_name = plugin_name[:-3]
1838
+ plugin_name = Path(plugin_name).stem
1839
+
1716
1840
  project_root = Path.cwd()
1717
1841
  plugins_dir = project_root / "plugins"
1718
1842
 
@@ -1721,6 +1845,24 @@ def plugin(plugin_name, files, private):
1721
1845
  click.echo("Run 'python -m includecpp init' first")
1722
1846
  return
1723
1847
 
1848
+ # v2.5.3: Auto-regenerate from existing .cp file if no files provided
1849
+ if not files:
1850
+ existing_cp = plugins_dir / f"{plugin_name}.cp"
1851
+ if existing_cp.exists():
1852
+ click.echo(f"Auto-regenerating from existing: {existing_cp}")
1853
+ source_files, header_files = _parse_cp_sources(existing_cp)
1854
+ if not source_files:
1855
+ click.secho("Error: No SOURCE() found in existing .cp file", fg='red', err=True)
1856
+ click.echo("Usage: includecpp plugin <name> <file1.cpp> [file2.h] ...")
1857
+ return
1858
+ # Combine and use as input files
1859
+ files = tuple(source_files + header_files)
1860
+ click.echo(f"Found sources: {', '.join(str(f) for f in files)}")
1861
+ else:
1862
+ click.secho("Error: No files provided and no existing .cp file found", fg='red', err=True)
1863
+ click.echo("Usage: includecpp plugin <name> <file1.cpp> [file2.h] ...")
1864
+ return
1865
+
1724
1866
  cpp_files = []
1725
1867
  h_files = []
1726
1868
 
@@ -2329,11 +2471,28 @@ def bug(message, get_bugs):
2329
2471
 
2330
2472
  click.echo("=" * 60)
2331
2473
 
2332
- def detect_compiler():
2474
+ # Compiler detection cache
2475
+ _compiler_cache = None
2476
+
2477
+ def detect_compiler(force_refresh=False):
2478
+ """Detect available C++ compiler with caching.
2479
+
2480
+ Checks g++ first per user requirement, then clang++, then cl (MSVC).
2481
+ Results are cached to avoid repeated PATH scans.
2482
+ """
2483
+ global _compiler_cache
2484
+
2485
+ if not force_refresh and _compiler_cache is not None:
2486
+ return _compiler_cache
2487
+
2488
+ # g++ first (user requirement)
2333
2489
  for compiler in ['g++', 'clang++', 'cl']:
2334
2490
  if shutil.which(compiler):
2335
- return compiler.replace('++', '').replace('cl', 'msvc')
2336
- return 'gcc'
2491
+ _compiler_cache = compiler.replace('++', '').replace('cl', 'msvc')
2492
+ return _compiler_cache
2493
+
2494
+ _compiler_cache = 'gcc'
2495
+ return _compiler_cache
2337
2496
 
2338
2497
  if __name__ == '__main__':
2339
2498
  cli()