11import logging
22import os
3+ from contextlib import contextmanager
34from pathlib import Path
4- from typing import Optional , Union
5+ from typing import List , Optional , Union
6+
7+ from seml .settings import SETTINGS
58
69
710def init_project (
@@ -10,6 +13,8 @@ def init_project(
1013 user_name : Optional [str ] = None ,
1114 user_mail : Optional [str ] = None ,
1215 template : str = 'default' ,
16+ git_remote : Optional [str ] = None ,
17+ git_commit : Optional [str ] = None ,
1318 yes : bool = False ,
1419):
1520 """
@@ -27,16 +32,22 @@ def init_project(
2732 The email of the user. If not given, ''.
2833 template : str
2934 The template to use for the project.
35+ git_repo : Optional[str]
36+ The URL of the git repository to use.
37+ git_commit : Optional[str]
38+ The commit to use.
39+ git_branch : Optional[str]
40+ The branch to use.
3041 yes : bool
3142 If True, no confirmation is asked before initializing the project.
3243 """
33- import importlib .resources
3444 from click import prompt
3545 from gitignore_parser import parse_gitignore
3646
3747 if directory is None :
3848 directory = Path ()
3949 directory = Path (directory ).absolute ()
50+
4051 # Ensure that the directory exists
4152 if not directory .exists ():
4253 directory .mkdir (parents = True )
@@ -48,84 +59,132 @@ def init_project(
4859 type = bool ,
4960 ):
5061 exit (1 )
51- logging . info ( f'Initializing project in " { directory } " using template " { template } "' )
52-
53- template_path = (
54- Path ( str ( importlib . resources . files ( 'seml' )))
55- / 'templates'
56- / 'project'
57- / template
58- )
59- if not template_path . exists ():
60- logging . error ( f'Template " { template } " does not exist' )
61- exit ( 1 )
62-
63- if project_name is None :
64- project_name = directory .name
65- if user_name is None :
66- user_name = os .getenv ('USER' , os .getenv ('USERNAME' , 'user' ))
67- if user_mail is None :
68- user_mail = 'my@mail.com'
69- format_map = dict (
70- project_name = project_name , user_name = user_name , user_mail = user_mail
71- )
72-
73- gitignore_path = template_path / '.gitignore'
74- if gitignore_path .exists ():
75- ignore_file = parse_gitignore (gitignore_path )
76- else :
77-
78- def ignore_file (file_path : str ):
79- return False
80-
81- # Copy files one-by-one
82- for src in template_path .glob ('**/*' ):
83- # skip files ignored by .gitignore
84- if ignore_file (str (src )):
85- continue
86- # construct destination
87- file_name = src .relative_to (template_path )
88- target_file_name = Path (str (file_name ).format_map (format_map ))
89- dst = directory / target_file_name
90- # Create directories
91- if src .is_dir ():
92- if not dst .exists ():
93- dst .mkdir ()
94- elif not dst .exists ():
95- # For templates fill in variables
96- if src .suffix .endswith ('.template' ):
97- dst = dst .with_suffix (src .suffix .removesuffix ('.template' ))
98- dst .write_text (src .read_text ().format_map (format_map ))
99- else :
100- # Other files copy directly
101- dst .write_bytes (src .read_bytes ())
62+
63+ tmp_dir = checkout_template_repo ( git_remote , git_commit )
64+ with checkout_template_repo ( git_remote , git_commit ) as tmp_dir :
65+ template_path = tmp_dir / 'templates' / template
66+ if not template_path . exists ():
67+ logging . error ( f'Template " { template } " does not exist' )
68+ exit ( 1 )
69+
70+ logging . info (
71+ f'Initializing project in " { directory } " using template " { template } ".'
72+ )
73+
74+ if project_name is None :
75+ project_name = directory .name
76+ if user_name is None :
77+ user_name = os .getenv ('USER' , os .getenv ('USERNAME' , 'user' ))
78+ if user_mail is None :
79+ user_mail = 'my@mail.com'
80+ format_map = dict (
81+ project_name = project_name , user_name = user_name , user_mail = user_mail
82+ )
83+
84+ gitignore_path = template_path / '.gitignore'
85+ if gitignore_path .exists ():
86+ ignore_file = parse_gitignore (gitignore_path )
87+ else :
88+
89+ def ignore_file (file_path : str ):
90+ return False
91+
92+ # Copy files one-by-one
93+ for src in template_path .glob ('**/*' ):
94+ # skip files ignored by .gitignore
95+ if ignore_file (str (src )):
96+ continue
97+ # construct destination
98+ file_name = src .relative_to (template_path )
99+ target_file_name = Path (str (file_name ).format_map (format_map ))
100+ dst = directory / target_file_name
101+ # Create directories
102+ if src .is_dir ():
103+ if not dst .exists ():
104+ dst .mkdir ()
105+ elif not dst .exists ():
106+ # For templates fill in variables
107+ if src .suffix .endswith ('.template' ):
108+ dst = dst .with_suffix (src .suffix .replace ('.template' , ' ' ))
109+ dst .write_text (src .read_text ().format_map (format_map ))
110+ else :
111+ # Other files copy directly
112+ dst .write_bytes (src .read_bytes ())
102113 logging .info ('Project initialized successfully' )
103114
104115
105- def get_available_templates ():
116+ @contextmanager
117+ def checkout_template_repo (
118+ git_remote : Optional [str ] = None , git_commit : Optional [str ] = None
119+ ):
120+ """
121+ Context manager to clone the template repository. The cloned repository
122+ is deleted after the context is left.
123+
124+ Parameters
125+ ----------
126+ git_remote : Optional[str]
127+ The git remote to use.
128+ git_commit : Optional[str]
129+ The git commit to use.
130+ """
131+ import tempfile
132+ from git import Repo
133+
134+ if git_remote is None :
135+ git_remote = SETTINGS .TEMPLATE_REMOTE
136+
137+ with tempfile .TemporaryDirectory (dir = SETTINGS .TMP_DIRECTORY ) as tmp_dir :
138+ try :
139+ repo = Repo .clone_from (git_remote , tmp_dir )
140+ if git_commit is not None :
141+ repo .head .reference = repo .commit (git_commit )
142+ repo .head .reset (index = True , working_tree = True )
143+ except Exception as e :
144+ logging .error (
145+ f'Failed to clone git repository "{ git_remote } " to "{ tmp_dir } "'
146+ )
147+ logging .error (e )
148+ exit (1 )
149+ yield Path (repo .working_dir )
150+
151+
152+ def get_available_templates (
153+ git_remote : Optional [str ] = None , git_commit : Optional [str ] = None
154+ ) -> List [str ]:
106155 """
107156 Return a list of available templates.
108157
158+ Parameters
159+ ----------
160+ git_remote : Optional[str]
161+ The git remote to use.
162+ git_commit : Optional[str]
163+ The git commit to use.
164+
109165 Returns
110166 -------
111167 List[str]
112168 A list of available templates.
113169 """
114- import importlib .resources
170+ with checkout_template_repo (git_remote , git_commit ) as repo :
171+ return [template .name for template in (repo / 'templates' ).iterdir ()]
115172
116- return [
117- template .name
118- for template in (
119- Path (str (importlib .resources .files ('seml' ))) / 'templates' / 'project'
120- ).iterdir ()
121- ]
122173
123-
124- def print_available_templates ():
174+ def print_available_templates (
175+ git_remote : Optional [str ] = None , git_commit : Optional [str ] = None
176+ ):
125177 """
126178 Print the available templates.
179+
180+ Parameters
181+ ----------
182+ git_remote : Optional[str]
183+ The git remote to use.
184+ git_commit : Optional[str]
185+ The git commit to use.
127186 """
128187 result = 'Available templates:'
129- for template in get_available_templates ():
188+ for template in get_available_templates (git_remote , git_commit ):
130189 result += f'\n - { template } '
131190 logging .info (result )
0 commit comments