예제 #1
0
    def __init__(self, requirement_string):
        try:
            req = REQUIREMENT.parseString(requirement_string)
        except ParseException as e:
            raise InvalidRequirement(
                "Invalid requirement, parse error at \"{0!r}\"".format(
                    requirement_string[e.loc:e.loc + 8]))

        self.name = req.name
        if req.url:
            parsed_url = urlparse.urlparse(req.url)
            if not (parsed_url.scheme and parsed_url.netloc) or (
                    not parsed_url.scheme and not parsed_url.netloc):
                raise InvalidRequirement("Invalid URL given")
            self.url = req.url
        else:
            self.url = None

        self.extras = set(req.extras.asList() if req.extras else [])
        constraint = req.specifier
        if not constraint:
            constraint = '*'

        self.constraint = VersionParser().parse_constraints(constraint)
        self.pretty_constraint = constraint

        self.marker = req.marker if req.marker else None
예제 #2
0
    def find_packages(self,
                      name: str,
                      constraint: Union[Constraint, str, None] = None,
                      extras: Union[list, None] = None) -> List[Package]:
        """
        Find packages on the remote server.
        """
        packages = []

        if constraint is not None and not isinstance(constraint,
                                                     BaseConstraint):
            version_parser = VersionParser()
            constraint = version_parser.parse_constraints(constraint)

        info = self.get_package_info(name)

        versions = []

        for version, release in info['releases'].items():
            if (not constraint or
                (constraint and constraint.matches(Constraint('=', version)))):
                versions.append(version)

        for version in versions:
            packages.append(self.package(name, version, extras=extras))

        return packages
예제 #3
0
    def find_packages(self, name, constraint=None, extras=None):
        packages = []

        if constraint is not None and not isinstance(constraint,
                                                     BaseConstraint):
            version_parser = VersionParser()
            constraint = version_parser.parse_constraints(constraint)

        key = name
        if constraint:
            key = f'{key}:{str(constraint)}'

        if self._cache.store('matches').has(key):
            versions = self._cache.store('matches').get(key)
        else:
            candidates = [str(c.version) for c in self._repository.find_all_candidates(name)]

            versions = []
            for version in candidates:
                if version in versions:
                    continue

                if (
                    not constraint
                    or (constraint and constraint.matches(Constraint('=', version)))
                ):
                    versions.append(version)

            self._cache.store('matches').put(key, versions, 5)

        for version in versions:
            packages.append(self.package(name, version, extras=extras))

        return packages
예제 #4
0
파일: repository.py 프로젝트: undu/poetry
    def find_packages(self, name, constraint=None,
                      extras=None,
                      allow_prereleases=False):
        name = name.lower()
        packages = []
        if extras is None:
            extras = []

        if constraint is None:
            constraint = '*'

        if not isinstance(constraint, BaseConstraint):
            parser = VersionParser()
            constraint = parser.parse_constraints(constraint)

        for package in self.packages:
            if name == package.name:
                pkg_constraint = Constraint('==', package.version)

                if constraint is None or constraint.matches(pkg_constraint):
                    for dep in package.requires:
                        for extra in extras:
                            if extra not in package.extras:
                                continue

                            reqs = package.extras[extra]
                            for req in reqs:
                                if req.name == dep.name:
                                    dep.activate()

                    packages.append(package)

        return packages
예제 #5
0
    def all_classifiers(self):
        classifiers = copy.copy(self.classifiers)

        # Automatically set python classifiers
        parser = VersionParser()
        if self.python_versions == '*':
            python_constraint = parser.parse_constraints('~2.7 || ^3.4')
        else:
            python_constraint = self.python_constraint

        for version in sorted(self.AVAILABLE_PYTHONS):
            if len(version) == 1:
                constraint = parser.parse_constraints(version + '.*')
            else:
                constraint = Constraint('=', version)

            if python_constraint.matches(constraint):
                classifiers.append(
                    'Programming Language :: Python :: {}'.format(version))

        # Automatically set license classifiers
        if self.license:
            classifiers.append(self.license.classifier)

        classifiers = set(classifiers)

        return sorted(classifiers)
예제 #6
0
def format_python_constraint(constraint):
    """
    This helper will help in transforming
    disjunctive constraint into proper constraint.
    """
    if not isinstance(constraint, MultiConstraint):
        return str(constraint)

    has_disjunctive = False
    for c in constraint.constraints:
        if isinstance(c, MultiConstraint) and c.is_disjunctive():
            has_disjunctive = True
            break

    parser = VersionParser()
    formatted = []
    accepted = []
    if not constraint.is_disjunctive() and not has_disjunctive:
        return str(constraint)

    for version in PYTHON_VERSION:
        version_constraint = parser.parse_constraints(version)
        matches = constraint.matches(version_constraint)
        if not matches:
            formatted.append('!=' + version)
        else:
            accepted.append(version)

    # Checking lower bound
    low = accepted[0]

    formatted.insert(0, '>=' + '.'.join(low.split('.')[:2]))

    return ', '.join(formatted)
예제 #7
0
파일: installer.py 프로젝트: blueyed/poetry
    def _filter_operations(self,
                           ops,
                           repo
                           ):  # type: (List[Operation], Repository) -> None
        extra_packages = [p.name for p in
                          self._get_extra_packages(repo)]
        for op in ops:
            if isinstance(op, Update):
                package = op.target_package
            else:
                package = op.package

            if op.job_type == 'uninstall':
                continue

            parser = VersionParser()
            python = '.'.join([str(i) for i in self._venv.version_info[:3]])
            if 'python' in package.requirements:
                python_constraint = parser.parse_constraints(
                    package.requirements['python']
                )
                if not python_constraint.matches(Constraint('=', python)):
                    # Incompatible python versions
                    op.skip('Not needed for the current python version')
                    continue

            if not package.python_constraint.matches(Constraint('=', python)):
                op.skip('Not needed for the current python version')
                continue

            if 'platform' in package.requirements:
                platform_constraint = GenericConstraint.parse(
                    package.requirements['platform']
                )
                if not platform_constraint.matches(
                        GenericConstraint('=', sys.platform)
                ):
                    # Incompatible systems
                    op.skip('Not needed for the current platform')
                    continue

            if self._update:
                extras = {}
                for extra, deps in self._package.extras.items():
                    extras[extra] = [dep.name for dep in deps]
            else:
                extras = {}
                for extra, deps in self._locker.lock_data.get('extras', {}).items():
                    extras[extra] = [dep.lower() for dep in deps]

            # If a package is optional and not requested
            # in any extra we skip it
            if package.optional:
                if package.name not in extra_packages:
                    op.skip('Not required')

            # If the package is a dev package and dev packages
            # are not requests, we skip it
            if package.category == 'dev' and not self.is_dev_mode():
                op.skip('Dev dependencies not requested')
예제 #8
0
    def __init__(self,
                 name: str,
                 constraint: str,
                 optional: bool = False,
                 category: str = 'main',
                 allows_prereleases: bool = False):
        self._name = name.lower()
        self._pretty_name = name
        self._parser = VersionParser()

        try:
            self._constraint = self._parser.parse_constraints(constraint)
        except ValueError:
            self._constraint = self._parser.parse_constraints('*')

        self._pretty_constraint = constraint
        self._optional = optional
        self._category = category
        self._allows_prereleases = allows_prereleases

        self._python_versions = '*'
        self._python_constraint = self._parser.parse_constraints('*')
        self._platform = '*'
        self._platform_constraint = self._parser.parse_constraints('*')

        self._extras = []
예제 #9
0
def test_format_python_constraint():
    parser = VersionParser()
    constraint = parser.parse_constraints('~2.7 || ^3.6')

    result = format_python_constraint(constraint)

    assert result == '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*'
예제 #10
0
    def __init__(self,
                 name,                     # type: str
                 constraint,               # type: str
                 optional=False,           # type: bool
                 category='main',          # type: str
                 allows_prereleases=False  # type: bool
                 ):
        self._name = canonicalize_name(name)
        self._pretty_name = name
        self._parser = VersionParser()

        try:
            if not isinstance(constraint, BaseConstraint):
                self._constraint = self._parser.parse_constraints(constraint)
            else:
                self._constraint = constraint
        except ValueError:
            self._constraint = self._parser.parse_constraints('*')

        self._pretty_constraint = constraint
        self._optional = optional
        self._category = category
        self._allows_prereleases = allows_prereleases

        self._python_versions = '*'
        self._python_constraint = self._parser.parse_constraints('*')
        self._platform = '*'
        self._platform_constraint = EmptyConstraint()

        self._extras = []
        self._in_extras = []
예제 #11
0
    def find_packages(self, name, constraint=None, extras=None):
        name = name.lower()
        packages = []
        if extras is None:
            extras = []

        if not isinstance(constraint, BaseConstraint):
            parser = VersionParser()
            constraint = parser.parse_constraints(constraint)

        for package in self.packages:
            if name == package.name:
                pkg_constraint = Constraint('==', package.version)

                if constraint is None or constraint.matches(pkg_constraint):
                    for extra in extras:
                        if extra in package.extras:
                            for dep in package.extras[extra]:
                                dep.activate()

                            package.requires += package.extras[extra]

                    packages.append(package)

        return packages
예제 #12
0
파일: package.py 프로젝트: radix/poetry
    def __init__(self, name, version, pretty_version=None):
        """
        Creates a new in memory package.
        """
        self._pretty_name = name
        self._name = canonicalize_name(name)

        self._version = str(parse_version(version))
        self._pretty_version = pretty_version or version

        self.description = ''

        self._stability = parse_stability(version)
        self._dev = self._stability == 'dev'

        self._authors = []

        self.homepage = None
        self.repository_url = None
        self.keywords = []
        self._license = None
        self.readme = None

        self.source_type = ''
        self.source_reference = ''
        self.source_url = ''

        self.requires = []
        self.dev_requires = []
        self.extras = {}
        self.requires_extras = []

        self._parser = VersionParser()

        self.category = 'main'
        self.hashes = []
        self.optional = False

        # Requirements for making it mandatory
        self.requirements = {}

        self.build = None
        self.include = []
        self.exclude = []

        self.classifiers = []

        self._python_versions = '*'
        self._python_constraint = self._parser.parse_constraints('*')
        self._platform = '*'
        self._platform_constraint = EmptyConstraint()

        self.cwd = None
예제 #13
0
    def find_packages(self,
                      name,                    # type: str
                      constraint=None,         # type: Union[Constraint, str, None]
                      extras=None,             # type: Union[list, None]
                      allow_prereleases=False  # type: bool
                      ):  # type: (...) -> List[Package]
        """
        Find packages on the remote server.
        """
        if constraint is not None and not isinstance(constraint, BaseConstraint):
            version_parser = VersionParser()
            constraint = version_parser.parse_constraints(constraint)

        info = self.get_package_info(name)

        packages = []

        for version, release in info['releases'].items():
            if not release:
                # Bad release
                self._log(
                    'No release information found for {}-{}, skipping'.format(
                        name, version
                    ),
                    level='debug'
                )
                continue

            package = Package(name, version)

            if package.is_prerelease() and not allow_prereleases:
                continue

            if (
                not constraint
                or (constraint and constraint.matches(Constraint('=', version)))
            ):
                if extras is not None:
                    package.requires_extras = extras

                packages.append(package)

        self._log(
            '{} packages found for {} {}'.format(
                len(packages), name, str(constraint)
            ),
            level='debug'
        )

        return packages
예제 #14
0
파일: builder.py 프로젝트: paralax/poetry
    def get_classifers(self):
        classifiers = []

        # Automatically set python classifiers
        parser = VersionParser()
        if self._package.python_versions == '*':
            python_constraint = parser.parse_constraints('~2.7 || ^3.4')
        else:
            python_constraint = self._package.python_constraint

        for version in sorted(self.AVAILABLE_PYTHONS):
            if python_constraint.matches(Constraint('=', version)):
                classifiers.append(f'Programming Language :: Python :: {version}')

        return classifiers
예제 #15
0
    def _filter_operations(self, ops: List[Operation]):
        for op in ops:
            if isinstance(op, Update):
                package = op.target_package
            else:
                package = op.package

            if op.job_type == 'uninstall':
                continue

            parser = VersionParser()
            python = '.'.join([str(i) for i in sys.version_info[:3]])
            if 'python' in package.requirements:
                python_constraint = parser.parse_constraints(
                    package.requirements['python'])
                if not python_constraint.matches(Constraint('=', python)):
                    # Incompatible python versions
                    op.skip('Not needed for the current python version')
                    continue

            if self._update:
                extras = {}
                for extra, deps in self._package.extras.items():
                    extras[extra] = [dep.name for dep in deps]
            else:
                extras = {}
                for extra, deps in self._locker.lock_data.get('extras', {}):
                    extras[extra] = [dep.lower() for dep in deps]

            # If a package is optional and not requested
            # in any extra we skip it
            if package.optional:
                drop = True
                for extra in self._extras:
                    if extra in extras and package.name in extras[extra]:
                        drop = False
                        continue

                if drop:
                    op.skip('Not required')
예제 #16
0
파일: resolve.py 프로젝트: paralax/poetry
    def handle(self):
        packages = self.argument('package')

        if not packages:
            package = self.poetry.package
            dependencies = package.requires + package.dev_requires
        else:
            requirements = self._determine_requirements(packages)
            requirements = self._format_requirements(requirements)

            # validate requirements format
            parser = VersionParser()
            for constraint in requirements.values():
                parser.parse_constraints(constraint)

            dependencies = []
            for name, constraint in requirements.items():
                dependencies.append(
                    Dependency(name, constraint)
                )

        solver = Solver(
            self.poetry.package,
            self.poetry.pool,
            Repository(),
            self.output
        )

        ops = solver.solve(dependencies)

        self.line('')
        self.line('Resolution results:')
        self.line('')

        for op in ops:
            package = op.package
            self.line(f'  - <info>{package.name}</info> '
                      f'(<comment>{package.version}</comment>)')
예제 #17
0
    def handle(self):
        packages = self.argument('name')
        is_dev = self.option('dev')

        section = 'dependencies'
        if is_dev:
            section = 'dev-dependencies'

        original_content = self.poetry.file.read()
        content = self.poetry.file.read()
        poetry_content = content['tool']['poetry']

        for name in packages:
            for key in poetry_content[section]:
                if key.lower() == name.lower():
                    raise ValueError(f'Package {name} is already present')

        requirements = self._determine_requirements(packages)
        requirements = self._format_requirements(requirements)

        # validate requirements format
        parser = VersionParser()
        for constraint in requirements.values():
            parser.parse_constraints(constraint)

        for name, constraint in requirements.items():
            if self.option('optional'):
                constraint = {'version': constraint, 'optional': True}

            poetry_content[section][name] = constraint

        # Write new content
        self.poetry.file.write(content)

        # Cosmetic new line
        self.line('')

        # Update packages
        self.reset_poetry()

        installer = Installer(self.output, self.venv, self.poetry.package,
                              self.poetry.locker, self.poetry.pool)

        installer.dry_run(self.option('dry-run'))
        installer.update(True)
        installer.whitelist(requirements)

        try:
            status = installer.run()
        except Exception:
            self.poetry.file.write(original_content)

            raise

        if status != 0 or self.option('dry-run'):
            # Revert changes
            if not self.option('dry-run'):
                self.error('\n'
                           'Addition failed, reverting pyproject.toml '
                           'to its original content.')

            self.poetry.file.write(original_content)

        return status
예제 #18
0
    def solve(self, requested, fixed=None) -> List[Operation]:
        resolver = Resolver(Provider(self._package, self._pool), UI(self._io))

        base = None
        if fixed is not None:
            base = DependencyGraph()
            for fixed_req in fixed:
                base.add_vertex(fixed_req.name, fixed_req, True)

        try:
            graph = resolver.resolve(requested, base=base)
        except ResolverError as e:
            raise SolverProblemError(e)

        packages = [v.payload for v in graph.vertices.values()]

        # Setting info
        for vertex in graph.vertices.values():
            tags = self._get_tags_for_vertex(vertex, requested)
            if 'main' in tags['category']:
                vertex.payload.category = 'main'
            else:
                vertex.payload.category = 'dev'

            if not tags['optional']:
                vertex.payload.optional = False
            else:
                vertex.payload.optional = True

            # Finding the less restrictive requirements
            requirements = {}
            parser = VersionParser()
            for req_name, reqs in tags['requirements'].items():
                for req in reqs:
                    if req_name == 'python':
                        if 'python' not in requirements:
                            requirements['python'] = req
                            continue

                        previous = parser.parse_constraints(requirements['python'])
                        current = parser.parse_constraints(req)

                        if current.matches(previous):
                            requirements['python'] = req

                    if req_name == 'platform':
                        if 'platform' not in requirements:
                            requirements['platform'] = req
                            continue

            vertex.payload.requirements = requirements

        operations = []
        for package in packages:
            installed = False
            for pkg in self._locked.packages:
                if package.name == pkg.name:
                    installed = True
                    # Checking version
                    if package.version != pkg.version:
                        operations.append(Update(pkg, package))

                    break

            if not installed:
                operations.append(Install(package))

        # Checking for removals
        for pkg in self._locked.packages:
            remove = True
            for package in packages:
                if pkg.name == package.name:
                    remove = False
                    break

            if remove:
                operations.append(Uninstall(pkg))

        return list(reversed(operations))
예제 #19
0
 def __init__(self, pool, parser=VersionParser()):
     self._pool = pool
     self._parser = parser
예제 #20
0
def parser():
    return VersionParser()
예제 #21
0
파일: add.py 프로젝트: undu/poetry
    def handle(self):
        from poetry.installation import Installer
        from poetry.semver.version_parser import VersionParser

        packages = self.argument('name')
        is_dev = self.option('dev')

        if (self.option('git') or self.option('path') or self.option('extras')) and len(packages) > 1:
            raise ValueError(
                'You can only specify one package '
                'when using the --git or --path options'
            )

        if self.option('git') and self.option('path'):
            raise RuntimeError(
                '--git and --path cannot be used at the same time'
            )

        section = 'dependencies'
        if is_dev:
            section = 'dev-dependencies'

        original_content = self.poetry.file.read()
        content = self.poetry.file.read()
        poetry_content = content['tool']['poetry']

        for name in packages:
            for key in poetry_content[section]:
                if key.lower() == name.lower():
                    raise ValueError(
                        'Package {} is already present'.format(name)
                    )

        if self.option('git') or self.option('path'):
            requirements = {
                packages[0]: ''
            }
        else:
            requirements = self._determine_requirements(
                packages,
                allow_prereleases=self.option('allow-prereleases')
            )
            requirements = self._format_requirements(requirements)

            # validate requirements format
            parser = VersionParser()
            for constraint in requirements.values():
                parser.parse_constraints(constraint)

        for name, constraint in requirements.items():
            constraint = {
                'version': constraint
            }

            if self.option('git'):
                del constraint['version']

                constraint['git'] = self.option('git')
            elif self.option('path'):
                del constraint['version']

                constraint['path'] = self.option('path')

            if self.option('optional'):
                constraint['optional'] = True

            if self.option('allow-prereleases'):
                constraint['allows-prereleases'] = True

            if self.option('extras'):
                constraint['extras'] = self.option('extras')

            if len(constraint) == 1 and 'version' in constraint:
                constraint = constraint['version']

            poetry_content[section][name] = constraint

        # Write new content
        self.poetry.file.write(content)

        # Cosmetic new line
        self.line('')

        # Update packages
        self.reset_poetry()

        installer = Installer(
            self.output,
            self.venv,
            self.poetry.package,
            self.poetry.locker,
            self.poetry.pool
        )

        installer.dry_run(self.option('dry-run'))
        installer.update(True)
        installer.whitelist(requirements)

        try:
            status = installer.run()
        except Exception:
            self.poetry.file.write(original_content)

            raise

        if status != 0 or self.option('dry-run'):
            # Revert changes
            if not self.option('dry-run'):
                self.error(
                    '\n'
                    'Addition failed, reverting pyproject.toml '
                    'to its original content.'
                )

            self.poetry.file.write(original_content)

        return status
예제 #22
0
파일: solver.py 프로젝트: blueyed/poetry
    def _get_tags_for_vertex(self, vertex, requested):
        category = 'dev'
        optional = True
        python_version = None
        platform = None

        if not vertex.incoming_edges:
            # Original dependency
            for req in requested:
                if vertex.payload.name == req.name:
                    category = req.category
                    optional = req.is_optional()

                    python_version = str(req.python_constraint)

                    platform = str(req.platform_constraint)

                    break

            return category, optional, python_version, platform

        parser = VersionParser()
        python_versions = []
        platforms = []
        for edge in vertex.incoming_edges:
            python_version = None
            platform = None
            for req in edge.origin.payload.requires:
                if req.name == vertex.payload.name:
                    python_version = req.python_versions
                    platform = req.platform

                    break

            (top_category,
             top_optional,
             top_python_version,
             top_platform) = self._get_tags_for_vertex(
                edge.origin, requested
            )

            if top_category == 'main':
                category = top_category

            optional = optional and top_optional

            # Take the most restrictive constraints
            if top_python_version is not None:
                if python_version is not None:
                    previous = parser.parse_constraints(python_version)
                    current = parser.parse_constraints(top_python_version)

                    if top_python_version != '*' and previous.matches(current):
                        python_versions.append(top_python_version)
                    else:
                        python_versions.append(python_version)
                else:
                    python_versions.append(top_python_version)
            elif python_version is not None:
                python_versions.append(python_version)

            if top_platform is not None:
                if platform is not None:
                    previous = GenericConstraint.parse(platform)
                    current = GenericConstraint.parse(top_platform)

                    if top_platform != '*' and previous.matches(current):
                        platforms.append(top_platform)
                    else:
                        platforms.append(platform)
                else:
                    platforms.append(top_platform)
            elif platform is not None:
                platforms.append(platform)

        if not python_versions:
            python_version = None
        else:
            # Find the least restrictive constraint
            python_version = python_versions[0]
            previous = parser.parse_constraints(python_version)
            for constraint in python_versions[1:]:
                current = parser.parse_constraints(constraint)

                if python_version == '*':
                    continue
                elif constraint == '*':
                    python_version = constraint
                elif current.matches(previous):
                    python_version = constraint

        if not platforms:
            platform = None
        else:
            platform = platforms[0]
            previous = GenericConstraint.parse(platform)
            for constraint in platforms[1:]:
                current = GenericConstraint.parse(constraint)

                if platform == '*':
                    continue
                elif constraint == '*':
                    platform = constraint
                elif current.matches(previous):
                    platform = constraint

        return category, optional, python_version, platform
예제 #23
0
    def increment_version(self, version, rule):
        from poetry.semver.version_parser import VersionParser

        parser = VersionParser()
        version_regex = (
            'v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?{}(?:\+[^\s]+)?'
        ).format(parser._modifier_regex)

        m = re.match(version_regex, version)
        if not m:
            raise ValueError(
                'The project\'s version doesn\'t seem to follow semver')

        if m.group(3):
            index = 2
        elif m.group(2):
            index = 1
        else:
            index = 0

        matches = m.groups()[:index + 1]
        base = '.'.join(matches)
        extra_matches = list(g or '' for g in m.groups()[4:])
        extras = version[len('.'.join(matches)):]
        increment = 1
        is_prerelease = (extra_matches[0] or extra_matches[1]) != ''
        bump_prerelease = rule in {
            'premajor', 'preminor', 'prepatch', 'prerelease'
        }
        position = -1

        if rule in {'major', 'premajor'}:
            if m.group(1) != '0' or m.group(2) != '0' or not is_prerelease:
                position = 0
        elif rule in {'minor', 'preminor'}:
            if m.group(2) != '0' or not is_prerelease:
                position = 1
        elif rule in {'patch', 'prepatch'}:
            if not is_prerelease:
                position = 2
        elif rule == 'prerelease' and not is_prerelease:
            position = 2

        if position != -1:
            extra_matches[0] = None

            base = parser._manipulate_version_string(matches,
                                                     position,
                                                     increment=increment)

        if bump_prerelease:
            # We bump the prerelease part of the version
            sep = ''
            if not extra_matches[0]:
                extra_matches[0] = 'alpha'
                extra_matches[1] = '.0'
                sep = '-'
            else:
                if extras.startswith(('.', '_', '-')):
                    sep = extras[0]

                prerelease = extra_matches[1]
                if not prerelease:
                    prerelease = '.1'

                psep = ''
                if prerelease.startswith(('.', '-')):
                    psep = prerelease[0]
                    prerelease = prerelease[1:]

                new_prerelease = str(int(prerelease) + 1)
                extra_matches[1] = '{}{}'.format(psep, new_prerelease)

            extras = '{}{}{}{}'.format(sep, extra_matches[0], extra_matches[1],
                                       extra_matches[2])
        else:
            extras = ''

        return '.'.join(base.split('.')[:max(index, position) + 1]) + extras