@@ -81,6 +81,7 @@ type alias ResultItemSource =
8181 , description : Maybe String
8282 , longDescription : Maybe String
8383 , licenses : List ResultPackageLicense
84+ , licenseExpression : Maybe LicenseExpression
8485 , maintainers : List ResultPackageMaintainer
8586 , teams : List ResultPackageTeam
8687 , platforms : List String
@@ -101,6 +102,19 @@ type alias ResultPackageLicense =
101102 }
102103
103104
105+ {- | Structured license expression tree mirroring the compound-license
106+ operators introduced in NixOS/nixpkgs#468378. AND licenses must all be
107+ satisfied; OR licenses offer a choice; WITH applies an exception; PLUS is
108+ "or any later version".
109+ -}
110+ type LicenseExpression
111+ = LicenseLeaf { fullName : String , url : Maybe String }
112+ | LicenseAnd ( List LicenseExpression )
113+ | LicenseOr ( List LicenseExpression )
114+ | LicenseWith LicenseExpression LicenseExpression
115+ | LicensePlus LicenseExpression
116+
117+
104118type alias ResultPackageMaintainer =
105119 { name : Maybe String
106120 , email : Maybe String
@@ -405,35 +419,38 @@ viewResultItem nixosChannels channel showInstallDetails show item =
405419 |> Maybe . withDefault []
406420 )
407421 ++ renderSource item nixosChannels channel trapClick createShortDetailsItem createGithubUrl
408- ++ ( let
409- licenses =
410- item. source. licenses
411- |> List . filterMap
412- ( \ license ->
413- case license. url of
414- Nothing ->
415- Maybe . map text license. fullName
416-
417- Just url ->
418- Just
419- ( createShortDetailsItem
420- ( Maybe . withDefault " Unknown" license. fullName)
421- url
422- )
423- )
424- in
425- optionals ( not ( List . isEmpty licenses))
426- [ li []
427- ( text
428- ( if List . length licenses == 1 then
429- " License: "
430-
431- else
432- " Licenses: "
422+ ++ ( case item. source. licenseExpression of
423+ Just expression ->
424+ [ li []
425+ ( text " License: "
426+ :: renderLicenseExpression createShortDetailsItem expression
433427 )
434- :: List . intersperse ( text " ▪ " ) licenses
435- )
436- ]
428+ ]
429+
430+ Nothing ->
431+ let
432+ licenses =
433+ item. source. licenses
434+ |> List . filterMap
435+ ( \ license ->
436+ case license. url of
437+ Nothing ->
438+ Maybe . map text license. fullName
439+
440+ Just url ->
441+ Just
442+ ( createShortDetailsItem
443+ ( Maybe . withDefault " Unknown" license. fullName)
444+ url
445+ )
446+ )
447+ in
448+ optionals ( not ( List . isEmpty licenses))
449+ [ li []
450+ ( text " License: "
451+ :: List . intersperse ( text " ▪ " ) licenses
452+ )
453+ ]
437454 )
438455 )
439456 )
@@ -936,6 +953,74 @@ viewResultItem nixosChannels channel showInstallDetails show item =
936953 )
937954
938955
956+ renderLicenseExpression :
957+ ( String -> String -> Html Msg )
958+ -> LicenseExpression
959+ -> List ( Html Msg )
960+ renderLicenseExpression mkLink expr =
961+ case expr of
962+ LicenseLeaf { fullName, url } ->
963+ case url of
964+ Just u ->
965+ [ mkLink fullName u ]
966+
967+ Nothing ->
968+ [ text fullName ]
969+
970+ LicenseAnd children ->
971+ renderJoined mkLink " AND" children
972+
973+ LicenseOr children ->
974+ renderJoined mkLink " OR" children
975+
976+ LicenseWith license exception ->
977+ renderChild mkLink license
978+ ++ [ text " " , span [ class " license-operator" ] [ text " WITH" ], text " " ]
979+ ++ renderChild mkLink exception
980+
981+ LicensePlus license ->
982+ renderChild mkLink license ++ [ text " +" ]
983+
984+
985+ renderJoined :
986+ ( String -> String -> Html Msg )
987+ -> String
988+ -> List LicenseExpression
989+ -> List ( Html Msg )
990+ renderJoined mkLink op children =
991+ let
992+ separator =
993+ [ text " " , span [ class " license-operator" ] [ text op ], text " " ]
994+ in
995+ children
996+ |> List . map ( renderChild mkLink)
997+ |> List . intersperse separator
998+ |> List . concat
999+
1000+
1001+ {- | Render a sub-expression, parenthesising compound children so the
1002+ precedence is unambiguous.
1003+ -}
1004+ renderChild :
1005+ ( String -> String -> Html Msg )
1006+ -> LicenseExpression
1007+ -> List ( Html Msg )
1008+ renderChild mkLink expr =
1009+ let
1010+ inner =
1011+ renderLicenseExpression mkLink expr
1012+ in
1013+ case expr of
1014+ LicenseLeaf _ ->
1015+ inner
1016+
1017+ LicensePlus _ ->
1018+ inner
1019+
1020+ _ ->
1021+ text " (" :: inner ++ [ text " )" ]
1022+
1023+
9391024renderSource :
9401025 Search . ResultItem ResultItemSource
9411026 -> List NixOSChannel
@@ -1120,6 +1205,9 @@ decodeResultItemSource =
11201205 |> Json . Decode . Pipeline . required " package_description" ( Json . Decode . nullable Json . Decode . string)
11211206 |> Json . Decode . Pipeline . required " package_longDescription" ( Json . Decode . nullable Json . Decode . string)
11221207 |> Json . Decode . Pipeline . required " package_license" ( Json . Decode . list decodeResultPackageLicense)
1208+ |> Json . Decode . Pipeline . optional " package_license_expression"
1209+ ( Json . Decode . nullable decodeLicenseExpression)
1210+ Nothing
11231211 |> Json . Decode . Pipeline . required " package_maintainers" ( Json . Decode . list decodeResultPackageMaintainer)
11241212 |> Json . Decode . Pipeline . required " package_teams" ( Json . Decode . list decodeResultPackageTeam)
11251213 |> Json . Decode . Pipeline . required " package_platforms" ( Json . Decode . map filterPlatforms ( Json . Decode . list Json . Decode . string))
@@ -1225,6 +1313,44 @@ decodeResultPackageLicense =
12251313 ( Json . Decode . field " url" ( Json . Decode . nullable Json . Decode . string))
12261314
12271315
1316+ decodeLicenseExpression : Json .Decode .Decoder LicenseExpression
1317+ decodeLicenseExpression =
1318+ let
1319+ recurse =
1320+ Json . Decode . lazy ( \ _ -> decodeLicenseExpression)
1321+ in
1322+ Json . Decode . field " kind" Json . Decode . string
1323+ |> Json . Decode . andThen
1324+ ( \ kind ->
1325+ case kind of
1326+ " leaf" ->
1327+ Json . Decode . map2
1328+ ( \ n u -> LicenseLeaf { fullName = n, url = u } )
1329+ ( Json . Decode . field " fullName" Json . Decode . string)
1330+ ( Json . Decode . field " url" ( Json . Decode . nullable Json . Decode . string))
1331+
1332+ " and" ->
1333+ Json . Decode . map LicenseAnd
1334+ ( Json . Decode . field " licenses" ( Json . Decode . list recurse))
1335+
1336+ " or" ->
1337+ Json . Decode . map LicenseOr
1338+ ( Json . Decode . field " licenses" ( Json . Decode . list recurse))
1339+
1340+ " with" ->
1341+ Json . Decode . map2 LicenseWith
1342+ ( Json . Decode . field " license" recurse)
1343+ ( Json . Decode . field " exception" recurse)
1344+
1345+ " plus" ->
1346+ Json . Decode . map LicensePlus
1347+ ( Json . Decode . field " license" recurse)
1348+
1349+ other ->
1350+ Json . Decode . fail ( " Unknown license expression kind: " ++ other)
1351+ )
1352+
1353+
12281354decodeResultPackageMaintainer : Json .Decode .Decoder ResultPackageMaintainer
12291355decodeResultPackageMaintainer =
12301356 Json . Decode . map3 ResultPackageMaintainer
0 commit comments