1313from datetime import datetime
1414from pathlib import Path
1515from pprint import pformat
16- from typing import TYPE_CHECKING , Any , Callable
16+ from typing import Any , Callable
1717
1818from deprecated import deprecated
19+ from packaging .version import Version
1920from setuptools .dist import Distribution
2021
21- if TYPE_CHECKING :
22- from packaging .version import Version
23-
2422DEFAULT_TEMPLATE = "{tag}"
2523DEFAULT_DEV_TEMPLATE = "{tag}.post{ccount}+git.{sha}"
2624DEFAULT_DIRTY_TEMPLATE = "{tag}.post{ccount}+git.{sha}.dirty"
2725DEFAULT_STARTING_VERSION = "0.0.1"
2826DEFAULT_SORT_BY = "creatordate"
2927ENV_VARS_REGEXP = re .compile (r"\{env:(?P<name>[^:}]+):?(?P<default>[^}]+\}*)?\}" , re .IGNORECASE | re .UNICODE )
3028TIMESTAMP_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 )
3329
3430LOG_FORMAT = "[%(asctime)s] %(levelname)+8s: %(message)s"
3531# setuptools v60.2.0 changed default logging level to DEBUG: https://github.com/pypa/setuptools/pull/2974
6258
6359
6460def _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 ())
6662 try :
6763 stdout = subprocess .check_output (cmd , shell = True , text = True , cwd = root ) # nosec
6864 except subprocess .CalledProcessError as e :
@@ -262,44 +258,44 @@ def read_version_from_file(name_or_path: str | os.PathLike, root: str | os.PathL
262258
263259
264260def 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 )
266262 for var , default in ENV_VARS_REGEXP .findall (template ):
267- log .log (DEBUG , "Variable: '%s' " , var )
263+ log .log (DEBUG , "Variable: %r " , var )
268264
269265 if default .upper () == "IGNORE" :
270266 default = ""
271267 elif not default :
272268 default = "UNKNOWN"
273- log .log (DEBUG , "Default: '%s' " , default )
269+ log .log (DEBUG , "Default: %r " , default )
274270
275271 value = os .environ .get (var , default )
276- log .log (DEBUG , "Value: '%s' " , value )
272+ log .log (DEBUG , "Value: %r " , value )
277273
278274 template , _ = ENV_VARS_REGEXP .subn (value , template , count = 1 )
279275
280- log .log (DEBUG , "Result: '%s' " , template )
276+ log .log (DEBUG , "Result: %r " , template )
281277 return template
282278
283279
284280def 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 )
286282
287283 now = datetime .now ()
288284 for fmt in TIMESTAMP_REGEXP .findall (template ):
289285 format_string = fmt or "%s"
290- log .log (DEBUG , "Format: '%s' " , format_string )
286+ log .log (DEBUG , "Format: %r " , format_string )
291287
292288 result = now .strftime (fmt or "%s" )
293- log .log (DEBUG , "Value: '%s' " , result )
289+ log .log (DEBUG , "Value: %r " , result )
294290
295291 template , _ = TIMESTAMP_REGEXP .subn (result , template , count = 1 )
296292
297- log .log (DEBUG , "Result: '%s' " , template )
293+ log .log (DEBUG , "Result: %r " , template )
298294 return template
299295
300296
301297def resolve_substitutions (template : str , * args , ** kwargs ) -> str :
302- log .log (DEBUG , "Template: '%s' " , template )
298+ log .log (DEBUG , "Template: %r " , template )
303299 log .log (DEBUG , "Args:%s" , pformat (args ))
304300
305301 while True :
@@ -359,7 +355,7 @@ def load_tag_formatter(
359355 package_name : str | None = None ,
360356 root : str | os .PathLike | None = None ,
361357) -> 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__ )
363359
364360 if callable (tag_formatter ):
365361 log .log (DEBUG , "Value is callable with signature %s" , inspect .Signature .from_callable (tag_formatter ))
@@ -383,16 +379,15 @@ def formatter(tag):
383379 return formatter
384380 except re .error as e :
385381 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
388383
389384
390385def load_branch_formatter (
391386 branch_formatter : str | Callable [[str ], str ],
392387 package_name : str | None = None ,
393388 root : str | os .PathLike | None = None ,
394389) -> 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__ )
396391
397392 if callable (branch_formatter ):
398393 log .log (DEBUG , "Value is callable with signature %s" , inspect .Signature .from_callable (branch_formatter ))
@@ -416,8 +411,7 @@ def formatter(branch):
416411 return formatter
417412 except re .error as e :
418413 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
421415
422416
423417# TODO: return Version object instead of str
@@ -426,7 +420,7 @@ def get_version_from_callback(
426420 package_name : str | None = None ,
427421 root : str | os .PathLike | None = None ,
428422) -> 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__ )
430424
431425 if callable (version_callback ):
432426 log .log (DEBUG , "Value is callable with signature %s" , inspect .Signature .from_callable (version_callback ))
@@ -447,10 +441,15 @@ def get_version_from_callback(
447441 except (ImportError , NameError ) as e :
448442 log .warning ("version_callback is not a valid reference: %s" , e )
449443
450- from packaging . version import Version
444+ return sanitize_version ( result )
451445
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
454453
455454
456455# TODO: return Version object instead of str
@@ -476,8 +475,8 @@ def version_from_git(
476475 for line in lines :
477476 if line .startswith ("Version:" ):
478477 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 )
481480
482481 if version_callback is not None :
483482 if version_file is not None :
@@ -488,70 +487,68 @@ def version_from_git(
488487
489488 from_file = False
490489 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 )
492491 tag = get_tag (sort_by = sort_by , root = root )
493492
494493 if tag is None :
495494 log .log (INFO , "No tag, checking for 'version_file'" )
496495 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 )
499498
500499 if not Path (version_file ).exists ():
501500 log .log (
502501 INFO ,
503- "version_file '%s' does not exist, return starting_version '%s' " ,
502+ "version_file '%s' does not exist, return starting_version %r " ,
504503 version_file ,
505504 starting_version ,
506505 )
507- return starting_version
506+ return sanitize_version ( starting_version )
508507
509508 log .log (INFO , "version_file '%s' does exist, reading its content" , version_file )
510509 from_file = True
511510 tag = read_version_from_file (version_file , root = root )
512511
513512 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 )
516515
517- log .log (DEBUG , "File content: '%s' " , tag )
516+ log .log (DEBUG , "File content: %r " , tag )
518517 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 )
522519
523520 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 )
525522 else :
526- log .log (INFO , "Latest tag: '%s' " , tag )
523+ log .log (INFO , "Latest tag: %r " , tag )
527524 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 )
529526
530527 if tag_formatter is not None :
531528 tag_fmt = load_tag_formatter (tag_formatter , package_name , root = root )
532529 tag = tag_fmt (tag )
533- log .log (DEBUG , "Tag after formatting: '%s' " , tag )
530+ log .log (DEBUG , "Tag after formatting: %r " , tag )
534531
535532 dirty = is_dirty (root = root )
536- log .log (INFO , "Is dirty: %s " , dirty )
533+ log .log (INFO , "Is dirty: %r " , dirty )
537534
538535 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 )
540537
541538 full_sha = head_sha if head_sha is not None else ""
542539 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 )
544541
545542 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 )
547544
548545 branch = get_branch (root = root )
549- log .log (INFO , "Current branch: '%s' " , branch )
546+ log .log (INFO , "Current branch: %r " , branch )
550547
551548 if branch_formatter is not None and branch is not None :
552549 branch_fmt = load_branch_formatter (branch_formatter , package_name , root = root )
553550 branch = branch_fmt (branch )
554- log .log (INFO , "Branch after formatting: '%s' " , branch )
551+ log .log (INFO , "Branch after formatting: %r " , branch )
555552
556553 if dirty :
557554 log .log (INFO , "Using template from 'dirty_template' option" )
@@ -564,26 +561,11 @@ def version_from_git(
564561 t = template
565562
566563 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 )
582566
583567
584568def main (config : dict | None = None , root : str | os .PathLike | None = None ) -> Version :
585- from packaging .version import Version
586-
587569 if not config :
588570 log .log (INFO , "No explicit config passed" )
589571 log .log (INFO , "Searching for config files in '%s' folder" , root or os .getcwd ())
@@ -640,7 +622,7 @@ def __main__():
640622 namespace = parser .parse_args ()
641623 log_level = VERBOSITY_LEVELS .get (namespace .verbose , logging .DEBUG )
642624 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 )) )
644626
645627
646628if __name__ == "__main__" :
0 commit comments