tweet-hs 1.0.0.1 → 1.0.1.0
raw patch · 8 files changed
+135/−100 lines, 8 filesdep −hspec-megaparsecdep ~megaparsecPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies removed: hspec-megaparsec
Dependency ranges changed: megaparsec
API changes (from Hackage documentation)
- Web.Tweet: showTweets :: Bool -> Either (ParseError Char Dec) Timeline -> String
+ Web.Tweet: showTweets :: Bool -> Either (ParseError Char Void) Timeline -> String
- Web.Tweet.API: deleteTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Dec) Timeline)
+ Web.Tweet.API: deleteTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Void) Timeline)
- Web.Tweet.API: deleteTweetResponseMem :: Integer -> Config -> IO (Either (ParseError Char Dec) Timeline)
+ Web.Tweet.API: deleteTweetResponseMem :: Integer -> Config -> IO (Either (ParseError Char Void) Timeline)
- Web.Tweet.API: favoriteTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Dec) Timeline)
+ Web.Tweet.API: favoriteTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Void) Timeline)
- Web.Tweet.API: getProfile :: String -> Int -> FilePath -> IO (Either (ParseError Char Dec) Timeline)
+ Web.Tweet.API: getProfile :: String -> Int -> FilePath -> IO (Either (ParseError Char Void) Timeline)
- Web.Tweet.API: getProfileMax :: String -> Int -> FilePath -> Maybe Int -> IO (Either (ParseError Char Dec) Timeline)
+ Web.Tweet.API: getProfileMax :: String -> Int -> FilePath -> Maybe Int -> IO (Either (ParseError Char Void) Timeline)
- Web.Tweet.API: getProfileMaxMem :: String -> Int -> Config -> Maybe Int -> IO (Either (ParseError Char Dec) Timeline)
+ Web.Tweet.API: getProfileMaxMem :: String -> Int -> Config -> Maybe Int -> IO (Either (ParseError Char Void) Timeline)
- Web.Tweet.API: getTimeline :: Int -> FilePath -> IO (Either (ParseError Char Dec) Timeline)
+ Web.Tweet.API: getTimeline :: Int -> FilePath -> IO (Either (ParseError Char Void) Timeline)
- Web.Tweet.API: getTimelineMem :: Int -> Config -> IO (Either (ParseError Char Dec) Timeline)
+ Web.Tweet.API: getTimelineMem :: Int -> Config -> IO (Either (ParseError Char Void) Timeline)
- Web.Tweet.API: mentions :: Int -> FilePath -> IO (Either (ParseError Char Dec) Timeline)
+ Web.Tweet.API: mentions :: Int -> FilePath -> IO (Either (ParseError Char Void) Timeline)
- Web.Tweet.API: mentionsMem :: Int -> Config -> IO (Either (ParseError Char Dec) Timeline)
+ Web.Tweet.API: mentionsMem :: Int -> Config -> IO (Either (ParseError Char Void) Timeline)
- Web.Tweet.API: retweetTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Dec) Timeline)
+ Web.Tweet.API: retweetTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Void) Timeline)
- Web.Tweet.API: retweetTweetResponseMem :: Integer -> Config -> IO (Either (ParseError Char Dec) Timeline)
+ Web.Tweet.API: retweetTweetResponseMem :: Integer -> Config -> IO (Either (ParseError Char Void) Timeline)
- Web.Tweet.API: unfavoriteTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Dec) Timeline)
+ Web.Tweet.API: unfavoriteTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Void) Timeline)
- Web.Tweet.API: unfavoriteTweetResponseMem :: Integer -> Config -> IO (Either (ParseError Char Dec) Timeline)
+ Web.Tweet.API: unfavoriteTweetResponseMem :: Integer -> Config -> IO (Either (ParseError Char Void) Timeline)
- Web.Tweet.API: unretweetTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Dec) Timeline)
+ Web.Tweet.API: unretweetTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Void) Timeline)
- Web.Tweet.API: unretweetTweetResponseMem :: Integer -> Config -> IO (Either (ParseError Char Dec) Timeline)
+ Web.Tweet.API: unretweetTweetResponseMem :: Integer -> Config -> IO (Either (ParseError Char Void) Timeline)
Files
- src/Web/Tweet/API.hs +25/−24
- src/Web/Tweet/API/Internal.hs +9/−8
- src/Web/Tweet/Exec.hs +10/−0
- src/Web/Tweet/Parser.hs +15/−9
- src/Web/Tweet/Utils.hs +6/−5
- stack.yaml +65/−38
- test/Spec.hs +3/−12
- tweet-hs.cabal +2/−4
src/Web/Tweet/API.hs view
@@ -3,14 +3,15 @@ -- | Module containing the functions directly dealing with twitter's API. Most functions in this module have two versions - one which takes a path to a TOML file containing api keys/secrets and tokens/secrets, the other takes api keys/secrets and tokens/secrets as an argument. module Web.Tweet.API where +import Control.Lens+import Control.Monad import qualified Data.ByteString.Lazy.Char8 as BSL-import Web.Tweet.Types-import Web.Tweet.Utils-import Control.Monad-import Control.Lens-import Text.Megaparsec.Error-import Web.Tweet.Utils.API-import Data.Composition+import Data.Composition+import Data.Void+import Text.Megaparsec.Error+import Web.Tweet.Types+import Web.Tweet.Utils+import Web.Tweet.Utils.API -- | Get tweets (text only) for some user getMarkov :: String -> Maybe Int -> FilePath -> IO [String]@@ -21,7 +22,7 @@ getAll screenName maxId filepath = do tweets <- either (error "Parse tweets failed") id <$> getProfileMax screenName 200 filepath maxId let lastId = _tweetId . last $ tweets- if (Just lastId) == maxId then + if (Just lastId) == maxId then pure [] else do@@ -46,11 +47,11 @@ pure . (view tweetId) . head . either (error "failed to parse tweet") id . getTweets . BSL.toStrict $ bytes -- | Gets user profile with max_id set.-getProfileMax :: String -> Int -> FilePath -> Maybe Int -> IO (Either (ParseError Char Dec) Timeline)+getProfileMax :: String -> Int -> FilePath -> Maybe Int -> IO (Either (ParseError Char Void) Timeline) getProfileMax = fmap (getTweets . BSL.toStrict) .*** getProfileRaw -- | Gets user profile with max_id set.-getProfileMaxMem :: String -> Int -> Config -> Maybe Int -> IO (Either (ParseError Char Dec) Timeline)+getProfileMaxMem :: String -> Int -> Config -> Maybe Int -> IO (Either (ParseError Char Void) Timeline) getProfileMaxMem = fmap (getTweets . BSL.toStrict) .*** getProfileRawMem -- | Gets user profile with max_id set.@@ -68,11 +69,11 @@ Nothing -> "?screen_name=" ++ screenName ++ "&count=" ++ (show count) } -- | Get mentions and parse response as a list of tweets-mentions :: Int -> FilePath -> IO (Either (ParseError Char Dec) Timeline)+mentions :: Int -> FilePath -> IO (Either (ParseError Char Void) Timeline) mentions = fmap (getTweets . BSL.toStrict) .* mentionsRaw -- | Get mentions and parse response as a list of tweets-mentionsMem :: Int -> Config -> IO (Either (ParseError Char Dec) Timeline)+mentionsMem :: Int -> Config -> IO (Either (ParseError Char Void) Timeline) mentionsMem = fmap (getTweets . BSL.toStrict) .* mentionsRawMem -- | Gets mentions@@ -86,7 +87,7 @@ where requestString = "?count=" ++ (show count) -- | Get user profile given screen name and how many tweets to return-getProfile :: String -> Int -> FilePath -> IO (Either (ParseError Char Dec) Timeline)+getProfile :: String -> Int -> FilePath -> IO (Either (ParseError Char Void) Timeline) getProfile screenName count filepath = getProfileMax screenName count filepath Nothing -- | Mute a user given their screen name@@ -126,11 +127,11 @@ where requestString = "?count=" ++ (show count) -- | Get a timeline-getTimeline :: Int -> FilePath -> IO (Either (ParseError Char Dec) Timeline)+getTimeline :: Int -> FilePath -> IO (Either (ParseError Char Void) Timeline) getTimeline = (fmap (getTweets . BSL.toStrict)) .* getTimelineRaw -- | Get a timeline-getTimelineMem :: Int -> Config -> IO (Either (ParseError Char Dec) Timeline)+getTimelineMem :: Int -> Config -> IO (Either (ParseError Char Void) Timeline) getTimelineMem = (fmap (getTweets . BSL.toStrict)) .* getTimelineRawMem -- | Get a user's timeline and return response as a bytestring@@ -152,11 +153,11 @@ deleteTweetMem = (fmap void) . deleteTweetRawMem -- | Get response, i.e. the tweet deleted-deleteTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Dec) Timeline)+deleteTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Void) Timeline) deleteTweetResponse = fmap (getTweets . BSL.toStrict) .* deleteTweetRaw -- | Get response, i.e. the tweet deleted-deleteTweetResponseMem :: Integer -> Config -> IO (Either (ParseError Char Dec) Timeline)+deleteTweetResponseMem :: Integer -> Config -> IO (Either (ParseError Char Void) Timeline) deleteTweetResponseMem = fmap (getTweets . BSL.toStrict) .* deleteTweetRawMem -- | Favorite a tweet given its id@@ -168,7 +169,7 @@ favoriteTweetMem = (fmap void) . favoriteTweetRawMem -- | Favorite a tweet and returned the (parsed) response-favoriteTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Dec) Timeline)+favoriteTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Void) Timeline) favoriteTweetResponse = fmap (getTweets . BSL.toStrict) .* favoriteTweetRaw -- | Unfavorite a tweet given its id@@ -180,11 +181,11 @@ unfavoriteTweetMem = (fmap void) . unfavoriteTweetRawMem -- | Unfavorite a tweet and returned the (parsed) response-unfavoriteTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Dec) Timeline)+unfavoriteTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Void) Timeline) unfavoriteTweetResponse = fmap (getTweets . BSL.toStrict) .* unfavoriteTweetRaw -- | Unfavorite a tweet and returned the (parsed) response-unfavoriteTweetResponseMem :: Integer -> Config -> IO (Either (ParseError Char Dec) Timeline)+unfavoriteTweetResponseMem :: Integer -> Config -> IO (Either (ParseError Char Void) Timeline) unfavoriteTweetResponseMem = fmap (getTweets . BSL.toStrict) .* unfavoriteTweetRawMem -- | Unretweet a tweet given its id@@ -196,11 +197,11 @@ unretweetTweetMem = (fmap void) . unretweetTweetRawMem -- | Unretweet a tweet and returned the (parsed) response-unretweetTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Dec) Timeline)+unretweetTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Void) Timeline) unretweetTweetResponse = fmap (getTweets . BSL.toStrict) .* unretweetTweetRaw -- | Unretweet a tweet and returned the (parsed) response-unretweetTweetResponseMem :: Integer -> Config -> IO (Either (ParseError Char Dec) Timeline)+unretweetTweetResponseMem :: Integer -> Config -> IO (Either (ParseError Char Void) Timeline) unretweetTweetResponseMem = fmap (getTweets . BSL.toStrict) .* unretweetTweetRawMem -- | Unfollow a user given their screen name@@ -244,11 +245,11 @@ retweetTweetMem = (fmap void) . retweetTweetRawMem -- | Retweet a tweet and returned the (parsed) response-retweetTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Dec) Timeline)+retweetTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Void) Timeline) retweetTweetResponse = fmap (getTweets . BSL.toStrict) .* retweetTweetRaw -- | Retweet a tweet and returned the (parsed) response-retweetTweetResponseMem :: Integer -> Config -> IO (Either (ParseError Char Dec) Timeline)+retweetTweetResponseMem :: Integer -> Config -> IO (Either (ParseError Char Void) Timeline) retweetTweetResponseMem = fmap (getTweets . BSL.toStrict) .* retweetTweetRawMem -- | Favorite a tweet given its id; return bytestring response
src/Web/Tweet/API/Internal.hs view
@@ -3,17 +3,18 @@ -- | Helper functions for the command line tool. module Web.Tweet.API.Internal where -import Web.Tweet.API-import Web.Tweet.Types-import Web.Tweet.Utils-import Text.Megaparsec.Error+import Data.Void+import Text.Megaparsec.Error+import Web.Tweet.API+import Web.Tweet.Types+import Web.Tweet.Utils --- | Show a user profile given screen name, how many tweets to return, +-- | Show a user profile given screen name, how many tweets to return, -- and whether to print them in color. showProfile :: String -> Int -> Bool -> FilePath -> IO String showProfile screenName count color = fmap (showTweets color) . getProfile screenName count --- | Show the most successful tweets by a given user, given their screen name. +-- | Show the most successful tweets by a given user, given their screen name. showBest :: String -> Int -> Bool -> FilePath -> IO String showBest screenName n color = fmap (showTweets color . pure . (take n . hits)) . getAll screenName Nothing @@ -23,8 +24,8 @@ -- | Display user timeline showTimeline :: Int -> Bool -> FilePath -> IO String-showTimeline count color = (fmap (showTweets color)) . getTimeline count +showTimeline count color = (fmap (showTweets color)) . getTimeline count -- | Display user timeline in color, as appropriate-showTweets :: Bool -> Either (ParseError Char Dec) Timeline -> String+showTweets :: Bool -> Either (ParseError Char Void) Timeline -> String showTweets color = (either show id) . (fmap (if color then displayTimelineColor else displayTimeline))
src/Web/Tweet/Exec.hs view
@@ -140,6 +140,8 @@ <> 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 "mentions" (info mentionsParser (progDesc "Fetch mentions")))) <*> (optional $ strOption (long "cred"@@ -184,6 +186,14 @@ -- | Parser for the unfollow subcommand unfol :: Parser Command unfol = Unfollow <$> user++-- | Parser for the unfollow subcommand+muteParser :: Parser Command+muteParser = Mute <$> user++-- | Parser for the unfollow subcommand+unmuteParser :: Parser Command+unmuteParser = Unmute <$> user -- | Parse a user screen name user :: Parser String
src/Web/Tweet/Parser.hs view
@@ -4,9 +4,11 @@ , getData ) where import qualified Data.ByteString as BS-import Text.Megaparsec.ByteString-import Text.Megaparsec.Lexer as L+import qualified Data.ByteString.Char8 as BSW import Text.Megaparsec+import Text.Megaparsec.Byte+import Text.Megaparsec.Byte.Lexer as L+import Text.Megaparsec import Web.Tweet.Types import Data.Monoid import qualified Data.Map as M@@ -14,7 +16,11 @@ import qualified Data.Text as T import qualified Data.Text.Encoding as TE import Control.Monad+import Data.Void+import Data.Word +type Parser = Parsec Void String+ -- | Parse some number of tweets parseTweet :: Parser Timeline parseTweet = many (try getData <|> (const (TweetEntity "" "" "" 0 Nothing 0 0) <$> eof))@@ -96,10 +102,10 @@ string "\\ud" str1 <- count 3 anyChar str2 <- string "\\ud" >> count 3 anyChar- let num = decodeUtf16 $ "d" <> str1 <> "d" <> str2- pure . head $ num+ let num = TE.decodeUtf16BE . BSW.pack $ "d" <> str1 <> "d" <> str2+ pure . T.head $ num -decodeUtf16 = T.unpack . TE.decodeUtf16BE . BS.concat . go+decodeUtf16 = TE.decodeUtf16BE . BS.concat . go where go [] = [] go (a:b:c:d:rest) = let sym = convert16 [a,b] [c,d] in sym : go rest@@ -122,11 +128,11 @@ -- | Parse escaped characters specialChar :: Char -> Parser Char specialChar c = do- string $ "\\" ++ pure c+ string $ (c:"\\") pure c -- | Convert a string of four hexadecimal digits to an integer. fromHex :: BS.ByteString -> Integer-fromHex = fromRight . (parse (L.hexadecimal :: Parser Integer) "")- where fromRight (Right a) = a- fromRight (Left x) = error (show x)+fromHex = fromRight . (parseMaybe (L.hexadecimal :: Parsec Void BS.ByteString Integer))+ where fromRight (Just a) = a+ fromRight _ = error "failed to parse hex"
src/Web/Tweet/Utils.hs view
@@ -15,11 +15,12 @@ import qualified Data.ByteString.Char8 as BS import Data.List import Data.List.Extra-import Web.Tweet.Parser.FastParser hiding (text)+import Data.Void+import Text.Megaparsec import Web.Tweet.Parser+import Web.Tweet.Parser.FastParser hiding (text) import Web.Tweet.Types import Web.Tweet.Utils.Colors-import Text.Megaparsec -- | filter out retweets, and sort by most successful. hits :: Timeline -> Timeline@@ -41,9 +42,9 @@ filterQuotes :: Timeline -> Timeline filterQuotes = filter ((==Nothing) . (view quoted)) --- | Get a list of tweets from a response, returning author, favorites, retweets, and content. -getTweets :: BS2.ByteString -> Either (ParseError Char Dec) Timeline-getTweets = parse parseTweet "" +-- | Get a list of tweets from a response, returning author, favorites, retweets, and content.+getTweets :: BS2.ByteString -> Either (ParseError Char 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.
stack.yaml view
@@ -1,39 +1,66 @@-resolver: lts-8.19+# This file was automatically generated by 'stack init'+#+# Some commonly used options have been documented as comments in this file.+# For advanced use and comprehensive documentation of the format, please see:+# https://docs.haskellstack.org/en/stable/yaml_configuration/++# Resolver to choose a 'specific' stackage snapshot or a compiler version.+# A snapshot resolver dictates the compiler version and the set of packages+# to be used for project dependencies. For example:+#+# resolver: lts-3.5+# resolver: nightly-2015-09-21+# resolver: ghc-7.10.2+# resolver: ghcjs-0.1.0_ghc-7.10.2+# resolver:+# name: custom-snapshot+# location: "./custom-snapshot.yaml"+resolver: lts-9.0++# User packages to be built.+# Various formats can be used as shown in the example below.+#+# packages:+# - some-directory+# - https://example.com/foo/bar/baz-0.0.2.tar.gz+# - location:+# git: https://github.com/commercialhaskell/stack.git+# commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a+# - location: https://github.com/commercialhaskell/stack/commit/e7b331f14bcffb8367cd58fbfc8b40ec7642100a+# extra-dep: true+# subdirs:+# - auto-update+# - wai+#+# A package marked 'extra-dep: true' will only be built if demanded by a+# non-dependency (i.e. a user package), and its test suites and benchmarks+# will not be run. This is useful for tweaking upstream packages. packages:- - .- - extra-dep: true- location:- git: https://github.com/haskell/deepseq- commit: 0b22c9825ef79c1ee41d2f19e7c997f5cdc93494- - extra-dep: true- location:- git: https://github.com/ekmett/semigroupoids.git- commit: c3297f970658ae874db098190327ec742044a2e6- - extra-dep: true- location:- git: http://github.com/ekmett/lens.git- commit: 7031e00f62a704a86c3149c75c1e2afc059af022- - extra-dep: true- location:- git: https://github.com/dreixel/syb.git- commit: f584ecd525179778063df2eb02dc6bbe406d7e75-flags:- tweet-hs:- llvm-fast: true- parallel-gc: false-ghc-options:- tweet-hs: -fdiagnostics-color=always-allow-newer: true-compiler: ghc-8.2.0.20170507-compiler-check: match-exact-setup-info:- ghc:- linux64:- 8.2.0.20170507:- url: https://downloads.haskell.org/~ghc/8.2.1-rc2/ghc-8.2.0.20170507-x86_64-deb8-linux.tar.xz- macosx:- 8.2.0.20170507:- url: https://downloads.haskell.org/~ghc/8.2.1-rc2/ghc-8.2.0.20170507-x86_64-apple-darwin.tar.xz- windows64:- 8.2.0.20170507:- url: https://downloads.haskell.org/~ghc/8.2.1-rc2/ghc-8.2.0.20170507-x86_64-unknown-mingw32.tar.xz+- .+# Dependency packages to be pulled from upstream that are not in the resolver+# (e.g., acme-missiles-0.3)+extra-deps: []++# Override default flag values for local packages and extra-deps+flags: {}++# Extra package databases containing global packages+extra-package-dbs: []++# Control whether we use the GHC we find on the path+# system-ghc: true+#+# Require a specific version of stack, using version ranges+# require-stack-version: -any # Default+# require-stack-version: ">=1.5"+#+# Override the architecture used by stack, especially useful on Windows+# arch: i386+# arch: x86_64+#+# Extra directories used by stack for building+# extra-include-dirs: [/path/to/dir]+# extra-lib-dirs: [/path/to/dir]+#+# Allow a newer minor version of GHC than the snapshot specifies+# compiler-check: newer-minor
test/Spec.hs view
@@ -2,28 +2,19 @@ import qualified Data.ByteString as BS import Test.Hspec-import Test.Hspec.Megaparsec import Web.Tweet.Parser.FastParser-import Web.Tweet.Parser import Web.Tweet.Sign import Data.Monoid-import Text.Megaparsec import System.Environment import Test.QuickCheck main :: IO () main = hspec $ do- describe "aeson parser" $ do- file <- runIO $ BS.readFile "test/data"- parallel $ it "parses sample tweets wrong" $ do- fastParse "" `shouldBe` Left "Error in $: not enough input"- describe "handrolled parser" $ do+ describe "fastParse" $ do file <- runIO $ BS.readFile "test/data"- parallel $ it "parses sample tweets right" $ do- parse parseTweet "test suite" `shouldSucceedOn` file- describe "config parser" $ do config <- runIO $ (<> "/.cred") <$> getEnv "HOME" configToml <- runIO $ (<> "/.cred.toml") <$> getEnv "HOME"+ parallel $ it "parses sample tweets wrong" $ do+ 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,5 +1,5 @@ name: tweet-hs-version: 1.0.0.1+version: 1.0.1.0 synopsis: Command-line tool for twitter description: a Command Line Interface Tweeter homepage: https://github.com/vmchale/command-line-tweeter#readme@@ -53,7 +53,7 @@ , http-client , http-types , authenticate-oauth- , megaparsec+ , megaparsec >= 6.0 , bytestring , split , optparse-applicative @@ -120,8 +120,6 @@ , hspec , QuickCheck , bytestring- , hspec-megaparsec- , megaparsec if flag(gold) ghc-options: -optl-fuse-ld=gold ld-options: -fuse-ld=gold