diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,23 @@
+Copyright (C) 2008 Samy Al Bahra
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+ 1. Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+    notice, this list of conditions and the following disclaimer in the
+    documentation and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ SUCH DAMAGE.
diff --git a/Porte/Ports.hs b/Porte/Ports.hs
new file mode 100644
--- /dev/null
+++ b/Porte/Ports.hs
@@ -0,0 +1,144 @@
+module Porte.Ports where
+import Control.Exception
+import Control.Monad
+import Data.ByteString.Search.KnuthMorrisPratt
+import qualified Data.ByteString.Lazy.Char8     as B
+import qualified Data.ByteString.Char8          as C 
+import qualified Data.List                      as L
+import qualified System.IO                      as I
+import qualified System.Environment             as E
+
+portsIndex = "/usr/ports/INDEX-8"
+portsPrefix = "/usr/ports/"
+
+type Name = B.ByteString
+type DistributionName = B.ByteString 
+type Path = B.ByteString
+type Prefix = B.ByteString
+type Comment = B.ByteString
+type DescriptionPath = B.ByteString
+type PortDescription = Maybe B.ByteString
+type Maintainer = B.ByteString
+type Categories = [B.ByteString]
+type ExtractDepends = [B.ByteString]
+type PatchDepends = [B.ByteString]
+type FetchDepends = [B.ByteString]
+type BuildDepends = [B.ByteString]
+type RunDepends = [B.ByteString]
+type Website = B.ByteString
+
+data Port = Port {
+              name :: Name,
+              distributionName :: DistributionName,
+              path :: Path,
+              comment :: Comment,
+              descriptionPath :: DescriptionPath,
+              maintainer :: Maintainer,
+              categories :: Categories,
+              buildDepends :: BuildDepends,
+              website :: Website,
+              extractDepends :: ExtractDepends
+            } deriving (Eq, Show, Read)
+
+type PortField = Port -> B.ByteString
+type PortQuery = [(PortField, String)]
+
+toField :: String -> Maybe PortField
+toField "name" = Just name
+toField "package" = Just distributionName
+toField "distributionName" = Just distributionName
+toField "path" = Just path
+toField "comment" = Just comment
+toField "category" = Just category
+toField "maintainer" = Just maintainer
+toField "website" = Just website
+toField _ = Nothing
+
+search :: PortQuery -> [Port] -> [Port]
+search (x:xs) p =
+  search xs $ filter f p
+  where
+  f p = case (matchSL (C.pack $ snd x) (fst x p)) of
+          [] -> False
+          _  -> True 
+search _ p = p
+
+find :: PortQuery -> [Port] -> [Port]
+find (x:xs) p = find xs $ filter f p
+                where
+                f p = (B.pack $ snd x) == (fst x p)
+find _ p = p
+
+description :: Port -> IO (Maybe B.ByteString)
+description p =
+  do
+  o <- try $ (B.readFile . B.unpack . descriptionPath) p
+  case (o) of
+    Left h  -> return Nothing 
+    Right h -> return $ Just $ B.filter (/= '\n') h
+
+glue :: (Port -> [B.ByteString]) -> Port -> B.ByteString
+glue f p = B.concat $ L.intersperse (B.pack " ") (f p) 
+category = glue categories 
+buildDepend = glue buildDepends
+extractDepend = glue extractDepends
+
+parseEntry :: B.ByteString -> Port
+parseEntry =
+  parseEntry' . B.split '|' 
+  where
+  parseEntry' 
+     (name           :
+      path           :
+      prefix         : 
+      comment        :
+      pkgDescr       :
+      maintainer     :
+      categories     :
+      buildDepends   :
+      runDepends     :
+      website        :
+      extractDepends : _) =
+        Port (B.tail . B.dropWhile (/= '/') $ path')
+             (name)
+             (path')
+             (comment)
+             (pkgDescr)
+             (maintainer)
+             (B.words categories)
+             (B.words buildDepends)
+             (website)
+             (B.words extractDepends)
+             where
+             path' = B.drop (B.length $ B.pack portsPrefix) $ path
+
+parseIndex :: B.ByteString -> [Port]
+parseIndex = map parseEntry . B.lines
+
+index :: (Maybe String) -> IO [Port]
+index (Just []) = index (Just portsIndex)
+index Nothing = do
+ o <- try $ E.getEnv "INDEX"
+ case o of
+   Right h -> index $ Just h
+   _       -> index $ Just portsIndex
+index (Just path) = do
+  o <- try $ B.readFile path
+  case o of
+    Left h -> error $ "INDEX file (" ++ path ++ ") could not be opened"
+    Right h -> return $ parseIndex h 
+
+putPorts :: [PortField] -> [Port] -> IO ()
+putPorts f = hPutPorts I.stdout f
+
+hPutPorts :: I.Handle -> [PortField] -> [Port] -> IO ()
+hPutPorts h f p = forM_ p (hPutPort h f)
+
+putPort :: [PortField] -> Port -> IO ()
+putPort f = hPutPort I.stdout f
+
+hPutPort :: I.Handle -> [PortField] -> Port -> IO ()
+hPutPort h [f] p = B.hPut h (f p) >> I.hPutChar h '\n' 
+hPutPort h (f:fs) p = B.hPut h (f p) >> I.hPutStr h ", " >> hPutPort h fs p  
+hPutPort h _ p = I.hPutChar h '\n' 
+
diff --git a/Porte/Statistics.hs b/Porte/Statistics.hs
new file mode 100644
--- /dev/null
+++ b/Porte/Statistics.hs
@@ -0,0 +1,19 @@
+module Porte.Statistics where
+import qualified Data.ByteString.Lazy.Char8 as B
+import qualified Porte.Ports                as P
+import qualified Data.Map                   as M 
+import qualified System.IO                  as I
+import Text.Printf(printf)
+
+type FrequencyMap = M.Map B.ByteString Int
+type FieldFrequency = (B.ByteString, Int) 
+
+mapFrequency :: [B.ByteString] -> FrequencyMap 
+mapFrequency (x:xs) = M.insertWith' (+) x 1 $ mapFrequency xs
+mapFrequency _ = M.empty
+
+fieldsFrequency f r = M.assocs $ mapFrequency (concat $ map f r)
+fieldFrequency f r = M.assocs $ mapFrequency (map f r)
+
+printFrequency :: FieldFrequency -> IO ()
+printFrequency (a, b) = I.putStr (printf "%5d " b) >> B.putStrLn a 
diff --git a/Porte/Tool.hs b/Porte/Tool.hs
new file mode 100644
--- /dev/null
+++ b/Porte/Tool.hs
@@ -0,0 +1,84 @@
+module Porte.Tool(toolMain) where
+import System.Environment
+import System.Exit
+import Control.Monad
+import qualified Data.ByteString.Lazy.Char8 as B
+import qualified Porte.Ports      as P
+import qualified Porte.Statistics as S
+import qualified System.IO        as I
+
+version = "0.0.2"
+
+-- Generic port print format
+printFormat = [P.path, P.comment, P.website]
+
+-- Exception raising toField
+toField :: String -> P.PortField
+toField f =
+  case (P.toField f) of
+    Nothing -> error $ "Unknown field: " ++ f
+    Just f  -> f
+
+-- Statistics mode
+doStatistics :: [String] -> IO ()
+doStatistics (a:m:_) =
+  case a of
+    "frequency" ->  case m of
+                      "categories"      -> doOutput $ S.fieldsFrequency P.categories 
+                      "buildDepends"    -> doOutput $ S.fieldsFrequency P.buildDepends
+                      "extractDepends"  -> doOutput $ S.fieldsFrequency P.extractDepends
+                      _                 -> doOutput $ S.fieldFrequency (toField m) 
+    _           -> error $ "Unknown statistics mode"
+   where
+   doOutput f = P.index Nothing >>= flip forM_ S.printFrequency . f 
+doStatistics _ = toolUsage >> die
+
+-- List all available ports in INDEX
+doIndex :: IO ()
+doIndex =
+  P.index Nothing >>= P.putPorts printFormat 
+
+-- Build port query from user input
+parseSearch :: [String] -> P.PortQuery
+parseSearch q =
+  build [] q
+  where
+  build p (f:q:qs) = build ((toField f, q) : p) qs 
+  build p (_:[]) = error "Parse error"
+  build p _ = p 
+
+-- Generic search action
+genericSearch :: (P.PortQuery -> [P.Port] -> [P.Port]) -> [String] -> IO ()
+genericSearch f [] = exit
+genericSearch f q = do
+  ports <- P.index Nothing
+  P.putPorts printFormat $ (f . parseSearch) q ports
+
+doSearch q = genericSearch P.search q 
+doFind q = genericSearch P.find q 
+
+die = exitWith (ExitFailure 1)
+exit = exitWith (ExitSuccess)
+
+hToolUsage :: I.Handle -> IO ()
+hToolUsage h = I.hPutStrLn h "Usage: porte [-hvlsfS]" 
+
+toolUsage = hToolUsage I.stderr
+
+printVersion :: IO ()
+printVersion = I.putStrLn $ "Porte.Tool " ++ version
+
+toolMain = do
+  argv <- getArgs
+  case (null argv) of 
+    True -> toolUsage >> die
+    _    -> case (head argv) of
+              "-h" -> hToolUsage I.stdout
+              "-v" -> printVersion
+              "-l" -> doIndex
+              "-s" -> doSearch $ tail argv
+              "-f" -> doFind $ tail argv
+              "-S" -> doStatistics $ tail argv
+              _    -> toolUsage >> die 
+
+ 
diff --git a/Porte/porte.hs b/Porte/porte.hs
new file mode 100644
--- /dev/null
+++ b/Porte/porte.hs
@@ -0,0 +1,3 @@
+import Porte.Tool 
+
+main = toolMain 
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/porte.cabal b/porte.cabal
new file mode 100644
--- /dev/null
+++ b/porte.cabal
@@ -0,0 +1,18 @@
+Name:                porte
+Version:             0.0.2
+Cabal-Version:       >= 1.2
+Synopsis:            FreeBSD ports interface
+Description:         Haskell FreeBSD ports analysis library
+Category:            System
+License:             BSD3
+License-file:        LICENSE
+Author:              Samy Al Bahra
+Maintainer:          sbahra@kerneled.org
+Build-Type:          Simple
+
+Library
+  Build-Depends:       base, bytestring, containers, stringsearch
+  Exposed-Modules:     Porte.Tool Porte.Statistics Porte.Ports
+
+Executable porte
+  Main-is:             Porte/porte.hs
