DEV Community

Riccardo Odone
Riccardo Odone

Posted on • Updated on • Originally published at odone.io

Tweeting a Blog Post via command line

You can keep reading here or jump to my blog to get the full experience, including the wonderful pink, blue and white palette.


In the previous post we have seen how to scaffold a blog post with a Haskell script. Today, we are going to automate tweeting.

The heart of the script is the tweet function which uses:

tweet :: String -> FilePath -> IO ()
tweet creds path =
--          ^ Blog post we want to tweet about.
--    ^ Twitter API credentials.
  parseYamlFrontmatter <$> Data.ByteString.readFile path
--                         ^ Read the blog post.
-- ^ Parse the frontmatter of the blog post.
    >>= \case
      Done _post frontmatter ->
--    ^ If the frontmatter was parsed successfully..
        basicTweet (mkTweet path frontmatter) creds >> pure ()
--      ^ ..then tweet..
      e ->
        error $ show e
--      ^ ..else stop execution and display the error `e`.
Enter fullscreen mode Exit fullscreen mode

The content of the tweet comes from mkTweet:

data Front =
  Front
    { title :: String
    , description :: String
    , tags :: [String]
    } deriving (Show, Generic, FromJSON)

mkTweet :: FilePath -> Front -> String
mkTweet path Front{..} = fold [title, " đź“’ ", description, "\n\n", htags, "\n\n", url]
--                ^ Same as `{ title = title, description = description, tags = tags }`
--                  but using two characters ;)
--                       ^ Fold strings to a single one.
--                         See previous post for a more sophisticated explanation!
  where
    base = "https://odone.io/posts"
    name = System.FilePath.Posix.takeBaseName path
    url = fold [base, "/", name, ".html"]
--  ^ Address to the blog post on odone.io.
    htags = Data.List.intercalate " " $ fmap ('#':) tags
--  ^ List of hashtags generated by appending '#'.
      in front of each tag coming from the frontmatter.
Enter fullscreen mode Exit fullscreen mode

There's still one piece missing. We want to pass as input the credentials for Twitter and the path to the blog post we want to tweet about. This is super eazyly done using optparse-applicative. Its readme is awesome, so please refer to that to learn more.

data Opts =
--   ^ The input we expect from the command line.
  Opts
    { creds :: String
    , post :: String
    }

main :: IO ()
main = do
  cs <- fmap (<> "/.cred.toml") getHomeDirectory
-- ^ Default path to the Twitter credentials file. See tweet-hs's docs for more info.
  execParser (opts cs) >>= (\Opts{..} -> tweet creds post)
-- ^ Parse the command line input and..
--  ..if successful then call `tweet`..
--  ..else show an error message and a summary on how to use the script correctly.
  where
    opts cs = info (parser cs <**> helper)
      (  fullDesc
      <> progDesc "Shares on Twitter a new blog POST using CREDS for authentication"
      )

parser :: FilePath -> Options.Applicative.Parser Opts
parser creds = Opts
      <$> strOption
         (  long "creds"
         <> metavar "CREDS"
         <> help "Path to creds .toml file"
         <> value creds
--          ^ `--creds` is optional and will use `~/.cred.toml` if not passed as input.
         <> showDefault
         )
      <*> argument str
--        ^ `post` is mandatory and the only argument to the script.
         (  metavar "POST"
         <> help "Path to blog post file"
         )
Enter fullscreen mode Exit fullscreen mode

With that in place, calling the script without the mandatory argument gets us:

$ ./tweet.hs
#
# Missing: POST
#
# Usage: tweet.hs [--creds CREDS] POST
#   Shares on Twitter a new blog POST using CREDS for authentication
Enter fullscreen mode Exit fullscreen mode

We can also call it with --help to get a detailed explanation:

$ ./tweet.hs --help
#
# Usage: tweet.hs [--creds CREDS] POST
#   Shares on Twitter a new blog POST using CREDS for authentication
#
# Available options:
#   --creds CREDS            Path to creds .toml
#                            file (default: "/Users/rysiek/.cred.toml")
#   POST                     Path to blog post file
#   -h,--help                Show this help text
Enter fullscreen mode Exit fullscreen mode

Instead, a proper call (e.g. ./tweet.hs posts/2019-12-26-scaffolding-a-blog-post.md) would tweet successfully:

Scaffolding a Blog Post 📒 Using a Haskell script to bootstrap a file from a template#FunctionalProgramming #Haskell #Scripthttps://t.co/lSaeHXw6EU

— Riccardo Odone (@riccardoodone) December 26, 2019

The whole script can be found on Github.


Get the latest content via email from me personally. Reply with your thoughts. Let's learn from each other. Subscribe to my PinkLetter!

Top comments (0)