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 :)