datepicker

An fzf-like tool to interactively select a date in a provided format

git clone https://git.8pit.net/datepicker.git

  1{-# LANGUAGE CPP #-}
  2
  3module Main where
  4
  5import Control.Exception (throwIO)
  6import Data.Time.Calendar qualified as Cal
  7import Data.Time.LocalTime
  8  ( LocalTime (LocalTime),
  9    ZonedTime (ZonedTime),
 10    getCurrentTimeZone,
 11    getZonedTime,
 12    localDay,
 13    zonedTimeToLocalTime,
 14  )
 15import DatePicker.CmdLine
 16  ( getCmdArgs,
 17    getTime,
 18    optDuration,
 19    optFormat,
 20    optLogical,
 21    optMonday,
 22    optNoTime,
 23    optSelect,
 24    optTime,
 25    optsPeriod,
 26  )
 27import DatePicker.UI qualified as UI
 28import DatePicker.UI.Month qualified as M
 29import DatePicker.UI.Time qualified as T
 30import DatePicker.Util (format, parseTime)
 31import Graphics.Vty qualified as V
 32import Graphics.Vty.Input.Events qualified as E
 33import Graphics.Vty.Platform.Unix (mkVtyWithSettings)
 34import Graphics.Vty.Platform.Unix.Settings qualified as VU
 35import System.Posix.IO (OpenMode (ReadWrite), defaultFileFlags, openFd)
 36
 37isTerm :: E.Event -> Bool
 38isTerm (E.EvKey key _) =
 39  key == E.KEsc || key == E.KChar 'q'
 40isTerm _ = False
 41
 42-- Make sure we read and write to /dev/tty instead of relying on stdin/stdout.
 43-- This allows using datepicker within pipes where stdin/stdout is redirected.
 44unixSettings :: IO VU.UnixSettings
 45unixSettings = do
 46#if MIN_VERSION_unix(2,8,0)
 47  ttyFd <- openFd "/dev/tty" ReadWrite defaultFileFlags
 48#else
 49  ttyFd <- openFd "/dev/tty" ReadWrite Nothing defaultFileFlags
 50#endif
 51
 52  -- Can't build upon defaultSettings here as it flushes standard input and
 53  -- if standard input is a pipe it may not necessarily be flushable.
 54  mb <- VU.currentTerminalName
 55  case mb of
 56    Nothing -> throwIO VU.MissingTermEnvVar
 57    Just t -> do
 58      return $
 59        VU.UnixSettings
 60          { VU.settingVmin = 1,
 61            VU.settingVtime = 100,
 62            VU.settingInputFd = ttyFd,
 63            VU.settingOutputFd = ttyFd,
 64            VU.settingTermName = t
 65          }
 66
 67main :: IO ()
 68main = do
 69  args <- getCmdArgs
 70  let dateFmt = optFormat args
 71
 72  localTime <- zonedTimeToLocalTime <$> getZonedTime
 73  today <- case optTime args of
 74    Nothing -> pure $ localDay localTime
 75    Just it -> getTime localTime it
 76  selected <- case optSelect args of
 77    Nothing -> pure today
 78    Just sd -> parseTime False dateFmt sd
 79
 80  let range = optsPeriod (optDuration args) today
 81      mview =
 82        M.mkMonthView
 83          range
 84          selected
 85          (if optMonday args then Cal.Monday else Cal.Sunday)
 86          (optLogical args)
 87  view <- case mview of
 88    Nothing -> fail "specified date is not in displayed range"
 89    Just x -> pure x
 90
 91  vty <- unixSettings >>= mkVtyWithSettings V.defaultConfig
 92  lt@(LocalTime date _) <- UI.showView view isTerm vty
 93
 94  timeZone <- getCurrentTimeZone
 95  let mkZonedTime local = ZonedTime local timeZone
 96
 97  if optNoTime args
 98    then V.shutdown vty >> putStrLn (format dateFmt $ mkZonedTime lt)
 99    else do
100      (LocalTime _ nowTime) <- zonedTimeToLocalTime <$> getZonedTime
101      (LocalTime _ time) <- UI.showView (T.mkTimeView nowTime lt) isTerm vty
102
103      let res = LocalTime date time
104      V.shutdown vty >> putStrLn (format dateFmt $ mkZonedTime res)