Skip to content

Commit c8d8aaf

Browse files
refactoring
1 parent f8f0e77 commit c8d8aaf

27 files changed

Lines changed: 1237 additions & 264 deletions

DOCS.md

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -458,22 +458,32 @@ All examples are in the `examples/` directory:
458458

459459
| Example | Description |
460460
|---------|-------------|
461-
| `quick_start.py` | 5-minute tour of all features |
462-
| `bdd_workflow.py` | Complete BDD workflow |
463-
| `token_efficiency.py` | Compare all format token usage |
464-
| `duplicate_detection.py` | Find duplicate functions |
465-
| `refactor_suggestions.py` | LLM-powered refactoring |
466-
| `generate_code.py` | Generate code with Ollama |
467-
| `compare_projects.py` | Compare two projects |
461+
| `01_quick_start.py` | Basic usage guide |
462+
| `02_refactoring.py` | Duplicate + quality analysis |
463+
| `03_reproduction.py` | Reproduce code from specs |
464+
| `04_project.py` | Project-level reproduction |
465+
| `05_llm_integration.py` | LLM integration demo |
466+
| `06_metrics.py` | Detailed reproduction metrics |
467+
| `08_format_benchmark.py` | Benchmark formats across files |
468+
| `09_async_benchmark.py` | Parallel benchmark |
469+
| `10_function_reproduction.py` | Function-level reproduction |
470+
| `11_token_benchmark.py` | Token-aware benchmark |
471+
| `12_comprehensive_analysis.py` | Comprehensive format analysis |
472+
| `13_project_benchmark.py` | Whole-project benchmark |
473+
| `14_repeatability_test.py` | Repeatability testing |
474+
| `15_unified_benchmark.py` | Unified benchmark runner |
475+
| `16_terminal_demo.py` | Terminal markdown rendering demo |
468476

469477
Run examples:
470478

471479
```bash
472-
python examples/quick_start.py
473-
python examples/bdd_workflow.py /path/to/project
474-
python examples/token_efficiency.py /path/to/project
475-
python examples/duplicate_detection.py /path/to/project
476-
python examples/refactor_suggestions.py /path/to/project --no-llm
480+
python examples/01_quick_start.py
481+
python examples/02_refactoring.py
482+
python examples/03_reproduction.py --show-spec
483+
python examples/04_project.py tests/samples/ --no-llm
484+
python examples/11_token_benchmark.py --folder tests/samples/ --no-llm
485+
python examples/15_unified_benchmark.py --type format --folder tests/samples/ --no-llm
486+
python examples/16_terminal_demo.py --folder tests/samples/
477487
```
478488

479489
---

README.md

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
[![PyPI version](https://badge.fury.io/py/code2logic.svg)](https://badge.fury.io/py/code2logic)
44
[![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
5-
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5+
[![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-yellow.svg)](https://www.apache.org/licenses/LICENSE-2.0)
66

77
**Convert source code to logical representation for LLM analysis.**
88

@@ -317,11 +317,11 @@ black code2logic
317317

318318
## 📈 Performance
319319

320-
|Codebase Size|Files|Lines|Time|Output Size|
321-
|---|---|---|---|---|
322-
|Small|10|1K|<1s|~5KB|
323-
|Medium|100|30K|~2s|~50KB|
324-
|Large|500|150K|~10s|~200KB|
320+
| Codebase Size | Files | Lines | Time | Output Size |
321+
| --- | --- | --- | --- | --- |
322+
| Small | 10 | 1K | <1s | ~5KB |
323+
| Medium | 100 | 30K | ~2s | ~50KB |
324+
| Large | 500 | 150K | ~10s | ~200KB |
325325

326326
Compact format is ~10-15x smaller than Markdown.
327327

@@ -331,12 +331,12 @@ Code2Logic can reproduce code from specifications using LLMs. Benchmark results:
331331

332332
### Format Comparison (Token Efficiency)
333333

334-
|Format|Score|Token Efficiency|Spec Tokens|Runs OK|
335-
|---|---|---|---|---|
336-
|**YAML**|**71.1%**|42.1|**366**|66.7%|
337-
|**Markdown**|65.6%|**48.7**|385|**100%**|
338-
|JSON|61.9%|23.7|605|66.7%|
339-
|Gherkin|51.3%|19.1|411|66.7%|
334+
| Format | Score | Token Efficiency | Spec Tokens | Runs OK |
335+
| --- | --- | --- | --- | --- |
336+
| **YAML** | **71.1%** | 42.1 | **366** | 66.7% |
337+
| **Markdown** | 65.6% | **48.7** | 385 | **100%** |
338+
| JSON | 61.9% | 23.7 | 605 | 66.7% |
339+
| Gherkin | 51.3% | 19.1 | 411 | 66.7% |
340340

341341
### Key Findings
342342

@@ -349,15 +349,18 @@ Code2Logic can reproduce code from specifications using LLMs. Benchmark results:
349349

350350
```bash
351351
# Token-aware benchmark
352-
python examples/11_token_benchmark.py --folder tests/samples/
352+
python examples/11_token_benchmark.py --folder tests/samples/ --no-llm
353353

354354
# Async multi-format benchmark
355-
python examples/09_async_benchmark.py --folder tests/samples/
355+
python examples/09_async_benchmark.py --folder tests/samples/ --no-llm
356356

357357
# Function-level reproduction
358-
python examples/10_function_reproduction.py --multi-lang
358+
python examples/10_function_reproduction.py --file tests/samples/sample_functions.py --no-llm
359359

360-
python examples/15_unified_benchmark.py --folder tests/samples/
360+
python examples/15_unified_benchmark.py --folder tests/samples/ --no-llm
361+
362+
# Terminal markdown rendering demo
363+
python examples/16_terminal_demo.py --folder tests/samples/
361364
```
362365

363366
## 🤝 Contributing

code2logic/__init__.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,11 @@
104104
ProjectReproducer,
105105
ProjectResult,
106106
FileResult,
107-
reproduce_project,
107+
reproduce_project as _reproduce_project_from_source,
108108
)
109109
from .refactor import (
110110
find_duplicates,
111-
analyze_quality,
111+
analyze_quality as _analyze_quality_from_path,
112112
suggest_refactoring,
113113
compare_codebases,
114114
quick_analyze,
@@ -160,7 +160,7 @@
160160
QualityAnalyzer,
161161
QualityReport,
162162
QualityIssue,
163-
analyze_quality,
163+
analyze_quality as _analyze_quality_from_project,
164164
get_quality_summary,
165165
)
166166
from .similarity import get_refactoring_suggestions
@@ -175,11 +175,24 @@
175175
from .reproducer import (
176176
SpecReproducer,
177177
SpecValidator,
178-
ReproductionResult,
178+
ReproductionResult as SpecReproductionResult,
179179
FileValidation,
180-
reproduce_project,
180+
reproduce_project as _reproduce_project_from_spec,
181181
validate_files,
182182
)
183+
184+
185+
def analyze_quality(target, *args, **kwargs):
186+
if isinstance(target, ProjectInfo):
187+
return _analyze_quality_from_project(target, *args, **kwargs)
188+
return _analyze_quality_from_path(str(target), *args, **kwargs)
189+
190+
191+
def reproduce_project(source: str, *args, **kwargs):
192+
src = str(source)
193+
if src.endswith(('.yaml', '.yml', '.json')):
194+
return _reproduce_project_from_spec(src, *args, **kwargs)
195+
return _reproduce_project_from_source(src, *args, **kwargs)
183196
from .toon_format import (
184197
TOONGenerator,
185198
TOONParser,
@@ -304,6 +317,7 @@
304317
# Metrics
305318
"ReproductionMetrics",
306319
"ReproductionResult",
320+
"SpecReproductionResult",
307321
"TextMetrics",
308322
"StructuralMetrics",
309323
"SemanticMetrics",

code2logic/benchmarks/results.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -159,14 +159,13 @@ def calculate_aggregates(self):
159159
# Format results
160160
if self.format_results:
161161
for r in self.format_results:
162-
if r.format_name not in self.format_scores:
163-
self.format_scores[r.format_name] = []
164162
self.format_scores[r.format_name] = r.score
165-
166-
if self.format_scores:
167-
best = max(self.format_scores.items(), key=lambda x: x[1])
168-
self.best_format = best[0]
169-
self.best_score = best[1]
163+
164+
# Best format (works for both format_results-derived and directly populated format_scores)
165+
if self.format_scores:
166+
best = max(self.format_scores.items(), key=lambda x: x[1])
167+
self.best_format = best[0]
168+
self.best_score = best[1]
170169

171170
def to_dict(self) -> Dict[str, Any]:
172171
d = asdict(self)

code2logic/benchmarks/runner.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -639,7 +639,12 @@ def run_project_benchmark(
639639

640640
client: Optional[BaseLLMClient]
641641
if self._should_use_llm():
642-
client = self._get_client()
642+
try:
643+
client = self._get_client()
644+
except Exception as e:
645+
client = None
646+
if verbose:
647+
render.warning(f"LLM not available ({str(e)[:80]}). Falling back to template mode.")
643648
else:
644649
client = None
645650

code2logic/cli.py

Lines changed: 82 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import time
1616
import logging
1717
import json
18+
import signal
1819
from datetime import datetime
1920

2021
from . import __version__
@@ -509,6 +510,11 @@ def main():
509510
"""Main CLI entry point."""
510511
cli_start = time.time()
511512

513+
try:
514+
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
515+
except Exception:
516+
pass
517+
512518
if len(sys.argv) > 1 and sys.argv[1] == 'llm':
513519
_code2logic_llm_cli(sys.argv[2:])
514520
return
@@ -524,12 +530,17 @@ def main():
524530
code2logic /path/to/project -f yaml # YAML (human-readable)
525531
code2logic /path/to/project -f json --flat # Flat JSON (for comparisons)
526532
code2logic /path/to/project -f compact # Ultra-compact text
533+
code2logic /path/to/project -f logicml # LogicML (compressed, reproduction-oriented)
534+
code2logic /path/to/project -f toon # TOON (token-oriented tabular format)
527535
528536
Output formats (token efficiency):
529537
csv - Best for LLM (~20K tokens/100 files) - flat table
530538
compact - Good for LLM (~25K tokens/100 files) - minimal text
531539
json - Standard (~35K tokens/100 files) - nested/flat
532540
yaml - Readable (~35K tokens/100 files) - nested/flat
541+
logicml - Compressed (best compression) - reproduction-oriented
542+
toon - Token-oriented (~JSON-size, more LLM-friendly) - tabular arrays
543+
gherkin - Behavioral scenarios - good for minimal implementations
533544
markdown - Documentation (~55K tokens/100 files)
534545
535546
Detail levels (columns in csv/json/yaml):
@@ -538,6 +549,39 @@ def main():
538549
full - + calls, lines, complexity, hash (16 columns)
539550
'''
540551
)
552+
553+
def _maybe_print_pretty_help() -> bool:
554+
"""Print colorized help as markdown when appropriate.
555+
556+
Returns True if help was printed and the CLI should exit early.
557+
"""
558+
force_pretty = os.environ.get("CODE2LOGIC_PRETTY_HELP") == "1" or bool(os.environ.get("FORCE_COLOR"))
559+
if not force_pretty:
560+
if not hasattr(sys.stdout, "isatty") or not sys.stdout.isatty():
561+
return False
562+
try:
563+
from .terminal import render
564+
except Exception:
565+
return False
566+
567+
help_md = f"""# code2logic
568+
569+
Convert source code to logical representation for LLM analysis.
570+
571+
## Usage
572+
573+
```bash
574+
code2logic [path] [options]
575+
```
576+
577+
## Help
578+
579+
```text
580+
{parser.format_help().rstrip()}
581+
```
582+
"""
583+
render.markdown(help_md)
584+
return True
541585

542586
parser.add_argument(
543587
'path',
@@ -547,13 +591,13 @@ def main():
547591
)
548592
parser.add_argument(
549593
'-f', '--format',
550-
choices=['markdown', 'compact', 'json', 'yaml', 'csv', 'gherkin'],
594+
choices=['markdown', 'compact', 'json', 'yaml', 'csv', 'gherkin', 'toon', 'logicml'],
551595
default='markdown',
552596
help='Output format (default: markdown)'
553597
)
554598
parser.add_argument(
555599
'-d', '--detail',
556-
choices=['minimal', 'standard', 'full'],
600+
choices=['minimal', 'standard', 'full', 'detailed'],
557601
default='standard',
558602
help='Detail level - columns to include (default: standard)'
559603
)
@@ -617,7 +661,15 @@ def main():
617661
help='Show saved LLM profiles'
618662
)
619663

664+
if len(sys.argv) == 1 or any(a in ("-h", "--help") for a in sys.argv[1:]):
665+
if not _maybe_print_pretty_help():
666+
parser.print_help()
667+
return
668+
620669
args = parser.parse_args()
670+
671+
if args.detail == 'detailed':
672+
args.detail = 'full'
621673

622674
# Initialize logger
623675
log = Logger(verbose=args.verbose, debug=args.debug)
@@ -642,6 +694,8 @@ def main():
642694
YAMLGenerator, CSVGenerator
643695
)
644696
from .gherkin import GherkinGenerator
697+
from .toon_format import TOONGenerator
698+
from .logicml import LogicMLGenerator
645699

646700
# Status check
647701
if args.status:
@@ -705,9 +759,10 @@ def main():
705759

706760
# Path is required for analysis
707761
if args.path is None:
708-
print("Error: path is required", file=sys.stderr)
709-
parser.print_help()
710-
sys.exit(1)
762+
# Keep behavior consistent with --help
763+
if not _maybe_print_pretty_help():
764+
parser.print_help()
765+
return
711766

712767
# Validate path
713768
if not os.path.exists(args.path):
@@ -774,6 +829,20 @@ def main():
774829
elif args.format == 'gherkin':
775830
generator = GherkinGenerator()
776831
output = generator.generate(project, detail=args.detail)
832+
833+
elif args.format == 'toon':
834+
generator = TOONGenerator()
835+
detail_map = {
836+
'minimal': 'compact',
837+
'standard': 'standard',
838+
'full': 'full',
839+
}
840+
output = generator.generate(project, detail=detail_map.get(args.detail, 'standard'))
841+
842+
elif args.format == 'logicml':
843+
generator = LogicMLGenerator()
844+
spec = generator.generate(project, detail=args.detail)
845+
output = spec.content
777846

778847
gen_time = time.time() - gen_start
779848

@@ -792,7 +861,14 @@ def main():
792861
log.success(f"Output written to: {args.output}")
793862
else:
794863
if not args.quiet:
795-
print(output)
864+
try:
865+
print(output, flush=True)
866+
except BrokenPipeError:
867+
try:
868+
sys.stdout.close()
869+
except Exception:
870+
pass
871+
os._exit(0)
796872

797873
# Final summary
798874
if args.verbose:

0 commit comments

Comments
 (0)