tweet-hs 1.0.1.43 → 1.0.2.1
raw patch · 14 files changed
+212/−190 lines, 14 filesdep −aesondep −data-defaultdep −hspecdep ~basedep ~containersPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies removed: aeson, data-default, hspec
Dependency ranges changed: base, containers
API changes (from Hackage documentation)
- Web.Tweet.Parser.FastParser: FastTweet :: !Int -> !Text -> User -> Maybe FastTweet -> !Int -> !Int -> FastTweet
- Web.Tweet.Parser.FastParser: [favorite_count] :: FastTweet -> !Int
- Web.Tweet.Parser.FastParser: [idNum] :: FastTweet -> !Int
- Web.Tweet.Parser.FastParser: [quoted_status] :: FastTweet -> Maybe FastTweet
- Web.Tweet.Parser.FastParser: [retweet_count] :: FastTweet -> !Int
- Web.Tweet.Parser.FastParser: [text] :: FastTweet -> !Text
- Web.Tweet.Parser.FastParser: [user] :: FastTweet -> User
- Web.Tweet.Parser.FastParser: data FastTweet
- Web.Tweet.Parser.FastParser: fastParse :: ByteString -> Either String [FastTweet]
- Web.Tweet.Parser.FastParser: fromFast :: FastTweet -> TweetEntity
- Web.Tweet.Parser.FastParser: instance Data.Aeson.Types.FromJSON.FromJSON Web.Tweet.Parser.FastParser.FastTweet
- Web.Tweet.Parser.FastParser: instance Data.Aeson.Types.FromJSON.FromJSON Web.Tweet.Parser.FastParser.User
- Web.Tweet.Parser.FastParser: instance GHC.Classes.Eq Web.Tweet.Parser.FastParser.FastTweet
- Web.Tweet.Parser.FastParser: instance GHC.Classes.Eq Web.Tweet.Parser.FastParser.User
- Web.Tweet.Parser.FastParser: instance GHC.Generics.Generic Web.Tweet.Parser.FastParser.FastTweet
- Web.Tweet.Parser.FastParser: instance GHC.Generics.Generic Web.Tweet.Parser.FastParser.User
- Web.Tweet.Parser.FastParser: instance GHC.Show.Show Web.Tweet.Parser.FastParser.FastTweet
- Web.Tweet.Parser.FastParser: instance GHC.Show.Show Web.Tweet.Parser.FastParser.User
- Web.Tweet.Utils: getTweetsFast :: ByteString -> Either String Timeline
+ Web.Tweet: [_replyTo] :: TweetEntity -> Maybe Int
+ Web.Tweet: pricklyTweet :: Tweet
+ Web.Tweet: showReplies :: String -> Int -> Bool -> FilePath -> IO String
+ Web.Tweet.API: getReplies :: String -> Int -> FilePath -> IO (Either (ParseErrorBundle String Void) Timeline)
+ Web.Tweet.API: loopMentions :: Maybe Int -> Maybe Int -> Set String -> String -> FilePath -> IO ()
+ Web.Tweet.API: loopReplies :: Maybe Int -> Maybe Int -> String -> Int -> FilePath -> IO (Either (ParseErrorBundle String Void) Timeline)
+ Web.Tweet.API: muteMentions :: String -> FilePath -> IO ()
+ Web.Tweet.API: muteRepliers :: String -> Int -> FilePath -> IO [String]
+ Web.Tweet.API: searchMentions :: Maybe Int -> String -> FilePath -> IO (Either (ParseErrorBundle String Void) Timeline)
+ Web.Tweet.API: searchMentionsRaw :: Maybe Int -> String -> FilePath -> IO ByteString
+ Web.Tweet.API: searchRaw :: String -> FilePath -> IO ByteString
+ Web.Tweet.API: searchReplies :: Maybe Int -> String -> Int -> FilePath -> IO (Either (ParseErrorBundle String Void) Timeline)
+ Web.Tweet.API: searchRepliesRaw :: Maybe Int -> String -> Int -> FilePath -> IO ByteString
- Web.Tweet: TweetEntity :: String -> String -> String -> Int -> [String] -> Maybe TweetEntity -> Int -> Int -> TweetEntity
+ Web.Tweet: TweetEntity :: String -> Maybe Int -> String -> String -> Int -> [String] -> Maybe TweetEntity -> Int -> Int -> TweetEntity
Files
- CHANGELOG.md +0/−5
- app/Main.hs +23/−1
- bench/Bench.hs +2/−9
- cabal.project.local +1/−0
- src/Web/Tweet.hs +2/−3
- src/Web/Tweet/API.hs +86/−2
- src/Web/Tweet/API/Internal.hs +3/−0
- src/Web/Tweet/Parser.hs +30/−21
- src/Web/Tweet/Parser/FastParser.hs +0/−38
- src/Web/Tweet/Types.hs +6/−7
- src/Web/Tweet/Utils.hs +7/−14
- src/Web/Tweet/Utils/API.hs +3/−2
- test/Spec.hs +0/−17
- tweet-hs.cabal +49/−71
− CHANGELOG.md
@@ -1,5 +0,0 @@-# tweet-hs--## 1.0.1.42-- * Update for new `megaparsec`
app/Main.hs view
@@ -1,6 +1,7 @@ module Main where import qualified Data.ByteString.Lazy.Char8 as BSL+import Data.Foldable (traverse_) import Data.Maybe import Data.Version import Options.Applicative@@ -33,6 +34,9 @@ | Mute { screenName' :: String } | Unmute { screenName' :: String } | Dump { screenName' :: String }+ | Replies { screenName' :: String, twId :: Integer }+ | MuteReplies { screenName' :: String, twId :: Integer }+ | MuteMentions { screenName' :: String } -- | query twitter to post stdin with no fancy options fromStdIn :: Int -> FilePath -> IO ()@@ -50,7 +54,7 @@ -- | Executes parser main :: IO ()-main = putStrLn bird >> execParser opts >>= select+main = execParser opts >>= select where versionInfo = infoOption ("tweet-hs version: " ++ showVersion version) (short 'v' <> long "version" <> help "Show version") opts = info (helper <*> versionInfo <*> program)@@ -74,6 +78,12 @@ selectCommand (SendInput maybeNum (Just rId) (Just h)) _ file = threadStdIn h (pure . read $ rId) (fromMaybe 15 maybeNum) file selectCommand (SendInput maybeNum (Just rId) Nothing) _ file = threadStdIn [] (pure . read $ rId) (fromMaybe 15 maybeNum) file selectCommand (SendInput maybeNum Nothing (Just h)) _ file = threadStdIn h Nothing (fromMaybe 15 maybeNum) file+selectCommand (Replies uname twid) c file = putStrLn =<< showReplies uname (fromIntegral twid) c file+selectCommand (MuteReplies uname twid) _ file = do+ muted <- muteRepliers uname (fromIntegral twid) file+ putStrLn "Muted:"+ traverse_ putStrLn muted+selectCommand (MuteMentions uname) _ file = muteMentions uname file selectCommand (Timeline maybeNum) c file = putStrLn =<< showTimeline (fromMaybe 11 maybeNum) c file selectCommand (Mentions maybeNum) c file = putStrLn =<< showTweets c <$> mentions (fromMaybe 11 maybeNum) file selectCommand (Profile maybeNum n False False) c file = putStrLn =<< showProfile (fromMaybe mempty n) (fromMaybe 11 maybeNum) c file@@ -139,12 +149,15 @@ <> command "rt" (info rt (progDesc "Retweet a tweet")) <> command "follow" (info fol (progDesc "Follow a user")) <> command "unfollow" (info unfol (progDesc "Unfollow a user"))+ <> command "replies" (info repliesParser (progDesc "Show replies to a tweet")) <> command "list" (info list (progDesc "List a user's favorites")) <> command "dump" (info dump (progDesc "Dump tweets (for debugging)")) <> command "block" (info blockParser (progDesc "Block a user")) <> command "unblock" (info unblockParser (progDesc "Unblock a user")) <> command "mute" (info muteParser (progDesc "Mute a user")) <> command "unmute" (info unmuteParser (progDesc "Unmute a user"))+ <> command "mute-replies" (info muteRepliesParser (progDesc "Mute everyone who replied to a particular tweet"))+ <> command "mute-mentions" (info muteMentionsParser (progDesc "Mute everyone who mentioned a particular user")) <> command "mentions" (info mentionsParser (progDesc "Fetch mentions"))) <*> optional (strOption (long "cred"@@ -239,6 +252,15 @@ getInt = read <$> argument str (metavar "TWEET_ID" <> help "ID of tweet")++repliesParser :: Parser Command+repliesParser = Replies <$> user <*> getInt++muteRepliesParser :: Parser Command+muteRepliesParser = MuteReplies <$> user <*> getInt++muteMentionsParser :: Parser Command+muteMentionsParser = MuteMentions <$> user -- | Parser for the user subcommand profile :: Parser Command
bench/Bench.hs view
@@ -1,10 +1,9 @@ module Main where import Criterion.Main-import qualified Data.ByteString as BS+import qualified Data.ByteString as BS import Text.Megaparsec import Web.Tweet.Parser-import Web.Tweet.Parser.FastParser setupEnv :: IO BS.ByteString setupEnv = BS.readFile "test/data"@@ -15,13 +14,7 @@ main :: IO () main = defaultMain [- env setupEnv $ \ file ->- bgroup "aeson parser"- [ bench "226" $ whnf fast file ]- , env setupEnv' $ \ file ->+ env setupEnv' $ \ file -> bgroup "handrolled parser" [ bench "226" $ whnf (parse parseTweet) file ] ]- where- fast = fmap (fmap fromFast) . fastParse-
cabal.project.local view
@@ -1,2 +1,3 @@ constraints: tweet-hs +development optimization: 2+max-backjumps: 40000
src/Web/Tweet.hs view
@@ -40,7 +40,6 @@ ) where import Control.Monad-import Data.Default import Data.List.Split (chunksOf) import Data.Maybe import Lens.Micro@@ -53,7 +52,7 @@ -- | Tweet a string given a path to credentials; return the id of the status. ----- > basicTweet "On the airplane." ".cred"+-- > basicTweet "On the airplane." ".cred.toml" basicTweet :: String -> FilePath -> IO Int basicTweet contents = tweetData (mkTweet contents) @@ -88,4 +87,4 @@ -- | Make a `Tweet` with only the contents. mkTweet :: String -> Tweet-mkTweet contents = over status (const contents) def+mkTweet contents = over status (pure contents) pricklyTweet
src/Web/Tweet/API.hs view
@@ -6,7 +6,11 @@ import Control.Composition import Control.Monad import qualified Data.ByteString.Lazy.Char8 as BSL+import Data.Containers.ListUtils (nubOrd)+import Data.Foldable (traverse_)+import Data.Functor (($>)) import Data.Maybe (isJust)+import qualified Data.Set as S import Data.Void import Lens.Micro import Lens.Micro.Extras@@ -23,13 +27,13 @@ getAll :: String -> Maybe Int -> FilePath -> IO Timeline getAll sn maxId filepath = do tweets <- either (error "Parse tweets failed") id <$> getProfileMax sn 200 filepath maxId- let lastId = _tweetId <$> tweets^?_last+ let lastId = _tweetId <$> tweets ^? _last if lastId == maxId then pure [] else do if isJust lastId- then putStrLn $ "fetching tweets since " ++ show (lastId^?!_Just) ++ "..."+ then putStrLn $ "fetching tweets since " ++ show (lastId ^?! _Just) ++ "..." else pure () next <- getAll sn lastId filepath pure (tweets ++ next)@@ -79,6 +83,86 @@ -- | Get mentions and parse response as a list of tweets mentionsMem :: Int -> Config -> IO (Either (ParseErrorBundle String Void) Timeline) mentionsMem = fmap (getTweets . BSL.toStrict) .* mentionsRawMem++searchRaw :: String -> FilePath -> IO BSL.ByteString+searchRaw str = getRequest ("https://api.twitter.com/1.1/search/tweets.json" ++ str)++searchMentionsRaw :: Maybe Int -- ^ Max ID+ -> String -- ^ Username+ -> FilePath+ -> IO BSL.ByteString+searchMentionsRaw maxid uname = searchRaw ("?q=to%3A" ++ uname ++ maxidUrl)+ where maxidUrl = case maxid of+ Just id' -> "&max_id=" ++ show id'+ Nothing -> ""++searchRepliesRaw :: Maybe Int -- ^ Max ID+ -> String -- ^ Username+ -> Int -- ^ Tweet ID+ -> FilePath+ -> IO BSL.ByteString+searchRepliesRaw maxid uname twid = searchRaw ("?q=to%3A" ++ uname ++ "&since_id=" ++ show twid ++ maxidUrl)+ where maxidUrl = case maxid of+ Just id' -> "&max_id=" ++ show id'+ Nothing -> ""++loopMentions :: Maybe Int -> Maybe Int -> S.Set String -> String -> FilePath -> IO ()+loopMentions pastMax maxid alreadyMuted uname fp =+ if maxid == pastMax+ then pure ()+ else do+ next <- searchMentions maxid uname fp+ case next of+ Right [] -> pure ()+ Left{} -> pure ()+ Right tws -> do+ let newMax = minimum (_tweetId <$> tws)+ let toMute = [ tw | tw <- nubOrd (_screenName <$> tws), tw `S.notMember` alreadyMuted ]+ let modAlready = thread [ S.insert k | k <- toMute ]+ putStrLn "Muted:"+ traverse_ (\u -> mute u fp *> putStrLn u) toMute+ loopMentions maxid (Just newMax) (modAlready alreadyMuted) uname fp++muteMentions :: String -- ^ Screen name+ -> FilePath+ -> IO ()+muteMentions = loopMentions (Just 0) Nothing mempty++loopReplies :: Maybe Int -> Maybe Int -> String -> Int -> FilePath -> IO (Either (ParseErrorBundle String Void) Timeline)+loopReplies pastMax maxid uname twid fp =+ if maxid == pastMax+ then pure (Right [])+ else do+ next <- searchReplies maxid uname twid fp+ case next of+ Right [] -> pure (Right [])+ Left x -> pure (Left x)+ Right tws -> let newMax = minimum (_tweetId <$> tws)+ in fmap (tws ++) <$> loopReplies maxid (Just newMax) uname twid fp++searchMentions :: Maybe Int -> String -> FilePath -> IO (Either (ParseErrorBundle String Void) Timeline)+searchMentions = fmap (getTweets . BSL.toStrict) .** searchMentionsRaw++searchReplies :: Maybe Int -> String -> Int -> FilePath -> IO (Either (ParseErrorBundle String Void) Timeline)+searchReplies = fmap (getTweets . BSL.toStrict) .*** searchRepliesRaw++getReplies :: String -- ^ Screen name+ -> Int -- ^ Tweet+ -> FilePath+ -> IO (Either (ParseErrorBundle String Void) Timeline)+getReplies str twid = fmap (fmap (filter p)) . loopReplies (Just 0) Nothing str twid+ where p entity = _replyTo entity == Just twid++muteRepliers :: String+ -> Int+ -> FilePath+ -> IO [String]+muteRepliers str twid fp = do+ us <- getReplies str twid fp+ case us of+ Left{} -> pure []+ -- TODO: add a delay so I don't get rate limited?+ Right xs -> let toMute = nubOrd (_screenName <$> xs) in traverse (\u -> mute u fp $> u) toMute -- | Gets mentions mentionsRaw :: Int -> FilePath -> IO BSL.ByteString
src/Web/Tweet/API/Internal.hs view
@@ -22,6 +22,9 @@ showBest' :: String -> Int -> Bool -> FilePath -> IO String showBest' sn n color = fmap (showTweets color . pure . take n . hits') . getAll sn Nothing +showReplies :: String -> Int -> Bool -> FilePath -> IO String+showReplies uname twid color = fmap (showTweets color) . getReplies uname twid+ -- | Display user timeline showTimeline :: Int -> Bool -> FilePath -> IO String showTimeline count color = fmap (showTweets color) . getTimeline count
src/Web/Tweet/Parser.hs view
@@ -1,35 +1,44 @@ {-# OPTIONS_GHC -fno-warn-unused-do-bind #-}+{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} -- | Module containing parsers for tweet and response data. module Web.Tweet.Parser ( parseTweet , getData ) where -import Control.Composition ((.*))-import qualified Data.ByteString as BS-import Text.Megaparsec.Byte-import Text.Megaparsec.Byte.Lexer as L-import Text.Megaparsec-import Web.Tweet.Types-import qualified Data.Map as M-import qualified Data.Text as T-import qualified Data.Text.Encoding as TE-import Control.Monad-import Data.Maybe-import Data.Void+import Control.Composition ((.*))+import Control.Monad+import qualified Data.ByteString as BS+import Data.List (isInfixOf)+import qualified Data.Map as M+import Data.Maybe+import qualified Data.Text as T+import qualified Data.Text.Encoding as TE+import Data.Void+import Text.Megaparsec+import Text.Megaparsec.Byte+import Text.Megaparsec.Byte.Lexer as L+import Web.Tweet.Types type Parser = Parsec Void String -- | Parse some number of tweets parseTweet :: Parser Timeline-parseTweet = many (try getData <|> (const (TweetEntity "" "" "" 0 mempty Nothing 0 0) <$> eof))+parseTweet = many (try getData <|> (TweetEntity "" Nothing "" "" 0 mempty Nothing 0 0 <$ eof)) +inReplyTo :: String -> Maybe Int+inReplyTo str =+ if "null" `isInfixOf` str+ then Nothing+ else pure (read str)+ -- | Parse a single tweet's: n, text, fave count, retweet count getData :: Parser TweetEntity getData = do- idNum <- read <$> filterStr "id" + idNum <- read <$> filterStr "id" t <- filterStr "text" skipMentions+ irt <- inReplyTo <$> filterStr "in_reply_to_status_id" n <- filterStr "name" screenn' <- filterStr "screen_name" --withheldCountries <- (catMaybes . sequence) <$> optional filterList@@ -40,12 +49,12 @@ "false" -> do rts <- read <$> filterStr "retweet_count" faves <- read <$> filterStr "favorite_count"- pure (TweetEntity t n screenn' idNum withheldCountries Nothing rts faves)+ pure (TweetEntity t irt n screenn' idNum withheldCountries Nothing rts faves) "true" -> do q <- parseQuoted rts <- read <$> filterStr "retweet_count" faves <- read <$> filterStr "favorite_count"- pure $ TweetEntity t n screenn' idNum withheldCountries q rts faves+ pure $ TweetEntity t irt n screenn' idNum withheldCountries q rts faves _ -> error "is_quote_status must have a value of \"true\" or \"false\"" -- | Parse a the quoted tweet@@ -55,7 +64,7 @@ contents <- optional $ string "\",\"quoted_status" case contents of (Just _) -> pure <$> getData- _ -> pure Nothing+ _ -> pure Nothing -- | Skip a set of square brackets [] skipInsideBrackets :: Parser ()@@ -91,7 +100,7 @@ unicodeChar :: Parser Char unicodeChar = toEnum . fromIntegral . f <$> go where go = string "\\u" >> count 4 anySingle- f = fromHex . filterEmoji . BS.pack . fmap (fromIntegral . fromEnum) + f = fromHex . filterEmoji . BS.pack . fmap (fromIntegral . fromEnum) emojiChar :: Parser Char emojiChar = go a@@ -103,7 +112,7 @@ where go [] = [] go (a:b:c:d:rest) = let sym = convert16 [a,b] [c,d] in sym : go rest- go _ = error "Internal error: decodeUtf16 failed."+ go _ = error "Internal error: decodeUtf16 failed." convert16 x y = BS.pack [(read . ("0x"<>)) x, (read . ("0x"<>)) y] -- | helper function to ignore emoji@@ -116,8 +125,8 @@ single '&' innards <- many $ anySingleBut ';' single ';'- pure . (\case - (Just a) -> a + pure . (\case+ (Just a) -> a Nothing -> '?') $ M.lookup innards (M.fromList [("amp",'&'),("gt",'>'),("lt",'<'),("quot",'"'),("euro",'€'),("ndash",'–'),("mdash",'—')]) -- | Parse escaped characters
− src/Web/Tweet/Parser/FastParser.hs
@@ -1,38 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE RecordWildCards #-}--module Web.Tweet.Parser.FastParser ( fastParse- , fromFast- , FastTweet (..)- ) where--import Data.Aeson-import qualified Data.ByteString as BS-import qualified Data.ByteString.Lazy as BSL-import Data.Text (unpack)-import qualified Data.Text as T-import GHC.Generics-import Web.Tweet.Types hiding (name, text)--data FastTweet = FastTweet- { idNum :: !Int- , text :: !T.Text- , user :: User- , quoted_status :: Maybe FastTweet- , retweet_count :: !Int- , favorite_count :: !Int- } deriving (Generic, Eq, Show)--data User = User { name :: !T.Text- , screen_name :: !T.Text }- deriving (Generic, Eq, Show)--instance FromJSON FastTweet--instance FromJSON User--fromFast :: FastTweet -> TweetEntity-fromFast FastTweet{..} = TweetEntity (unpack text) (unpack . name $ user) (unpack . screen_name $ user) idNum mempty (fmap fromFast quoted_status) retweet_count favorite_count--fastParse :: BS.ByteString -> Either String [FastTweet]-fastParse = eitherDecode . BSL.fromStrict
src/Web/Tweet/Types.hs view
@@ -1,11 +1,6 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}- -- | Exports the `Tweet` type, a datatype for building tweets easily module Web.Tweet.Types where -import Data.Default-import GHC.Generics import Lens.Micro import Web.Authenticate.OAuth @@ -14,11 +9,12 @@ { _status :: String , _handles :: [String] , _replyID :: Maybe Int- } deriving (Generic, Default)+ } -- | Data type for tweets as they are returned data TweetEntity = TweetEntity { _text :: String+ , _replyTo :: Maybe Int , _name :: String , _screenName :: String , _tweetId :: Int@@ -26,7 +22,10 @@ , _quoted :: Maybe TweetEntity , _retweets :: Int , _favorites :: Int- } deriving (Generic, Default, Eq, Show)+ } deriving (Eq)++pricklyTweet :: Tweet+pricklyTweet = Tweet undefined [] Nothing -- | Stores data like (name, text, favoriteCount, retweetCount) type Timeline = [TweetEntity]
src/Web/Tweet/Utils.hs view
@@ -2,7 +2,6 @@ module Web.Tweet.Utils ( hits , hits'- , getTweetsFast , getTweets , displayTimeline , displayTimelineColor@@ -15,15 +14,14 @@ ) where import Control.Composition-import qualified Data.ByteString as BS2-import qualified Data.ByteString.Char8 as BS+import qualified Data.ByteString as BS2+import qualified Data.ByteString.Char8 as BS import Data.List import Data.List.Extra import Data.Void import Lens.Micro.Extras import Text.Megaparsec import Web.Tweet.Parser-import Web.Tweet.Parser.FastParser hiding (text) import Web.Tweet.Types import Web.Tweet.Utils.Colors @@ -51,14 +49,9 @@ getTweets :: BS2.ByteString -> Either (ParseErrorBundle String Void) Timeline getTweets = parse parseTweet "" . BS.unpack --- | Get a list of tweets from a response, returning author, favorites, retweets, and content.--- This version uses aeson, which it's far faster, but also has worse error messages.-getTweetsFast :: BS2.ByteString -> Either String Timeline-getTweetsFast = fmap (fmap fromFast) . fastParse- -- | Display Timeline without color displayTimeline :: Timeline -> String-displayTimeline (TweetEntity content u sn idTweet _ Nothing rts fave:rest) = concat [u+displayTimeline (TweetEntity content _ u sn idTweet _ Nothing rts fave:rest) = concat [u , " (" , sn , ")"@@ -75,7 +68,7 @@ , show idTweet ,"\n\n" ,displayTimeline rest]-displayTimeline (TweetEntity content u sn idTweet _ (Just q) rts fave:rest) = concat [u+displayTimeline (TweetEntity content _ u sn idTweet _ (Just q) rts fave:rest) = concat [u , " (" , sn , ")"@@ -104,7 +97,7 @@ -- | Display Timeline in color displayTimelineColor :: Timeline -> String-displayTimelineColor (TweetEntity content u sn idTweet _ Nothing rts fave:rest) = concat [toYellow u+displayTimelineColor (TweetEntity content _ u sn idTweet _ Nothing rts fave:rest) = concat [toYellow u , " (" , sn , ")"@@ -120,7 +113,7 @@ , toBlue (show idTweet) , "\n\n" , displayTimelineColor rest]-displayTimelineColor (TweetEntity content u sn idTweet _ (Just q) rts fave:rest) = concat [toYellow u+displayTimelineColor (TweetEntity content _ u sn idTweet _ (Just q) rts fave:rest) = concat [toYellow u , " (" , sn , ")"@@ -152,7 +145,7 @@ -- | sort tweets by most successful sortTweets :: Timeline -> Timeline sortTweets = sortBy compareTweet- where compareTweet (TweetEntity _ _ _ _ _ _ r1 f1) (TweetEntity _ _ _ _ _ _ r2 f2) = compare (2*r2 + f2) (2*r1 + f1)+ where compareTweet (TweetEntity _ _ _ _ _ _ _ r1 f1) (TweetEntity _ _ _ _ _ _ _ r2 f2) = compare (2*r2 + f2) (2*r1 + f1) -- | helper function to get the key as read from a file keyLinePie :: String -> String
src/Web/Tweet/Utils/API.hs view
@@ -9,6 +9,7 @@ , urlString , strEncode ) where +import Control.Monad ((<=<)) import qualified Data.ByteString.Char8 as BS import qualified Data.ByteString.Lazy.Char8 as BSL import Data.Char@@ -32,11 +33,11 @@ -- | Make a GET request to twitter given a request string getRequest :: String -> FilePath -> IO BSL.ByteString-getRequest = flip ((. getRequestMem) . (>>=) . mkConfigToml)+getRequest str = getRequestMem str <=< mkConfigToml -- | Make a POST request to twitter given a request string postRequest :: String -> FilePath -> IO BSL.ByteString-postRequest = flip ((. postRequestMem) . (>>=) . mkConfigToml)+postRequest str = postRequestMem str <=< mkConfigToml -- | Make a POST request to twitter given a request string postRequestMem :: String -> Config -> IO BSL.ByteString
− test/Spec.hs
@@ -1,17 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--import System.Environment-import Test.Hspec-import Web.Tweet.Parser.FastParser-import Web.Tweet.Sign--main :: IO ()-main = hspec $- describe "fastParse" $ do- -- file <- runIO $ BS.readFile "test/data"- config <- runIO $ (<> "/.cred") <$> getEnv "HOME"- configToml <- runIO $ (<> "/.cred.toml") <$> getEnv "HOME"- parallel $ it "parses sample tweets wrong" $- fastParse "" `shouldBe` Left "Error in $: not enough input"- parallel $ it "parses a config file the same way with the toml parser" $- ((==) <$> mkConfigToml configToml <*> mkConfig config) >>= (`shouldBe` True)
tweet-hs.cabal view
@@ -1,68 +1,62 @@-cabal-version: >=1.10-name: tweet-hs-version: 1.0.1.43-license: BSD3-license-file: LICENSE-copyright: 2016-2018 Vanessa McHale-maintainer: vamchale@gmail.com-author: Vanessa McHale-stability: stable-homepage: https://github.com/vmchale/command-line-tweeter#readme-synopsis: Command-line tool for twitter-description:- a Command Line Interface Tweeter-category: Web-build-type: Simple+cabal-version: >=1.10+name: tweet-hs+version: 1.0.2.1+license: BSD3+license-file: LICENSE+copyright: 2016-2019 Vanessa McHale+maintainer: vamchale@gmail.com+author: Vanessa McHale+stability: stable+synopsis: Command-line tool for twitter+description: a Command Line Interface Tweeter+category: Web+build-type: Simple extra-source-files: README.md- CHANGELOG.md cabal.project.local bash/mkCompletions test/data source-repository head- type: git+ type: git location: https://hub.darcs.net/vmchale/tweet-hs flag llvm-fast- description:- Enable build with llvm backend- default: False+ description: Enable build with llvm backend+ default: False flag library- description:- Don't build an executable- default: False+ description: Don't build an executable+ default: False flag development- description:- Enable -Werror- default: False- manual: True+ description: Enable -Werror+ default: False+ manual: True flag parallel-gc- description:- Use parallel garbage collector- default: False+ description: Use parallel garbage collector+ default: False library exposed-modules: Web.Tweet- Web.Tweet.Parser.FastParser Web.Tweet.Parser Web.Tweet.Sign Web.Tweet.API Web.Tweet.Utils- hs-source-dirs: src++ hs-source-dirs: src other-modules: Web.Tweet.Types Web.Tweet.Utils.Colors Web.Tweet.API.Internal Web.Tweet.Utils.API+ default-language: Haskell2010- default-extensions: LambdaCase- ghc-options: -Wall -Wincomplete-uni-patterns- -Wincomplete-record-updates+ ghc-options:+ -Wall -Wincomplete-uni-patterns -Wincomplete-record-updates+ build-depends: base >=4.11 && <5, http-client-tls -any,@@ -75,75 +69,59 @@ microlens -any, unordered-containers -any, htoml-megaparsec >=2.1.0.0,- data-default -any, text -any,- containers -any,+ containers >=0.6.0.0, ansi-wl-pprint -any, composition-prelude -any,- extra -any,- aeson -any- + extra -any+ if flag(development) ghc-options: -Werror executable tweet- main-is: Main.hs- hs-source-dirs: app- other-modules:- Paths_tweet_hs+ main-is: Main.hs+ hs-source-dirs: app+ other-modules: Paths_tweet_hs default-language: Haskell2010- ghc-options: -Wall -Wincomplete-uni-patterns- -Wincomplete-record-updates+ ghc-options:+ -Wall -Wincomplete-uni-patterns -Wincomplete-record-updates+ build-depends: base -any, tweet-hs -any, optparse-applicative -any, directory -any, bytestring -any- + if flag(library) buildable: False- + if flag(llvm-fast) ghc-options: -fllvm -optlo-O3 -O3- + if flag(parallel-gc) ghc-options: -rtsopts -with-rtsopts=-N- - if flag(development)- ghc-options: -Werror -test-suite tweeths-test- type: exitcode-stdio-1.0- main-is: Spec.hs- hs-source-dirs: test- default-language: Haskell2010- ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall- -Wincomplete-uni-patterns -Wincomplete-record-updates- build-depends:- base -any,- tweet-hs -any,- hspec -any- if flag(development) ghc-options: -Werror benchmark tweeths-bench- type: exitcode-stdio-1.0- main-is: Bench.hs- hs-source-dirs: bench+ type: exitcode-stdio-1.0+ main-is: Bench.hs+ hs-source-dirs: bench default-language: Haskell2010- ghc-options: -Wall -Wincomplete-uni-patterns- -Wincomplete-record-updates+ ghc-options:+ -Wall -Wincomplete-uni-patterns -Wincomplete-record-updates+ build-depends: base -any, criterion -any, tweet-hs -any, bytestring -any, megaparsec -any- + if flag(llvm-fast) ghc-options: -fllvm -optlo-O3 -O3- + if flag(development) ghc-options: -Werror