88from pathlib import Path
99from typing import Iterable , List , Optional , Tuple , Union , cast , overload
1010
11- from specfile .exceptions import SpecfileException
11+ from specfile .exceptions import DuplicateSourceException
1212from specfile .rpm import Macros
1313from specfile .sourcelist import Sourcelist , SourcelistEntry
1414from specfile .tags import Comments , Tag , Tags
@@ -100,14 +100,25 @@ def _extract_number(self) -> Optional[str]:
100100 @property
101101 def number (self ) -> int :
102102 """Source number."""
103- return self ._number or int (self ._extract_number () or 0 )
103+ if self ._number is not None :
104+ return self ._number
105+ return int (self ._extract_number () or 0 )
104106
105107 @property
106108 def number_digits (self ) -> int :
107- """Number of digits in the source number."""
108- if self ._number :
109+ """
110+ Gets number of digits in the source number.
111+
112+ Returns 0 if the source has no number, 1 if the source number
113+ has no leading zeros and the actual number of digits if there are
114+ any leading zeros.
115+ """
116+ if self ._number is not None :
109117 return 0
110- return len (self ._extract_number () or "" )
118+ number = self ._extract_number ()
119+ if not number :
120+ return 0
121+ return len (number ) if number .startswith ("0" ) else 1
111122
112123 @property
113124 def location (self ) -> str :
@@ -378,13 +389,14 @@ def _get_tag_format(
378389 Tuple in the form of (name, separator).
379390 """
380391 prefix = self .PREFIX .capitalize ()
381- if self ._detect_implicit_numbering ():
392+ if number_digits_override is not None :
393+ number_digits = number_digits_override
394+ else :
395+ number_digits = reference .number_digits
396+ if self ._detect_implicit_numbering () or number_digits == 0 :
382397 suffix = ""
383398 else :
384- if number_digits_override is not None :
385- suffix = f"{ number :0{number_digits_override }} "
386- else :
387- suffix = f"{ number :0{reference .number_digits }} "
399+ suffix = f"{ number :0{number_digits }} "
388400 name = f"{ prefix } { suffix } "
389401 diff = len (reference ._tag .name ) - len (name )
390402 if diff >= 0 :
@@ -404,23 +416,34 @@ def _get_initial_tag_setup(self, number: int = 0) -> Tuple[int, str, str]:
404416 Tuple in the form of (index, name, separator).
405417 """
406418 prefix = self .PREFIX .capitalize ()
407- if self ._default_to_implicit_numbering :
419+ if (
420+ self ._default_to_implicit_numbering
421+ or self ._default_source_number_digits == 0
422+ ):
408423 suffix = ""
409424 else :
410425 suffix = f"{ number :0{self ._default_source_number_digits }} "
411426 return len (self ._tags ) if self ._tags else 0 , f"{ prefix } { suffix } " , ": "
412427
413- def _deduplicate_tag_names (self ) -> None :
414- """Eliminates duplicate numbers in source tag names."""
428+ def _deduplicate_tag_names (self , start : int = 0 ) -> None :
429+ """
430+ Eliminates duplicate numbers in source tag names.
431+
432+ Args:
433+ start: Starting index, defaults to the first source tag.
434+ """
415435 tags = self ._get_tags ()
416436 if not tags :
417437 return
418- tag_sources = sorted ( list (zip (* tags ))[0 ], key = lambda ts : ts . number )
438+ tag_sources = list (zip (* tags [ start :] ))[0 ]
419439 for ts0 , ts1 in zip (tag_sources , tag_sources [1 :]):
420- if ts1 .number <= ts0 .number :
421- ts1 ._tag .name , ts1 ._tag ._separator = self ._get_tag_format (
422- ts0 , ts0 .number + 1
423- )
440+ if ts1 .number == ts0 .number :
441+ if ts1 ._number is not None :
442+ ts1 ._number = ts0 .number + 1
443+ else :
444+ ts1 ._tag .name , ts1 ._tag ._separator = self ._get_tag_format (
445+ ts1 , ts0 .number + 1
446+ )
424447
425448 def insert (self , i : int , location : str ) -> None :
426449 """
@@ -431,11 +454,11 @@ def insert(self, i: int, location: str) -> None:
431454 location: Location of the new source.
432455
433456 Raises:
434- SpecfileException if duplicates are disallowed and there
435- already is a source with the same location.
457+ DuplicateSourceException if duplicates are disallowed and there
458+ already is a source with the same location.
436459 """
437460 if not self ._allow_duplicates and location in self :
438- raise SpecfileException (f"Source '{ location } ' already exists" )
461+ raise DuplicateSourceException (f"Source '{ location } ' already exists" )
439462 items = self ._get_items ()
440463 if i > len (items ):
441464 i = len (items )
@@ -453,7 +476,7 @@ def insert(self, i: int, location: str) -> None:
453476 index ,
454477 Tag (name , location , Macros .expand (location ), separator , Comments ()),
455478 )
456- self ._deduplicate_tag_names ()
479+ self ._deduplicate_tag_names (i )
457480 else :
458481 container .insert (
459482 index ,
@@ -480,11 +503,11 @@ def insert_numbered(self, number: int, location: str) -> int:
480503 Index of the newly inserted source.
481504
482505 Raises:
483- SpecfileException if duplicates are disallowed and there
484- already is a source with the same location.
506+ DuplicateSourceException if duplicates are disallowed and there
507+ already is a source with the same location.
485508 """
486509 if not self ._allow_duplicates and location in self :
487- raise SpecfileException (f"Source '{ location } ' already exists" )
510+ raise DuplicateSourceException (f"Source '{ location } ' already exists" )
488511 tags = self ._get_tags ()
489512 if tags :
490513 # find the nearest source tag
@@ -501,7 +524,7 @@ def insert_numbered(self, number: int, location: str) -> int:
501524 self ._tags .insert (
502525 index , Tag (name , location , Macros .expand (location ), separator , Comments ())
503526 )
504- self ._deduplicate_tag_names ()
527+ self ._deduplicate_tag_names (i )
505528 return i
506529
507530 def remove (self , location : str ) -> None :
@@ -515,6 +538,21 @@ def remove(self, location: str) -> None:
515538 if source .location == location :
516539 del container [index ]
517540
541+ def remove_numbered (self , number : int ) -> None :
542+ """
543+ Removes a source by number.
544+
545+ Args:
546+ number: Number of the source to be removed.
547+ """
548+ items = self ._get_items ()
549+ try :
550+ container , index = next ((c , i ) for s , c , i in items if s .number == number )
551+ except StopIteration :
552+ pass
553+ else :
554+ del container [index ]
555+
518556 def count (self , location : str ) -> int :
519557 """
520558 Counts sources by location.
0 commit comments