packages feed

misfortune 0.1 → 0.1.1.1

raw patch · 7 files changed

+95/−108 lines, 7 filesdep ~basePVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependency ranges changed: base

API changes (from Hackage documentation)

- Data.Fortune: openFortuneFile :: FilePath -> Char -> Bool -> IO FortuneFile
+ Data.Fortune: openFortuneFile :: Char -> Bool -> FilePath -> IO FortuneFile
- Data.Fortune: withFortuneFile :: FilePath -> Char -> Bool -> (FortuneFile -> IO a) -> IO a
+ Data.Fortune: withFortuneFile :: Char -> Bool -> FilePath -> (FortuneFile -> IO a) -> IO a
- Data.Fortune: withFortuneFiles :: [FilePath] -> Char -> Bool -> ([FortuneFile] -> IO a) -> IO a
+ Data.Fortune: withFortuneFiles :: Char -> Bool -> [FilePath] -> ([FortuneFile] -> IO a) -> IO a

Files

README.md view
@@ -14,8 +14,6 @@      misfortune-strfile path/to/file -Note that `misfortune` uses a different format for its index files than `fortune` does.  If there is an existing fortune-mod index file, it will not be overwritten.- To use the fortune API in your Haskell programs:      import Data.Fortune
misfortune.cabal view
@@ -1,5 +1,5 @@ name:                   misfortune-version:                0.1+version:                0.1.1.1 stability:              experimental  cabal-version:          >= 1.6
src/Data/Fortune.hs view
@@ -226,7 +226,7 @@         then return "Very few profundities can be expressed in less than 80 characters."         else randomFortune paths -randomFortune paths = withFortuneFiles paths '%' False $ \fs -> do+randomFortune paths = withFortuneFiles '%' False paths $ \fs -> do     randomFortuneFromRandomFile . rvar =<< defaultFortuneDistribution fs  -- |Select a random fortune file from a specified distribution and then select a@@ -250,9 +250,6 @@     | f <- fs     ] --- TODO: allow the filter to assign a weight to each fortune, then weight the files--- by the total weight in their fortune-distributions (discarding zeros at both levels).- -- |Like 'defaultFortuneDistribution', but filtering the fortunes.  In addition to the -- fortune file, the tuples in the distribution include a distribution over the -- matching fortune indices in that file, assigning equal weight to each.@@ -270,19 +267,19 @@         ]  -- |Perform an action with an open 'FortuneFile', ensuring the file is closed--- when the action exits.-withFortuneFile :: FilePath -> Char -> Bool -> (FortuneFile -> IO a) -> IO a-withFortuneFile path delim writeMode = -    bracket (openFortuneFile path delim writeMode)+-- when the action finishes.+withFortuneFile :: Char -> Bool -> FilePath -> (FortuneFile -> IO a) -> IO a+withFortuneFile delim writeMode path = +    bracket (openFortuneFile delim writeMode path)              closeFortuneFile  -- |Perform an action with many open 'FortuneFile's, ensuring the files are closed--- when the action exits.-withFortuneFiles :: [FilePath] -> Char -> Bool -> ([FortuneFile] -> IO a) -> IO a-withFortuneFiles [] _ _ action = action []-withFortuneFiles (p:ps) delim writeMode action =-    withFortuneFile  p  delim writeMode $ \p ->-        withFortuneFiles ps delim writeMode $ \ps ->+-- when the action finishes.+withFortuneFiles :: Char -> Bool -> [FilePath] -> ([FortuneFile] -> IO a) -> IO a+withFortuneFiles _ _ [] action = action []+withFortuneFiles delim writeMode (p:ps) action =+    withFortuneFile delim writeMode p $ \p ->+        withFortuneFiles delim writeMode ps $ \ps ->             action (p:ps)  mapFortunesWithIndexM p f = 
src/Data/Fortune/FortuneFile.hs view
@@ -1,4 +1,6 @@ {-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE RecordWildCards #-} module Data.Fortune.FortuneFile      ( FortuneFile      , fortuneFilePath@@ -52,21 +54,15 @@ -- using @delim@ as the character between strings, allowing writing if -- @writeMode@ is set.  If no file exists at the specified path, an error -- will be thrown or the file will be created, depending on @writeMode@.-openFortuneFile :: FilePath -> Char -> Bool -> IO FortuneFile-openFortuneFile path delim writeMode = do-    exists <- doesFileExist path-    when (not (exists || writeMode))-        (fail ("openFortuneFile: file does not exist: " ++ show path))+openFortuneFile :: Char -> Bool -> FilePath -> IO FortuneFile+openFortuneFile fortuneDelim fortuneWritable fortunePath = do+    exists <- doesFileExist fortunePath+    when (not (exists || fortuneWritable))+        (fail ("openFortuneFile: file does not exist: " ++ show fortunePath))     -    fileRef <- newMVar Nothing-    ixRef   <- newMVar Nothing-    return FortuneFile-        { fortunePath       = path-        , fortuneDelim      = delim-        , fortuneWritable   = writeMode-        , fortuneFile       = fileRef-        , fortuneIndex      = ixRef-        }+    fortuneFile  <- newMVar Nothing+    fortuneIndex <- newMVar Nothing+    return FortuneFile{..}  -- |Close a fortune file. Subsequent accesses will fail. closeFortuneFile :: FortuneFile -> IO ()@@ -167,6 +163,9 @@          return getOne +-- try to decode the first UTF-8 char in a buffer.  If the decoding fails +-- (returns replacement_char), then check if the whole buffer was used.+-- if it was, we probably just need more data so return Nothing. tryDecode bs = case U.decode bs of     Just (c, n)         | c /= U.replacement_char || n /= BS.length bs@@ -226,10 +225,14 @@          return nextFortune +#if !MIN_VERSION_base(4,6,0)+ modifyIORef' r f = do     x <- readIORef r     writeIORef r $! f x +#endif+ getByIndex file (IndexEntry loc len _ _) = do     hSeek file AbsoluteSeek (toInteger loc)     BS.hGet file len@@ -262,7 +265,6 @@ -- needed and updating the index. appendFortune :: FortuneFile -> T.Text -> IO () appendFortune f fortune = do-    -- TODO: detect whether a reindex is actually needed     rebuildIndex f     withFileAndIndex f $ \file ix -> do         offset <- max 0 . getMax . offsetAfter <$> getStats ix
src/Data/Fortune/Index.hs view
@@ -214,7 +214,6 @@ checkIndex (Index file hdrRef) =     either Just id <$> try (withMVar hdrRef (checkIndex_ file)) --- TODO: also random spot-check for validity of table entries (mostly that they are consistent with the stats). checkIndex_ file hdr =     case checkHeader hdr of         Just problem -> return (Just (HeaderProblem problem))@@ -232,7 +231,6 @@         count = numFortunes (stats hdr)      res <- action file base (getSum count)     -    -- TODO: build flag to control paranoia level?     checkIndex_ file hdr >>= maybe (return res) throwIO      @@ -243,7 +241,6 @@         hSeek file AbsoluteSeek 0         BS.hPut file (runPut (putHeader newHdr))     -    -- TODO: build flag to control paranoia level?     checkIndex_ file newHdr >>= maybe (return newHdr) throwIO  -- |Get some cached stats about the fortunes indexed in this file.
src/Fortune.hs view
@@ -81,17 +81,20 @@  data Length = Short | Long -type FortuneFilter = FortuneFile -> Int -> IndexEntry -> IO Bool+type FortuneFilter = FortuneFile -> Maybe (Int, IndexEntry) -> IO Bool  data Args = Args     { equalProb         :: Bool     , printDist         :: Bool     , dumpFortunes      :: Maybe FilePath     , fortuneFilters    :: [FortuneFilter]-    , threshold         :: Threshold     , fortuneFiles      :: [FortuneFile]     } +-- run all configured filters for an individual fortune+filterFile    args file     = andM [p file Nothing       | p <- fortuneFilters args]+filterFortune args file i e = andM [p file (Just (i, e)) | p <- fortuneFilters args]+ parseArgs = do     (opts, files, errors) <- getOpt Permute flags <$> getArgs     when (not (null errors))       (usage errors)@@ -105,21 +108,6 @@          when (Path `elem` opts) (printPath fortuneType)     -    let threshold = fromMaybe defaultThreshold (listToMaybe (opts >>= f))-            where f (LL n) = [Lines n]; f (N  n) = [Chars n]; f _ = []-        -        filterLength len _ _ e = return (checkThreshold threshold len (indexEntryStats e))-        filterRegex   rx f i _ = matchTest rx . T.unpack <$> getFortune f i-        -        mkRegex :: String -> Regex-        mkRegex = makeRegexOpts (compUTF8 + caseOpt) execBlank-            where caseOpt = if I `elem` opts then compCaseless else 0-        -        mkFilter L      = Just (filterLength Long)-        mkFilter S      = Just (filterLength Short)-        mkFilter (M rx) = Just (filterRegex (mkRegex rx))-        mkFilter _      = Nothing-         fortuneFiles <- if null files         then defaultFortuneFiles fortuneType         else do@@ -131,25 +119,46 @@                 else usage ["Fortune database not found: " ++ file | file <- missing]          -- open them all-    fortuneFiles <- mapM (\f -> openFortuneFile f '%' False) fortuneFiles-    -    -- pre-filter files based on stats in the headers.  saves time filtering fortunes.-    let lengthRestriction = listToMaybe (opts >>= f)-            where f L = [Long]; f S = [Short]; f _ = []-    fortuneFiles <- filterFortuneFiles threshold lengthRestriction fortuneFiles+    fortuneFiles <- mapM (openFortuneFile '%' False) fortuneFiles          return Args         { equalProb = E `elem` opts         , printDist = F `elem` opts         , dumpFortunes = listToMaybe [ path | D path <- opts ]-        , fortuneFilters = catMaybes (map mkFilter opts)+        , fortuneFilters = parseFilters opts         , ..         } -filterFortuneFiles threshold =-    maybe return-        (\r -> filterM (fmap (checkThreshold threshold r) . getStats <=< getIndex))+parseFilters opts = mapMaybe (parseFilter opts) opts+parseFilter opts opt = case opt of+    L      -> Just (filterLength Long)+    S      -> Just (filterLength Short)+    (M rx) -> Just (filterRegex (mkRegex rx))+    _      -> Nothing+    where+        filterLength len _ (Just (_, e)) = return (checkThreshold threshold len (indexEntryStats e))+        filterLength len f Nothing       = checkThreshold threshold len <$> (getStats =<< getIndex f)+        +        filterRegex   rx f (Just (i, _)) = matchTest rx . T.unpack <$> getFortune f i+        filterRegex   _  _ Nothing       = return True+        +        mkRegex :: String -> Regex+        mkRegex = makeRegexOpts (compUTF8 + caseOpt) execBlank+        +        caseOpt = if I `elem` opts then compCaseless else 0+        threshold = fromMaybe defaultThreshold (listToMaybe (opts >>= f))+        f (LL n) = [Lines n]; f (N  n) = [Chars n]; f _ = [] ++-- longest one is long+checkThreshold t Long  s =      overThreshold t (maxChars s) (maxLines s)+-- shortest one is not long+checkThreshold t Short s = not (overThreshold t (minChars s) (minLines s))++overThreshold (Chars n) c l = c > n+overThreshold (Lines n) c l = l > n++ -- find a fortune file... 2 main cases: -- 1) the path is a simple name (contains no /'s): --      first check the given search path.@@ -163,8 +172,14 @@ --           offensive one, because the user didn't say "-o". -- 2) the path is not a simple name (contains at least one /): --      Just check for the file.-resolve searchPath fullSearchPath file = case splitPath file of-    [_] -> do+resolve searchPath fullSearchPath file+    | any (`elem` pathSeparators) file = do+        exists <- doesFileExist file+        return $! if exists+            then Right [file]+            else Left file+    +    | otherwise = do         files <- findFortuneFileIn searchPath file         if null files             then do@@ -172,78 +187,56 @@                     then findFortuneFileIn fullSearchPath file                     else return []                 -                if null files-                    then return (Left file)-                    else return (Right files)+                return $! if null files+                    then Left file+                    else Right files                              else return (Right files)-    _ -> do-        exists <- doesFileExist file-        if exists-            then return (Right [file])-            else return (Left file)  main = do     args <- parseArgs-    let fortunes = fortuneFiles args+    -- pre-filter files that cannot possibly match.  saves time filtering fortunes.+    fortunes <- filterM (filterFile args) (fortuneFiles args)     +    dist <- getDist args fortunes+    +    when (numEvents dist == 0) $ do+        hPutStrLn stderr "No fortunes matched the filter criteria"+        exitWith (ExitFailure 2)+         case dumpFortunes args of         Nothing -> return ()         Just outPath -> do-            out <- openFortuneFile outPath '%' True+            out <- openFortuneFile '%' True outPath                          sequence_-                [ do-                    let allFilters i e = andM [p file i e | p <- fortuneFilters args]-                    selected <- filterFortunesWithIndexM allFilters file-                    sequence_-                        [ getFortune file i >>= appendFortune out-                        | i <- selected-                        ]-                | file <- fortunes+                [ getFortune file i >>= appendFortune out+                | (file, iDist) <- snd <$> toList dist+                , i             <- snd <$> toList iDist                 ]                          exitWith ExitSuccess     -    dist <- getDist args fortunes-    -    when (numEvents dist == 0) $ do-        hPutStrLn stderr "No fortunes matched the filter criteria"-        exitWith (ExitFailure 2)-         if printDist args         then sequence_-            -- TODO: merge paths into a tree for nicer presentation-            [ printf "%5d %8s: %s\n" n pctStr (fortuneFilePath file)-            | (weight, (file, n, _)) <- toList dist+            [ printf "%5d %8s: %s\n" (numEvents iDist) pctStr (fortuneFilePath file)+            | (weight, (file, iDist)) <- toList dist             , let pctStr = printf "(%.2f%%)" (100 * weight / totalWeight dist) :: String             ]         else do-            (file, _, fortuneDist) <- sample dist+            (file, fortuneDist) <- sample dist             fortune <- sample fortuneDist-            -- putStrLn (fortuneFilePath file ++ " - " ++ show fortune)             putStrLn . T.unpack =<< getFortune file fortune --- longest one is long-checkThreshold t Long  s =      overThreshold t (maxChars s) (maxLines s)--- shortest one is not long-checkThreshold t Short s = not (overThreshold t (minChars s) (minLines s))--overThreshold (Chars n) c l = c > n-overThreshold (Lines n) c l = l > n--getDist :: Args -> [FortuneFile] -> IO (Categorical Float (FortuneFile, Int, RVar Int))+getDist :: Args -> [FortuneFile] -> IO (Categorical Float (FortuneFile, Categorical Float Int)) getDist args files = equalize <$> case fortuneFilters args of     [] -> do         dist <- defaultFortuneDistribution files         let f file = do                 n <- getNumFortunes file-                return (file, n, uniform 0 (n-1))+                return (file, fromObservations [0 .. n-1])         T.mapM f dist-    filters -> do-        dist <- fortuneDistributionWhere (\f i e -> andM [p f i e | p <- filters]) files-        let f (file, iDist) = (file, numEvents iDist, rvar iDist)-        return (fmap f dist)+    _ -> fortuneDistributionWhere (filterFortune args) files     where          equalize             | equalProb args = mapCategoricalPs (const 1)
src/StrFile.hs view
@@ -8,6 +8,6 @@     mapM_ index args  index file = do-    fortune <- openFortuneFile file '%' True+    fortune <- openFortuneFile '%' True file     rebuildIndex fortune