Archive for July 2009

Making a choice from a list in Haskell, Vty (part 4)

After part 3 in this series, which might have been the longest post I’ve ever put on this blog, follows a much short post. In fact it’s so short it’s rather silly.

In this post I’ll modify the Option type to render into multiple lines; two in fact (it’s easy to see that it would work with more lines).

So, to start off, I add a second string to Option:

data Option = Option { optionRange::(Int, Int), optionS1::String, optionS2::String }
    deriving (Show)

Next the definition for Pretty is changed to render an Option on two lines:

instance Pretty Option where
    pretty (Option _ s1 s2) = string s1 <> line <> indent 2 (string s2)

Due to the change to Option I also need to modify optionIsInRange:

optionIsInRange (Option (b, e) _ _) i = b <= i && i <= e

Finally the options need to be modified as well:

options = ozFromListWithMod (optionSetRange 0) [Option (0, 0) ((show i) ++ " Foo") "Bar" | i <- [0..2]]

That’s all there’s to it. Short and sweet.

Making a choice from a list in Haskell, Vty (part 3)

This is the third part, and it’s likely to be the longest one in the series. The three previous parts have been rather short, but now it’s time for a longer post because in this one I completely change the representation of the options that are rendered.

Instead of using a list and an integer I’ll use what is basically a zipper (with some extra fields for book keeping). At the same time I also add a new field to the Option type to keep track of how many lines the option renders to. At the moment it will always be one line, but the next part will actually make use of it. (Yes, that part probably should have been kept in a separate part, but this happens to be how I wrote the code.)

First some changes to the Option type and its implementation of Pretty:

data Option = Option { optionRange::(Int, Int), optionS1::String }
    deriving (Show)
 
instance Pretty Option where
    pretty (Option _ s) = string s

Then two functions related to the “range” of an Option. The first to update based on a new start line (new beginning), the second checks whether a line falls within the range of an Option:

optionSetRange nb o = let
        l = length $ lines $ show $ pretty o
    in o { optionRange = (nb, nb + l - 1) }
 
optionIsInRange (Option (b, e) _) i = b <= i && i <= e

Now it’s time to introduce the zipper that replaces the list of options. The basic idea is that there are two parts to a list, the left side (ozLS) and the right side (ozRS), and a current item. In this list zipper the current item is the first item on the right side:

data OptionZipper = OptionZipper { ozIdx::Int, ozLS::[Option], ozRS::[Option]
}
    deriving (Show)

Making the zipper an instance of Pretty is as simple as this:

instance Pretty OptionZipper where
    pretty = vcat . map pretty . ozToList

It’s useful to be able to both convert to and from lists (as seen just above in the Pretty instance):

ozFromList l = OptionZipper 0 [] l
ozFromListWithMod f = ozCursorMod f . ozFromList
 
ozToList (OptionZipper _ l r) = reverse l ++ r

The function for getting the current item is obvious. At the same time I’ll define a function that applies a function to the item at the cursor.

ozCursor (OptionZipper _ _ (r:_)) = Just r
ozCursor _ = Nothing
 
ozCursorMod f o@(OptionZipper _ _ (r:rs)) = o { ozRS = (f r:rs) }
ozCursorMod _ o = o

Usually a list zipper has functions to move the cursor, i.e. move items between the left and right sides. In this zipper there is some extra bookkeeping that has to be done to make sure that the index is correct and that the current item has a correct range:

ozLeft (OptionZipper _ (l:ls) rs) = let
        (newIdx, _) = optionRange l
    in OptionZipper newIdx ls (l:rs)
ozLeft o = o
 
ozRight (OptionZipper _ ls (r:rs)) = let
        (_, pe) = optionRange r
    in ozCursorMod (optionSetRange $ pe + 1) $ OptionZipper (pe + 1) (r:ls) rs
ozRight o = o

That’s all good and well, but what I really need is to be able to navigate based on lines. Expressing that using ozLeft and ozRight is fairly straight forward. Let’s start with shifting to the next line, ozNextLine, it has two cases, one general case and one when the cursor points to the last item:

ozNextLine o@(OptionZipper i _ [c]) =
    if optionIsInRange c (i + 1)
        then o { ozIdx = i + 1 }
        else o
ozNextLine o = let
        c = fromJust $ ozCursor o
        i = ozIdx o
    in if optionIsInRange c (i + 1)
        then o { ozIdx = i + 1 }
        else ozRight o

Anyone who pays attention will realise that this definition of ozNextLine isn’t complete. The zipper is capable of pointing to the empty spot after the last item (when ozRS is [], as would be the case for an empty list turned into a zipper). For this occasion that is all right, but this would need some attention when using this in a proper program.

The definition of ozPreviousLine also has two cases:

ozPreviousLine o@(OptionZipper 0 _ _) = o
ozPreviousLine o = let
        c = fromJust $ ozCursor o
        i = ozIdx o
    in if optionIsInRange c (i - 1)
        then o { ozIdx = i - 1 }
        else ozLeft o

Yes, this function also has some assumptions built into it, just like for ozNextLine it’s enough to just realise that for this exercise.

That’s it for the zipper, now it’s possible to create the options:

options = ozFromListWithMod (optionSetRange 0) [Option (0, 0) ((show i) ++ " Foo") | i <- [0..99]]

The introduction of the zipper requires large changes to both getChoice and _getChoice. The changes are however very straight forward and in my opinion they make both functions easier to read and understand. I’ll simply copy in the definitions without any comments in the hope that thanks to using a zipper the code is self-explanatory :-) It might be worth pointing out though that render is still passed a list of strings to render, so it requires no changes at this point.

getChoice vt opts = do
    (sx, sy) <- getSize vt
    _getChoice vt opts sx sy
 
 
_getChoice vt opts sx sy =
    let
        _converted_opts = lines $ show $ pretty opts
        _idx = ozIdx opts
        _calcTop winHeight listLength idx = max 0 ((min listLength ((max 0 (idx - winHeight `div` 2)) + winHeight)) - winHeight)
        _top = _calcTop sy (length _converted_opts) _idx
        _visible_opts = take sy (drop _top _converted_opts)
    in do
        update vt (render _visible_opts (_idx - _top) sx)
        k <- getEvent vt
        case k of
            EvKey KDown [] -> _getChoice vt (ozNextLine opts) sx sy
            EvKey KUp [] -> _getChoice vt (ozPreviousLine opts) sx sy
            EvKey KEsc [] -> shutdown vt >> return Nothing
            EvKey KEnter [] -> shutdown vt >> return (Just $ (_idx, ozCursor opts))
            EvResize nx ny -> _getChoice vt opts nx ny
            _ -> _getChoice vt opts sx sy

Ping server in Haskell (not that kind of ping, and rather silly)

Yesterday I needed to do some tests involving tunneling of network connections. Rather than firing up the full client-server setup that I want to tunnel I thought I’d use someting simple to test with first. Instead of looking online for a simple server to use, or hack one up using netcat, or even hack one in Python I decided to hack one in Haskell:

module Main where
 
import Control.Monad
import System.Environment(getArgs)
import Network
import System.IO
 
main = withSocketsDo $ do
    [port_str] <- getArgs
    let port = fromIntegral (read port_str :: Int)
    serv_sock <- listenOn (PortNumber port)
    forever $ do
        (handle, host, port) <- accept serv_sock
        cmd <- hGetLine handle
        when (cmd == "Ping") $ hPutStr handle "Pong"
        hClose handle

XML prettifier in Haskell

I don’t know how many times I’ve gone looking for one of these but my search-fu is weak and I always give up, instead resorting to manual editing in Vim (no I hardly ever need the entire file to be pretty, only one or two tags that I’m interested in). Anyway, here’s a quick hack in Haskell, relying on xml for the heavy lifting:

#! /usr/bin/env runhaskell
 
module Main where
 
import Control.Monad
import System.Environment
import Text.XML.Light.Input
import Text.XML.Light.Output
 
main = do
    fn <- liftM (!! 0) $ getArgs
    xml_contents <- readFile fn
    let (Just doc) = parseXMLDoc xml_contents
    writeFile ("pretty-" ++ fn) (ppTopElement doc)

Making a choice from a list in Haskell, Vty (part 2)

Following on the previous part is another baby step. This just changes the options from a list of strings to a list of objects (the only requirement that they implement Pretty):

data Option = Option { optionValue::String }
    deriving (Show)
 
instance Pretty Option where
    pretty (Option s) = string s

After this it’s an easy step to replace the list of strings with a list of Option:

options = [Option ((show i) ++ " Foo") | i <- [0..99]]

That’s it! Yes, yet another ridiculously short post, but I promise the next one will be considerably longer.

Making a choice from a list in Haskell, Vty (part 1)

After posting the zeroth part of this series I realised I hadn’t said anything about the final goal of this exercise. The first post contained code for choosing one one-line string (String) out of a list of one-line strings ([String]). What I really want is the ability to choose one item out of a list of items, where each may render to be multiple lines. It would also be really cool if an item could be collapsed and expanded in the rendering. This is the first step in my journey towards these loosely specified requriements.

Rendering items into strings sounds like pretty-printing to me, so I played around a little with a few pretty-printing libraries. Finally I settled on the Wadler/Leijen Pretty Printer (Text.PrettyPrint.Leijen). I didn’t really have any strong reason for choosing it, beyond that it comes with its own type class whereas the pretty-printing library that ships with GHC (Text.PrettyPrint.HughesPJ) doesn’t (though there is a package on HackageDB with a class for it).

I did the smallest change I could think of to add pretty-printing. First the module needs to be imported of course:

import Text.PrettyPrint.Leijen

Then I added a function to turn a list of items into a document (Doc) where each item is pretty-printed on its own line:

myPrettyList :: Pretty a => [a] -> Doc
myPrettyList = vcat . map pretty

I then decided that _getChoice should be left unchanged and instead modified getChoice to turn the list of items into a list of strings:

getChoice vt opts = let
        _converted_opts = myPrettyList opts
    in do
        (sx, sy) <- getSize vt
        _getChoice vt (lines $ show _converted_opts) 0 sx sy

That’s it. The first step, albeit a small one.

Setting up Epiphany to play with Seed extensions

Since the Python extensions to Epiphany have been removed from the repository I thought it was high time to start playing with what seems to be the replacement to Python extensions: Seed extensions. The first step is of course to get a version of Epiphany that supports Seed extensions. After a few emails on the mailing list I’ve come to a recipe (I’ve done this twice now on different machines to I’m fairly confident it works). I should probably preface this by saying that I run an up-to-date Arch system, if you run something else you might need to do a bit more, or less if you’re lucky :-)

  1. Make sure the following packages are installedi: libsoup, libwebkit, gnome-common, intltool, libffi
  2. Clone the following Git repositories from git.gnome.org: epiphany-extensions, epiphany, seed, gobject-inspection, gnome-js-common, gir-repository
  3. Decide on a $prefix, i.e. where you want it all installed (I use ~/opt/gnome-trunk). Then export the following environment variables:

    export PATH=$prefix/bin:$PATH
    export PKG_CONFIG_PATH=$prefix/lib/pkgconfig
    
  4. Then configure, build and install everything. Use the autogen.sh script to create the configuration, and make sure to pass it prefix=$prefix each time. Some of the modules need extra arguments as well. This is the order and extra arguments I used:

    1. gnome-js-common (--disable-seed --disable-gjs)
    2. seed
    3. gnome-js-common (--disable-gjs)
    4. gobject-introspection
    5. gir-repository
    6. epiphany (--enable-introspection --enable-seed)
    7. epiphany-extensions

After that you can put your extensions in ~/.gnome2/epiphany/extensions/. I have two instances of Epiphany installed, a stable from my distro, and the dev version I built myself. I haven’t managed to run them both side by side, but beyond that there seems to be no bad interaction between them.

  1. You might need a few more packages depending on what desktop environment you use. Those were the packages I needed to add to my machine where I run Gnome and do regular non-Gnome development[back]

Dataenc finally making it into Debian

Erik de Castro Lopo is having a lot more success with getting dataenc into Debian than I ever had.