licensor 0.1.0 → 0.2.0
raw patch · 5 files changed
+498/−128 lines, 5 filesdep +bytestringdep +cmdargsdep +http-conduitdep −HTTPdep ~base
Dependencies added: bytestring, cmdargs, http-conduit, licensor
Dependencies removed: HTTP
Dependency ranges changed: base
Files
- CHANGELOG.md +11/−0
- Main.hs +50/−110
- README.md +176/−1
- licensor.cabal +51/−17
- src/Licensor.hs +210/−0
+ CHANGELOG.md view
@@ -0,0 +1,11 @@+# 0.2.0 (2016-09-18)++## Enhancements++- Allow use in multi-package project (f545a4c)+- Handle exceptions (ec640bb)+- Replace HTTP with http-conduit (f6735cb)+- Add command line arguments (2558a05)+- Add a library (835ac70)++# 0.1.0 (2016-08-13)
Main.hs view
@@ -1,63 +1,69 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE RecordWildCards #-} +----------------------------------------------------------------------+-- |+-- Module: Main+-- Description:+--+--+--+----------------------------------------------------------------------+ module Main ( main ) where +-- licensor+import Licensor+ -- base import Control.Monad import Data.List import Data.Monoid ((<>))+import qualified Data.Version as Version+import System.Environment import qualified System.Exit as Exit-import System.IO -- Cabal-import Distribution.License-import Distribution.Package import Distribution.PackageDescription-import Distribution.PackageDescription.Parse-import Distribution.Simple.Utils import Distribution.Text-import Distribution.Verbosity +-- cmdargs+import System.Console.CmdArgs+ -- containers-import Data.Map.Strict (Map) import qualified Data.Map.Strict as Map-import Data.Set (Set) import qualified Data.Set as Set -- directory-import System.Directory---- HTTP-import Network.HTTP- ( getRequest- , getResponseBody- , simpleHTTP- )---- process-import System.Process+import System.Directory (doesFileExist) -- | -- -- -newtype License' = License' { _getLicense :: License }- deriving (Eq, Read, Show, Text)+data LiArgs =+ LiArgs+ {+ }+ deriving (Data) -- | -- -- -instance Ord License' where- compare =- comparing display+liArgs :: String -> Mode (CmdArgs LiArgs)+liArgs s =+ cmdArgsMode $+ LiArgs+ {+ }+ &= program s+ &= summary (unwords ["licensor", Version.showVersion version]) -- |@@ -66,12 +72,20 @@ main :: IO () main = do+ LiArgs <- cmdArgsRun . liArgs =<< getProgName+ maybePackage <- getPackage pid <- case maybePackage of- Nothing ->- Exit.die "Error: No Cabal file found."+ Nothing -> do+ stack <- doesFileExist "stack.yaml"+ if stack+ then do+ putStrLn "Found stack.yaml..."+ return Nothing+ else+ Exit.die "Error: No Cabal file found." Just PackageDescription{..} -> do putStrLn $@@ -81,7 +95,7 @@ <> "License: " <> display license <> ")"- return package+ return (Just package) maybeDependencies <- getDependencies @@ -90,9 +104,11 @@ Exit.die "Error: ..." Just dependencies -> do- dependenciesByLicense <-- fmap (Set.map display) <$> orderPackagesByLicense pid dependencies+ (dependenciesByLicense', failed) <-+ orderPackagesByLicense pid dependencies + let dependenciesByLicense = fmap (Set.map display) dependenciesByLicense'+ forM_ (Map.keys dependenciesByLicense) $ \license -> let@@ -107,82 +123,6 @@ <> ": " <> intercalate ", " (Set.toList n) ---- |--------getPackage :: IO (Maybe PackageDescription)-getPackage = do- currentDirectory <- getCurrentDirectory- fmap getPackageDescription <$> findPackageDesc currentDirectory- >>= either (const (return Nothing)) (fmap Just)----- |--------getPackageDescription :: FilePath -> IO PackageDescription-getPackageDescription =- fmap packageDescription . readPackageDescription silent----- |--------getDependencies :: IO (Maybe (Set PackageIdentifier))-getDependencies =- fmap Set.fromList . sequence . fmap simpleParse . lines- <$> readProcess "stack" ["list-dependencies", "--separator", "-"] ""----- |--------getPackageLicense :: PackageIdentifier -> IO License'-getPackageLicense p@PackageIdentifier{..} = do- let- url =- "http://hackage.haskell.org/package/"- <> display p- <> "/"- <> unPackageName pkgName- <> ".cabal"- pd <- simpleHTTP (getRequest url) >>= getResponseBody- (file, handle) <- openTempFile "/tmp" "licensor"- hClose handle- writeFile file pd- PackageDescription{license} <- getPackageDescription file- hClose handle- removeFile file- return (License' license)----- |--------orderPackagesByLicense- :: PackageIdentifier- -> Set PackageIdentifier- -> IO (Map License' (Set PackageIdentifier))-orderPackagesByLicense p =- let- insertPackage package orderedPackages' = do- license <- getPackageLicense package- orderedPackages <- orderedPackages'- return $- if p == package- then- orderedPackages- else- Map.insertWith- Set.union- license- (Set.singleton package)- orderedPackages- in- foldr insertPackage (pure mempty)+ unless (null failed) $ do+ putStr "Failed: "+ print failed
README.md view
@@ -1,1 +1,176 @@-# licensor+# The not so great automatic Haskell licensor++[![][1]][0]++[0]: https://circleci.com/gh/jpvillaisaza/licensor+[1]: https://circleci.com/gh/jpvillaisaza/licensor.svg?style=svg++![][2]+![][3]++[2]: https://www.stackage.org/package/licensor/badge/lts+[3]: https://www.stackage.org/package/licensor/badge/nightly++Licensor is a program that generates a report of the dependencies and+transitive dependencies of a Haskell project and their licenses.++## Description++Choosing a license for a software project or determining whether a+particular dependency can be added to a project can be projects+themselves. Unless starting from scratch, programmers should consider+the licenses of the dependencies and transitive dependencies of their+projects to make informed decisions and avoid license compatibility+issues.++Of course, this is just a starting point. "Beyond (...) general+observations, it is difficult, if not impossible, to provide precise+guidance about what licenses may or may not be compatible with each+other. (...) Programmers who are considering combining code governed+by two or more different licenses should proceed cautiously" (Andrew+M. St. Laurent).++## Disclaimer++Licensor is not a lawyer and does not provide legal advice.++For more information about licenses and license compatibility issues,+read the text of the licenses or consult with a lawyer before making+any decision.++## Related programs++Licensor is not the only license compatibility helper for Haskell:++- Licensor uses a Cabal library and Stack program approach for+ detecting licenses and listing dependencies, respectively. For a+ Cabal library and program approach, consider using+ the [cabal-dependency-licenses][rp-01] program.++[rp-01]: https://hackage.haskell.org/package/cabal-dependency-licenses++## Installation and usage++To install Licensor, use Cabal:++```+$ cabal update && cabal install licensor+```++Then, run the `licensor` executable inside a Haskell project:++```+$ licensor+```++To see the license report for Licensor, clone the repository:++```+$ git clone https://github.com/jpvillaisaza/licensor+```++And run `licensor` inside the project:++```+$ cd licensor/ && licensor+```++Or build and run `licensor` inside the project:++```+$ cd licensor/ && stack build --exec licensor+```++For more information, run `licensor --help`:++```+licensor 0.2.0++licensor [OPTIONS]++Common flags:+ -? --help Display help message+ -V --version Print version information+ --numeric-version Print just the version number+```++## Notes++### Dependencies++Licensor uses the Stack program to list dependencies for a Haskell+project. A future enhancement could be to use the Stack library.++### Licenses and license detection++Licensor uses the Cabal library to detect the license of a Haskell+project and its dependencies (including transitive dependencies). To+do so, it uses the license field in the package description. A future+enhancement could be to use both the license and licence file fields+in the package description.++Cabal provides an enumeration of common open source and free software+licenses. These are the licenses that appear in the reports generated+by Licensor:++License | Description+------------------------- | -------------------------+GPL | GNU General Public License+AGPL | GNU Affero General Public License+LGPL | GNU Lesser General Public License+BSD2 | BSD 2-Clause License+BSD3 | BSD 3-Clause License+BSD4 | BSD 4-Clause License+MIT | MIT License+ISC | ISC License+MPL | Mozilla Public License+Apache | Apache License+PublicDomain | Public domain+AllRightsReserved | All rights reserved+UnspecifiedLicense | Unspecified license (All rights reserved)+OtherLicense | Other license+UnknownLicense | Unknown license++## Contribution guidelines++Feel free to create issues for reporting bugs and suggesting+enhancements, or to fork the repository and open a pull request.++## License++Licensor is licensed under the MIT License.+See [LICENSE.md](LICENSE.md).++### License report++Licensor (0.2.0) depends on the following libraries:++Library | License+------------------------- | -------------------------+base | BSD3+bytestring | BSD3+Cabal | BSD3+cmdargs | BSD3+containers | BSD3+directory | BSD3+http-conduit | BSD3+process | BSD3++And the following licenses (including transitive dependencies):++License | Number of libraries+------------------------- | -------------------------+BSD3 | 68+MIT | 7++(Tested with Licensor 0.2.0, Stack 1.2.0, and LTS 7.0.)++## Additional resources++- [Choose a License](http://choosealicense.com/)+- [License compatibility][ar-01]+- [Understanding open source and free software licensing][ar-02]+ (Andrew M. St. Laurent)++[ar-01]: https://en.wikipedia.org/wiki/License_compatibility+[ar-02]: http://www.oreilly.com/openbook/osfreesoft/book/
licensor.cabal view
@@ -1,33 +1,67 @@-name: licensor-version: 0.1.0-synopsis: A license compatibility helper-description: A license compatibility helper.-homepage: https://github.com/jpvillaisaza/licensor-bug-reports: https://github.com/jpvillaisaza/licensor/issues-license: MIT-license-file: LICENSE.md-author: Juan Pedro Villa Isaza <jpvillaisaza@gmail.com>-maintainer: Juan Pedro Villa Isaza <jpvillaisaza@gmail.com>-copyright: 2016 Juan Pedro Villa Isaza-category: Distribution-extra-source-files: README.md-build-type: Simple-cabal-version: >= 1.10+name: licensor+version: 0.2.0 +build-type: Simple+cabal-version: >= 1.21++license: MIT+license-file: LICENSE.md++copyright: 2016 Juan Pedro Villa Isaza+author: Juan Pedro Villa Isaza <jpvillaisaza@gmail.com>+maintainer: Juan Pedro Villa Isaza <jpvillaisaza@gmail.com>++stability: Experimental++homepage: https://github.com/jpvillaisaza/licensor+bug-reports: https://github.com/jpvillaisaza/licensor/issues++synopsis: A license compatibility helper+description: A license compatibility helper.++category: Distribution++tested-with: GHC == 7.10.3, GHC == 8.0.1++extra-source-files: CHANGELOG.md, README.md+++library+ hs-source-dirs:+ src+ exposed-modules:+ Licensor+ other-modules:+ Paths_licensor+ build-depends:+ base >= 4.8 && < 4.10+ , bytestring+ , Cabal >= 1.22 && < 1.25+ , containers+ , directory+ , http-conduit >= 2.1 && < 2.3+ , process+ default-language:+ Haskell2010+ ghc-options:+ -Wall++ executable licensor main-is: Main.hs build-depends: base >= 4.8 && < 5.0 , Cabal >= 1.22 && < 1.25+ , cmdargs >= 0.10 && < 0.11 , containers , directory- , HTTP >= 4000.3 && < 4000.4- , process+ , licensor default-language: Haskell2010 ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N+ source-repository head type: git
+ src/Licensor.hs view
@@ -0,0 +1,210 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}++----------------------------------------------------------------------+-- |+-- Module: Licensor+-- Description:+--+--+--+----------------------------------------------------------------------++module Licensor+ ( LiLicense(..)+ , LiPackage(..)+ , getDependencies+ , getPackage+ , orderPackagesByLicense+ , version+ )+ where++-- base+import qualified Control.Exception as Exception+import Data.Monoid ((<>))+import Data.Version (Version)+import System.IO++-- bytestring+import qualified Data.ByteString.Lazy as ByteString++-- Cabal+import Distribution.License+import Distribution.Package+import Distribution.PackageDescription+import Distribution.PackageDescription.Parse+import Distribution.Simple.Utils+import Distribution.Text+import Distribution.Verbosity++-- containers+import Data.Map.Strict (Map)+import qualified Data.Map.Strict as Map+import Data.Set (Set)+import qualified Data.Set as Set++-- directory+import System.Directory++-- http-conduit+--import Network.HTTP.Client.Conduit+import Network.HTTP.Simple++-- licensor+import qualified Paths_licensor++-- process+import System.Process+++-- |+--+--++newtype LiLicense = LiLicense { getLicense :: License }+ deriving (Eq, Read, Show, Text)+++-- |+--+--++instance Ord LiLicense where+ compare =+ comparing display+++-- |+--+--++data LiPackage =+ LiPackage+ { liPackageId :: PackageIdentifier+ , liPackageDependencies :: Set LiPackage+ , liPackageLicense :: License+ }+++-- |+--+--++getPackage :: IO (Maybe PackageDescription)+getPackage = do+ currentDirectory <- getCurrentDirectory+ fmap getPackageDescription <$> findPackageDesc currentDirectory+ >>= either (const (return Nothing)) (fmap Just)+++-- |+--+--++getPackageDescription :: FilePath -> IO PackageDescription+getPackageDescription =+ fmap packageDescription . readPackageDescription silent+++-- |+--+--++getDependencies :: IO (Maybe (Set PackageIdentifier))+getDependencies = do+ eitherDeps <-+ Exception.try $ readProcess "stack" ["list-dependencies", "--separator", "-"] ""++ case eitherDeps of+ Left (_ :: IOError) ->+ return Nothing++ Right deps ->+ return $ fmap Set.fromList $ sequence $ fmap simpleParse (lines deps)+++-- |+--+--++getPackageLicense :: PackageIdentifier -> IO (Maybe LiLicense)+getPackageLicense p@PackageIdentifier{..} = do+ putStr $ display p ++ "..."+ let+ url =+ "GET https://hackage.haskell.org/package/"+ <> display p+ <> "/"+ <> unPackageName pkgName+ <> ".cabal"++ req <- parseRequest url+ eitherPd <- Exception.try $ fmap getResponseBody (httpLBS req)++ case eitherPd of+ Left (_ :: HttpException) ->+ return Nothing++ Right pd -> do++ (file, handle) <- openTempFile "/tmp" "licensor"+ hClose handle+ ByteString.writeFile file pd+ PackageDescription{license} <- getPackageDescription file+ hClose handle+ removeFile file++ putStrLn $ display license++ return $ Just (LiLicense license)+++-- |+--+--++orderPackagesByLicense+ :: Maybe PackageIdentifier+ -> Set PackageIdentifier+ -> IO (Map LiLicense (Set PackageIdentifier), Set PackageIdentifier)+orderPackagesByLicense maybeP =+ let+ cond =+ maybe (const False) (==) maybeP++ insertPackage package orderedPackages' = do+ maybeLicense <- getPackageLicense package++ (orderedPackages, failed) <- orderedPackages'+ return $+ if cond package+ then+ (orderedPackages, failed)+ else+ case maybeLicense of+ Nothing ->+ ( orderedPackages, Set.insert package failed+ )++ Just license ->+ ( Map.insertWith+ Set.union+ license+ (Set.singleton package)+ orderedPackages+ , failed+ )+ in+ foldr insertPackage (pure (mempty, mempty))+++-- |+--+--++version :: Version+version =+ Paths_licensor.version