{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-} module Distribution.SPDX.LicenseId ( LicenseId (..), licenseId, licenseName, licenseIsOsiApproved, licenseIsFsfLibre, mkLicenseId, licenseIdList, -- * Helpers licenseIdMigrationMessage, ) where import Distribution.Compat.Prelude import Prelude () import Distribution.Compat.Lens (set) import Distribution.Pretty import Distribution.Parsec import Distribution.Utils.Generic (isAsciiAlphaNum) import Distribution.Utils.Structured (Structured (..), nominalStructure, typeVersion) import Distribution.SPDX.LicenseListVersion import qualified Data.Binary.Get as Binary import qualified Data.Binary.Put as Binary import qualified Data.Map.Strict as Map import qualified Distribution.Compat.CharParsing as P import qualified Text.PrettyPrint as Disp ------------------------------------------------------------------------------- -- LicenseId ------------------------------------------------------------------------------- -- | SPDX License identifier data LicenseId {{ licenseIds }} deriving (Eq, Ord, Enum, Bounded, Show, Read, Typeable, Data, Generic) instance Binary LicenseId where -- Word16 is encoded in big endianess -- https://github.com/kolmodin/binary/blob/master/src/Data/Binary/Class.hs#L220-LL227 put = Binary.putWord16be . fromIntegral . fromEnum get = do i <- Binary.getWord16be if i > fromIntegral (fromEnum (maxBound :: LicenseId)) then fail "Too large LicenseId tag" else return (toEnum (fromIntegral i)) -- note: remember to bump version each time the definition changes instance Structured LicenseId where structure p = set typeVersion 306 $ nominalStructure p instance Pretty LicenseId where pretty = Disp.text . licenseId -- | -- >>> eitherParsec "BSD-3-Clause" :: Either String LicenseId -- Right BSD_3_Clause -- -- >>> eitherParsec "BSD3" :: Either String LicenseId -- Left "...Unknown SPDX license identifier: 'BSD3' Do you mean BSD-3-Clause?" -- instance Parsec LicenseId where parsec = do n <- some $ P.satisfy $ \c -> isAsciiAlphaNum c || c == '-' || c == '.' v <- askCabalSpecVersion maybe (fail $ "Unknown SPDX license identifier: '" ++ n ++ "' " ++ licenseIdMigrationMessage n) return $ mkLicenseId (cabalSpecVersionToSPDXListVersion v) n instance NFData LicenseId where rnf l = l `seq` () -- | Help message for migrating from non-SPDX license identifiers. -- -- Old 'License' is almost SPDX, except for 'BSD2', 'BSD3'. This function -- suggests SPDX variant: -- -- >>> licenseIdMigrationMessage "BSD3" -- "Do you mean BSD-3-Clause?" -- -- Also 'OtherLicense', 'AllRightsReserved', and 'PublicDomain' aren't -- valid SPDX identifiers -- -- >>> traverse_ (print . licenseIdMigrationMessage) [ "OtherLicense", "AllRightsReserved", "PublicDomain" ] -- "SPDX license list contains plenty of licenses. See https://spdx.org/licenses/. Also they can be combined into complex expressions with AND and OR." -- "You can use NONE as a value of license field." -- "Public Domain is a complex matter. See https://wiki.spdx.org/view/Legal_Team/Decisions/Dealing_with_Public_Domain_within_SPDX_Files. Consider using a proper license." -- -- SPDX License list version 3.0 introduced "-only" and "-or-later" variants for GNU family of licenses. -- See -- >>> licenseIdMigrationMessage "GPL-2.0" -- "SPDX license list 3.0 deprecated suffixless variants of GNU family of licenses. Use GPL-2.0-only or GPL-2.0-or-later." -- -- For other common licenses their old license format coincides with the SPDX identifiers: -- -- >>> traverse eitherParsec ["GPL-2.0-only", "GPL-3.0-only", "LGPL-2.1-only", "MIT", "ISC", "MPL-2.0", "Apache-2.0"] :: Either String [LicenseId] -- Right [GPL_2_0_only,GPL_3_0_only,LGPL_2_1_only,MIT,ISC,MPL_2_0,Apache_2_0] -- licenseIdMigrationMessage :: String -> String licenseIdMigrationMessage = go where go l | gnuVariant l = "SPDX license list 3.0 deprecated suffixless variants of GNU family of licenses. Use " ++ l ++ "-only or " ++ l ++ "-or-later." go "BSD3" = "Do you mean BSD-3-Clause?" go "BSD2" = "Do you mean BSD-2-Clause?" go "AllRightsReserved" = "You can use NONE as a value of license field." go "OtherLicense" = "SPDX license list contains plenty of licenses. See https://spdx.org/licenses/. Also they can be combined into complex expressions with AND and OR." go "PublicDomain" = "Public Domain is a complex matter. See https://wiki.spdx.org/view/Legal_Team/Decisions/Dealing_with_Public_Domain_within_SPDX_Files. Consider using a proper license." -- otherwise, we don't know go _ = "" gnuVariant = flip elem ["GPL-2.0", "GPL-3.0", "LGPL-2.1", "LGPL-3.0", "AGPL-3.0" ] ------------------------------------------------------------------------------- -- License Data ------------------------------------------------------------------------------- -- | License SPDX identifier, e.g. @"BSD-3-Clause"@. licenseId :: LicenseId -> String {% for l in licenses %} licenseId {{l.constructor}} = {{l.id}} {% endfor %} -- | License name, e.g. @"GNU General Public License v2.0 only"@ licenseName :: LicenseId -> String {% for l in licenses %} licenseName {{l.constructor}} = {{l.name}} {% endfor %} -- | Whether the license is approved by Open Source Initiative (OSI). -- -- See . licenseIsOsiApproved :: LicenseId -> Bool {% for l in licenses %} {% if l.isOsiApproved %} licenseIsOsiApproved {{l.constructor}} = True {% endif %} {% endfor %} licenseIsOsiApproved _ = False -- | Whether the license is considered libre by Free Software Foundation (FSF). -- -- See -- -- @since 3.4.0.0 -- licenseIsFsfLibre :: LicenseId -> Bool {% for l in licenses %} {% if l.isFsfLibre %} licenseIsFsfLibre {{l.constructor}} = True {% endif %} {% endfor %} licenseIsFsfLibre _ = False ------------------------------------------------------------------------------- -- Creation ------------------------------------------------------------------------------- licenseIdList :: LicenseListVersion -> [LicenseId] licenseIdList LicenseListVersion_3_0 = {{licenseList_3_0}} ++ bulkOfLicenses licenseIdList LicenseListVersion_3_2 = {{licenseList_3_2}} ++ bulkOfLicenses licenseIdList LicenseListVersion_3_6 = {{licenseList_3_6}} ++ bulkOfLicenses licenseIdList LicenseListVersion_3_9 = {{licenseList_3_9}} ++ bulkOfLicenses -- | Create a 'LicenseId' from a 'String'. mkLicenseId :: LicenseListVersion -> String -> Maybe LicenseId mkLicenseId LicenseListVersion_3_0 s = Map.lookup s stringLookup_3_0 mkLicenseId LicenseListVersion_3_2 s = Map.lookup s stringLookup_3_2 mkLicenseId LicenseListVersion_3_6 s = Map.lookup s stringLookup_3_6 mkLicenseId LicenseListVersion_3_9 s = Map.lookup s stringLookup_3_9 stringLookup_3_0 :: Map String LicenseId stringLookup_3_0 = Map.fromList $ map (\i -> (licenseId i, i)) $ licenseIdList LicenseListVersion_3_0 stringLookup_3_2 :: Map String LicenseId stringLookup_3_2 = Map.fromList $ map (\i -> (licenseId i, i)) $ licenseIdList LicenseListVersion_3_2 stringLookup_3_6 :: Map String LicenseId stringLookup_3_6 = Map.fromList $ map (\i -> (licenseId i, i)) $ licenseIdList LicenseListVersion_3_6 stringLookup_3_9 :: Map String LicenseId stringLookup_3_9 = Map.fromList $ map (\i -> (licenseId i, i)) $ licenseIdList LicenseListVersion_3_9 -- | Licenses in all SPDX License lists bulkOfLicenses :: [LicenseId] bulkOfLicenses = {{licenseList_all}}