diff --git a/Frames.cabal b/Frames.cabal index 6552f9f..ad541e3 100644 --- a/Frames.cabal +++ b/Frames.cabal @@ -212,6 +212,15 @@ executable kata04 hs-source-dirs: demo default-language: Haskell2010 +executable modcsv + if !flag(demos) + buildable: False + main-is: ModifyCSV.hs + if flag(demos) + build-depends: base, Frames, microlens, pipes + hs-source-dirs: demo + default-language: Haskell2010 + test-suite spec type: exitcode-stdio-1.0 hs-source-dirs: test diff --git a/demo/ModifyCSV.hs b/demo/ModifyCSV.hs new file mode 100644 index 0000000..8f2b2df --- /dev/null +++ b/demo/ModifyCSV.hs @@ -0,0 +1,23 @@ +{-# language DataKinds, FlexibleContexts, TemplateHaskell #-} +-- | A demonstration of ingesting a CSV file, modifying the data (in +-- this case multiplying a column by 2), then writing it back out to a +-- new CSV file. +import Frames +import Frames.CSV (pipeToCSV, consumeTextLines) +import Lens.Micro +import Pipes ((>->), Effect) +import qualified Pipes.Prelude as P + +tableTypes "Row" "data/data1.csv" + +myFun :: Row -> Row +myFun = age %~ (*2) + +myPipeline :: MonadSafe m => Effect m () +myPipeline = readTable "data/data1.csv" + >-> P.map myFun + >-> pipeToCSV + >-> consumeTextLines "data/dataMod.csv" + +main :: IO () +main = runSafeEffect myPipeline diff --git a/shell.nix b/shell.nix index 3379ab3..2973be2 100644 --- a/shell.nix +++ b/shell.nix @@ -1,16 +1,19 @@ -{ compiler ? "ghc863" +{ compiler ? "ghc865" , withHoogle ? true }: let pkgs = import {}; + all-hies = import (fetchTarball "https://github.com/infinisil/all-hies/tarball/master") {}; + hie = all-hies.selection { selector = p: { inherit (p) ghc865; };}; f = import ./default.nix; packageSet = pkgs.haskell.packages.${compiler}; hspkgs = ( if withHoogle then packageSet.override { overrides = (self: super: { - ghc = super.ghc // { withPackages = f: super.ghc.withHoogle (ps: f ps ++ [ ps.cabal-install ]); }; + ghc = super.ghc // { withPackages = f: super.ghc.withHoogle (ps: f ps ++ [ ]); }; + # Cabal = super.Cabal_2_4_1_0; vinyl = pkgs.haskell.lib.dontBenchmark (super.callPackage ~/Projects/Vinyl {}); # pipes-safe = super.callCabal2nix "pipes-safe" (pkgs.fetchFromGitHub { # owner = "Gabriel439"; @@ -19,6 +22,12 @@ packageSet = pkgs.haskell.packages.${compiler}; # sha256 = "0i2l36xwqskhm5kcdvmyfy2n7cf2i2qdsrap5nib825gq2mnv9z5"; # }) {}; # Chart = super.callHackage "Chart" "1.9" {}; + cabal-install-2_4 = super.callHackage "cabal-install" "2.4.1.0" { + Cabal = super.Cabal_2_4_1_0; + }; + hackage-security = pkgs.haskell.lib.dontCheck (super.callHackage "hackage-security" "0.5.3.0" { + Cabal = super.Cabal_2_4_1_0; + }); SVGFonts = pkgs.haskell.lib.doJailbreak (super.callHackage "SVGFonts" "1.6.0.3" {}); # intero = pkgs.haskell.lib.dontCheck (super.callPackage ~/src/intero {}); Chart = super.callCabal2nix "Chart" (pkgs.fetchFromGitHub { @@ -40,5 +49,18 @@ packageSet = pkgs.haskell.packages.${compiler}; else packageSet ); drv = hspkgs.callPackage f {}; + ghc = hspkgs.ghc.withHoogle (_: drv.passthru.getBuildInputs.haskellBuildInputs); in - if pkgs.lib.inNixShell then drv.env else drv +# if pkgs.lib.inNixShell then drv.env else drv +pkgs.mkShell { + buildInputs = [ hie # hspkgs.cabal-install + hspkgs.cabal-install-2_4 + ghc ]; + shellHook = '' + export NIX_GHC='${ghc}/bin/ghc' + export NIX_GHCPKG='${ghc}/bin/ghc-pkg' + export NIX_GHC_DOCDIR='${drv.compiler.doc}/share/doc/ghc/html' + export NIX_GHC_LIBDIR='${ghc}/lib/${drv.compiler.name}' + export HIE_HOOGLE_DATABASE='${ghc}/share/doc/hoogle/default.hoo' + ''; +} diff --git a/src/Frames/CSV.hs b/src/Frames/CSV.hs index 88758fc..9cfac7e 100644 --- a/src/Frames/CSV.hs +++ b/src/Frames/CSV.hs @@ -364,12 +364,12 @@ produceCSV recs = do -- streaming input that you wish to use to produce streaming output. pipeToCSV :: forall ts m. (Monad m, ColumnHeaders ts, RecordToList ts, - RecMapMethod Show ElField ts) + RecMapMethod ShowCSV ElField ts) => P.Pipe (Record ts) T.Text m () pipeToCSV = P.yield (T.intercalate "," (map T.pack header)) >> go where header = columnHeaders (Proxy :: Proxy (Record ts)) go :: P.Pipe (Record ts) T.Text m () - go = P.map (T.intercalate "," . map T.pack . showFields) + go = P.map (T.intercalate "," . showFieldsCSV) -- | Write a header row with column names followed by a line of text -- for each 'Record' to the given file. diff --git a/src/Frames/Frame.hs b/src/Frames/Frame.hs index a9163f2..12509a2 100644 --- a/src/Frames/Frame.hs +++ b/src/Frames/Frame.hs @@ -29,7 +29,7 @@ boxedFrame xs = Frame (V.length v) (v V.!) instance Eq r => Eq (Frame r) where Frame l1 r1 == Frame l2 r2 = - l1 == l2 && and (map (\i -> r1 i == r2 i) [0 .. l1 - 1]) + l1 == l2 && all (\i -> r1 i == r2 i) [0 .. l1 - 1] -- | The 'Monoid' instance for 'Frame' provides a mechanism for -- vertical concatenation of 'Frame's. That is, @f1 <> f2@ will return diff --git a/src/Frames/ShowCSV.hs b/src/Frames/ShowCSV.hs index 209ed7d..7b01d5a 100644 --- a/src/Frames/ShowCSV.hs +++ b/src/Frames/ShowCSV.hs @@ -14,3 +14,4 @@ instance ShowCSV Bool where instance ShowCSV Int where instance ShowCSV Double where instance ShowCSV Text where + showCSV = id diff --git a/test/Spec.hs b/test/Spec.hs index 18f3d43..f042409 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -132,10 +132,11 @@ main = do -- The test data isn't formatted quite how we'd do it: text -- fields aren't quoted, and salaries represented as Doubles -- do not have decimal points. - let csvInput' = T.replace "Joe" "\"Joe\"" - . T.replace "Sarah" "\"Sarah\"" - . T.replace "\"80,000\"" "80000.0" - $ T.pack csvInput + -- let csvInput' = T.replace "Joe" "\"Joe\"" + -- . T.replace "Sarah" "\"Sarah\"" + -- . T.replace "\"80,000\"" "80000.0" + -- $ T.pack csvInput + let csvInput' = T.replace "\"80,000\"" "80000.0" (T.pack csvInput) it "Produces expected output" $ T.unlines (map T.pack csvOutput) `shouldBe` csvInput' it "Produces parseable output" $