77# See https://aboutcode.org for more information about nexB OSS projects.
88#
99
10+ import logging
1011import os
1112import uuid
12- from fnmatch import fnmatchcase
13- import logging
1413import sys
1514
15+ from fnmatch import fnmatchcase
16+
1617import attr
17- from packageurl import normalize_qualifiers
18- from packageurl import PackageURL
1918import saneyaml
2019
2120from commoncode import filetype
21+ from commoncode .fileutils import as_posixpath
2222from commoncode .datautils import choices
2323from commoncode .datautils import Boolean
2424from commoncode .datautils import Date
2525from commoncode .datautils import Integer
2626from commoncode .datautils import List
2727from commoncode .datautils import Mapping
2828from commoncode .datautils import String
29- from commoncode .fileutils import as_posixpath
3029from commoncode .resource import Resource
3130from license_expression import combine_expressions
3231from license_expression import Licensing
32+ from packageurl import normalize_qualifiers
33+ from packageurl import PackageURL
3334
3435try :
3536 from typecode import contenttype
4142except ImportError :
4243 licensing = None
4344
45+ # FIXME: what if licensing is not importable?
4446from packagedcode .licensing import get_declared_license_expression_spdx
4547
4648"""
@@ -963,7 +965,7 @@ def get_license_detections_and_expression(self):
963965 return [], None
964966
965967 if self .datasource_id :
966- default_relation_license = get_default_relation_license (
968+ default_relation_license = get_default_relation_license (
967969 datasource_id = self .datasource_id ,
968970 )
969971 else :
@@ -1017,12 +1019,11 @@ def add_to_package(package_uid, resource, codebase):
10171019
10181020class DatafileHandler :
10191021 """
1020- A base handler class to handle any package manifests, lockfiles and data
1021- files. Each subclass handles a package datafile format to parse datafiles
1022- and assemble Package and Depdencies from these:
1022+ A base handler class to handle any package manifest, lockfile, package database
1023+ and related data files. Each subclass handles a package datafile format to parse
1024+ datafiles and assemble Package and Dependencies from these:
10231025
10241026 - parses a datafile format and yields package data.
1025-
10261027 - assembles this datafile package data in top-level packages and dependencies
10271028 - assigns package files to their package
10281029 """
@@ -1033,6 +1034,16 @@ class DatafileHandler:
10331034 # can only contain ASCII letters, digits and underscore. Must be lowercase
10341035 datasource_id = None
10351036
1037+ # style of package data processed by this handler, either app for application package like npm,
1038+ # sys for system packages like rpm, or info for informational data file that provides hints but
1039+ # is not a package manifest, like with a README file
1040+ # possible values are app, sys and info
1041+ datasource_type = 'app'
1042+
1043+ # tuple of specifically supported operating systems. If None or empty, all platforms are supported
1044+ # possible values are win, mac, linux, freebsd
1045+ supported_oses = tuple ()
1046+
10361047 # Sequence of known fnmatch-style case-insensitive glob patterns (e.g., Unix
10371048 # shell style patterns) that apply on the whole POSIX path for package
10381049 # datafiles recognized and parsed by this parser. See fnmatch.fnmatch().
@@ -1053,7 +1064,7 @@ class DatafileHandler:
10531064 # Informational: Default primary language for this parser.
10541065 default_primary_language = None
10551066
1056- # If the datafilehandler contains only resolved dependencies
1067+ # If the handler is for a lockfile that contains locked/pinned, pre- resolved dependencies
10571068 is_lockfile = False
10581069
10591070 # Informational: Description of this parser
@@ -1062,7 +1073,9 @@ class DatafileHandler:
10621073 # Informational: URL that documents this file format
10631074 documentation_url = None
10641075
1065- # Default Relation between license elements detected in an `extracted_license_statement`
1076+ # Default license expression relation between the license detected in an
1077+ # `extracted_license_statement` for this data file.
1078+ # This may vary for each data file based on conventions and specifications.
10661079 default_relation_license = None
10671080
10681081 @classmethod
@@ -1491,11 +1504,44 @@ def get_top_level_resources(cls, manifest_resource, codebase):
14911504 """
14921505 pass
14931506
1507+ @classmethod
1508+ def validate (cls ):
1509+ """
1510+ Validate this class.
1511+ Raise ImproperlyConfiguredDatafileHandler exception on errors.
1512+ """
1513+
1514+ did = cls .datasource_id
1515+ if not did :
1516+ raise ImproperlyConfiguredDatafileHandler (
1517+ f'The handler { cls !r} has an empty datasource_id { did !r} .' )
1518+
1519+ DATASOURCE_TYPES = 'app' , 'sys' , 'info' ,
1520+ dfs = cls .datasource_type
1521+ if dfs not in DATASOURCE_TYPES :
1522+ raise ImproperlyConfiguredDatafileHandler (
1523+ f'The handler { did !r} : { cls !r} has an invalid '
1524+ f'datasource_type: { dfs !r} : must be one of { DATASOURCE_TYPES !r} .'
1525+ )
1526+
1527+ oses = 'linux' , 'win' , 'max' , 'freebsd' ,
1528+ soses = cls .supported_oses
1529+ if soses and not all (s in oses for s in soses ):
1530+ raise ImproperlyConfiguredDatafileHandler (
1531+ f'The handler { cls .datasource_id !r} : { cls !r} has invalid '
1532+ f'supported_oses: { soses !r} : must be empty or among { oses !r} '
1533+ )
1534+
1535+
1536+ class ImproperlyConfiguredDatafileHandler (Exception ):
1537+ """ScanCode Package Datafile Handler is not properly configured"""
1538+ pass
1539+
14941540
14951541class NonAssemblableDatafileHandler (DatafileHandler ):
14961542 """
1497- A handler that has no default implmentation for the assemble method, e.g.,
1498- it will not alone trigger the creation of a top-level Pacakge .
1543+ A handler with a default implementation of an assemble method doing nothing , e.g.,
1544+ it will not alone trigger the creation of a top-level Package .
14991545 """
15001546
15011547 @classmethod
@@ -1528,8 +1574,8 @@ def build_purl(mapping):
15281574 subpath = mapping .get ('subpath' )
15291575 return PackageURL (
15301576 type = ptype ,
1531- name = name ,
15321577 namespace = namespace ,
1578+ name = name ,
15331579 version = version ,
15341580 qualifiers = qualifiers ,
15351581 subpath = subpath ,
@@ -1601,10 +1647,10 @@ def from_package_data(cls, package_data, datafile_path, package_only=False):
16011647 license_match ['from_file' ] = datafile_path
16021648
16031649 package = cls .from_dict (package_data_mapping )
1604-
1650+
16051651 if not package .package_uid :
16061652 package .package_uid = build_package_uid (package .purl )
1607-
1653+
16081654 if not package_only :
16091655 package .populate_license_fields ()
16101656 package .populate_holder_field ()
@@ -1763,7 +1809,7 @@ def refresh_license_expressions(self, default_relation='AND'):
17631809 self .declared_license_expression_spdx = get_declared_license_expression_spdx (
17641810 declared_license_expression = self .declared_license_expression ,
17651811 )
1766-
1812+
17671813 if self .other_license_detections :
17681814 self .other_license_expression = str (combine_expressions (
17691815 expressions = [
0 commit comments