codeplain 0.3.10.dev4__py3-none-any.whl → 0.3.11__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.
cli_output/status.py CHANGED
@@ -6,6 +6,8 @@ from typing import Optional
6
6
  import codeplain_REST_api as codeplain_api
7
7
  from plain2code_console import console
8
8
 
9
+ PLANS_URL = "https://platform.codeplain.ai/plans"
10
+
9
11
 
10
12
  def _create_progress_bar(remaining: int, total: int, width: int = 30) -> str:
11
13
  """Create a Unicode progress bar."""
@@ -42,12 +44,12 @@ def _display_credit_line(plan_credits: dict) -> None:
42
44
  bar = _create_progress_bar(remaining, total, width=30)
43
45
 
44
46
  # Format plan type label
45
- plan_label = "Free trial" if plan_type == "free" else plan_type.upper() + " plan"
47
+ plan_label = "Free credits" if plan_type == "free" else plan_type.upper() + " plan"
46
48
 
47
49
  if is_expired:
48
- console.print(f" {plan_label:10} {bar} expired {formatted_date}")
50
+ console.print(f" {plan_label:12} {bar} expired {formatted_date}")
49
51
  else:
50
- console.print(f" {plan_label:10} {bar} {remaining:2} of {total} remaining expires {formatted_date}")
52
+ console.print(f" {plan_label:12} {bar} {remaining:2} of {total} remaining expires {formatted_date}")
51
53
 
52
54
 
53
55
  def _display_bucket_credit_line(bucket: dict, label: str) -> None:
@@ -71,9 +73,9 @@ def _display_bucket_credit_line(bucket: dict, label: str) -> None:
71
73
  bar = _create_progress_bar(remaining, total, width=30)
72
74
 
73
75
  if is_expired:
74
- console.print(f" {label:10} {bar} expired {formatted_date}")
76
+ console.print(f" {label:12} {bar} expired {formatted_date}")
75
77
  else:
76
- console.print(f" {label:10} {bar} {remaining:2} of {total} remaining expires {formatted_date}")
78
+ console.print(f" {label:12} {bar} {remaining:2} of {total} remaining expires {formatted_date}")
77
79
 
78
80
 
79
81
  def _display_status_message(plan_credits: Optional[dict], purchased_credits: list, promo_credits: list) -> None:
@@ -107,7 +109,11 @@ def _display_status_message(plan_credits: Optional[dict], purchased_credits: lis
107
109
  break
108
110
 
109
111
  if not has_remaining:
110
- console.print("\nNo rendering credits remaining. Upgrade to continue rendering.")
112
+ console.print(
113
+ f"\nNo rendering credits remaining. Upgrade to the PRO plan at {PLANS_URL} to continue rendering."
114
+ )
115
+ else:
116
+ console.print(f"\nTo manage your plan navigate to {PLANS_URL}")
111
117
 
112
118
 
113
119
  def print_status(api_key: str, api_url: str, client_version: str) -> None:
@@ -148,7 +154,7 @@ def print_status(api_key: str, api_url: str, client_version: str) -> None:
148
154
  # Display rendering credits section
149
155
  console.print("Rendering credits:")
150
156
 
151
- # Display plan credits (free trial or subscription)
157
+ # Display plan credits (free credits or subscription)
152
158
  if plan_credits:
153
159
  _display_credit_line(plan_credits)
154
160
 
@@ -162,4 +168,3 @@ def print_status(api_key: str, api_url: str, client_version: str) -> None:
162
168
 
163
169
  # Display status messages and management link
164
170
  _display_status_message(plan_credits, purchased_credits, promo_credits)
165
- console.print("\nTo manage your plan navigate to https://platform.codeplain.ai/plans")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: codeplain
3
- Version: 0.3.10.dev4
3
+ Version: 0.3.11
4
4
  Summary: Transform plain language specifications into working code
5
5
  License-File: LICENSE
6
6
  Classifier: Environment :: Console
@@ -8,7 +8,7 @@ Classifier: Intended Audience :: Developers
8
8
  Classifier: Operating System :: OS Independent
9
9
  Classifier: Topic :: Software Development :: Code Generators
10
10
  Requires-Python: ==3.11.*
11
- Requires-Dist: gitpython==3.1.55
11
+ Requires-Dist: gitpython==3.1.59
12
12
  Requires-Dist: mistletoe==1.6.0
13
13
  Requires-Dist: networkx==3.6.1
14
14
  Requires-Dist: python-frontmatter==1.3.0
@@ -16,9 +16,9 @@ Requires-Dist: python-liquid2==0.4.0
16
16
  Requires-Dist: pyyaml==6.0.3
17
17
  Requires-Dist: requests==2.34.2
18
18
  Requires-Dist: rich==15.0.0
19
- Requires-Dist: sentry-sdk==2.66.1
19
+ Requires-Dist: sentry-sdk==2.68.1
20
20
  Requires-Dist: textual>=7.5.0
21
- Requires-Dist: tiktoken==0.13.0
21
+ Requires-Dist: tiktoken==0.14.0
22
22
  Requires-Dist: transitions==0.9.3
23
23
  Provides-Extra: dev
24
24
  Requires-Dist: black==26.5.1; extra == 'dev'
@@ -1,19 +1,20 @@
1
1
  change_detection.py,sha256=CazORZrezeZSw2I6VBvieN-c2SEjieZPo6pNrKwmMVA,6915
2
- codeplain_REST_api.py,sha256=Iu3htCE05k-8BOA6I2acz4Ezr74cHQIXmhBIsgnX4Ng,21232
2
+ codeplain_REST_api.py,sha256=ptb2u0mC5FJqMNeUbcc-bNXNhy9NRJZa5vI80uSFSwQ,22715
3
3
  concept_utils.py,sha256=TpyiQ4DfMtO231j-lPj65i0suRTPr8t9j3QNf9KiM4A,8080
4
4
  conformance_fix_journal.py,sha256=7wL3UOc3BWaUo5MCmiFbb3kIwOQ6iEpdQSyEmedo8Vk,9488
5
5
  diff_utils.py,sha256=AjiQlqo5pRos_8hVXZo5yBurl5BzSrTMGrQv4dCtRCg,1198
6
6
  event_bus.py,sha256=g5XLwRfjrvTIcB6urjREuCJzZj5TA93hy8lKXLGXuCE,650
7
- file_utils.py,sha256=MNZ5li1ofIMOYB6FCrMa_UkX6nkGCgSZuaqzhqVc1Ok,11682
7
+ file_utils.py,sha256=IshKvld9LJLwXSB3lxZ1HQyVHa93I626BsTScBu95ss,12118
8
+ git_preflight.py,sha256=vB2A8GyYz_iWr8jOE4POFLBHr3qihmedWuidspGdsrw,1509
8
9
  git_utils.py,sha256=gCUA6_9zg19f6k_0aVcRnIVlAJSBD0Vt6takx-0pKME,16547
9
10
  memory_management.py,sha256=CwIOQqF7Xo1sNfvP69zQRK_CpjinQD9dbolhRPl6lTk,2446
10
11
  metadata_utils.py,sha256=YO17o7IAma5TT1ZN0pTwrLVr_KxFQ8APv02KqKPpI5k,1641
11
12
  module_renderer.py,sha256=IJBle8mbBARn_xrekjSpsVID7719yy2rNPkM15-ONoI,6982
12
13
  partial_rendering.py,sha256=uzZc2j4d3pAXzfiHw_y1TK4yLRsLOb1Mm21570AGxJ8,13837
13
14
  path_resolution.py,sha256=0tMqU0a1g4DYhTOp7QjLVSEkv_cgasb4NI1mK809dUM,2233
14
- plain2code.py,sha256=NsZf7JS2bGmOQ6HsbiTqNA3EMXXY5YQW6nBBXCG7m-U,17392
15
+ plain2code.py,sha256=FV13ItuD9FQN4FhS4H7RXHh7MoQaAlGSh1ctkjeMSoo,20368
15
16
  plain2code_arguments.py,sha256=xMK-R13Vu005bHz3MUDY8F0Cf3ED9hCPqNsC6jGZmkk,17620
16
- plain2code_console.py,sha256=5N4_GIV8qE-2zvFh6qoOHhhsuwCF_3r7jG19S-YWtfk,5906
17
+ plain2code_console.py,sha256=YGBONkQtwZUkrjxTKhqcBoRzW0EmSfYHPH8P4_FM60I,6040
17
18
  plain2code_events.py,sha256=rgDF5PDtNr_lEPvJmd1tIQXZrzwgzZW5b-_pykugqe4,1587
18
19
  plain2code_exceptions.py,sha256=rtFEQbs1ruzSlGXIGJZoZry9sjYzmhPKHiUQIECcbL0,2567
19
20
  plain2code_logger.py,sha256=YQ-ENYBHKXROVv6Q52xh7eXrFkj_3Lex__N9zfxlw9Y,5729
@@ -22,15 +23,15 @@ plain2code_read_config.py,sha256=D5CxnmTRjddupecWvstcUm0kIJWWzq5-Zmk6sU0wbIg,223
22
23
  plain2code_state.py,sha256=uePQlC29Pbhm1yXfdCFebONYdy6jo13gTTmXSwC11cQ,3168
23
24
  plain2code_telemetry.py,sha256=e4UCAoFklJMDWGQsL50i7nTLiOBy8gs86A0XO4WLgPo,4597
24
25
  plain2code_utils.py,sha256=unOxuqSQRpwG8YF6uoQhnZ3ZSTJRIUCPLdJ-1lRZR5M,1534
25
- plain_file.py,sha256=Kn8m_w6c04_RR-FBjAU05UcLOTDk4zcctpJXghYdZ8I,35757
26
- plain_modules.py,sha256=kUGR2mX908FEKOO8b2CJuk2rn9iRj8qyWGFMWTdfzoY,25977
26
+ plain_file.py,sha256=yzdHYBZye1UjI6K-vcod5u9hPqQ_zBQS050yypdmB3M,37614
27
+ plain_modules.py,sha256=nXlmw0J2rKkuU1Y3A68LVtZ1Ffdc-D5iMjxaXSaSHTQ,25963
27
28
  plain_spec.py,sha256=7nb5lLJKHZ3uu9THkSXyJ5brIKkbGW0ooVfRclLdlzA,16062
28
29
  system_config.py,sha256=sA8wiPmBMtRsLwSQ40U5pVdLazAafbV-1Z19Yp0HiSQ,5314
29
30
  usage_summary.py,sha256=MA4WcVaAwz6IWCN4k_rgxVcwO4I157wN7BZlFi3h0R4,1282
30
31
  cli_output/__init__.py,sha256=MBKl0YbVpWE-tBa3V5_aVbB7vbaqlIK2NsHTjRUZqis,284
31
32
  cli_output/dry_run.py,sha256=6NKhNWPtD0CBK_QHSsSqIKLTzibTwxS9d8ltP9d1bgU,1473
32
33
  cli_output/render_summary.py,sha256=-yRoI7ZZTS9IHyp1vd-sFleGXTVbxlrCkvuM5TX0Mp4,1168
33
- cli_output/status.py,sha256=MvdocFXL1aA73NMp92gQb0Nd-f7-iw66XVUiHK33sEM,6194
34
+ cli_output/status.py,sha256=TrRo-BYUJmzjVzPlMUDpE76FUSpSsN_JdsKigKMrdmg,6294
34
35
  config/__init__.py,sha256=beYSsJWmBNHDP5rYmVDouqgEeP3t1lkkepbXJ-oq0F8,37
35
36
  config/system_config.yaml,sha256=of5R9vukSohU9AlkGJOcAVQA1UJgxYmDy6islU6iFuE,508
36
37
  docs/generate_cli.py,sha256=0FHVhICbM8g7ahdInTNih3hNKpty1wbV5rdGECMJj48,656
@@ -40,7 +41,7 @@ render_machine/code_renderer.py,sha256=-vp_ltU7e3ssX9hbS8IY4ETSp-39JAuoJ3ZltKpdt
40
41
  render_machine/conformance_tests.py,sha256=PNTCnJNd_xNIhpTPPS8vlx8qws3cTgQSTLdADRab2YM,9617
41
42
  render_machine/implementation_code_helpers.py,sha256=Fxm-IInoZjAIZn41_-WXWj7VIYczpW4yH44Gt8CYMAQ,2378
42
43
  render_machine/render_context.py,sha256=iaJWI_EMSn51LNF_b7KRvlir46ZgUkjbaEiVgSvMTCI,26314
43
- render_machine/render_types.py,sha256=IY16mGnt5uoZlLKy16Wmpq0_M5PAitVDvt2khnDhKU0,8479
44
+ render_machine/render_types.py,sha256=A5BHAkjmDHQ0pd5rR6THlRY5z6rzrdJdcgZFhN7yw9w,8860
44
45
  render_machine/render_utils.py,sha256=yA3X15E_Lebk_-MxkSz9rM_qy-Pes8EQ9Ye-smkHCDw,9320
45
46
  render_machine/state_machine_config.py,sha256=0pL4PyrZ5F2UumvHLMRfhyh64M4pDvf-9zEs-5OAEU8,27866
46
47
  render_machine/states.py,sha256=Lu-7upbs9_bjJsy0P738qx5IiN0DzVcFdhF2-m6YjnQ,2067
@@ -53,8 +54,8 @@ render_machine/actions/create_dist.py,sha256=ykER7i2OvWAXjrlAZnkQHpiNl00vSMF8WDx
53
54
  render_machine/actions/distill_conformance_test_memory.py,sha256=ENjfhTWqRLePHeRF4t8zNRhWKDV2rU4pLYuU_CZhhmo,2639
54
55
  render_machine/actions/exit_with_error.py,sha256=W6TMULHJcHEaXE4WUnlELEu0_l7IZy9HWaPkXyqg7ck,1279
55
56
  render_machine/actions/finish_functional_requirement.py,sha256=usQgPSg6PHpBOR1RhK7lPz9zjY-2VJJDqi3T0nlqdLw,841
56
- render_machine/actions/fix_conformance_test.py,sha256=mHk-UJNEZ7Y65YLLUBljJ3Z5zBVA4jIYRSJ0VCd_LOw,11012
57
- render_machine/actions/fix_unit_tests.py,sha256=hvwvCJDk_-8fHMpkNFyeBSG1IshIcufDpymF00ulPXY,2301
57
+ render_machine/actions/fix_conformance_test.py,sha256=Yy6niLefPIyR3vSxyyX4NUi3NJ4dVc0aEAH9JBCBYX4,11572
58
+ render_machine/actions/fix_unit_tests.py,sha256=pqZzp7snSAe967o_kbh1yAhp9pZIMFY9uoJK1HGcCuc,3532
58
59
  render_machine/actions/prepare_repositories.py,sha256=HyZ1R6E3H5V-G4WDSGfhxl_arskmW9FcaxNdQr6JjVs,3867
59
60
  render_machine/actions/prepare_testing_environment.py,sha256=kIsXgV6-jOlqp-04cimPgTQoHPrVIu0zW-H_Jj-jowE,2106
60
61
  render_machine/actions/refactor_code.py,sha256=gyPsnqgHseL2bkENu4WeGCRyV0R18_GH3JOrPdoxKvg,2609
@@ -71,12 +72,15 @@ tests/__init__.py,sha256=Wk73Io62J15BtlLVIzxmASDWaaJkQLevS4BLK5LDAQg,16
71
72
  tests/conftest.py,sha256=QZcp08htUlJGgmHDlRWFgsWXZ8o8IBWTD5QqJaUMlU8,790
72
73
  tests/test_arg_provenance.py,sha256=d_J7aCeHMXBpctBWTt60WU-I8pEKN7XRXRTJZZGCdo0,3441
73
74
  tests/test_change_detection.py,sha256=jH8SEhndX400DNqIKmcYLzxHqq9WFdgmuvY3XPq-ZFc,13534
74
- tests/test_cli_output.py,sha256=ZV2F1NNW4EiZvMNkhF9XUZH6bFJbddfkZof5qAFxe3Y,18885
75
+ tests/test_cli_output.py,sha256=9yTk0pD2w6vkiSdSkYwneUmEtNqX4x64F6PzYShUEcw,19331
75
76
  tests/test_conformance_fix_journal.py,sha256=olTeAuxXgFwQ8LSQyt0FqtBy9_XY_gK-Ei_pUuJ0C3M,10389
76
- tests/test_console_log_styles.py,sha256=63DQWiwWNhe_rMP70bW9DAB6-EPaqU3Wpwyh8eR09pc,4080
77
+ tests/test_console_log_styles.py,sha256=sxlhynMAP-kmqhBn1-pKvZXdCnlKSyCGJqwYKrh6sQ0,6468
77
78
  tests/test_distill_conformance_test_memory.py,sha256=Tftw9l-gLVwIU6QttY03h2VedvuFxpIsCmZIzvWfxMA,5481
79
+ tests/test_dry_run_link_validation.py,sha256=ivvYXQPqUkQ8glbFyUrdCninCxF2mg-rAsDcfjcB7MM,2140
78
80
  tests/test_file_utils.py,sha256=R_ZIhrTtwlhHxgmbBtA8PFEU5-QB4Fi5skJ7PS2KzdU,3614
81
+ tests/test_fix_unit_tests_conformance_context.py,sha256=IEsXK0xMLRuHmoQGBwrY0ENWVng2n-db_YzSO2qBfqM,9136
79
82
  tests/test_folder_path_resolution.py,sha256=K9v684xkPE0HOAsovg8uDixpZmaIk7ho-Wd3rTjKLF0,6560
83
+ tests/test_git_preflight.py,sha256=6aQqInDzpk03pS-6UfkGQWjErrIXTEdHs5oeq3ZYzTw,3186
80
84
  tests/test_git_utils.py,sha256=h3rWhhs4XVRYqzVzdmj51siF_AYD-GkbZiefK2iGfWo,15614
81
85
  tests/test_headless_logging.py,sha256=soNESeotKQaU-SQyxtjpq35l-dLVsauZ_lJ-wk9i2mI,6419
82
86
  tests/test_imports.py,sha256=1ncbqQhtQW237kIjyV-Ufl7-R-PuduvoYFj2iS-DXW4,2608
@@ -89,30 +93,32 @@ tests/test_plain2code_state.py,sha256=cowPZS9oRbv9J49CE4P3Rc47Q5oQOby4bTf2OlnRlB
89
93
  tests/test_plain2code_utils.py,sha256=-W8a7FquMJQ_pTdBoBF1XprzNL2G555QhdtXCy6BDyQ,1237
90
94
  tests/test_plain_modules.py,sha256=Fn3FeH-M4exnyFJDZzc9YWaLEfBmiqPH5TpiBT9jgAs,33033
91
95
  tests/test_plainfile.py,sha256=wZ_PN4GULPWIpl_AK_r_6jkgtX0Ch8EHmN5IhX7huOE,11027
92
- tests/test_plainfileparser.py,sha256=l8GfXkVaSzgH74fX45Wp5jX0q3QlO6t_TgDG72dc28o,23867
96
+ tests/test_plainfileparser.py,sha256=OY7dCilaUEeM4vAOShQYsZXKQzzUOi7nGYBaROu8Te8,26606
93
97
  tests/test_plainspec.py,sha256=ng55E8ajrH2-gPC1p_QDIe_zXplPvoqHJ-d57UWKFTw,3577
94
98
  tests/test_prepare_repositories_layout.py,sha256=PhJQLnHfjfjo6CTWoicyEKgL0_GtjwB2OKYAWSBVHtk,14638
99
+ tests/test_render_finished_report.py,sha256=QtViWItlo8YB8bLOv3YwvfLprVZgZ9r8Q1Td7_vGi18,7445
95
100
  tests/test_requires.py,sha256=6tnHYaKudxN-RiQjSFoi9nHe_x0ze3QF-f-j67zAE74,1250
96
101
  tests/test_resolve_config_file.py,sha256=b87tDOvyzbsnE-QRWbZ97ou7NuyaAgskv-dbYBum31c,2830
97
102
  tests/test_script_path_resolution.py,sha256=tiEqMevQRXr9YpfHN0xldOOQpLAUgnSqTzeZWkKHaUY,4156
98
103
  tests/test_system_config.py,sha256=gH5qs9q4lh5CljAdz28HUrJzuKqCvkZherFuv7enMak,2787
99
104
  tests/test_telemetry.py,sha256=LINvx1_Oz1OJ3ho2RcIcAA29IsNfUbkVVCbFaQcrBaA,8133
105
+ tests/test_tui_components.py,sha256=O_PB6z_Xt7YWebsD21b99PVgFtqq5c03lVtYytIT5Ps,6200
100
106
  tests/test_tui_usage.py,sha256=sfiAGTpRrT3MRr0-8DH3qjuECSxjODVgwzrK_K3iPXs,7959
101
107
  tests/test_usage_summary.py,sha256=Ps0kVzvmdZYeXrBR-cqkn3szTV5neYKs1PmmURngeIc,1079
102
108
  tests/e2e/conftest.py,sha256=5Dm67UTOE-M6hMb-mqZvwa6Ek_wmZgsR7fUv9ZEqCwY,4164
103
109
  tests/e2e/test_hello_world_python.py,sha256=2Cgs3-79dtQh5gVab257tFDHXTznefZeYqbTjlM4Nng,1632
104
110
  tests/e2e/test_hello_world_python_windows.py,sha256=ggynH_Wm4011M500zoPNNgvD-s4vZ3hvlorf1yARgQQ,2019
105
111
  tui/__init__.py,sha256=_fy7FowY0RkWdn4kC7XV2jgvQPCj-2gE8fNUb-Mny0A,60
106
- tui/components.py,sha256=XkC4Aq6VyTnFnBQs8PEHwVdAOczhm0ebKN92kiYMbq0,25791
112
+ tui/components.py,sha256=tAgP7paI8bR6XAWOWyiUsKexRurEx1CNz_4z81Ty0G4,27195
107
113
  tui/models.py,sha256=KMjlWubmPD41GA-BjgiqkaV9v7tLJ0fuWKBDPmZj4JA,1493
108
- tui/plain2code_tui.py,sha256=8F7ZYRpmTiWRdTeQ63LPf2LGrreYjIYXvf_9XNSPyHs,17104
114
+ tui/plain2code_tui.py,sha256=b6FOEXt_dcVhpfOrAHvg-Rvn0bSH18moy5zmUaz3NS4,17552
109
115
  tui/plain_module_render_choice_tui.py,sha256=XwBMMzyDat9FYqMkKdIV7kmIevddfwGA5Qk9kgy5sYY,12112
110
116
  tui/spinner.py,sha256=Ro6Gd9Przf-whuHqPRY6HwI0T57yJjyNPbhDbigZKZE,2471
111
117
  tui/state_handlers.py,sha256=zbUS_D9eU8qIwVpaxPEp9zL9bpg8gXsWNoUaqg9TLrQ,16664
112
118
  tui/styles.css,sha256=kiC7Og_2G1E7owoiZgV95-3_dSeQnsNj3qzaY04xkDg,6727
113
- tui/widget_helpers.py,sha256=ToO9MV2bF2Rp-XMNcjIYclnB_qmgO5sLw4UtQ2CyTMg,6652
114
- codeplain-0.3.10.dev4.dist-info/METADATA,sha256=AHNwzkViIw83FQTt9C5UKXgVVALnEwJ3li-gXTzaUxU,7256
115
- codeplain-0.3.10.dev4.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
116
- codeplain-0.3.10.dev4.dist-info/entry_points.txt,sha256=oDZkBqu9WhtZApb_K6ia8-fn9aojwmAsgnKELceX5T4,46
117
- codeplain-0.3.10.dev4.dist-info/licenses/LICENSE,sha256=pCeKgQ1mXE5OmNUuKOfOZh1T1vqkeCUxghE7N8qgnzc,11345
118
- codeplain-0.3.10.dev4.dist-info/RECORD,,
119
+ tui/widget_helpers.py,sha256=jitm2WHiKj-NVFL-hULxWzckPreRbUTl9OIZLN31-ek,6997
120
+ codeplain-0.3.11.dist-info/METADATA,sha256=WtLGmKTaR0d2o383f3bvaZ6JtDdF86bBNjcRxncpLSQ,7251
121
+ codeplain-0.3.11.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
122
+ codeplain-0.3.11.dist-info/entry_points.txt,sha256=oDZkBqu9WhtZApb_K6ia8-fn9aojwmAsgnKELceX5T4,46
123
+ codeplain-0.3.11.dist-info/licenses/LICENSE,sha256=pCeKgQ1mXE5OmNUuKOfOZh1T1vqkeCUxghE7N8qgnzc,11345
124
+ codeplain-0.3.11.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.32.0
2
+ Generator: hatchling 1.32.4
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
codeplain_REST_api.py CHANGED
@@ -11,6 +11,10 @@ from plain2code_state import RunState
11
11
  MAX_RETRIES = 4
12
12
  RETRY_DELAY = 3
13
13
 
14
+ # A report the render does not consume must not be able to hold the process open
15
+ # at exit, and requests waits indefinitely by default.
16
+ REPORT_TIMEOUT_SECONDS = 5
17
+
14
18
  RETRY_ERROR_CODES = [
15
19
  "LLMInternalError",
16
20
  ]
@@ -125,6 +129,7 @@ class CodeplainAPI:
125
129
  run_state: Optional[RunState],
126
130
  num_retries: int = MAX_RETRIES,
127
131
  silent: bool = False,
132
+ timeout: Optional[int] = None,
128
133
  ):
129
134
  if run_state is not None:
130
135
  self._extend_payload_with_run_state(payload, run_state)
@@ -134,7 +139,7 @@ class CodeplainAPI:
134
139
 
135
140
  for attempt in range(num_retries + 1):
136
141
  try:
137
- response = requests.post(endpoint_url, headers=headers, json=payload)
142
+ response = requests.post(endpoint_url, headers=headers, json=payload, timeout=timeout)
138
143
 
139
144
  try:
140
145
  response_json = response.json()
@@ -173,6 +178,27 @@ class CodeplainAPI:
173
178
  }
174
179
  return self.post_request(endpoint_url, headers, payload, None, num_retries=0, silent=True)
175
180
 
181
+ def render_finished(self, outcome: str, run_state: RunState):
182
+ """Report the terminal state a render reached.
183
+
184
+ Called once per render, whatever the outcome. The render state machine
185
+ lives here, so the API cannot tell a finished render from one still
186
+ going; it has to be told.
187
+
188
+ No retries and no console output: a report has no result the render
189
+ needs, and must not add minutes to a render that has already ended nor
190
+ print retry noise over the exit summary.
191
+ """
192
+ endpoint_url = f"{self.api_url}/render_finished"
193
+ headers = {"X-API-Key": self.api_key, "Content-Type": "application/json"}
194
+ payload = {
195
+ "outcome": outcome,
196
+ }
197
+
198
+ return self.post_request(
199
+ endpoint_url, headers, payload, run_state, num_retries=0, silent=True, timeout=REPORT_TIMEOUT_SECONDS
200
+ )
201
+
176
202
  def render_functional_requirement(
177
203
  self,
178
204
  frid: str,
@@ -237,6 +263,7 @@ class CodeplainAPI:
237
263
  required_modules,
238
264
  unittests_issue,
239
265
  run_state: RunState,
266
+ conformance_tests_fixes: list[dict] | None = None,
240
267
  ):
241
268
  endpoint_url = f"{self.api_url}/fix_unittests_issue"
242
269
  headers = {"X-API-Key": self.api_key, "Content-Type": "application/json"}
@@ -251,6 +278,10 @@ class CodeplainAPI:
251
278
  "unittests_issue": unittests_issue,
252
279
  "unittest_batch_id": run_state.unittest_batch_id,
253
280
  }
281
+ # Implementation code changes made by the conformance tests fixer right before this unit tests run;
282
+ # only sent when unit tests are processed inside the conformance tests phase.
283
+ if conformance_tests_fixes is not None:
284
+ payload["conformance_tests_fixes"] = conformance_tests_fixes
254
285
 
255
286
  return self.post_request(endpoint_url, headers, payload, run_state)
256
287
 
file_utils.py CHANGED
@@ -180,6 +180,20 @@ def open_from(dirs, file_name):
180
180
  return None
181
181
 
182
182
 
183
+ def resolve_linked_resource(dirs, file_name):
184
+ """Return the first existing path for file_name across dirs, or None if not found.
185
+
186
+ Mirrors the search order used by open_from so that validating a linked resource and
187
+ loading it can never disagree.
188
+ """
189
+ for dir in dirs:
190
+ full_file_name = os.path.join(dir, file_name)
191
+ if os.path.exists(full_file_name):
192
+ return full_file_name
193
+
194
+ return None
195
+
196
+
183
197
  def load_linked_resources(template_dirs: list[str], resources_list, module_name: str):
184
198
  linked_resources = {}
185
199
 
git_preflight.py ADDED
@@ -0,0 +1,41 @@
1
+ """Verify a working git is available before GitPython is imported.
2
+
3
+ GitPython probes for the git executable on import and raises from module scope
4
+ if it is missing or broken, so the CLI would crash with a traceback before
5
+ main() could explain it.
6
+ """
7
+
8
+ import shutil
9
+ import subprocess
10
+ import sys
11
+
12
+ from plain2code_console import console
13
+
14
+ GIT_MISSING_MESSAGE = "git is not installed. Please install git and try again."
15
+ GIT_BROKEN_MESSAGE = "git is installed but not working. Please repair your git installation and try again."
16
+ MACOS_BROKEN_GIT_HINT = (
17
+ "This usually means the Command Line Tools are missing; install them with 'xcode-select --install'."
18
+ )
19
+
20
+
21
+ def git_runs(git_path):
22
+ """Whether `git version` succeeds. A git on PATH can still be unusable: on
23
+ macOS without the Command Line Tools /usr/bin/git is a stub that exits with
24
+ an xcrun error, and GIT_PYTHON_REFRESH=quiet does not suppress that."""
25
+ try:
26
+ return subprocess.run([git_path, "version"], capture_output=True, timeout=10).returncode == 0
27
+ except (OSError, subprocess.SubprocessError):
28
+ return False
29
+
30
+
31
+ def require_git():
32
+ """Exit with a clean message unless a working git is available."""
33
+ git_path = shutil.which("git")
34
+ if git_path is None:
35
+ console.error(f"{GIT_MISSING_MESSAGE}\n")
36
+ sys.exit(1)
37
+
38
+ if not git_runs(git_path):
39
+ hint = f"\n{MACOS_BROKEN_GIT_HINT}" if sys.platform == "darwin" else ""
40
+ console.error(f"{GIT_BROKEN_MESSAGE}{hint}\n")
41
+ sys.exit(1)
plain2code.py CHANGED
@@ -61,6 +61,11 @@ from tui.plain_module_render_choice_tui import PlainModuleRenderChoiceTUI
61
61
  DEFAULT_TEMPLATE_DIRS = "standard_template_library"
62
62
  RENDER_THREAD_SHUTDOWN_TIMEOUT = 0.7
63
63
 
64
+ # The terminal states a render can reach, as the API names them.
65
+ RENDER_OUTCOME_COMPLETED = "completed"
66
+ RENDER_OUTCOME_FAILED = "failed"
67
+ RENDER_OUTCOME_CANCELLED = "cancelled"
68
+
64
69
  # Exceptions that represent expected, user-facing error conditions. They are
65
70
  # reported to the user directly and must never be sent to Sentry as crashes.
66
71
  EXPECTED_EXCEPTIONS = (
@@ -177,6 +182,52 @@ def _check_connection(codeplainAPI: codeplain_api.CodeplainAPI) -> Optional[str]
177
182
  return response.get("user_email")
178
183
 
179
184
 
185
+ def _render_outcome(run_state: RunState, interrupted: bool = False) -> str:
186
+ """Name the terminal state a render reached.
187
+
188
+ Ctrl-C is the user stopping the render, so it is a cancellation. It does not
189
+ set render_cancelled: the exit summary reads that flag, and its wording for
190
+ an interrupt stays as it is.
191
+
192
+ Cancellation is tested first. A cancelled render leaves render_succeeded
193
+ False, so asking about success first would report it as a failure.
194
+ """
195
+ if interrupted or run_state.render_cancelled:
196
+ return RENDER_OUTCOME_CANCELLED
197
+ if run_state.render_succeeded:
198
+ return RENDER_OUTCOME_COMPLETED
199
+ return RENDER_OUTCOME_FAILED
200
+
201
+
202
+ def report_render_finished(
203
+ codeplainAPI: codeplain_api.CodeplainAPI, run_state: RunState, interrupted: bool = False
204
+ ) -> None:
205
+ """Tell the API which terminal state this render reached.
206
+
207
+ The API cannot work it out for itself - the state machine lives here - and
208
+ main()'s finally is the one place that sees every ending: success, failure,
209
+ cancellation, an unexpected crash and a keyboard interrupt alike. The state
210
+ machine's own completed and failed hooks see only two of the five.
211
+
212
+ Reported only once the connection check has set a user email, which is
213
+ exactly the condition "the API knows who we are". A missing key, an invalid
214
+ key and an outdated client all leave it unset, and none of them got as far
215
+ as a render.
216
+
217
+ Never raises. A report must not change the exit code, and must not mask the
218
+ error that ended the render.
219
+ """
220
+ if not run_state.user_email:
221
+ return
222
+
223
+ try:
224
+ codeplainAPI.render_finished(_render_outcome(run_state, interrupted), run_state)
225
+ except Exception:
226
+ # Deliberately silent: logging has stopped by now, and a failed report
227
+ # is not the user's problem.
228
+ pass
229
+
230
+
180
231
  def warn_if_acceptance_tests_without_conformance_script(plain_module, args) -> None:
181
232
  """Warn when any loaded module (including required modules) defines acceptance tests
182
233
  but no conformance tests script is configured.
@@ -203,8 +254,18 @@ def warn_if_acceptance_tests_without_conformance_script(plain_module, args) -> N
203
254
  )
204
255
 
205
256
 
257
+ def validate_linked_resources(plain_module) -> None:
258
+ """Load every linked resource exactly the way rendering does, so dry-run
259
+ surfaces the same errors (binary file, missing file, oversized base64 blob)."""
260
+ for module in plain_module.all_required_modules + [plain_module]:
261
+ resources_list: list[dict] = []
262
+ plain_spec.collect_linked_resources(module.plain_source, resources_list, None, True)
263
+ file_utils.load_linked_resources(module.template_dirs, resources_list, module.module_name)
264
+
265
+
206
266
  def render( # noqa: C901
207
267
  plain_module: plain_modules.PlainModule,
268
+ codeplainAPI: codeplain_api.CodeplainAPI,
208
269
  args,
209
270
  run_state: RunState,
210
271
  event_bus: EventBus,
@@ -215,10 +276,6 @@ def render( # noqa: C901
215
276
  if args.render_range or args.render_from:
216
277
  render_range = plain_spec.compute_render_range(args, plain_module.plain_source)
217
278
 
218
- codeplainAPI = codeplain_api.CodeplainAPI(args.api_key, console)
219
- assert args.api is not None and args.api != "", "API URL is required"
220
- codeplainAPI.api_url = args.api
221
-
222
279
  run_state.user_email = _check_connection(codeplainAPI)
223
280
 
224
281
  stop_event = threading.Event()
@@ -393,6 +450,12 @@ def main(): # noqa: C901
393
450
  return
394
451
 
395
452
  if args.dry_run:
453
+ try:
454
+ validate_linked_resources(plain_module)
455
+ except Exception as e:
456
+ console.error(f"Error: {str(e)}")
457
+ return
458
+
396
459
  console.info("Printing dry run output...\n")
397
460
  render_range = plain_spec.compute_render_range(args, plain_module.plain_source)
398
461
  print_dry_run_output(plain_module.plain_source, render_range)
@@ -404,6 +467,10 @@ def main(): # noqa: C901
404
467
  if not args.api:
405
468
  args.api = system_config.default_api_url
406
469
 
470
+ assert args.api is not None and args.api != "", "API URL is required"
471
+ codeplainAPI = codeplain_api.CodeplainAPI(args.api_key, console)
472
+ codeplainAPI.api_url = args.api
473
+
407
474
  run_state = RunState(spec_filename=args.filename, replay_with=args.replay_with)
408
475
 
409
476
  if args.headless:
@@ -416,6 +483,7 @@ def main(): # noqa: C901
416
483
 
417
484
  exc_info = None
418
485
  error_message = None
486
+ interrupted = False
419
487
 
420
488
  try:
421
489
  # Validate API key is present
@@ -423,10 +491,11 @@ def main(): # noqa: C901
423
491
  raise MissingAPIKey(
424
492
  "Your API key is required. Please set the CODEPLAIN_API_KEY environment variable or provide it with the --api-key argument.\n"
425
493
  )
426
- render(plain_module, args, run_state, event_bus, default_log_level)
494
+ render(plain_module, codeplainAPI, args, run_state, event_bus, default_log_level)
427
495
  except BaseException as e:
428
496
  if isinstance(e, KeyboardInterrupt):
429
497
  error_message = "Keyboard interrupt"
498
+ interrupted = True
430
499
  else:
431
500
  error_message = str(e) if str(e) else repr(e)
432
501
 
@@ -445,6 +514,8 @@ def main(): # noqa: C901
445
514
  # Remove any scratch extractions created for archive-only ("<module>.module") modules.
446
515
  for module in plain_module.all_required_modules + [plain_module]:
447
516
  module.cleanup_scratch()
517
+ # Last, so nothing the user is waiting for sits behind a network call.
518
+ report_render_finished(codeplainAPI, run_state, interrupted)
448
519
 
449
520
  if args.headless and (exc_info is not None or not run_state.render_succeeded):
450
521
  sys.exit(1)
plain2code_console.py CHANGED
@@ -27,7 +27,7 @@ class Plain2CodeConsole(Console):
27
27
  DEBUG_STYLE = Style(color="purple")
28
28
 
29
29
  def __init__(self):
30
- super().__init__()
30
+ super().__init__(emoji=False)
31
31
  try:
32
32
  import tiktoken
33
33
 
@@ -66,9 +66,10 @@ class Plain2CodeConsole(Console):
66
66
  """
67
67
  logger.log(level, " ".join(map(str, args)), extra={"log_color": color})
68
68
  style = base_style + Style(color=color) if color else base_style
69
- # Log messages must render exactly as logged: don't interpret square brackets
70
- # in interpolated content (error texts, file names) as Rich markup, and don't
71
- # let the repr highlighter restyle brackets and numbers inside them.
69
+ # Messages must render exactly as logged and not pick its own styling:
70
+ # -- no Rich markup (via square brackets) in interpolated content (error texts, file names)
71
+ # -- no repr highlighter restyling brackets and numbers inside them.
72
+ # -- no emoji substitution, but it's set False in __init__, otherwise it won't reach renderables like Trees
72
73
  kwargs.setdefault("markup", False)
73
74
  kwargs.setdefault("highlight", False)
74
75
  super().print(*args, **kwargs, style=style)