diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,3 @@
+#!/usr/bin/env runhaskell
+import Distribution.Simple
+main = defaultMain
diff --git a/csv-add-id.hs b/csv-add-id.hs
new file mode 100644
--- /dev/null
+++ b/csv-add-id.hs
@@ -0,0 +1,22 @@
+import System.Environment
+import Data.Char
+import Utils
+
+addIdLn :: Int -> [String] -> [String]
+addIdLn 0 = ("ID":)
+addIdLn i = (show i:)
+
+addId :: Int -> FilePath -> IO ()
+addId i = interactTable $ zipWith addIdLn [i..]
+
+main :: IO ()
+main = do
+    args <- getArgs
+    let (args2, start) =
+          case args of
+            "--help":_ -> err
+            "--start":i:xs
+              | all isDigit i -> (xs, read i)
+            _                 -> (args, 0)
+    addId start $ getInput err args2
+  where err = error "Usage: csv-add-id [--start <int>] [<file>|-]"
diff --git a/csv-nptools.cabal b/csv-nptools.cabal
new file mode 100644
--- /dev/null
+++ b/csv-nptools.cabal
@@ -0,0 +1,47 @@
+Name:           csv-nptools
+Cabal-Version:  >=1.4
+Version:        0.2.0
+License:        GPL
+Copyright:      (c) Nicolas Pouillard
+Author:         Nicolas Pouillard
+Maintainer:     Nicolas Pouillard <nicolas.pouillard@gmail.com>
+Category:       Development, Text, Utils
+Synopsis:       A collection of CSV tools
+Description:    A collection of CSV tools
+Stability:      Experimental
+Build-Type:     Simple
+
+--executable csv2html
+--    main-is: csv2html.hs
+--    Build-depends: base>=3&&<5, html, tabular, csv
+--    ghc-options: -Wall -Odph
+
+executable csv2json
+    main-is: csv2json.hs
+    Build-depends: base>=3&&<5, txt-sushi, json
+    ghc-options: -Wall -Odph
+
+executable csv-add-id
+    main-is: csv-add-id.hs
+    Build-depends: base>=3&&<5, txt-sushi
+    ghc-options: -Wall -Odph
+
+executable csv-reformat
+    main-is: csv-reformat.hs
+    Build-depends: base>=3&&<5, txt-sushi
+    ghc-options: -Wall -Odph
+
+executable csv-tr
+    main-is: csv-tr.hs
+    Build-depends: base>=3&&<5, txt-sushi
+    ghc-options: -Wall -Odph
+
+executable csv-transpose
+    main-is: csv-transpose.hs
+    Build-depends: base>=3&&<5, txt-sushi
+    ghc-options: -Wall -Odph
+
+executable csv-pad
+    main-is: csv-pad.hs
+    Build-depends: base>=3&&<5, txt-sushi
+    ghc-options: -Wall -Odph
diff --git a/csv-pad.hs b/csv-pad.hs
new file mode 100644
--- /dev/null
+++ b/csv-pad.hs
@@ -0,0 +1,6 @@
+import System.Environment (getArgs)
+import Utils (interactTable,padTbl,getInput)
+
+main :: IO ()
+main = interactTable (padTbl "") . getInput err =<< getArgs
+  where err = error "Usage: csv-pad [<file>|-]"
diff --git a/csv-reformat.hs b/csv-reformat.hs
new file mode 100644
--- /dev/null
+++ b/csv-reformat.hs
@@ -0,0 +1,34 @@
+import Control.Arrow
+import System.Environment
+import System.Console.GetOpt
+import Database.TxtSushi.FlatFile
+import Utils
+
+reformat :: (String, String) -> FilePath -> IO ()
+reformat (inDelim, outDelim)
+   = (putStr . formatTable outFormat . parseTable inFormat =<<)
+   . getContentsFromFileOrStdin
+  where inFormat        = mkFormat inDelim
+        outFormat       = mkFormat outDelim
+        mkFormat delim  = Format "\"" delim ["\n","\r","\r\n","\n\r"]
+
+type Settings = (String, String)
+type Flag = Settings -> Settings
+
+options :: [OptDescr Flag]
+options =
+  [ Option "d"  [] (ReqArg (first   . const . evalStr) "STRING") "Input delim (default ',')"
+  , Option "D"  [] (ReqArg (second  . const . evalStr) "STRING") "Output delim (default 0xfe)"
+  ]
+
+usage :: String -> a
+usage msg = error $ unlines [msg, usageInfo header options]
+  where header = "Usage: csv-reformat [-d <input-delim>] [-D <output-delim>] [<file-or-dash>]"
+
+main :: IO ()
+main = do args <- getArgs
+          let (flags, nonopts, errs) = getOpt Permute options args
+              opts = foldr ($) (",", "\254") flags
+              err  = usage (concat errs)
+          if null errs then reformat opts (getInput err nonopts)
+                       else err
diff --git a/csv-tr.hs b/csv-tr.hs
new file mode 100644
--- /dev/null
+++ b/csv-tr.hs
@@ -0,0 +1,30 @@
+import System.Environment
+import Utils
+
+csvTrCell :: String -> String -> (String -> String)
+csvTrCell src dst = foldr ((.).map) id $ zipWith' err substChar src dst
+  where err = error "Source and destination should have the same length"
+
+csvTr :: String -> String -> FilePath -> IO ()
+csvTr src dst = interactTable (map . map $ csvTrCell (onStr src) (onStr dst))
+  where onStr = expandRanges . evalStr
+
+main :: IO ()
+main = do
+    args <- getArgs
+    case args of
+      src : dst : xs -> csvTr src dst (getInput err xs)
+      _              -> err
+
+  where err = error $ unlines  ["Usage: csv-tr <src> <dst> [<file>|-]"
+                               ,"Examples: csv-tr ',\\n\\t' '...'"
+                               ,"          csv-tr a-z A-Z"
+                               ,"Characters:"
+                               ,"\\a    <alert character>"
+                               ,"\\b    <backspace>"
+                               ,"\\f    <form-feed>"
+                               ,"\\n    <newline>"
+                               ,"\\r    <carriage return>"
+                               ,"\\t    <tab>"
+                               ,"\\v    <vertical tab>"
+                               ]
diff --git a/csv-transpose.hs b/csv-transpose.hs
new file mode 100644
--- /dev/null
+++ b/csv-transpose.hs
@@ -0,0 +1,8 @@
+import Data.List (transpose)
+import System.Environment (getArgs)
+import Utils (interactTable,padTbl,getInput)
+
+main :: IO ()
+main = interactTable (transpose . padTbl "") . getInput err =<< getArgs
+  where err = error "Usage: csv-transpose [<file>|-]"
+
diff --git a/csv2json.hs b/csv2json.hs
new file mode 100644
--- /dev/null
+++ b/csv2json.hs
@@ -0,0 +1,26 @@
+import Data.Functor
+import Text.JSON
+import Database.TxtSushi.FlatFile
+import System.Environment
+import Utils
+
+{-
+  Encoding note:
+    Nothing fancy is done about encoding, on both ends.
+    So it basically works for UTF-8 which does not mess
+    up with the delimitors of both CSV and JSON.
+ -}
+
+csv2json :: FilePath -> IO JSValue
+csv2json =
+  fmap (JSArray . map (JSArray . map (JSString . toJSString))
+                . parseTable csvFormat)
+     . getContentsFromFileOrStdin
+
+main :: IO ()
+main = putStrLn . encode . mayJSArray
+   =<< mapM csv2json . addDefaultInput
+   =<< getArgs
+
+  where mayJSArray [x] = x
+        mayJSArray xs  = JSArray xs
