<commit_before># -*- coding: utf-8 -*-


# Copyright (C) 2015  Red Hat, Inc.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA


"""
This module provides classes for manipulating .treeinfo files.
Treeinfo files provide details about installable trees in Fedora composes and media.
"""


__all__ = (
    "TreeInfo",
    "Variant",
    "VARIANT_TYPES",
)


import os
import hashlib
import re

import six

import productmd.common
import productmd.composeinfo


#: supported variant types
VARIANT_TYPES = [
    "variant",
    "optional",
    "addon",
]


def compute_checksum(path, checksum_type):
    checksum = hashlib.new(checksum_type)
    fo = open(path, "rb")
    while True:
        chunk = fo.read(1024**2)
        if not chunk:
            break
        checksum.update(chunk)
    fo.close()
    return checksum.hexdigest().lower()


class TreeInfo(productmd.common.MetadataBase):
    def __init__(self):
        super(productmd.common.MetadataBase, self)
        self.header = Header(self)              #: (:class:`productmd.common.Header`) -- Metadata header
        self.release = Release(self)            #: (:class:`.Release`) -- Release details
        self.base_product = BaseProduct(self)   #: (:class:`.BaseProduct`) -- Base product details (optional)
        self.tree = Tree(self)                  #: (:class:`.Tree`) -- Tree details
        self.variants = Variants(self)          #: (:class:`.Variants`) -- Release variants
        self.checksums = Checksums(self)        #: (:class:`.Checksums`) -- Checksums of images included in a tree
        self.images = Images(self)              #: (:class:`.Images`) -- Paths to images included in a tree
        self.stage2 = Stage2(self)              #: (:class:`.Stage2`) -- Stage 2 image path (for Anaconda installer)
        self.media = Media(self)                #: (:class:`.Media`) -- Media set information (optional)

    def __str__(self):
        result = "%s-%s" % (self.release.short, self.release.version)
        if self.release.is_layered:
            result += "-%s-%s" % (self.base_product.short, self.base_product.version)
        variant = sorted(self.variants)[0]
        result += " %s.%s" % (variant, self.tree.arch)
        return result

    def __getitem__(self, name):
        return self.variants[name]

    def __delitem__(self, name):
        del self.variants[name]

    def _get_parser(self):
        return productmd.common.SortedConfigParser()

    def parse_file(self, f):
        # parse file, return parser or dict with data
        f.seek(0)
        parser = productmd.common.SortedConfigParser()
        parser.read_file(f)
        return parser

    def build_file(self, parser, f):
        # build file from parser or dict with data
        parser.write(f)

    def serialize(self, parser):
        self.validate()
        self.header.serialize(parser)
        self.release.serialize(parser)
        if self.release.is_layered:
            self.base_product.serialize(parser)
        self.tree.serialize(parser)
        self.variants.serialize(parser)
        self.checksums.serialize(parser)
        self.images.serialize(parser)
        self.stage2.serialize(parser)
        self.media.serialize(parser)
        # HACK: generate [general] section for compatibility
        general = General(self)
        general.serialize(parser)

    def deserialize(self, parser):
        self.header.deserialize(parser)
        self.release.deserialize(parser)
        if self.release.is_layered:
            self.base_product.deserialize(parser)
        self.tree.deserialize(parser)
        self.variants.deserialize(parser)
        self.checksums.deserialize(parser)
        self.images.deserialize(parser)
        self.stage2.deserialize(parser)
        self.media.deserialize(parser)
        self.validate()
        self.header.set_current_version()
        return parser


class Header(productmd.common.Header):

    def serialize(self, parser):
        self.validate()
        parser.add_section(self._section)
        # write *current* version, because format gets converted on save
        parser.set(self._section, "version", ".".join([str(i) for i in productmd.common.VERSION]))

    def deserialize(self, parser):
        if parser.has_option(self._section, "version"):
            self.version = parser.get(self._section, "version")
        self.validate()


class BaseProduct(productmd.common.MetadataBase):
    """
    :class:`.BaseProduct` provides information about operating system a :class:`.Release` runs on.
    """

    def __init__(self, metadata):
        super(BaseProduct, self).__init__()
        self._section = "base_product"
        self._metadata = metadata
        self.name = None                #: (*str*) -- base product name, for example: "Fedora", "Red Hat Enterprise Linux"
        self.short = None               #: (*str*) -- base product short name, for example: "F", "RHEL"
        self.version = None             #: (*str*) -- base product *major* version, for example: "21", "7"

    def _validate_name(self):
        self._assert_type("name", list(six.string_types))

    def _validate_version(self):
        self._assert_type("version", list(six.string_types))
        self._assert_matches_re("version", [r"^\d+(\.\d+)*$"])

    def _validate_short(self):
        self._assert_type("short", list(six.string_types))

    def serialize(self, parser):
        self.validate()
        parser.add_section(self._section)
        parser.set(self._section, "name", self.name)
        parser.set(self._section, "version", self.version)
        parser.set(self._section, "short", self.short)

    def deserialize(self, parser):
        self.name = parser.get(self._section, "name")
        self.version = parser.get(self._section, "version")
        self.short = parser.get(self._section, "short")
        self.validate()
<commit_msg>Update version validation in treeinfo<commit_after># -*- coding: utf-8 -*-


# Copyright (C) 2015  Red Hat, Inc.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA


"""
This module provides classes for manipulating .treeinfo files.
Treeinfo files provide details about installable trees in Fedora composes and media.
"""


__all__ = (
    "TreeInfo",
    "Variant",
    "VARIANT_TYPES",
)


import os
import hashlib
import re

import six

import productmd.common
import productmd.composeinfo


#: supported variant types
VARIANT_TYPES = [
    "variant",
    "optional",
    "addon",
]


def compute_checksum(path, checksum_type):
    checksum = hashlib.new(checksum_type)
    fo = open(path, "rb")
    while True:
        chunk = fo.read(1024**2)
        if not chunk:
            break
        checksum.update(chunk)
    fo.close()
    return checksum.hexdigest().lower()


class TreeInfo(productmd.common.MetadataBase):
    def __init__(self):
        super(productmd.common.MetadataBase, self)
        self.header = Header(self)              #: (:class:`productmd.common.Header`) -- Metadata header
        self.release = Release(self)            #: (:class:`.Release`) -- Release details
        self.base_product = BaseProduct(self)   #: (:class:`.BaseProduct`) -- Base product details (optional)
        self.tree = Tree(self)                  #: (:class:`.Tree`) -- Tree details
        self.variants = Variants(self)          #: (:class:`.Variants`) -- Release variants
        self.checksums = Checksums(self)        #: (:class:`.Checksums`) -- Checksums of images included in a tree
        self.images = Images(self)              #: (:class:`.Images`) -- Paths to images included in a tree
        self.stage2 = Stage2(self)              #: (:class:`.Stage2`) -- Stage 2 image path (for Anaconda installer)
        self.media = Media(self)                #: (:class:`.Media`) -- Media set information (optional)

    def __str__(self):
        result = "%s-%s" % (self.release.short, self.release.version)
        if self.release.is_layered:
            result += "-%s-%s" % (self.base_product.short, self.base_product.version)
        variant = sorted(self.variants)[0]
        result += " %s.%s" % (variant, self.tree.arch)
        return result

    def __getitem__(self, name):
        return self.variants[name]

    def __delitem__(self, name):
        del self.variants[name]

    def _get_parser(self):
        return productmd.common.SortedConfigParser()

    def parse_file(self, f):
        # parse file, return parser or dict with data
        f.seek(0)
        parser = productmd.common.SortedConfigParser()
        parser.read_file(f)
        return parser

    def build_file(self, parser, f):
        # build file from parser or dict with data
        parser.write(f)

    def serialize(self, parser):
        self.validate()
        self.header.serialize(parser)
        self.release.serialize(parser)
        if self.release.is_layered:
            self.base_product.serialize(parser)
        self.tree.serialize(parser)
        self.variants.serialize(parser)
        self.checksums.serialize(parser)
        self.images.serialize(parser)
        self.stage2.serialize(parser)
        self.media.serialize(parser)
        # HACK: generate [general] section for compatibility
        general = General(self)
        general.serialize(parser)

    def deserialize(self, parser):
        self.header.deserialize(parser)
        self.release.deserialize(parser)
        if self.release.is_layered:
            self.base_product.deserialize(parser)
        self.tree.deserialize(parser)
        self.variants.deserialize(parser)
        self.checksums.deserialize(parser)
        self.images.deserialize(parser)
        self.stage2.deserialize(parser)
        self.media.deserialize(parser)
        self.validate()
        self.header.set_current_version()
        return parser


class Header(productmd.common.Header):

    def serialize(self, parser):
        self.validate()
        parser.add_section(self._section)
        # write *current* version, because format gets converted on save
        parser.set(self._section, "version", ".".join([str(i) for i in productmd.common.VERSION]))

    def deserialize(self, parser):
        if parser.has_option(self._section, "version"):
            self.version = parser.get(self._section, "version")
        self.validate()


class BaseProduct(productmd.common.MetadataBase):
    """
    :class:`.BaseProduct` provides information about operating system a :class:`.Release` runs on.
    """

    def __init__(self, metadata):
        super(BaseProduct, self).__init__()
        self._section = "base_product"
        self._metadata = metadata
        self.name = None                #: (*str*) -- base product name, for example: "Fedora", "Red Hat Enterprise Linux"
        self.short = None               #: (*str*) -- base product short name, for example: "F", "RHEL"
        self.version = None             #: (*str*) -- base product *major* version, for example: "21", "7"

    def _validate_name(self):
        self._assert_type("name", list(six.string_types))

    def _validate_version(self):
        self._assert_type("version", list(six.string_types))
        if re.match('^\d', self.version):
            self._assert_matches_re("version", [r"^\d+(\.\d+)*$"])

    def _validate_short(self):
        self._assert_type("short", list(six.string_types))

    def serialize(self, parser):
        self.validate()
        parser.add_section(self._section)
        parser.set(self._section, "name", self.name)
        parser.set(self._section, "version", self.version)
        parser.set(self._section, "short", self.short)

    def deserialize(self, parser):
        self.name = parser.get(self._section, "name")
        self.version = parser.get(self._section, "version")
        self.short = parser.get(self._section, "short")
        self.validate()

{'fraction_non_alphanumeric': 0.11785279805352798, 'fraction_numerical': 0.005170316301703163, 'mean_word_length': 2.685346035304007, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5964479', 'n_tokens_mistral': 3808, 'n_tokens_neox': 3673, 'n_words': 995}