bbdb-0.8: Ability to read, write, and modify BBDB files

Safe HaskellSafe



This module can read and write BBDB files, and provides a few handy functions for getting at fields inside of BBDB data.

BBDB (now version 3) ( is short for the Insidious Big Brother Database, which is a contact management utility that can be integrated into Emacs (the one true editor.) Since bbdb.el is implemented in elisp, it can be difficult to "get at" the data inside a .bbdb file with external programs. Many years ago, I wrote a BBDB interface for perl, but having experience enlightenment at the hands of the category gods, I`m now dabbling with Haskell. But having been a loyal Emacs user for many years now, I wanted a way to spam my friends while still using my favorite programming language. Hence the module Data.BBDB.

The following is the data layout for a BBDB record. I have created a sample record with my own data. Each field is just separated by a space. I have added comments to the right

["Henry"                                   The first name - a string
"Laxen"                                    The last name - a string
nil                                        Affixes - a comma separated list
("Henry, Enrique")                         Also Known As - a comma separated list
("Elegant Solutions")                      Organizations- a comma separated list
 ["reno" 775 624 1851 0]                   Phone number field - US style
 ["chapala" "011-52-376-765-3181"]         Phone number field - International style
 ["mailing"                                The address location, then a list
 ("10580 N. McCarran Blvd." "#115-396")    for the street address, then one each
 "Reno" "Nevada" "89503" "USA"             for City, State, Zip Code, and country
 ["home"                                   another Address field
 ("Villa Alta #6" "Gaviotas #10")            The street list
 "Chapala" "Jalisco"                       City State
 "45900" "Mexico"                          Zip and country
 ""              the net addresses - a list of strings
 (notes . "Always split aces and eights")  The notes field - a list of alists
 (birthday . "6/15")
"169cc701-8754-45f5-aba8-c89c2dc60a49"     The hash field based
                                           on names, organizations, akas, and emails
"2010-09-03"                               The creation date
"2017-11-06 13:58:33 +0000"                The last modifcation time
nil                                        The cache vector - always nil

Inside the .bbdb file, this looks like: ["Henry" "Laxen" nil ("Henry, Enrique") ("Elegant Solutions") (["reno" 775 624 1851 0] ["chapala" "011-52-376-765-3181"]) (["mailing" ("10580 N. McCarran Blvd." "#115-396") "Reno" "Nevada" "89503" "USA"] ["home" ("Via Alta 10") "Chapala" "Jalisco" "45900" "Mexico"]) ("" "") ((notes . "Always split aces and eights") (birthday . "6/15")) "169cc701-8754-45f5-aba8-c89c2dc60a49" "2010-09-03" "2017-11-06 13:58:33 +0000" nil]

When parsed, this is represented inside Haskell as:

       (BBDB{firstName = Just "Henry", lastName = Just "Laxen",
             affix = Nothing
             aka = Just ["Henry, Enrique"], company = Just ["Elegant Solutions"],
             phone =
                 [USStyle "reno" ["775", "624", "1851", "0"],
                  InternationalStyle "chapala" "011-52-376-765-3181"],
             address =
                 [Address{location = "mailing",
                          streets =
                            Just ["10580 N. McCarran Blvd.", "#115-396"],
                          city = Just "Reno", state = Just "Nevada",
                          zipcode = Just "89503", country = Just "USA"},
                  Address{location = "home",
                          streets = Just ["Via Alta #6", "Gaviotas #10"],
                          city = Just "Chapala", state = Just "Jalisco",
                          zipcode = Just "45900", country = Just "Mexico"}],
             net = Just ["", ""],
             notes =
                 (Note{unnote =
                         [("notes", "Always split aces and eights"),
                          ("birthday", "6/15")]})})]
            hash = "169cc701-8754-45f5-aba8-c89c2dc60a49",
            creation = "2010-09-03",  
            modification = "2017-11-06 13:58:33 +0000" 



type Location = String Source #

A Location is just a synonym for String. Each BBDB Address and Phone field must be associated with a location, such as home or work

type Street = String Source #

A Street is also a synonym for String. Each Address may have a list of Streets associated with it.

type Symbol = String Source #

A Symbol is just a String, but Lisp only wants alphanumerics and the characters _ (underscore) and - (dash)

data Address Source #

An Address must have a location, and may have associated streets, a city, a state, a zipcode, and an country.

type Alist = (Symbol, String) Source #

An Alist is an Association List. Lisp writes these as (key . value) We convert these to a tuple in haskell where fst is key and snd is value.

data Note Source #

The Note field of a BBDB record is just a list of associations. If you don't provide a your own key, the BBDB will use the word "note"





Eq Note Source # 


(==) :: Note -> Note -> Bool #

(/=) :: Note -> Note -> Bool #

Ord Note Source # 


compare :: Note -> Note -> Ordering #

(<) :: Note -> Note -> Bool #

(<=) :: Note -> Note -> Bool #

(>) :: Note -> Note -> Bool #

(>=) :: Note -> Note -> Bool #

max :: Note -> Note -> Note #

min :: Note -> Note -> Note #

Show Note Source # 


showsPrec :: Int -> Note -> ShowS #

show :: Note -> String #

showList :: [Note] -> ShowS #

LispAble Note Source # 


asLisp :: Note -> String Source #

LispAble (Maybe Note) Source # 

data Phone Source #

For some unknow reason, BBDB can have phones in two different formats. In USStyle, the phone is list of integers, in the form of Area code, Prefix, Number, and Extension. I don't bother to convert the strings of digits to actual integers. In InternationalStyle, the phone number is just a String.


data BBDB Source #

The record fields of the BBDB data type





Eq BBDB Source # 


(==) :: BBDB -> BBDB -> Bool #

(/=) :: BBDB -> BBDB -> Bool #

Ord BBDB Source # 


compare :: BBDB -> BBDB -> Ordering #

(<) :: BBDB -> BBDB -> Bool #

(<=) :: BBDB -> BBDB -> Bool #

(>) :: BBDB -> BBDB -> Bool #

(>=) :: BBDB -> BBDB -> Bool #

max :: BBDB -> BBDB -> BBDB #

min :: BBDB -> BBDB -> BBDB #

Show BBDB Source # 


showsPrec :: Int -> BBDB -> ShowS #

show :: BBDB -> String #

showList :: [BBDB] -> ShowS #

LispAble BBDB Source # 


asLisp :: BBDB -> String Source #

data BBDBFile Source #

At the beginning of a BBDB file are a variable number of comments, which specify the encoding type and the version. We just ignore them. Comments starts with a ; (semi-colon) and continue to end of line

class LispAble s where Source #

LispAble is how we convert from our internal representation of a BBDB record, to one that will make Lisp and Emacs happy. (Sans bugs)

testInverse = do
  let inFile = "/home/henry/.bbdb"
  actualBBDBFile <- readFile inFile
  parsedBBDBdata <- readBBDB inFile
  let bbdbDataOut = asLisp parsedBBDBdata
  print $ actualBBDBFile == bbdbDataOut

should print True

Minimal complete definition



asLisp :: s -> String Source #

type Hash = String Source #

Since file-format 9, BBDB now includes there more fields, which | are always present and used internally. | Synonym for String

type CreationDate = String Source #

Synonym for String

type ModificationTime = String Source #

Synonym for String

bbdbDefault :: BBDB Source #

A BBDB record containing no data

key :: (x, y) -> x Source #

Given an Alist, return the key

value :: (x, y) -> y Source #

Given an Alist, return the value

parseBBDB :: String -> Either ParseError [BBDBFile] Source #

parse the string as a BBDB File

bbdbFileParse :: Parser [BBDBFile] Source #

The Parser for a BBDB file, as it is written on disk. If you read a .bbdb file with:

testParse :: FilePath -> IO (Either ParseError [BBDBFile])
testParse filename = do
  b <- readFile filename
  return $  parse bbdbFileParse "bbdb" b

You will get IO (Right [BBDBFile]) if the parse went ok

justEntry :: BBDBFile -> Maybe BBDB Source #

converts a BBDB comment to nothing, and a BBDB entry to just the entry

justEntries :: [BBDBFile] -> [BBDB] Source #

returns a list of only the actual bbdb entries, removing the comments

readBBDB :: String -> IO [BBDBFile] Source #

read the given file and call error if the parse failed, otherwise return the entire file as a list of BBDBFile records.

wantNote :: (Alist -> Bool) -> BBDB -> Bool Source #

Notes inside a BBDB record are awkward to get at. This helper function digs into the record and applies a function to each Alist element of the record. It returns true if it any of the Alists in the note return true. For example:

hasBirthday :: BBDB -> Bool
hasBirthday = wantNote (\x -> key x == "birthday")

will return True for any BBDB record that has a "birthday" key in it's notes field

getNote :: String -> BBDB -> Maybe String Source #

Lookup the value whose key is the given string. If found returns Just the value, otherwise Nothing For example:

getBirthday :: BBDB -> Maybe String
getBirthday = getNote "birthday"

mapBBDB :: (BBDB -> BBDB) -> [BBDBFile] -> [BBDBFile] Source #

This and filterBBDB are the main functions you should use to manipulate a set of BBDB entries. You supply a function that applies a transformation on a BBDB record, and this function will apply that transformation to every BBDBEntry in a BBDB file. Sample usage:

starCompanies = do
  b <- readBBDB "/home/henry/.bbdb"
  writeFile "/home/henry/.bbdb-new" $ asLisp . mapBBDB starCompany $ b
    starCompany x = case (company x) of
      Nothing -> x
      Just y -> x { company = Just ("*" ++ y) }

Prepend a star ("*") to each company field of a BBDB file and write the result out as a new bbdb file.

filterBBDB :: (BBDB -> Bool) -> [BBDBFile] -> [BBDBFile] Source #

Just like mapBBDB except it filters. You supply a function that takes a BBDB record to a Bool, and filterBBDB will return a new list of BBDBFile that satisfy that condition. Sample usage:

import Text.Regex.Posix
-- do regex matching while ignoring case, so "reno" matches "Reno"
matches x = match (makeRegexOpts compIgnoreCase defaultExecOpt x :: Regex)
getReno = do
  b <- readBBDB "/home/henry/.bbdb"
  let c = justEntries . filterBBDB hasReno $ b
  mapM_ print $ map (\a -> (firstName a, lastName a, address a)) c
    isReno :: Maybe String -> Bool
    isReno = maybe False (matches "reno")
    anyAddressHasReno :: [Address] -> Bool
    anyAddressHasReno = any id . map (isReno . city)
    hasReno :: BBDB -> Bool
    hasReno = maybe False anyAddressHasReno . address

print the name and all addresses of anyone in the BBDB file who live in Reno.