13
13
from datetime import datetime
14
14
from pathlib import Path
15
15
from pprint import pformat
16
- from typing import TYPE_CHECKING , Any , Callable
16
+ from typing import Any , Callable
17
17
18
18
from deprecated import deprecated
19
+ from packaging .version import Version
19
20
from setuptools .dist import Distribution
20
21
21
- if TYPE_CHECKING :
22
- from packaging .version import Version
23
-
24
22
DEFAULT_TEMPLATE = "{tag}"
25
23
DEFAULT_DEV_TEMPLATE = "{tag}.post{ccount}+git.{sha}"
26
24
DEFAULT_DIRTY_TEMPLATE = "{tag}.post{ccount}+git.{sha}.dirty"
27
25
DEFAULT_STARTING_VERSION = "0.0.1"
28
26
DEFAULT_SORT_BY = "creatordate"
29
27
ENV_VARS_REGEXP = re .compile (r"\{env:(?P<name>[^:}]+):?(?P<default>[^}]+\}*)?\}" , re .IGNORECASE | re .UNICODE )
30
28
TIMESTAMP_REGEXP = re .compile (r"\{timestamp:?(?P<fmt>[^:}]+)?\}" , re .IGNORECASE | re .UNICODE )
31
- LOCAL_REGEXP = re .compile (r"[^a-z\d.]+" , re .IGNORECASE )
32
- VERSION_PREFIX_REGEXP = re .compile (r"^[^\d]+" , re .IGNORECASE | re .UNICODE )
33
29
34
30
LOG_FORMAT = "[%(asctime)s] %(levelname)+8s: %(message)s"
35
31
# setuptools v60.2.0 changed default logging level to DEBUG: https://github.com/pypa/setuptools/pull/2974
62
58
63
59
64
60
def _exec (cmd : str , root : str | os .PathLike | None = None ) -> list [str ]:
65
- log .log (DEBUG , "Executing '%s' at '%s'" , cmd , root or os .getcwd ())
61
+ log .log (DEBUG , "Executing %r at '%s'" , cmd , root or os .getcwd ())
66
62
try :
67
63
stdout = subprocess .check_output (cmd , shell = True , text = True , cwd = root ) # nosec
68
64
except subprocess .CalledProcessError as e :
@@ -262,44 +258,44 @@ def read_version_from_file(name_or_path: str | os.PathLike, root: str | os.PathL
262
258
263
259
264
260
def substitute_env_variables (template : str ) -> str :
265
- log .log (DEBUG , "Substitute environment variables in template '%s' " , template )
261
+ log .log (DEBUG , "Substitute environment variables in template %r " , template )
266
262
for var , default in ENV_VARS_REGEXP .findall (template ):
267
- log .log (DEBUG , "Variable: '%s' " , var )
263
+ log .log (DEBUG , "Variable: %r " , var )
268
264
269
265
if default .upper () == "IGNORE" :
270
266
default = ""
271
267
elif not default :
272
268
default = "UNKNOWN"
273
- log .log (DEBUG , "Default: '%s' " , default )
269
+ log .log (DEBUG , "Default: %r " , default )
274
270
275
271
value = os .environ .get (var , default )
276
- log .log (DEBUG , "Value: '%s' " , value )
272
+ log .log (DEBUG , "Value: %r " , value )
277
273
278
274
template , _ = ENV_VARS_REGEXP .subn (value , template , count = 1 )
279
275
280
- log .log (DEBUG , "Result: '%s' " , template )
276
+ log .log (DEBUG , "Result: %r " , template )
281
277
return template
282
278
283
279
284
280
def substitute_timestamp (template : str ) -> str :
285
- log .log (DEBUG , "Substitute timestampts in template '%s' " , template )
281
+ log .log (DEBUG , "Substitute timestamps in template %r " , template )
286
282
287
283
now = datetime .now ()
288
284
for fmt in TIMESTAMP_REGEXP .findall (template ):
289
285
format_string = fmt or "%s"
290
- log .log (DEBUG , "Format: '%s' " , format_string )
286
+ log .log (DEBUG , "Format: %r " , format_string )
291
287
292
288
result = now .strftime (fmt or "%s" )
293
- log .log (DEBUG , "Value: '%s' " , result )
289
+ log .log (DEBUG , "Value: %r " , result )
294
290
295
291
template , _ = TIMESTAMP_REGEXP .subn (result , template , count = 1 )
296
292
297
- log .log (DEBUG , "Result: '%s' " , template )
293
+ log .log (DEBUG , "Result: %r " , template )
298
294
return template
299
295
300
296
301
297
def resolve_substitutions (template : str , * args , ** kwargs ) -> str :
302
- log .log (DEBUG , "Template: '%s' " , template )
298
+ log .log (DEBUG , "Template: %r " , template )
303
299
log .log (DEBUG , "Args:%s" , pformat (args ))
304
300
305
301
while True :
@@ -359,7 +355,7 @@ def load_tag_formatter(
359
355
package_name : str | None = None ,
360
356
root : str | os .PathLike | None = None ,
361
357
) -> Callable :
362
- log .log (INFO , "Parsing tag_formatter '%s' of type '%s' " , tag_formatter , type (tag_formatter ).__name__ )
358
+ log .log (INFO , "Parsing tag_formatter %r of type %r " , tag_formatter , type (tag_formatter ).__name__ )
363
359
364
360
if callable (tag_formatter ):
365
361
log .log (DEBUG , "Value is callable with signature %s" , inspect .Signature .from_callable (tag_formatter ))
@@ -383,16 +379,15 @@ def formatter(tag):
383
379
return formatter
384
380
except re .error as e :
385
381
log .error ("tag_formatter is not valid regexp: %s" , e )
386
-
387
- raise ValueError ("Cannot parse tag_formatter" )
382
+ raise ValueError ("Cannot parse tag_formatter" ) from e
388
383
389
384
390
385
def load_branch_formatter (
391
386
branch_formatter : str | Callable [[str ], str ],
392
387
package_name : str | None = None ,
393
388
root : str | os .PathLike | None = None ,
394
389
) -> Callable :
395
- log .log (INFO , "Parsing branch_formatter '%s' of type '%s' " , branch_formatter , type (branch_formatter ).__name__ )
390
+ log .log (INFO , "Parsing branch_formatter %r of type %r " , branch_formatter , type (branch_formatter ).__name__ )
396
391
397
392
if callable (branch_formatter ):
398
393
log .log (DEBUG , "Value is callable with signature %s" , inspect .Signature .from_callable (branch_formatter ))
@@ -416,8 +411,7 @@ def formatter(branch):
416
411
return formatter
417
412
except re .error as e :
418
413
log .error ("branch_formatter is not valid regexp: %s" , e )
419
-
420
- raise ValueError ("Cannot parse branch_formatter" )
414
+ raise ValueError ("Cannot parse branch_formatter" ) from e
421
415
422
416
423
417
# TODO: return Version object instead of str
@@ -426,7 +420,7 @@ def get_version_from_callback(
426
420
package_name : str | None = None ,
427
421
root : str | os .PathLike | None = None ,
428
422
) -> str :
429
- log .log (INFO , "Parsing version_callback %s of type %s " , version_callback , type (version_callback ))
423
+ log .log (INFO , "Parsing version_callback %r of type %r " , version_callback , type (version_callback ). __name__ )
430
424
431
425
if callable (version_callback ):
432
426
log .log (DEBUG , "Value is callable with signature %s" , inspect .Signature .from_callable (version_callback ))
@@ -447,10 +441,15 @@ def get_version_from_callback(
447
441
except (ImportError , NameError ) as e :
448
442
log .warning ("version_callback is not a valid reference: %s" , e )
449
443
450
- from packaging . version import Version
444
+ return sanitize_version ( result )
451
445
452
- log .log (INFO , "Result %s" , result )
453
- return Version (result ).public
446
+
447
+ def sanitize_version (version : str ) -> str :
448
+ log .log (INFO , "Before sanitization %r" , version )
449
+
450
+ result = str (Version (version ))
451
+ log .log (INFO , "Result %r" , result )
452
+ return result
454
453
455
454
456
455
# TODO: return Version object instead of str
@@ -476,8 +475,8 @@ def version_from_git(
476
475
for line in lines :
477
476
if line .startswith ("Version:" ):
478
477
result = line [8 :].strip ()
479
- log .log (INFO , "Return '%s' " , result )
480
- return result
478
+ log .log (INFO , "Return %r " , result )
479
+ return sanitize_version ( result )
481
480
482
481
if version_callback is not None :
483
482
if version_file is not None :
@@ -488,70 +487,68 @@ def version_from_git(
488
487
489
488
from_file = False
490
489
log .log (INFO , "Getting latest tag" )
491
- log .log (DEBUG , "Sorting tags by '%s' " , sort_by )
490
+ log .log (DEBUG , "Sorting tags by %r " , sort_by )
492
491
tag = get_tag (sort_by = sort_by , root = root )
493
492
494
493
if tag is None :
495
494
log .log (INFO , "No tag, checking for 'version_file'" )
496
495
if version_file is None :
497
- log .log (INFO , "No 'version_file' set, return starting_version '%s' " , starting_version )
498
- return starting_version
496
+ log .log (INFO , "No 'version_file' set, return starting_version %r " , starting_version )
497
+ return sanitize_version ( starting_version )
499
498
500
499
if not Path (version_file ).exists ():
501
500
log .log (
502
501
INFO ,
503
- "version_file '%s' does not exist, return starting_version '%s' " ,
502
+ "version_file '%s' does not exist, return starting_version %r " ,
504
503
version_file ,
505
504
starting_version ,
506
505
)
507
- return starting_version
506
+ return sanitize_version ( starting_version )
508
507
509
508
log .log (INFO , "version_file '%s' does exist, reading its content" , version_file )
510
509
from_file = True
511
510
tag = read_version_from_file (version_file , root = root )
512
511
513
512
if not tag :
514
- log .log (INFO , "File is empty, return starting_version '%s' " , version_file , starting_version )
515
- return starting_version
513
+ log .log (INFO , "File is empty, return starting_version %r " , version_file , starting_version )
514
+ return sanitize_version ( starting_version )
516
515
517
- log .log (DEBUG , "File content: '%s' " , tag )
516
+ log .log (DEBUG , "File content: %r " , tag )
518
517
if not count_commits_from_version_file :
519
- result = VERSION_PREFIX_REGEXP .sub ("" , tag ) # for tag "v1.0.0" drop leading "v" symbol
520
- log .log (INFO , "Return '%s'" , result )
521
- return result
518
+ return sanitize_version (tag )
522
519
523
520
tag_sha = get_latest_file_commit (version_file , root = root )
524
- log .log (DEBUG , "File content: '%s' " , tag )
521
+ log .log (DEBUG , "File SHA-256: %r " , tag_sha )
525
522
else :
526
- log .log (INFO , "Latest tag: '%s' " , tag )
523
+ log .log (INFO , "Latest tag: %r " , tag )
527
524
tag_sha = get_sha (tag , root = root )
528
- log .log (INFO , "Tag SHA-256: '%s' " , tag_sha )
525
+ log .log (INFO , "Tag SHA-256: %r " , tag_sha )
529
526
530
527
if tag_formatter is not None :
531
528
tag_fmt = load_tag_formatter (tag_formatter , package_name , root = root )
532
529
tag = tag_fmt (tag )
533
- log .log (DEBUG , "Tag after formatting: '%s' " , tag )
530
+ log .log (DEBUG , "Tag after formatting: %r " , tag )
534
531
535
532
dirty = is_dirty (root = root )
536
- log .log (INFO , "Is dirty: %s " , dirty )
533
+ log .log (INFO , "Is dirty: %r " , dirty )
537
534
538
535
head_sha = get_sha (root = root )
539
- log .log (INFO , "HEAD SHA-256: '%s' " , head_sha )
536
+ log .log (INFO , "HEAD SHA-256: %r " , head_sha )
540
537
541
538
full_sha = head_sha if head_sha is not None else ""
542
539
ccount = count_since (tag_sha , root = root ) if tag_sha is not None else None
543
- log .log (INFO , "Commits count between HEAD and latest tag: %s " , ccount )
540
+ log .log (INFO , "Commits count between HEAD and latest tag: %r " , ccount )
544
541
545
542
on_tag = head_sha is not None and head_sha == tag_sha and not from_file
546
- log .log (INFO , "HEAD is tagged: %s " , on_tag )
543
+ log .log (INFO , "HEAD is tagged: %r " , on_tag )
547
544
548
545
branch = get_branch (root = root )
549
- log .log (INFO , "Current branch: '%s' " , branch )
546
+ log .log (INFO , "Current branch: %r " , branch )
550
547
551
548
if branch_formatter is not None and branch is not None :
552
549
branch_fmt = load_branch_formatter (branch_formatter , package_name , root = root )
553
550
branch = branch_fmt (branch )
554
- log .log (INFO , "Branch after formatting: '%s' " , branch )
551
+ log .log (INFO , "Branch after formatting: %r " , branch )
555
552
556
553
if dirty :
557
554
log .log (INFO , "Using template from 'dirty_template' option" )
@@ -564,26 +561,11 @@ def version_from_git(
564
561
t = template
565
562
566
563
version = resolve_substitutions (t , sha = full_sha [:8 ], tag = tag , ccount = ccount , branch = branch , full_sha = full_sha )
567
- log .log (INFO , "Version number after resolving substitutions: '%s'" , version )
568
-
569
- # Ensure local version label only contains permitted characters
570
- public , sep , local = version .partition ("+" )
571
- local_sanitized = LOCAL_REGEXP .sub ("." , local )
572
- if local_sanitized != local :
573
- log .log (INFO , "Local version part after sanitization: '%s'" , local_sanitized )
574
-
575
- public_sanitized = VERSION_PREFIX_REGEXP .sub ("" , public ) # for version "v1.0.0" drop leading "v" symbol
576
- if public_sanitized != public :
577
- log .log (INFO , "Public version part after sanitization: '%s'" , public_sanitized )
578
-
579
- result = (public_sanitized + sep + local_sanitized ) or "0.0.0"
580
- log .log (INFO , "Result: '%s'" , result )
581
- return result
564
+ log .log (INFO , "Version number after resolving substitutions: %r" , version )
565
+ return sanitize_version (version )
582
566
583
567
584
568
def main (config : dict | None = None , root : str | os .PathLike | None = None ) -> Version :
585
- from packaging .version import Version
586
-
587
569
if not config :
588
570
log .log (INFO , "No explicit config passed" )
589
571
log .log (INFO , "Searching for config files in '%s' folder" , root or os .getcwd ())
@@ -640,7 +622,7 @@ def __main__():
640
622
namespace = parser .parse_args ()
641
623
log_level = VERBOSITY_LEVELS .get (namespace .verbose , logging .DEBUG )
642
624
logging .basicConfig (level = log_level , format = LOG_FORMAT , stream = sys .stderr )
643
- print (main (root = namespace .root ). public )
625
+ print (str ( main (root = namespace .root )) )
644
626
645
627
646
628
if __name__ == "__main__" :
0 commit comments