1
1
use std:: fmt:: Write ;
2
2
use std:: path:: Path ;
3
3
4
- use anyhow:: { Context , Result } ;
4
+ use anyhow:: { anyhow , Context , Result } ;
5
5
use owo_colors:: OwoColorize ;
6
6
use pep440_rs:: Version ;
7
7
use pep508_rs:: PackageName ;
@@ -27,7 +27,8 @@ use crate::printer::Printer;
27
27
pub ( crate ) async fn init (
28
28
explicit_path : Option < String > ,
29
29
name : Option < PackageName > ,
30
- r#virtual : bool ,
30
+ package : bool ,
31
+ project_kind : InitProjectKind ,
31
32
no_readme : bool ,
32
33
python : Option < String > ,
33
34
no_workspace : bool ,
@@ -72,7 +73,8 @@ pub(crate) async fn init(
72
73
init_project (
73
74
& path,
74
75
& name,
75
- r#virtual,
76
+ package,
77
+ project_kind,
76
78
no_readme,
77
79
python,
78
80
no_workspace,
@@ -93,16 +95,10 @@ pub(crate) async fn init(
93
95
}
94
96
}
95
97
96
- let project = if r#virtual { "workspace" } else { "project" } ;
97
98
match explicit_path {
98
99
// Initialized a project in the current directory.
99
100
None => {
100
- writeln ! (
101
- printer. stderr( ) ,
102
- "Initialized {} `{}`" ,
103
- project,
104
- name. cyan( )
105
- ) ?;
101
+ writeln ! ( printer. stderr( ) , "Initialized project `{}`" , name. cyan( ) ) ?;
106
102
}
107
103
// Initialized a project in the given directory.
108
104
Some ( path) => {
@@ -112,8 +108,7 @@ pub(crate) async fn init(
112
108
113
109
writeln ! (
114
110
printer. stderr( ) ,
115
- "Initialized {} `{}` at `{}`" ,
116
- project,
111
+ "Initialized project `{}` at `{}`" ,
117
112
name. cyan( ) ,
118
113
path. display( ) . cyan( )
119
114
) ?;
@@ -128,7 +123,8 @@ pub(crate) async fn init(
128
123
async fn init_project (
129
124
path : & Path ,
130
125
name : & PackageName ,
131
- r#virtual : bool ,
126
+ package : bool ,
127
+ project_kind : InitProjectKind ,
132
128
no_readme : bool ,
133
129
python : Option < String > ,
134
130
no_workspace : bool ,
@@ -245,57 +241,7 @@ async fn init_project(
245
241
RequiresPython :: greater_than_equal_version ( & interpreter. python_minor_version ( ) )
246
242
} ;
247
243
248
- if r#virtual {
249
- // Create the `pyproject.toml`, but omit `[build-system]`.
250
- let pyproject = indoc:: formatdoc! { r#"
251
- [project]
252
- name = "{name}"
253
- version = "0.1.0"
254
- description = "Add your description here"{readme}
255
- requires-python = "{requires_python}"
256
- dependencies = []
257
- "# ,
258
- readme = if no_readme { "" } else { "\n readme = \" README.md\" " } ,
259
- requires_python = requires_python. specifiers( ) ,
260
- } ;
261
-
262
- fs_err:: create_dir_all ( path) ?;
263
- fs_err:: write ( path. join ( "pyproject.toml" ) , pyproject) ?;
264
- } else {
265
- // Create the `pyproject.toml`.
266
- let pyproject = indoc:: formatdoc! { r#"
267
- [project]
268
- name = "{name}"
269
- version = "0.1.0"
270
- description = "Add your description here"{readme}
271
- requires-python = "{requires_python}"
272
- dependencies = []
273
-
274
- [build-system]
275
- requires = ["hatchling"]
276
- build-backend = "hatchling.build"
277
- "# ,
278
- readme = if no_readme { "" } else { "\n readme = \" README.md\" " } ,
279
- requires_python = requires_python. specifiers( ) ,
280
- } ;
281
-
282
- fs_err:: create_dir_all ( path) ?;
283
- fs_err:: write ( path. join ( "pyproject.toml" ) , pyproject) ?;
284
-
285
- // Create `src/{name}/__init__.py`, if it doesn't exist already.
286
- let src_dir = path. join ( "src" ) . join ( & * name. as_dist_info_name ( ) ) ;
287
- let init_py = src_dir. join ( "__init__.py" ) ;
288
- if !init_py. try_exists ( ) ? {
289
- fs_err:: create_dir_all ( & src_dir) ?;
290
- fs_err:: write (
291
- init_py,
292
- indoc:: formatdoc! { r#"
293
- def hello() -> str:
294
- return "Hello from {name}!"
295
- "# } ,
296
- ) ?;
297
- }
298
- }
244
+ project_kind. init ( name, path, & requires_python, no_readme, package) ?;
299
245
300
246
if let Some ( workspace) = workspace {
301
247
if workspace. excludes ( path) ? {
@@ -339,3 +285,171 @@ async fn init_project(
339
285
340
286
Ok ( ( ) )
341
287
}
288
+
289
+ #[ derive( Debug , Clone , Default ) ]
290
+ pub ( crate ) enum InitProjectKind {
291
+ #[ default]
292
+ Application ,
293
+ Library ,
294
+ }
295
+
296
+ impl InitProjectKind {
297
+ /// Initialize this project kind at the target path.
298
+ fn init (
299
+ & self ,
300
+ name : & PackageName ,
301
+ path : & Path ,
302
+ requires_python : & RequiresPython ,
303
+ no_readme : bool ,
304
+ package : bool ,
305
+ ) -> Result < ( ) > {
306
+ match self {
307
+ InitProjectKind :: Application => {
308
+ init_application ( name, path, requires_python, no_readme, package)
309
+ }
310
+ InitProjectKind :: Library => {
311
+ init_library ( name, path, requires_python, no_readme, package)
312
+ }
313
+ }
314
+ }
315
+
316
+ /// Whether or not this project kind is packaged by default.
317
+ pub ( crate ) fn packaged_by_default ( & self ) -> bool {
318
+ matches ! ( self , InitProjectKind :: Library )
319
+ }
320
+ }
321
+
322
+ fn init_application (
323
+ name : & PackageName ,
324
+ path : & Path ,
325
+ requires_python : & RequiresPython ,
326
+ no_readme : bool ,
327
+ package : bool ,
328
+ ) -> Result < ( ) > {
329
+ // Create the `pyproject.toml`
330
+ let mut pyproject = pyproject_project ( name, requires_python, no_readme) ;
331
+
332
+ // Include additional project configuration for packaged applications
333
+ if package {
334
+ // Since it'll be packaged, we can add a `[project.scripts]` entry
335
+ pyproject. push ( '\n' ) ;
336
+ pyproject. push_str ( & pyproject_project_scripts ( name, "hello" , "hello" ) ) ;
337
+
338
+ // Add a build system
339
+ pyproject. push ( '\n' ) ;
340
+ pyproject. push_str ( pyproject_build_system ( ) ) ;
341
+ }
342
+
343
+ fs_err:: create_dir_all ( path) ?;
344
+
345
+ // Create the source structure.
346
+ if package {
347
+ // Create `src/{name}/__init__.py`, if it doesn't exist already.
348
+ let src_dir = path. join ( "src" ) . join ( & * name. as_dist_info_name ( ) ) ;
349
+ let init_py = src_dir. join ( "__init__.py" ) ;
350
+ if !init_py. try_exists ( ) ? {
351
+ fs_err:: create_dir_all ( & src_dir) ?;
352
+ fs_err:: write (
353
+ init_py,
354
+ indoc:: formatdoc! { r#"
355
+ def hello():
356
+ print("Hello from {name}!")
357
+ "# } ,
358
+ ) ?;
359
+ }
360
+ } else {
361
+ // Create `hello.py` if it doesn't exist
362
+ // TODO(zanieb): Only create `hello.py` if there are no other Python files?
363
+ let hello_py = path. join ( "hello.py" ) ;
364
+ if !hello_py. try_exists ( ) ? {
365
+ fs_err:: write (
366
+ path. join ( "hello.py" ) ,
367
+ indoc:: formatdoc! { r#"
368
+ def main():
369
+ print("Hello from {name}!")
370
+
371
+ if __name__ == "__main__":
372
+ main()
373
+ "# } ,
374
+ ) ?;
375
+ }
376
+ }
377
+ fs_err:: write ( path. join ( "pyproject.toml" ) , pyproject) ?;
378
+
379
+ Ok ( ( ) )
380
+ }
381
+
382
+ fn init_library (
383
+ name : & PackageName ,
384
+ path : & Path ,
385
+ requires_python : & RequiresPython ,
386
+ no_readme : bool ,
387
+ package : bool ,
388
+ ) -> Result < ( ) > {
389
+ if !package {
390
+ return Err ( anyhow ! ( "Library projects must be packaged" ) ) ;
391
+ }
392
+
393
+ // Create the `pyproject.toml`
394
+ let mut pyproject = pyproject_project ( name, requires_python, no_readme) ;
395
+
396
+ // Always include a build system if the project is packaged.
397
+ pyproject. push ( '\n' ) ;
398
+ pyproject. push_str ( pyproject_build_system ( ) ) ;
399
+
400
+ fs_err:: create_dir_all ( path) ?;
401
+ fs_err:: write ( path. join ( "pyproject.toml" ) , pyproject) ?;
402
+
403
+ // Create `src/{name}/__init__.py`, if it doesn't exist already.
404
+ let src_dir = path. join ( "src" ) . join ( & * name. as_dist_info_name ( ) ) ;
405
+ let init_py = src_dir. join ( "__init__.py" ) ;
406
+ if !init_py. try_exists ( ) ? {
407
+ fs_err:: create_dir_all ( & src_dir) ?;
408
+ fs_err:: write (
409
+ init_py,
410
+ indoc:: formatdoc! { r#"
411
+ def hello() -> str:
412
+ return "Hello from {name}!"
413
+ "# } ,
414
+ ) ?;
415
+ }
416
+
417
+ Ok ( ( ) )
418
+ }
419
+
420
+ /// Generate the `[project]` section of a `pyproject.toml`.
421
+ fn pyproject_project (
422
+ name : & PackageName ,
423
+ requires_python : & RequiresPython ,
424
+ no_readme : bool ,
425
+ ) -> String {
426
+ indoc:: formatdoc! { r#"
427
+ [project]
428
+ name = "{name}"
429
+ version = "0.1.0"
430
+ description = "Add your description here"{readme}
431
+ requires-python = "{requires_python}"
432
+ dependencies = []
433
+ "# ,
434
+ readme = if no_readme { "" } else { "\n readme = \" README.md\" " } ,
435
+ requires_python = requires_python. specifiers( ) ,
436
+ }
437
+ }
438
+
439
+ /// Generate the `[build-system]` section of a `pyproject.toml`.
440
+ fn pyproject_build_system ( ) -> & ' static str {
441
+ indoc:: indoc! { r#"
442
+ [build-system]
443
+ requires = ["hatchling"]
444
+ build-backend = "hatchling.build"
445
+ "# }
446
+ }
447
+
448
+ /// Generate the `[project.scripts]` section of a `pyproject.toml`.
449
+ fn pyproject_project_scripts ( package : & PackageName , executable_name : & str , target : & str ) -> String {
450
+ let module_name = package. as_dist_info_name ( ) ;
451
+ indoc:: formatdoc! { r#"
452
+ [project.scripts]
453
+ {executable_name} = "{module_name}:{target}"
454
+ "# }
455
+ }
0 commit comments