packages feed

flowsim-0.2.7: src/Config.hs

{-| Module Config - replaces Options, use CmdArgs instead -}
{-# LANGUAGE DeriveDataTypeable #-}

module Config (Config(..), mkconf
              ,inform, whenLoud, whenNotQuiet) where

import Data.Char (toLower)
import Data.List (isPrefixOf, intersperse)
import System.Console.CmdArgs
import System.Directory (doesFileExist)
import Control.Monad (when)
import System.IO (hPutStrLn, stderr, stdin)
import EmpFile

import Bio.Sequence

import Generations.GenBase
import Generations.GS20
import Generations.Titanium
import Generations.Empirical

version :: String
version = "flowsim v0.2.7, copyright 2010 Ketil Malde"

-- data GenName = Titanium | FLX | GS20 deriving (Typeable, Data, Show, Eq)
type GenName = String
type Dist    = String

data Config = Conf { generation :: GenName
                   , degradation:: Dist
                   , model  :: FilePath
                   , qualmethod  :: GenName
                   , discardfilters :: [String]
                   , trimfilters :: [String]
                   , flowkey :: String
                   , hplinput :: FilePath
                   , flowlength :: Int
                   , flowcycle :: String
                   , inputs :: [FilePath]
                   , output :: FilePath
                   } deriving (Typeable, Data, Show, Read)

modes :: Mode Config
modes = mode $ Conf 
  { generation     = "Titanium" &= text "454 generation to simulate"           & typ "GEN"   & flag "G"
  , degradation    = def &= text "model for degradation of the flow model" & typ "DIST"
  , model          = def &= text "empirical distribution for flow generation" & typFile
  , qualmethod     = def &= text "method for calculating quality"          & typ "STRING"
  , discardfilters = def &= text "discarding filters to apply"             & typ "DFILT"
  , trimfilters    = def &= text "trimming filters to apply"               & typ "TFILT"
  , flowkey        = def &= text "sequence key to start each read (TCAG)"
  , hplinput       = def &= text "input genome for HPL count estimate" & typFile
  , flowlength     = def &= text "number of flow cycles to run"
  , flowcycle      = def &= text "sequence nucleotides in each flow cycle (TACG)"
  , inputs         = def &= args & typFile
  , output         = def &= text "output file"
  } &= prog "flowsim"
    & text "simulate 454 pyrosequencing."
    & helpSuffix hs
      where     
        hs = ["Generations (GEN): "++concat (intersperse ", " $ map fst generations)
             , "Distributions (DIST): Uniform a b, Normal mu sigma, LogNormal mu sigma"
             , "Discarding filters (DFILT): ...."
             , "Trimming filters (TFILT): ...."]
    
mkconf :: IO (Generation,IO [Sequence Nuc],HPLprob,FilePath)
mkconf = do
  cf <- cmdArgs version [modes]
  let inp = map castToNuc `fmap` if null (inputs cf) then hReadFasta stdin else concat `fmap` mapM readFasta (inputs cf)
  gen <- mkgen cf
  whenLoud $ inform ("Generation: "++generation cf ++ "\n" ++ show gen)
  when (null $ output cf) $ error "Please specify an output file with -o"
  hplc <- case hplinput cf of 
    []   -> return default_hplc
    file -> (mkHPLprobs . freqtable . concatMap (hpls (f_cycle gen) . seqdata)) `fmap` readFasta file
  return (gen, inp, hplc, output cf)
  
default_hplc :: HPLprob
default_hplc _ l = 3/4**fromIntegral l

generations :: [(GenName,Generation)]
generations = [("GS20", gs20), ("Titanium",titanium),("EmpTitanium",tiEmp)]

lookupGen :: String -> Generation 
lookupGen g = case map snd . filter (isPrefixOf (map toLower g) . map toLower . fst) $ generations of
  [x] -> x
  _ -> error ("Generation '"++g++"' is non-existing or ambigous\nAvailable generations are: "++show (map fst generations))

-- if non-null, pick field f from generation str, else pick it from the default
setFromGen :: Generation -> (Generation -> field) -> String -> field
setFromGen dflt f str = if null str then f dflt else f (lookupGen str)

-- similar, but use Read to parse the string
setFromStr :: (Read a) => Generation -> (Generation -> a) -> String -> a
setFromStr dflt f str = if null str then f dflt else read str
  
mkgen :: Config -> IO Generation  
mkgen (Conf g deg fm qm df tf fk hpl fl fc _inputs _output) = do
  let gen = lookupGen g
      setG = setFromGen gen
      setD = setFromStr gen 
      setF fs | null fs   = disc_filters gen
              | otherwise = concatMap (maybe [] id) $ map (flip lookup filterlist) fs
      setT fs | null fs   = trim_filters gen
              | otherwise = concatMap (maybe [] id) $ map (flip lookup trimlist) fs
      my_qcall = if null qm then qcall gen 
                 else case qm of 
                   "gs20tab" -> qual_gs20_tab
                   "dexact"  -> qual_exact_decreasing
                   "fexact"  -> qual_exact_fixed 
                   _ -> error ("No such quality call method:"++show qm
                               ++"\nAvailable methods: gs20tab, dexact, fexact.")
  my_model <- if null fm then return (models gen)
              else do
                f <- doesFileExist fm
                ms <- if f then readPdf fm else return (parse_models fm)
                return $ combine_models (models gen) ms
  return $ gen  
          { qcall        = my_qcall
          , degrade      = setD degrade deg
          , models       = my_model
          , disc_filters = setF df 
          , trim_filters = setT tf
          , f_key        = setG f_key fk
          , f_len        = if fl > 0 then fl else f_len gen
          , f_cycle      = setG f_cycle fc
          }

parse_models :: String -> [Distribution]
parse_models str = read ("["++str++"]")

-- filterlist :: [DiscardFilter]
filterlist = [] -- todo: export from SFF_filters

-- trimlist  :: [Trimfilter]
trimlist   = [] 

-- Logging
inform :: String -> IO ()
inform = hPutStrLn stderr

whenLoud, whenNotQuiet :: IO () -> IO ()
whenLoud action = flip when action =<< isLoud
-- note that isQuiet = return False in CmdArgs!
whenNotQuiet action = do  n <- isNormal
                          l <- isLoud
                          when (n || l) action