packages feed

setop (empty) → 0.1.0.0

raw patch · 10 files changed

+487/−0 lines, 10 filesdep +basedep +containersdep +doctestsetup-changed

Dependencies added: base, containers, doctest, hlint, hspec, optparse-applicative, protolude, setop, text

Files

+ LICENSE.txt view
@@ -0,0 +1,9 @@+The MIT License (MIT)++Copyright 2017, Médéric Hurier++Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ README.org view
@@ -0,0 +1,103 @@+* Setop: Perform set operations on files+** Rationale++[[https://en.wikipedia.org/wiki/Set_(mathematics)#Basic_operations][Set operations]] are a convenient solution to common problems:++- create a list of tasks without duplicates (set union)+- filter tasks done out of tasks to do (set difference)+- find common elements in a database (set intersection)+- and remove theses elements (set symmetric difference)++Setop helps you run these set operations on your files.++** Usage++Let's introduce two line-separated files: =A.txt= and =B.txt=.++=A.txt= contains all numbers from 0 to 5 included+#+BEGIN_EXAMPLE+0+1+2+3+4+5+#+END_EXAMPLE++=B.txt= contains all even numbers from 0 to 8 included+#+BEGIN_EXAMPLE+0+2+4+6+8+#+END_EXAMPLE++*** Set Union (U/Union):++#+BEGIN_SRC bash+$ setop A.txt U B.txt+0+1+2+3+4+5+6+8+#+END_SRC`++*** Set Difference (D/Diff):++#+BEGIN_SRC bash+$ setop A.txt D B.txt+1+3+5+#+END_SRC`++*** Set Intersection (I/Inter):++#+BEGIN_SRC bash+$ setop A.txt I B.txt+0+2+4+#+END_SRC`++*** Set Symmetric Difference (J/Disj):++#+BEGIN_SRC bash+$ setop A.txt J B.txt+1+3+5+6+8+#+END_SRC`++*** Reading A.txt from STDIN:++#+BEGIN_SRC bash+$ cat A.txt | setop STDIN Diff B.txt+0+2+4+#+END_SRC`++*** Reading B.txt from STDIN:++#+BEGIN_SRC bash+$ cat B.txt | setop A.txt Disj STDIN+1+3+5+6+8+#+END_SRC`++** Notes++- the resulting set is *sorted in ascending order*+- some set operations are *not commutative* (e.g. A Diff B /= B Diff A)+- Setop is based on [[https://github.com/pcapriotti/optparse-applicative][optparse-applicative]] and supports [[https://github.com/pcapriotti/optparse-applicative#bash-zsh-and-fish-completions][bash/fish/zsh completions]]
+ Setup.hs view
@@ -0,0 +1,4 @@+import Distribution.Simple++main :: IO()+main = defaultMain
+ bin/Main.hs view
@@ -0,0 +1,80 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE OverloadedStrings #-}++module Main where++import           Protolude++import qualified Setop               as S+import qualified Data.Text           as T+import qualified Data.Text.IO        as TIO+import qualified Options.Applicative as OPT++data Options = Options+  { left  :: FilePath+  , setop :: Text+  , right :: FilePath }++stdinKey :: FilePath+stdinKey = "STDIN" -- special file path for input++progDesc :: Text+progDesc = "Perform a set operation on two files."++inputHelp :: Text+inputHelp = "either a file or the 'STDIN' literal."++setopHelp :: Text+setopHelp = "U / Union, D / Diff, J / Disj, I / Inter."++options :: OPT.Parser Options+options = Options+  <$> OPT.strArgument (OPT.metavar "LEFT" <> OPT.help (toS inputHelp))+  <*> OPT.strArgument (OPT.metavar "SETOP" <> OPT.help (toS setopHelp))+  <*> OPT.strArgument (OPT.metavar "RIGHT" <> OPT.help (toS inputHelp))++parser :: OPT.ParserInfo Options+parser = OPT.info+  (options <**> OPT.helper)+  (OPT.fullDesc <> OPT.progDesc (toS progDesc))++main :: IO ()+main = do+  Options{..} <- OPT.execParser parser+  when (left == stdinKey && right == stdinKey)+    (throwIO $ AssertionFailed "LEFT and RIGHT arguments cannot both be STDIN")+  operation <- case toSetOp setop of+    Left err -> throwIO err+    Right op -> return op+  lContent <- contentOf left+  rContent <- contentOf right+  let lSet = fromText lContent+      rSet = fromText rContent+      oSet = operation lSet rSet+  TIO.putStr $ toText oSet++-- |Convert set to text+toText :: Set Text -> Text+toText = T.unlines . S.toList++-- |Convert text to set op+toSetOp :: Text -> Either PatternMatchFail (Set Text -> Set Text -> Set Text)+toSetOp "U"     = return S.union+toSetOp "Union" = return S.union+toSetOp "D"     = return S.difference+toSetOp "Diff"  = return S.difference+toSetOp "J"     = return S.disjunction+toSetOp "Disj"  = return S.disjunction+toSetOp "I"     = return S.intersection+toSetOp "Inter" = return S.intersection+toSetOp setop   = Left $ PatternMatchFail ("invalid set operation: "  ++ toS setop)++-- |Convert lines to a set+fromText :: Text -> Set Text+fromText = S.fromList . filter (not . T.null) . T.lines++-- |Return content of file+contentOf :: FilePath -> IO Text+contentOf fp = if fp /= stdinKey+               then TIO.readFile fp -- file+               else TIO.getContents -- stdin
+ setop.cabal view
@@ -0,0 +1,86 @@+name:                  setop+version:               0.1.0.0+synopsis:              Perform set operations on files.+description:           Find more information on the project homepage.+author:                Médéric Hurier <fmind@users.noreply.github.com>+maintainer:            Médéric Hurier <fmind@users.noreply.github.com>+copyright:             (c) 2017 Médéric Hurier+homepage:              https://github.com/fmind/setop+bug-reports:           https://github.com/fmind/setop/issues+license:               MIT+license-file:          LICENSE.txt+extra-source-files:    README.org+category:              Tools+tested-with:           GHC+build-type:            Simple+cabal-version:         >=1.10++source-repository head+  type:                git+  location:            https://github.com/fmind/setop++library+  hs-source-dirs:      src+  default-language:    Haskell2010+  default-extensions:  OverloadedStrings, NoImplicitPrelude+  ghc-options:         -Wall+  exposed-modules:     Setop+  build-depends:       base+                     , protolude+                     , containers++executable setop+  hs-source-dirs:      bin+  main-is:             Main.hs+  default-language:    Haskell2010+  default-extensions:  OverloadedStrings, NoImplicitPrelude+  ghc-options:         -Wall -threaded -rtsopts -with-rtsopts=-N+  build-depends:       base >= 4 && < 5+                     , text+                     , protolude+                     , optparse-applicative+                     , setop++test-suite hspec+  hs-source-dirs:      test+  main-is:             hspec.hs+  default-language:    Haskell2010+  default-extensions:  OverloadedStrings, NoImplicitPrelude, ScopedTypeVariables+  type:                exitcode-stdio-1.0+  ghc-options:         -Wall -threaded -rtsopts -with-rtsopts=-N+  other-modules:       SetopSpec+  build-depends:       base+                     , protolude+                     , containers+                     , hspec+                     , setop++test-suite hlint+  hs-source-dirs:      test+  main-is:             hlint.hs+  default-language:    Haskell2010+  type:                exitcode-stdio-1.0+  ghc-options:         -Wall -threaded -rtsopts -with-rtsopts=-N+  build-depends:       base+                     , hlint++test-suite doctest+  hs-source-dirs:      test+  main-is:             doctest.hs+  default-language:    Haskell2010+  type:                exitcode-stdio-1.0+  ghc-options:         -Wall -threaded -rtsopts -with-rtsopts=-N+  build-depends:       base+                     , doctest++-- benchmark criterion+--   hs-source-dirs:      bench+--   main-is:             bench.hs+--   default-language:    Haskell2010+--   default-extensions:  OverloadedStrings+--   type:                exitcode-stdio-1.0+--   ghc-options:         -Wall -threaded -rtsopts -with-rtsopts=-N+--   other-modules:       SetopBench+--   build-depends:       base+--                      , criterion+--                      , setop
+ src/Setop.hs view
@@ -0,0 +1,50 @@+-- |Implementation of Set Operations.++module Setop where++import Protolude+import qualified Data.Set as S++-- |Set construction.+fromList :: Ord a => [a] -> Set a+fromList = S.fromList++-- |Set export to list.+toList :: Set a -> [a]+toList = S.toAscList++-- |Set Union on two sets.+--+-- >>> fromList [0, 1, 2] `union` fromList [0, 2, 4]+-- fromList [0,1,2,4]+-- >>> fromList [0, 2, 4] `union` fromList [0, 1, 2]+-- fromList [0,1,2,4]+union :: Ord a => Set a -> Set a -> Set a+union = S.union++-- |Set Difference on two sets.+--+-- >>> fromList [0, 1, 2] `difference` fromList [0, 2, 4]+-- fromList [1]+-- >>> fromList [0, 2, 4] `difference` fromList [0, 1, 2]+-- fromList [4]+difference :: Ord a => Set a -> Set a -> Set a+difference = S.difference++-- |Set Disjunction on two sets.+--+-- >>> fromList [0, 1, 2] `disjunction` fromList [0, 2, 4]+-- fromList [1,4]+-- >>> fromList [0, 2, 4] `disjunction` fromList [0, 1, 2]+-- fromList [1,4]+disjunction :: Ord a => Set a -> Set a -> Set a+disjunction a b = (a `difference` b) `union` (b `difference` a)++-- |Set Intersection on two sets.+--+-- >>> fromList [0, 1, 2] `intersection` fromList [0, 2, 4]+-- fromList [0,2]+-- >>> fromList [0, 2, 4] `intersection` fromList [0, 1, 2]+-- fromList [0,2]+intersection :: Ord a => Set a -> Set a -> Set a+intersection = S.intersection
+ test/SetopSpec.hs view
@@ -0,0 +1,138 @@+module SetopSpec (spec) where++import Setop+import Protolude++import Test.Hspec+import Test.Hspec.QuickCheck++import qualified Data.Set as S++spec :: Spec+spec = do+    describe "union" $ do+        it "on two given sets" $ do+          let a = fromList [1, 3, 5, 7] :: Set Int+              b = fromList [0, 3, 5, 9] :: Set Int+              s = fromList [0, 1, 3, 5, 7, 9] :: Set Int+          (a `union` b) == s && (b `union` a) == s++        prop "property: A ∪ ∅ = A" $+          \ (a::Set Int) ->+            a `union` S.empty == a++        prop "property: A ∪ A = A" $+          \ (a::Set Int) ->+            a `union` a == a++        prop "property: A ⊆ (A ∪ B)" $+          \ (a::Set Int) (b::Set Int) ->+            a `S.isSubsetOf` (a `union` b)++        prop "property: A ∪ B = B ∪ A" $+          \ (a::Set Int) (b::Set Int) ->+            a `union` b == b `union` a++        prop "property: A ∪ (B ∪ C) = (A ∪ B) ∪ C" $+          \ (a::Set Int) (b::Set Int) (c::Set Int) ->+            a `union` (b `union` c) == (a `union` b) `union` c++        prop "property: A ⊆ B if and only if A ∪ B = B" $+          \ (a::Set Int) (b::Set Int) ->+            if a `union` b == b+            then a `S.isSubsetOf` b+            else not (a `S.isSubsetOf` b)++    describe "difference" $ do+        it "on two given sets" $ do+          let a = fromList [1, 3, 5, 7] :: Set Int+              b = fromList [0, 3, 5, 9] :: Set Int+              sab = fromList [1, 7] :: Set Int+              sba = fromList [0, 9] :: Set Int+          (a `difference` b) == sab && (b `difference` a) == sba++        prop "property: ∅ - A = ∅" $+          \ (a::Set Int) ->+            S.empty `difference` a == S.empty++        prop "property: A - ∅ = A" $+          \ (a::Set Int) ->+            a `difference` S.empty == a++        prop "property: A - A = ∅" $+          \ (a::Set Int) ->+            a `difference` a == S.empty++        prop "property: A - B ≠ B - A for A ≠ B" $+          \ (a::Set Int) (b::Set Int) ->+            if a /= b+            then a `difference` b /= b `difference` a+            else a `difference` a == S.empty++        prop "property: if A ⊆ B then A - B = ∅" $+          \ (a::Set Int) (b::Set Int) ->+            if a `S.isSubsetOf` b+            then a `difference` b == S.empty+            else a `difference` b /= S.empty++    describe "disjunction" $ do+        it "on two given sets" $ do+          let a = fromList [1, 3, 5, 7] :: Set Int+              b = fromList [0, 3, 5, 9] :: Set Int+              s = fromList [0, 1, 7, 9] :: Set Int+          (a `disjunction` b) == s && (b `disjunction` a) == s++        prop "property: A △ ∅ = A" $+          \ (a::Set Int) ->+            a `disjunction` S.empty == a++        prop "property: A △ A = ∅" $+          \ (a::Set Int) ->+            a `disjunction` a == S.empty++        prop "property: A △ B = B △ A" $+          \ (a::Set Int) (b::Set Int) ->+            a `disjunction` b == b `disjunction` a++        prop "property: A △ (B △ C) = (A △ B) △ C" $+          \ (a::Set Int) (b::Set Int) (c::Set Int)->+            a `disjunction` (b `disjunction` c) == (a `disjunction` b) `disjunction` c++        prop "property: if A ⊆ B then A △ B = B - a" $+          \ (a::Set Int) (b::Set Int) ->+            if a `S.isSubsetOf` b+            then a `disjunction` b == b `difference` a+            else a `disjunction` b /= b `difference` a++    describe "intersection" $ do+        it "on two given sets" $ do+          let a = fromList [1, 3, 5, 7] :: Set Int+              b = fromList [0, 3, 5, 9] :: Set Int+              s = fromList [3, 5] :: Set Int+          (a `intersection` b) == s && (b `intersection` a) == s++        prop "property: A ∩ ∅ = ∅" $+          \ (a::Set Int) ->+            a `intersection` S.empty == S.empty++        prop "property: A ∩ A = A" $+          \ (a::Set Int) ->+            a `intersection` a == a++        prop "property: A ∩ B ⊆ A" $+          \ (a::Set Int) (b::Set Int) ->+            (a `intersection` b) `S.isSubsetOf` a++        prop "property: A ∩ B = B ∩ A" $+          \ (a::Set Int) (b::Set Int) ->+            a `intersection` b == b `intersection` a++        prop "property: A ∩ (B ∩ C) = (A ∩ B) ∩ C" $+          \ (a::Set Int) (b::Set Int) (c::Set Int) ->+            a `intersection` (b `intersection` c) == (a `intersection` b) `intersection` c++        prop "property: A ⊆ B if and only if A ∩ B = A" $+          \ (a::Set Int) (b::Set Int) ->+            if a `intersection` b == a+            then a `S.isSubsetOf` b+            else not (a `S.isSubsetOf` b)
+ test/doctest.hs view
@@ -0,0 +1,6 @@+module Main where++import Test.DocTest (doctest)++main :: IO ()+main = doctest ["bin", "src"]
+ test/hlint.hs view
@@ -0,0 +1,10 @@+module Main where++import Language.Haskell.HLint (hlint)+import System.Exit (exitFailure, exitSuccess)++main :: IO ()+main = do+  hints <- hlint ["bin", "src"]+  if null hints then exitSuccess+                else exitFailure
+ test/hspec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}