Showing posts with label haskell. Show all posts
Showing posts with label haskell. Show all posts

Saturday, 26 September 2009

Haskell: Parsing SVN log output

Problem:
I decided to solve a problem not done properly previously at work. We use lotus notes to track of bugs in the system. Developers would jot down what files were changed for that bug. In our SVN, we would write the bug number when we commit. The problem was everybody had their own idea on how they want to write the commit description. The previous build engineer tried to google for an existing solution instead of building one. The tool was useless and this was a problem for whoever was tasked to perform deployment.

Over one weekend, I decided to solve this problem. Here's the code to parse the output from svn log.
module SVN (parse, parseFile, Log(MkLog, revision, author, comment, changes), FileChange(Add, Delete, Modify, Unknown)) where

import String (split, isBlankLine, trim)
import List (partition)
import Maybe (catMaybes, Maybe(Just, Nothing))

data FileChange = Add String | Delete String | Modify String | Unknown String
    deriving (Show)

data Log = MkLog {
    revision :: String,
    author :: String,
    comment :: String,
    changes :: [FileChange]
    } deriving (Show)

parseFile :: FilePath -> IO [Log]
parseFile path = do
  content <- readFile path
  return (parse content)

parse :: String -> [Log]
parse xs = parseLogs (lines xs) [] 

parseLogs :: [String] -> [Maybe Log] -> [Log] 
parseLogs [] logs = catMaybes logs
parseLogs xs logs = parseLogs rest ((parseLog log) : logs)
  where (log, (_:rest)) = splitBy isSeperatorLine xs

parseLog :: [String] -> Maybe Log
parseLog []       = Nothing
parseLog xs
  | len > 4   = Just (MkLog revision author (trim (unlines comment)) (parseFileChanges filechanges))
  | otherwise = Nothing
  where len                              = length xs
        ((x:_:filechanges), (_:comment)) = splitBy isBlankLine xs
        (revision:author:_)              = map trim (split '|' x)

parseFileChanges :: [String] -> [FileChange]
parseFileChanges [] = []
parseFileChanges xs = map parseFileChange xs

parseFileChange :: String -> FileChange
parseFileChange [] = Unknown []
parseFileChange x
  | t == "A"  = Add path
  | t == "D"  = Delete path
  | t == "M"  = Modify path
  | otherwise = Unknown x
  where (t:ts) = words x
        path   = head ts

isSeperatorLine :: String -> Bool
isSeperatorLine []    = False
isSeperatorLine (x:_) = '-' == x

splitBy :: (a -> Bool) -> [a] -> ([a], [a])
splitBy _    [] = ([], [])
splitBy pred xs = splitBy' pred [] xs
  where splitBy' _    xs []         = (xs, []) 
        splitBy' pred xs yys@(y:ys) = if (pred y) then (xs, yys) else splitBy' pred (xs ++ [y]) ys  

String library is my own implementation and it should be obvious what those functions do.

Thursday, 28 February 2008

Haskell: A simple database application with takusen + sqlite3

Takusen is a Haskell library to access databases. The easiest way to get takusen by darcs. If you don't have darcs installed, you can learn about it from here. If you don't want to use darcs then you would have to download the tar file and extract it. Instructions to install Takusen can be found here. If you're too lazy to read that here's how would install takusen with darcs:
$ mkdir takusen
$ cd takusen
$ darcs get http://darcs.haskell.org/takusen
$ ghc --make Setup
$ Setup configure
$ Setup build
$ Setup install

Next, we will create a sqlite3 database with a simple table tbl1:
$ mkdir sqlite_demo
$ cd sqlite_demo
$ sqlite3 demo.db
SQLite version 3.5.6
Enter ".help" for instructions
sqlite> create table tbl1 (key TEXT, value TEXT);
sqlite> insert into table tbl1 values('k1', 'v1');
sqlite> insert into table tbl1 values('k2', 'v2');
sqlite> insert into table tbl1 values('k3', 'v3');
sqlite> .quit

Create sqlite_demo.hs and enter this:
{-# OPTIONS -fglasgow-exts #-}
{-# OPTIONS -fallow-overlapping-instances #-}
module Main (main) where
import Database.Sqlite.Enumerator
import Control.Monad.Trans (liftIO)
import System.Environment

query1Iteratee :: (Monad m) => String -> String -> IterAct m [(String, String)]
query1Iteratee a b accum = result' ((a,b):accum)

main :: IO ()
main = do
flip catchDB reportRethrow $
withSession (connect "demo.db") (do
let iter (s::String) (_::String) = result s
result <- doQuery (sql "select * from tbl1") query1Iteratee []      
liftIO $ putStrLn $ show result            
)
Compile with the following command and run:
$ ghc --make sqlite_demo -o sqlite_demo
$ ./sqlite_demo
You should get this:
[("k3","v3"),("k2","v2"),("k1","v1")]

Saturday, 5 January 2008

Haskell: Read the content of a file into a list of string

module Main (main) where

import IO
import System.Environment

main :: IO ()
main = do { x <- getArgs           
; hdl <- openFile (x !! 0) ReadMode           
; y <- readSources hdl []           
; print y }  
readSources :: Handle -> [String] -> IO [String]
readSources hdl s = do { t <- hIsEOF hdl                        
; if t then return s else do { x <- hGetLine hdl; readSources hdl (s ++ [x]) } }

Friday, 31 August 2007

Haskell: It always begins with a hello world

I used to go through digg and reddit on a >5 times a day basis. On one fateful day, I came across an article about Software Transaction Management (STM). The article led me to this page by Simon Peyton Jones from Microsoft Research. Pugs was impressive but this was what started me on my journey with Haskell (Yes, it wasn't too long ago. Let's see how it goes a year from today).

Took me a while to figure what's going on in Haskell as it's really different from anything I've come across. I did play around with Lisp and Scheme before but either I haven't worked with the 2 for very long or I must have very bad memory retention. Anyway,
here's my hello world implementation.

main :: IO ()
main = putStrLn "Hello, World!"