+ status_ids = map status_id statuses
+
+
+-- | Parse one username from a 'String'.
+--
+-- Examples:
+--
+-- >>> parse_username "@washington_irving"
+-- Just "washington_irving"
+-- >>> parse_username "washington_irving"
+-- Nothing
+-- >>> parse_username "Everbody loves @washington_irving, even Raymond"
+-- Just "washington_irving"
+--
+-- >>> parse_username "herp @@@ derp @washington_irving foo@@BAR"
+-- Just "washington_irving"
+--
+-- >>> parse_username "tailing at sign y'all @"
+-- Nothing
+--
+parse_username :: String -> Maybe String
+parse_username s
+ | null parse_result = Nothing
+ | otherwise = Just parse_result
+ where
+ -- | A list of characters valid in a Twitter username.
+ --
+ username_chars :: String
+ username_chars = ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ "_"
+
+ -- | Take a string and drop everything (including the \'@\') up to
+ -- the first character of the first username (if one exists).
+ --
+ start_name :: String -> String
+ start_name w =
+ case dropWhile (/= '@') w of
+ [] -> []
+ (_:xs) -> xs
+
+ parse_userchars :: String -> String
+ parse_userchars = takeWhile (`elem` username_chars)
+
+ -- | Parse a username from the given String by dropping all
+ -- characters that don't belong to it. This function calls
+ -- itself recursively until it gets a username or runs out of
+ -- string.
+ --
+ parse_name :: String -> String
+ parse_name [] = []
+ parse_name rest@(_:xs) =
+ let ucs = (parse_userchars . start_name) rest in
+ case ucs of
+ [] -> parse_name xs
+ _ -> ucs
+
+ parse_result :: String
+ parse_result = parse_name s
+
+
+-- | Parse all usernames of the form \@username from a status.
+--
+-- Examples:
+--
+-- >>> let u = User "washington_irving"
+-- >>> let b = "YO WHERE'S @BONUS500 and @@@ I LOVE @AT SIGNS@"
+-- >>> let s = Status Nothing 8675309 False False b u
+-- >>> parse_usernames_from_status s
+-- ["BONUS500","AT"]
+--
+parse_usernames_from_status :: Status -> [String]
+parse_usernames_from_status status =
+ mapMaybe parse_username status_words
+ where
+ status_words = splitWs (text status)
+
+
+-- | Get all referenced users' timeline URLs.
+--
+make_user_timeline_urls :: Status -> [String]
+make_user_timeline_urls status =
+ map screen_name_to_timeline_url usernames
+ where
+ usernames = parse_usernames_from_status status
+
+
+status_tests :: TestTree
+status_tests =
+ testGroup "Status Tests" [ test_parse_usernames ]
+
+
+test_parse_usernames :: TestTree
+test_parse_usernames =
+ testCase description $ actual @?= expected
+ where
+ description = "all usernames are parsed"
+
+ dummy_user = User { screen_name = "nobody" }
+ dummy_text = "Hypothesis: @donsbot and @bonus500 are two " ++
+ "personalities belonging to the same person."
+ dummy_status = Status { status_id = 1,
+ created_at = Nothing,
+ text = dummy_text,
+ user = dummy_user,
+ reply = False,
+ retweeted = False
+ }
+
+ actual = parse_usernames_from_status dummy_status
+ expected = ["donsbot", "bonus500"]