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.

Clojure: Getting started with Clojure + Oracle

I had a hard time googling on how to connect clojure to an oracle database using clojure's sql library (i.e. clojure.contrib.sql). In the end I had to look into the code to see how they were contructing the jdbc url. At work, we already have a properties file which contains the driver class and url to connect to oracle via jdbc. The content looks something like this:

DBURL=jdbc\:oracle\:thin\:@localhost\:1655\:ORCL
DRIVER=oracle.jdbc.OracleDriver
DBUSER=john
DBPASSWORD=password

To translate that to a database spec to be used by clojure.contrib.sql, I had this function:

(defn get-db-spec
  [properties]
  (let [classname (.getProperty properties "DRIVER")
        url (.getProperty properties "DBURL")
        user (.getProperty properties "DBUSER")
        password (.getProperty properties "DBPASSWORD")
        groups (next(first (re-seq #"jdbc:(\w+):(.+)" url)))]
    {:classname classname
     :subprotocol (first groups)
     :subname (second groups)
     :url url
     :user user
     :password password}))

It's kinda odd that I had to deconstruct the url and then have the library reconstruct it again. Probably there's a better way to do this, but there are more interesting things to worry about :P

Saturday, 19 September, 2009

Connecting to Starhub using Huawei E1750 on Archlinux

I have a Asus 1005HA running Archlinux. Recently signed up for Starhub's mobile broadband plan, which gave me a Huawei E1750. I could not google much on how to connect using this device from linux. So this is how I got mine to connect from bits and pieces of information from googling.

This is my uname:
Linux nettie 2.6.30-ARCH #1 SMP PREEMPT Wed Sep 9 12:37:32 UTC 2009 i686 Intel(R) Atom(TM) CPU N280 @ 1.66GHz GenuineIntel GNU/Linux


This is what we're trying to get when we type lsusb:
Bus 005 Device 002: ID 0b05:b700 ASUSTek Computer, Inc. Broadcom Bluetooth 2.1
Bus 005 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 004 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 003 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 001 Device 005: ID 12d1:1001 Huawei Technologies Co., Ltd. E620 USB Modem
Bus 001 Device 002: ID 13d3:5095 IMC Networks 
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub


Take note of the vendor id and product id of the Huawei device. 12d1 is the vendor id and 1001 is the product id. When you first plug it in the product id should be 1446. You should get something like this:
Bus 001 Device 005: ID 12d1:1446 Huawei Technologies Co., Ltd. E620 USB Modem


To switch to a product id of 1001, I used a combination modem-modeswitch and usb-modeswitch. modem-modeswitch comes by default for me in archlinux /lib/udev/modem-modeswitch. usb-modeswitch will need to be installed. I did this by getting the PKGBUILD from AUR and changing the content to use the latest usb-modeswitch (http://www.draisberghof.de/usb_modeswitch/). Add the following to your /etc/usb_modeswitch.conf:
# Huawei E1750
#
DefaultVendor= 0x12d1
DefaultProduct= 0x1446
MessageEndpoint= 0x01
MessageContent="55534243000000000000000000000011060000000000000000000000000000"
CheckSuccess=5


You don't really need the last line "CheckSuccess=5". I have it there to just check if the switch was successful. I then wrote the following script to be executed with sudo:
#!/bin/sh
modprobe -r usb-storage
modprobe -r usbserial
/lib/udev/modem-modeswitch --vendor 0x12d1 --product 0x1446 --type option-zerocd
modprobe usbserial vendor=0x12d1 product=0x1446
/usr/bin/usb_modeswitch


This is what works for me. I'm not sure why it does. Sometimes I need to execute this several times to get it to work. Most of the time, once is enough after boot. You should be expecting the creation of 3 /dev/ttyUSB*. You can by "ls /dev/ttyUSB*" or from dmesg. But most importantly, you must have the switch of the product id to 1001 is lsusb.

After this all we need is wvdial. This is my /etc/wvdial.conf
[Dialer Defaults]
Modem = /dev/ttyUSB0
Modem Type = Analog Modem
Baud = 460800
Init1 = ATZ
Init2 = ATQ0 V1 E1 S0=0 &C1 &D2
Init3 =
Area Code =
Phone = *99#
Username = star
Password = hub
Ask Password = off
Dial Command = ATDT
Stupid Mode = on
Compuserve = off
Force Address =
Idle Seconds = 0
DialMessage1 =
DialMessage2 =
ISDN = off
Check Def Route = on
Auto DNS = on


Execute "sudo wvdial" and just wait for the connection and setting up of nameserver. Hope this helps somebody :)

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")]

Wednesday, 16 January, 2008

F#: A simple oracle database application

The following is an example of how to connect to an Oracle database, retrieve some results and display them in a DataGrid in F#:
(* Use lightweight F# syntax *)
#light

#r @"System.Data.OracleClient.dll";;

(* Import managed assemblies *)
open System
open System.Data
open System.Data.Common
open System.Data.OracleClient
open System.Collections
open System.Text
open System.Windows.Forms

let form =
let f = new Form()
f.Visible <- true
f.TopMost <- true
f.Text <- "F# Query"
f.Height <- 600
f.Width <- 800
f
let grid =
let g = new DataGrid()
g.Dock <- DockStyle.Fill
g
form.Controls.Add(grid)
let connection_string = "Server=SID;Uid=johndoe;Pwd=password;"
let sql = "select * from tblwb_job"
let data_table = new Data.DataTable()
try
let connection = new OracleClient.OracleConnection(connection_string)
let command = new OracleClient.OracleCommand (sql, connection)
let adapter = new OracleDataAdapter(command)
let r = adapter.Fill(data_table)
connection.Close()
with
| :? System.InvalidOperationException as ex ->
ignore(MessageBox.Show(ex.Message))
| _ ->
ignore(MessageBox.Show("Unknown Exception"))

grid.DataSource <- data_table
do Application.Run(form)

Pretty sad that I couldn't play with Haskell as much as I want. At least, I can work on some functional language while working on real work.

Saturday, 5 January, 2008

F#: Simple POSIX regular expression example


module Main (main) where

import System.IO
import Text.Printf
import Text.Regex.Posix
import Text.Regex.Base

text = "http://www.abc.com/xyz"
regex = "http://([^/]+)"

main = print (getFirstMatch( text =~ regex :: [[String]]))

getFirstMatch :: [[String]] -> String
getFirstMatch s = (s !! 0) !! 1

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]) } }