<commit_before>#
# Copyright (c) 2004-2006 rPath, Inc.
#
# This program is distributed under the terms of the Common Public License,
# version 1.0. A copy of this license should have been distributed with this
# source file in a file called LICENSE. If it is not present, the license
# is always available at http://www.opensource.org/licenses/cpl.php.
#
# This program is distributed in the hope that it will be useful, but
# without any waranty; without even the implied warranty of merchantability
# or fitness for a particular purpose. See the Common Public License for
# full details.
#
import copy
from itertools import chain, izip

from conary.build.recipe import Recipe, RECIPE_TYPE_GROUP
from conary.build.errors import RecipeFileError, CookError, GroupPathConflicts
from conary.build.errors import GroupDependencyFailure, GroupCyclesError
from conary.build.errors import GroupAddAllError
from conary.build import macros
from conary.build import use
from conary import conaryclient
from conary.deps import deps
from conary import errors
from conary.lib import graph, util
from conary.repository import trovesource
from conary import trove
from conary import versions


class _BaseGroupRecipe(Recipe):
    """ Defines a group recipe as collection of groups and provides
        operations on those groups.
    """
    ignore = 1
    def __init__(self):
        self.groups = {}
        self.defaultGroup = None

    def _addGroup(self, groupName, group):
        if groupName in self.groups:
            raise RecipeFileError, 'Group %s defined twice' % groupName
        self.groups[groupName] = group

    def _hasGroup(self, groupName):
        return groupName in self.groups

    def _getGroup(self, groupName):
        group = self.groups.get(groupName, None)
        if not group:
            raise RecipeFileError, "No such group '%s'" % groupName
        return group

    def _getGroups(self, groupName):
        if groupName is None:
            return [self.defaultGroup]
        elif isinstance(groupName, (list, tuple)):
            return [self._getGroup(x) for x in groupName]
        else:
            return [self._getGroup(groupName)]

    def _setDefaultGroup(self, group):
        self.defaultGroup = group

    def _getDefaultGroup(self):
        if not self.defaultGroup:
            return self.groups.get(self.name, None)
        return self.defaultGroup

    def iterGroupList(self):
        return self.groups.itervalues()

    def getGroupNames(self):
        return self.groups.keys()

    def getPrimaryGroupNames(self):
        """ 
        Return the list of groups in this GroupRecipe that are not included in 
        any other groups.
        """
        unseen = set(self.getGroupNames())

        for group in self.iterGroupList():
            unseen.difference_update([x[0] for x in group.iterNewGroupList()])
        return unseen




class GroupRecipe(_BaseGroupRecipe):
    """
        Provides the recipe interface for creating a group.  Commands not meant 
        to be used in recipes have an underscore in front of them.  

        Most user commands take a groupName parameter.  This parameter 
        specifies the group that a particular command applies to.  For example,
        r.add('foo', groupName='group-bar') tries to add 'foo' to 'group-bar'.
        The group specified must have been created before it may be added to.
        groupName may also be a list of groups, in which case the command will
        be applied to all groups.  If groupName is not supplied or is None,
        then the command applies to the current default group.

        There are several parameters to groups that are set at the time of 
        group creation.  Although they are normally passed as parameters
        to r.createNewGroup(), for the base group, they are set as variables
        in the recipe class.  

        Note that setting these parameters affects not only the value for 
        the base group, but also the default value for newly created groups.
        So, for example, if you turn on autoResolve in the base group,
        all other groups created will have autoResolve turned on by default.

        The following parameters are settable:

            depCheck (default False): if set to True, then conary will check
                for dependency closure in this group, and raise an error if 
                it is not found.

            autoResolve (default False): if set to True, then conary will
                include any extra troves needed to make this group dependency
                complete.  

            checkOnlyByDefaultDeps (default True): By default, conary
                checks only the dependencies of the troves in a group that
                are installed by default.  By setting this option to False,
                conary will check the dependencies of byDefault False troves
                as well.

            checkPathConflicts (default True): By default, conary checks for
                path conflicts in each group, to ensure that the group can
                be installed without path conflicts.  Setting this to False
                will disable that check.
    """
    Flags = use.LocalFlags
    ignore = 1
    _recipeType = RECIPE_TYPE_GROUP

    depCheck = False
    autoResolve = False
    checkOnlyByDefaultDeps = True
    checkPathConflicts = True

    def __init__(self, repos, cfg, label, flavor, extraMacros={}):
        self.repos = repos
        self.cfg = cfg
        self.labelPath = [ label ]
        self.flavor = flavor
        self.macros = macros.Macros()
        self.macros.update(extraMacros)

        self.replaceSpecs = []

        _BaseGroupRecipe.__init__(self)
        group = self.createGroup(self.name, depCheck = self.depCheck, 
                         autoResolve = self.autoResolve, 
                         checkOnlyByDefaultDeps = self.checkOnlyByDefaultDeps,
                         checkPathConflicts = self.checkPathConflicts,
                         byDefault = True)
        self._setDefaultGroup(group)

    def _parseFlavor(self, flavor):
        assert(flavor is None or isinstance(flavor, str))
        if flavor is None:
            return None
        flavorObj = deps.parseFlavor(flavor)
        if flavorObj is None:
            raise ValueError, 'invalid flavor: %s' % flavor
        return flavorObj

    def Requires(self, requirement, groupName = None):
        """
        Cause a group to have a runtime requirement of the trove requirement.
        """
        for group in self._getGroups(groupName):
            group.addRequires(requirement)
    
    def add(self, name, versionStr = None, flavor = None, source = None,
            byDefault = None, ref = None, components = None, groupName = None):
        """
            Adds a trove to the specified groups.

            Params:

            name (required): the name of the trove to add
            versionStr: a version specifier like that passed to 
                repquery that determines the trove returned.  
            flavor: a flavor limiter like that passed to repquery
                that determines the trove returned.
            source: a piece of data used by some programs that 
                read group recipes.  Specifies the source from which this
                trove originates.  Generally not necessary.
            byDefault: whether to include this trove byDefault or not.  
                defaults to the byDefault setting for troves, as specified
                in createNewGroup.
            ref: trove reference to search for this trove in.  See addReference
                for more information.
            components: Specify a set of components of this trove to include.
                Only relevant when adding packages.  Specified as a list,
                such as r.add('foo', components=['runtime', 'lib'])
            groupName: group to add this trove to.
        """

        flavor = self._parseFlavor(flavor)
        for group in self._getGroups(groupName):
            group.addSpec(name, versionStr = versionStr, flavor = flavor,
                          source = source, byDefault = byDefault, ref = ref,
                          components = components)

    # maintain addTrove for backwards compatability
    addTrove = add

    def remove(self, name, versionStr = None, flavor = None, groupName = None):
        """ Remove a trove added to this group, either by an addAll
            line or by an addTrove line.  

            Note that if the trove is not included explicitly, by an add()
            line, but rather implicitly, perhaps because it is a component in 
            a package that you have added, then removing the trove only
            changes its byDefault setting, so that installing this group
            will not install the trove.

            You can remove troves from a super group that are included due to
            a sub group you include.  For example, suppose group have 
            group-os, which is your top level group, and it includes
            group-dist, which includes a package foo.

            You can add r.remove('foo', groupName='group-os'), and if you
            install group-os, 'foo' will not be installed.

            name (required): the name of the trove to add
            versionStr: a version specifier like that passed to 
                repquery that determines the trove returned.  
            flavor: a flavor limiter like that passed to repquery
                that determines the trove returned.
            groupName: group to add this trove to.
        """
        flavor = self._parseFlavor(flavor)
        for group in self._getGroups(groupName):
            group.removeSpec(name, versionStr = versionStr, flavor = flavor)

    def removeComponents(self, componentList, groupName = None):
        """
            Specify a set of components that should, by default not be 
            included when installing this group.

            Example:
                r.removeComponents(['devel', 'devellib'])
        """
        if not isinstance(componentList, (list, tuple)):
            componentList = [ componentList ]
        for group in self._getGroups(groupName):
            group.removeComponents(componentList)

    def setByDefault(self, byDefault = True, groupName = None):
        """
            Set whether troves are added to this group with byDefault True
            or not.

            example:
            r.setByDefault(False, groupName='group-os')
        """
        for group in self._getGroups(groupName):
            group.setByDefault(byDefault)

    def addAll(self, name, versionStr = None, flavor = None, ref = None,
                                                            recurse=True, 
                                                            groupName = None):
        """ 
            Add all of the troves directly contained in the given 
            reference to groupName.  For example, if the cooked group-foo 
            contains references to the troves 
            foo1=<version>[flavor] and foo2=<version>[flavor],
            the lines followed by
            r.addAll(name, versionStr, flavor)
            would be equivalent to you having added the addTrove lines
            r.add('foo1', <version>) 
            r.add('foo2', <version>) 

            parameters:
            ref: trove reference to search for this trove in.  See addReference
                 for more information.
            recurse (default True): If True, then if the trove you specify in
                addAll contains groups, new groups are created in your recipe
                that match those contained groups, and the addAll command is
                recursed on those groups.  Note that if those subgroups are
                already created in your group, those already created groups
                will be used.  Otherwise, the default settings will be used
                when creating any new groups.
            groupName: group to add these troves to
        """
        flavor = self._parseFlavor(flavor)
        for group in self._getGroups(groupName):
            group.addAll(name, versionStr, flavor, ref = ref, recurse = recurse)

    def addNewGroup(self, name, groupName = None, byDefault = True):
        """
        Add one newly created group to another newly created group.

        Params:
            name: name of group to add
            groupName: name of group(s) to add this group to.  Defaults to 
                current group.
            byDefault: Whether to add this group byDefault True.  Defaults to
            True.
        """
        #FIXME: this should default to whatever the current byDefault default
        # is!
        if not self._hasGroup(name):
            raise RecipeFileError, 'group %s has not been created' % name

        for group in self._getGroups(groupName):
            group.addNewGroup(name, byDefault, explicit = True)

    def setDefaultGroup(self, groupName):
        """
            Sets the current group that all commands apply to if no group
            is specified with the command.
        """
        self._setDefaultGroup(self._getGroup(groupName))

    def addReference(self, name, versionStr = None, flavor = None, ref = None):
        """
            Returns a reference to a trove, usually a group trove, that can
            then be passed in to future r.add/addAll command as the ref 
            parameter.  Passing in a ref  will cause those commands to search 
            for the trove to be added in the reference.

            For example, if there is an already existing trove group-foo,
            which contains foo version 1.0, and there is another foo that
            is version 2.0.
        """
        flavor = self._parseFlavor(flavor)
        return GroupReference(((name, versionStr, flavor),), ref)

    def replace(self, name, newVersionStr = None, newFlavor = None, ref = None, 
                groupName = None):
        """
            Replace all troves with a particular name with a new version of the
            trove.

            Parameters:
            name: the name of the trove to replace
            newVersionStr: the new version to add, like in r.add
            newFlavor: the new flavor to add, like in r.add
            ref: the reference to search for the new trove in, like in r.add
            groupName: the group to limit this replace to.

            Note that by default, r.replace affects _all_ groups.  This is
            different from other group commands.
        """
        newFlavor = self._parseFlavor(newFlavor)
        if groupName is None:
            self.replaceSpecs.append(((name, newVersionStr, newFlavor), ref))
        else:
            for group in self._getGroups(groupName):
                group.replaceSpec(name, newVersionStr, newFlavor, ref)

    def iterReplaceSpecs(self):
        return iter(self.replaceSpecs)

    def setLabelPath(self, *path):
        """ Set the labelPath to search for troves on. """
        self.labelPath = [ versions.Label(x) for x in path ]

    def getLabelPath(self):
        return self.labelPath

    def getSearchFlavor(self):
        return self.flavor

    def getChildGroups(self, groupName):
        return [ (self._getGroup(x[0]), x[1], x[2]) for x in self._getGroup(groupName).iterNewGroupList() ]

    def createGroup(self, groupName, depCheck = False, autoResolve = False,
                    byDefault = None, checkOnlyByDefaultDeps = None,
                    checkPathConflicts = None):
        """
            Creates a new group.

            Params:
            
            groupName:  The name of the group to created.  Must start with 
                group-.
            depCheck: whether to check for dependency closure for this group.  
                Defaults to the setting for the current group.
            autoResolve: whether to resolve dependencies for this group. 
                Defaults to the setting for the current group.
            byDefault: whether to add troves to this group byDefault True 
                or byDefault False by default.  Defaults to the setting for
                the current group.
            checkOnlyByDefaultDeps:  whether to include byDefault False troves
                in this group.  Defaults to the setting for the current group.
            checkPathConflicts:  whether to check path conflicts for this
                group.  Defaults to the setting for the current group.
        """
        if self._hasGroup(groupName):
            raise RecipeFileError, 'group %s was already created' % groupName
        elif not groupName.startswith('group-'):
            raise RecipeFileError, 'group names must start with "group-"'

        origGroup = self._getDefaultGroup()
        if byDefault is None:
            byDefault = origGroup.byDefault

        if checkOnlyByDefaultDeps is None:
            checkOnlyByDefaultDeps = origGroup.checkOnlyByDefaultDeps

        newGroup = SingleGroup(groupName, depCheck, autoResolve, 
                                checkOnlyByDefaultDeps, 
                                checkPathConflicts, byDefault)
        self._addGroup(groupName, newGroup)
        return newGroup
<commit_msg>ensure checkPathConflicts settings are passed to newly created groups correctly<commit_after>#
# Copyright (c) 2004-2006 rPath, Inc.
#
# This program is distributed under the terms of the Common Public License,
# version 1.0. A copy of this license should have been distributed with this
# source file in a file called LICENSE. If it is not present, the license
# is always available at http://www.opensource.org/licenses/cpl.php.
#
# This program is distributed in the hope that it will be useful, but
# without any waranty; without even the implied warranty of merchantability
# or fitness for a particular purpose. See the Common Public License for
# full details.
#
import copy
from itertools import chain, izip

from conary.build.recipe import Recipe, RECIPE_TYPE_GROUP
from conary.build.errors import RecipeFileError, CookError, GroupPathConflicts
from conary.build.errors import GroupDependencyFailure, GroupCyclesError
from conary.build.errors import GroupAddAllError
from conary.build import macros
from conary.build import use
from conary import conaryclient
from conary.deps import deps
from conary import errors
from conary.lib import graph, util
from conary.repository import trovesource
from conary import trove
from conary import versions


class _BaseGroupRecipe(Recipe):
    """ Defines a group recipe as collection of groups and provides
        operations on those groups.
    """
    ignore = 1
    def __init__(self):
        self.groups = {}
        self.defaultGroup = None

    def _addGroup(self, groupName, group):
        if groupName in self.groups:
            raise RecipeFileError, 'Group %s defined twice' % groupName
        self.groups[groupName] = group

    def _hasGroup(self, groupName):
        return groupName in self.groups

    def _getGroup(self, groupName):
        group = self.groups.get(groupName, None)
        if not group:
            raise RecipeFileError, "No such group '%s'" % groupName
        return group

    def _getGroups(self, groupName):
        if groupName is None:
            return [self.defaultGroup]
        elif isinstance(groupName, (list, tuple)):
            return [self._getGroup(x) for x in groupName]
        else:
            return [self._getGroup(groupName)]

    def _setDefaultGroup(self, group):
        self.defaultGroup = group

    def _getDefaultGroup(self):
        if not self.defaultGroup:
            return self.groups.get(self.name, None)
        return self.defaultGroup

    def iterGroupList(self):
        return self.groups.itervalues()

    def getGroupNames(self):
        return self.groups.keys()

    def getPrimaryGroupNames(self):
        """ 
        Return the list of groups in this GroupRecipe that are not included in 
        any other groups.
        """
        unseen = set(self.getGroupNames())

        for group in self.iterGroupList():
            unseen.difference_update([x[0] for x in group.iterNewGroupList()])
        return unseen




class GroupRecipe(_BaseGroupRecipe):
    """
        Provides the recipe interface for creating a group.  Commands not meant 
        to be used in recipes have an underscore in front of them.  

        Most user commands take a groupName parameter.  This parameter 
        specifies the group that a particular command applies to.  For example,
        r.add('foo', groupName='group-bar') tries to add 'foo' to 'group-bar'.
        The group specified must have been created before it may be added to.
        groupName may also be a list of groups, in which case the command will
        be applied to all groups.  If groupName is not supplied or is None,
        then the command applies to the current default group.

        There are several parameters to groups that are set at the time of 
        group creation.  Although they are normally passed as parameters
        to r.createNewGroup(), for the base group, they are set as variables
        in the recipe class.  

        Note that setting these parameters affects not only the value for 
        the base group, but also the default value for newly created groups.
        So, for example, if you turn on autoResolve in the base group,
        all other groups created will have autoResolve turned on by default.

        The following parameters are settable:

            depCheck (default False): if set to True, then conary will check
                for dependency closure in this group, and raise an error if 
                it is not found.

            autoResolve (default False): if set to True, then conary will
                include any extra troves needed to make this group dependency
                complete.  

            checkOnlyByDefaultDeps (default True): By default, conary
                checks only the dependencies of the troves in a group that
                are installed by default.  By setting this option to False,
                conary will check the dependencies of byDefault False troves
                as well.

            checkPathConflicts (default True): By default, conary checks for
                path conflicts in each group, to ensure that the group can
                be installed without path conflicts.  Setting this to False
                will disable that check.
    """
    Flags = use.LocalFlags
    ignore = 1
    _recipeType = RECIPE_TYPE_GROUP

    depCheck = False
    autoResolve = False
    checkOnlyByDefaultDeps = True
    checkPathConflicts = True

    def __init__(self, repos, cfg, label, flavor, extraMacros={}):
        self.repos = repos
        self.cfg = cfg
        self.labelPath = [ label ]
        self.flavor = flavor
        self.macros = macros.Macros()
        self.macros.update(extraMacros)

        self.replaceSpecs = []

        _BaseGroupRecipe.__init__(self)
        group = self.createGroup(self.name, depCheck = self.depCheck, 
                         autoResolve = self.autoResolve, 
                         checkOnlyByDefaultDeps = self.checkOnlyByDefaultDeps,
                         checkPathConflicts = self.checkPathConflicts,
                         byDefault = True)
        self._setDefaultGroup(group)

    def _parseFlavor(self, flavor):
        assert(flavor is None or isinstance(flavor, str))
        if flavor is None:
            return None
        flavorObj = deps.parseFlavor(flavor)
        if flavorObj is None:
            raise ValueError, 'invalid flavor: %s' % flavor
        return flavorObj

    def Requires(self, requirement, groupName = None):
        """
        Cause a group to have a runtime requirement of the trove requirement.
        """
        for group in self._getGroups(groupName):
            group.addRequires(requirement)
    
    def add(self, name, versionStr = None, flavor = None, source = None,
            byDefault = None, ref = None, components = None, groupName = None):
        """
            Adds a trove to the specified groups.

            Params:

            name (required): the name of the trove to add
            versionStr: a version specifier like that passed to 
                repquery that determines the trove returned.  
            flavor: a flavor limiter like that passed to repquery
                that determines the trove returned.
            source: a piece of data used by some programs that 
                read group recipes.  Specifies the source from which this
                trove originates.  Generally not necessary.
            byDefault: whether to include this trove byDefault or not.  
                defaults to the byDefault setting for troves, as specified
                in createNewGroup.
            ref: trove reference to search for this trove in.  See addReference
                for more information.
            components: Specify a set of components of this trove to include.
                Only relevant when adding packages.  Specified as a list,
                such as r.add('foo', components=['runtime', 'lib'])
            groupName: group to add this trove to.
        """

        flavor = self._parseFlavor(flavor)
        for group in self._getGroups(groupName):
            group.addSpec(name, versionStr = versionStr, flavor = flavor,
                          source = source, byDefault = byDefault, ref = ref,
                          components = components)

    # maintain addTrove for backwards compatability
    addTrove = add

    def remove(self, name, versionStr = None, flavor = None, groupName = None):
        """ Remove a trove added to this group, either by an addAll
            line or by an addTrove line.  

            Note that if the trove is not included explicitly, by an add()
            line, but rather implicitly, perhaps because it is a component in 
            a package that you have added, then removing the trove only
            changes its byDefault setting, so that installing this group
            will not install the trove.

            You can remove troves from a super group that are included due to
            a sub group you include.  For example, suppose group have 
            group-os, which is your top level group, and it includes
            group-dist, which includes a package foo.

            You can add r.remove('foo', groupName='group-os'), and if you
            install group-os, 'foo' will not be installed.

            name (required): the name of the trove to add
            versionStr: a version specifier like that passed to 
                repquery that determines the trove returned.  
            flavor: a flavor limiter like that passed to repquery
                that determines the trove returned.
            groupName: group to add this trove to.
        """
        flavor = self._parseFlavor(flavor)
        for group in self._getGroups(groupName):
            group.removeSpec(name, versionStr = versionStr, flavor = flavor)

    def removeComponents(self, componentList, groupName = None):
        """
            Specify a set of components that should, by default not be 
            included when installing this group.

            Example:
                r.removeComponents(['devel', 'devellib'])
        """
        if not isinstance(componentList, (list, tuple)):
            componentList = [ componentList ]
        for group in self._getGroups(groupName):
            group.removeComponents(componentList)

    def setByDefault(self, byDefault = True, groupName = None):
        """
            Set whether troves are added to this group with byDefault True
            or not.

            example:
            r.setByDefault(False, groupName='group-os')
        """
        for group in self._getGroups(groupName):
            group.setByDefault(byDefault)

    def addAll(self, name, versionStr = None, flavor = None, ref = None,
                                                            recurse=True, 
                                                            groupName = None):
        """ 
            Add all of the troves directly contained in the given 
            reference to groupName.  For example, if the cooked group-foo 
            contains references to the troves 
            foo1=<version>[flavor] and foo2=<version>[flavor],
            the lines followed by
            r.addAll(name, versionStr, flavor)
            would be equivalent to you having added the addTrove lines
            r.add('foo1', <version>) 
            r.add('foo2', <version>) 

            parameters:
            ref: trove reference to search for this trove in.  See addReference
                 for more information.
            recurse (default True): If True, then if the trove you specify in
                addAll contains groups, new groups are created in your recipe
                that match those contained groups, and the addAll command is
                recursed on those groups.  Note that if those subgroups are
                already created in your group, those already created groups
                will be used.  Otherwise, the default settings will be used
                when creating any new groups.
            groupName: group to add these troves to
        """
        flavor = self._parseFlavor(flavor)
        for group in self._getGroups(groupName):
            group.addAll(name, versionStr, flavor, ref = ref, recurse = recurse)

    def addNewGroup(self, name, groupName = None, byDefault = True):
        """
        Add one newly created group to another newly created group.

        Params:
            name: name of group to add
            groupName: name of group(s) to add this group to.  Defaults to 
                current group.
            byDefault: Whether to add this group byDefault True.  Defaults to
            True.
        """
        #FIXME: this should default to whatever the current byDefault default
        # is!
        if not self._hasGroup(name):
            raise RecipeFileError, 'group %s has not been created' % name

        for group in self._getGroups(groupName):
            group.addNewGroup(name, byDefault, explicit = True)

    def setDefaultGroup(self, groupName):
        """
            Sets the current group that all commands apply to if no group
            is specified with the command.
        """
        self._setDefaultGroup(self._getGroup(groupName))

    def addReference(self, name, versionStr = None, flavor = None, ref = None):
        """
            Returns a reference to a trove, usually a group trove, that can
            then be passed in to future r.add/addAll command as the ref 
            parameter.  Passing in a ref  will cause those commands to search 
            for the trove to be added in the reference.

            For example, if there is an already existing trove group-foo,
            which contains foo version 1.0, and there is another foo that
            is version 2.0.
        """
        flavor = self._parseFlavor(flavor)
        return GroupReference(((name, versionStr, flavor),), ref)

    def replace(self, name, newVersionStr = None, newFlavor = None, ref = None, 
                groupName = None):
        """
            Replace all troves with a particular name with a new version of the
            trove.

            Parameters:
            name: the name of the trove to replace
            newVersionStr: the new version to add, like in r.add
            newFlavor: the new flavor to add, like in r.add
            ref: the reference to search for the new trove in, like in r.add
            groupName: the group to limit this replace to.

            Note that by default, r.replace affects _all_ groups.  This is
            different from other group commands.
        """
        newFlavor = self._parseFlavor(newFlavor)
        if groupName is None:
            self.replaceSpecs.append(((name, newVersionStr, newFlavor), ref))
        else:
            for group in self._getGroups(groupName):
                group.replaceSpec(name, newVersionStr, newFlavor, ref)

    def iterReplaceSpecs(self):
        return iter(self.replaceSpecs)

    def setLabelPath(self, *path):
        """ Set the labelPath to search for troves on. """
        self.labelPath = [ versions.Label(x) for x in path ]

    def getLabelPath(self):
        return self.labelPath

    def getSearchFlavor(self):
        return self.flavor

    def getChildGroups(self, groupName):
        return [ (self._getGroup(x[0]), x[1], x[2]) for x in self._getGroup(groupName).iterNewGroupList() ]

    def createGroup(self, groupName, depCheck = False, autoResolve = False,
                    byDefault = None, checkOnlyByDefaultDeps = None,
                    checkPathConflicts = None):
        """
            Creates a new group.

            Params:
            
            groupName:  The name of the group to created.  Must start with 
                group-.
            depCheck: whether to check for dependency closure for this group.  
                Defaults to the setting for the current group.
            autoResolve: whether to resolve dependencies for this group. 
                Defaults to the setting for the current group.
            byDefault: whether to add troves to this group byDefault True 
                or byDefault False by default.  Defaults to the setting for
                the current group.
            checkOnlyByDefaultDeps:  whether to include byDefault False troves
                in this group.  Defaults to the setting for the current group.
            checkPathConflicts:  whether to check path conflicts for this
                group.  Defaults to the setting for the current group.
        """
        if self._hasGroup(groupName):
            raise RecipeFileError, 'group %s was already created' % groupName
        elif not groupName.startswith('group-'):
            raise RecipeFileError, 'group names must start with "group-"'

        origGroup = self._getDefaultGroup()
        if byDefault is None:
            byDefault = origGroup.byDefault

        if checkOnlyByDefaultDeps is None:
            checkOnlyByDefaultDeps = origGroup.checkOnlyByDefaultDeps

        if checkPathConflicts is None:
            checkPathConflicts = origGroup.checkPathConflicts

        newGroup = SingleGroup(groupName, depCheck, autoResolve, 
                                checkOnlyByDefaultDeps, 
                                checkPathConflicts, byDefault)
        self._addGroup(groupName, newGroup)
        return newGroup

{'fraction_non_alphanumeric': 0.05904197132822845, 'fraction_numerical': 0.0013817721227474236, 'mean_word_length': 2.0470134198754497, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3714786', 'n_tokens_mistral': 8672, 'n_tokens_neox': 8318, 'n_words': 3575}