77import sys
88import sysconfig
99import urllib .request
10- import zipfile
1110from importlib import resources
1211from typing import Any , Dict , List , Optional
1312
@@ -97,65 +96,66 @@ def bootstrap(native_instance):
9796 print ("Initialized PythonNative project." )
9897
9998
100- def _extract_zip_to_destination ( zip_path : str , destination : str ) -> None :
101- with zipfile . ZipFile ( zip_path , "r" ) as zf :
102- zf . extractall ( destination )
99+ def _copy_dir ( src : str , dst : str ) -> None :
100+ os . makedirs ( os . path . dirname ( dst ), exist_ok = True )
101+ shutil . copytree ( src , dst , dirs_exist_ok = True )
103102
104103
105- def _extract_bundled_template ( zip_name : str , destination : str ) -> None :
104+ def _copy_bundled_template_dir ( template_dir : str , destination : str ) -> None :
106105 """
107- Extract a bundled template zip into the destination directory.
108- Tries package resources first; falls back to repo root `templates/` at dev time.
106+ Copy a bundled template directory into the destination directory.
107+ Tries the repository `templates/` first during development, then
108+ package resources when installed from a wheel.
109+ The result should be `${destination}/{template_dir}`.
109110 """
110- # Dev-first: prefer repository templates if running from a checkout (avoid stale packaged zips)
111+ dest_path = os .path .join (destination , template_dir )
112+
113+ # Dev-first: prefer local source templates if running from a checkout (avoid stale packaged data)
111114 try :
112115 # __file__ -> src/pythonnative/cli/pn.py, so go up to src/, then to repo root
113116 src_dir = os .path .dirname (os .path .dirname (os .path .dirname (__file__ )))
117+ # Check templates located inside the source package tree
118+ local_pkg_templates = os .path .join (src_dir , "pythonnative" , "templates" , template_dir )
119+ if os .path .isdir (local_pkg_templates ):
120+ _copy_dir (local_pkg_templates , dest_path )
121+ return
114122 repo_root = os .path .abspath (os .path .join (src_dir , ".." ))
115123 repo_templates = os .path .join (repo_root , "templates" )
116- candidate = os .path .join (repo_templates , zip_name )
117- if os .path .exists ( candidate ):
118- _extract_zip_to_destination ( candidate , destination )
124+ candidate_dir = os .path .join (repo_templates , template_dir )
125+ if os .path .isdir ( candidate_dir ):
126+ _copy_dir ( candidate_dir , dest_path )
119127 return
120128 except Exception :
121129 pass
122130
123- # Try to load from installed package resources next (if templates are packaged inside the module)
131+ # Try to load from installed package resources ( templates packaged inside the module)
124132 try :
125- cand = resources .files ("pythonnative" ).joinpath ("templates" ).joinpath (zip_name )
133+ cand = resources .files ("pythonnative" ).joinpath ("templates" ).joinpath (template_dir )
126134 with resources .as_file (cand ) as p :
127135 resource_path = str (p )
128- if os .path .exists (resource_path ):
129- _extract_zip_to_destination (resource_path , destination )
130- return
131- except Exception :
132- # Not packaged inside the module; try data-files installation locations next
133- pass
134-
135- # Try sysconfig data dir (where data-files are typically installed)
136- try :
137- data_dir = sysconfig .get_paths ().get ("data" )
138- if data_dir :
139- candidate = os .path .join (data_dir , "pythonnative" , "templates" , zip_name )
140- if os .path .exists (candidate ):
141- _extract_zip_to_destination (candidate , destination )
136+ if os .path .isdir (resource_path ):
137+ _copy_dir (resource_path , dest_path )
142138 return
143139 except Exception :
144140 pass
145141
146- # Try site-packages purelib/platlib (some environments place data files here)
142+ # Last resort: check typical data-file locations
147143 try :
148- purelib = sysconfig .get_paths ().get ("purelib" )
149- platlib = sysconfig .get_paths ().get ("platlib" )
150- for base in filter (None , [purelib , platlib ]):
151- candidate = os .path .join (base , "pythonnative" , "templates" , zip_name )
152- if os .path .exists (candidate ):
153- _extract_zip_to_destination (candidate , destination )
144+ data_paths = sysconfig .get_paths ()
145+ search_bases = [
146+ data_paths .get ("data" ),
147+ data_paths .get ("purelib" ),
148+ data_paths .get ("platlib" ),
149+ ]
150+ for base in filter (None , search_bases ):
151+ candidate_dir = os .path .join (base , "pythonnative" , "templates" , template_dir )
152+ if os .path .isdir (candidate_dir ):
153+ _copy_dir (candidate_dir , dest_path )
154154 return
155155 except Exception :
156156 pass
157157
158- raise FileNotFoundError (f"Could not find bundled template { zip_name } . Ensure templates are packaged." )
158+ raise FileNotFoundError (f"Could not find bundled template directory { template_dir } . Ensure templates are packaged." )
159159
160160
161161def _github_json (url : str ) -> Any :
@@ -199,8 +199,8 @@ def create_android_project(project_name: str, destination: str) -> None:
199199 :param project_name: The name of the project.
200200 :param destination: The directory where the project will be created.
201201 """
202- # Extract the Android template project from bundled zip
203- _extract_bundled_template ("android_template.zip " , destination )
202+ # Copy the Android template project directory
203+ _copy_bundled_template_dir ("android_template" , destination )
204204
205205
206206def create_ios_project (project_name : str , destination : str ) -> None :
@@ -210,8 +210,8 @@ def create_ios_project(project_name: str, destination: str) -> None:
210210 :param project_name: The name of the project.
211211 :param destination: The directory where the project will be created.
212212 """
213- # Extract the iOS template project from bundled zip
214- _extract_bundled_template ("ios_template.zip " , destination )
213+ # Copy the iOS template project directory
214+ _copy_bundled_template_dir ("ios_template" , destination )
215215
216216
217217def run_project (args : argparse .Namespace ) -> None :
0 commit comments