patat 0.9.0.0 → 0.9.2.0
raw patch · 8 files changed
+175/−52 lines, 8 filesdep ~skylightingPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependency ranges changed: skylighting
API changes (from Hackage documentation)
+ Patat.EncodingFallback: NoFallback :: EncodingFallback
+ Patat.EncodingFallback: Utf8Fallback :: EncodingFallback
+ Patat.EncodingFallback: data EncodingFallback
+ Patat.EncodingFallback: instance GHC.Classes.Eq Patat.EncodingFallback.EncodingFallback
+ Patat.EncodingFallback: instance GHC.Show.Show Patat.EncodingFallback.EncodingFallback
+ Patat.EncodingFallback: readFile :: FilePath -> IO (EncodingFallback, Text)
+ Patat.EncodingFallback: withHandle :: Handle -> EncodingFallback -> IO a -> IO a
+ Patat.Presentation: [pEncodingFallback] :: Presentation -> !EncodingFallback
+ Patat.Presentation.Internal: [pEncodingFallback] :: Presentation -> !EncodingFallback
- Patat.Presentation: Presentation :: !FilePath -> ![Inline] -> ![Inline] -> !PresentationSettings -> !Seq Slide -> !Seq Breadcrumbs -> !Index -> !SyntaxMap -> Presentation
+ Patat.Presentation: Presentation :: !FilePath -> !EncodingFallback -> ![Inline] -> ![Inline] -> !PresentationSettings -> !Seq Slide -> !Seq Breadcrumbs -> !Index -> !SyntaxMap -> Presentation
- Patat.Presentation.Internal: Presentation :: !FilePath -> ![Inline] -> ![Inline] -> !PresentationSettings -> !Seq Slide -> !Seq Breadcrumbs -> !Index -> !SyntaxMap -> Presentation
+ Patat.Presentation.Internal: Presentation :: !FilePath -> !EncodingFallback -> ![Inline] -> ![Inline] -> !PresentationSettings -> !Seq Slide -> !Seq Breadcrumbs -> !Index -> !SyntaxMap -> Presentation
- Patat.Presentation.SpeakerNotes: write :: Handle -> SpeakerNotes -> IO ()
+ Patat.Presentation.SpeakerNotes: write :: Handle -> EncodingFallback -> SpeakerNotes -> IO ()
Files
- CHANGELOG.md +29/−0
- README.md +25/−22
- lib/Patat/EncodingFallback.hs +56/−0
- lib/Patat/Main.hs +13/−6
- lib/Patat/Presentation/Internal.hs +10/−8
- lib/Patat/Presentation/Read.hs +31/−11
- lib/Patat/Presentation/SpeakerNotes.hs +8/−3
- patat.cabal +3/−2
CHANGELOG.md view
@@ -1,5 +1,34 @@ # Changelog +## 0.9.2.0 (2023-09-26)++ * Read configuration from XDG standard directory (#146)++ The per-user patat configuration file was `$HOME/.patat.yaml`,+ which does not follow the XDG standard. We now support+ `$XDG_CONFIG_DIRECTORY/patat/config.yaml` (typically `$XDG_CONFIG_DIRECTORY`+ is set to `$HOME/.config`) which is compliant with the standard.++ Note that `$HOME/.patat.yaml` is still supported for backward-compatibility,+ but anything in `$XDG_CONFIG_DIRECTORY` takes precedence.++ * Support filenames in bash completion (#145) (#126)++## 0.9.1.0 (2023-09-25)++ * Fall back to forcing UTF-8 if decoding fails (#144) (#127)++ When we try to read a file that is encoded in UTF-8, and the system locale+ is not set to UTF-8, the GHC runtime system will throw an error.++ While this typically indicates that the user should update their system+ locale using e.g. the `LANG` environment variable, we want to provide a good+ initial experience for people unfamiliar with this, and in 2023 it's+ reasonable to assume files may be encoded in UTF-8.++ * Dependency updates:+ - Bump `skylighting` upper bound to 0.15 (#143)+ ## 0.9.0.0 (2023-09-13) * Add proper support for speaker notes (#142)
README.md view
@@ -1,34 +1,31 @@-patat-=====+🥔 patat+========  [](https://hackage.haskell.org/package/patat) []() -`patat` (**P**resentations **A**top **T**he **A**NSI **T**erminal) is a small-tool that allows you to show presentations using only an ANSI terminal. It does-not require `ncurses`.--Features:+`patat` (**P**resentations **A**top **T**he **A**NSI **T**erminal) is a+feature-rich presentation tool that runs in the terminal. -- Leverages the great [Pandoc] library to support many input formats including- [Literate Haskell].-- Supports [smart slide splitting](#input-format).-- Slides can be split up into [multiple fragments](#fragmented-slides)-- There is a [live reload](#running) mode.-- Supports [evaluating code snippets and showing the result](#evaluating-code).+- Understands most markdown extensions and many other input formats+ (rST, Org-mode...) by building on top of [Pandoc].+- [Evaluate code snippets and show the result](#evaluating-code).+- Syntax highlighting for nearly one hundred languages generated from [Kate]+ syntax files.+- [Automatically reload](#running) your slides as you edit them. - Display [speaker notes](#speaker-notes) in a second window or monitor.-- [Auto advancing](#auto-advancing) with configurable delay.+- [Incremental slide display](#fragmented-slides). - Experimental [images](#images) support.+- Supports [smart slide splitting](#input-format).+- [Auto advancing](#auto-advancing) with configurable delay. - Optionally [re-wrapping](#line-wrapping) text to terminal width with proper indentation.-- Syntax highlighting for nearly one hundred languages generated from [Kate]- syntax files. - [Theming](#theming) support including 24-bit RGB.-- Written in [Haskell].+- Hihgly portable as it only requires an ANSI terminal as opposed to+ something like `ncurses`.  [Kate]: https://kate-editor.org/-[Haskell]: http://haskell.org/ [Pandoc]: http://pandoc.org/ Table of Contents@@ -67,6 +64,7 @@ - Ubuntu: <https://packages.ubuntu.com/bionic/patat> - openSUSE: <https://build.opensuse.org/package/show/openSUSE:Factory:ARM/patat> - Fedora: <https://src.fedoraproject.org/rpms/patat>+- NixOS: <https://search.nixos.org/packages?show=haskellPackages.patat> You can also find generic Linux and Mac OS binaries here: <https://github.com/jaspervdj/patat/releases>.@@ -247,10 +245,14 @@ ------------- `patat` is fairly configurable. The configuration is done using [YAML]. There-are two places where you can put your configuration:+are several places where you can put your configuration. -1. In the presentation file itself, using the [Pandoc metadata header].-2. In `$HOME/.patat.yaml`+1. For per-user configuration you can use+ `$XDG_CONFIG_DIRECTORY/patat/config.yaml`+ (typically `$HOME/.config/patat/config.yaml`) or `$HOME/.patat.yaml`.+2. In the presentation file itself, using the [Pandoc metadata header].+ These settings take precedence over anything specified in the per-user+ configuration file. [YAML]: http://yaml.org/ [Pandoc metadata header]: http://pandoc.org/MANUAL.html#extension-yaml_metadata_block@@ -268,7 +270,8 @@ Hello world. ``` -Or we can use a normal presentation and have the following `$HOME/.patat.yaml`:+Or we can use a "plain" presentation and have the following+`$XDG_CONFIG_DIRECTORY/patat/config.yaml`: key: val
+ lib/Patat/EncodingFallback.hs view
@@ -0,0 +1,56 @@+-- | When we try to read a file that is encoded in UTF-8, and the system locale+-- is not set to UTF-8, the GHC runtime system will throw an error:+--+-- <https://github.com/jaspervdj/patat/issues/127>+--+-- However, we don't want to force people to use UTF-8 for everything. So what+-- we do is provide a replacement readFile, which first tries to read the file+-- in the system locale, and then falls back to forcing UTF-8.+--+-- If we forced UTF-8, we also want to propagate that to the output handle;+-- otherwise will get errors when we try to display these characters; so+-- withHandle should be used on the output handle (typically stdout).+module Patat.EncodingFallback+ ( EncodingFallback (..)+ , readFile+ , withHandle+ ) where+++--------------------------------------------------------------------------------+import Control.Exception (bracket, throwIO)+import Control.Monad (when)+import qualified Data.Text as T+import qualified Data.Text.IO as T+import Prelude hiding (readFile)+import qualified System.IO as IO+import qualified System.IO.Error as IO+++--------------------------------------------------------------------------------+data EncodingFallback = NoFallback | Utf8Fallback+ deriving (Eq, Show)+++--------------------------------------------------------------------------------+readFile :: FilePath -> IO (EncodingFallback, T.Text)+readFile path = IO.catchIOError readSystem $ \ioe -> do+ when (IO.isDoesNotExistError ioe) $ throwIO ioe -- Don't retry on these+ readUtf8+ where+ readSystem = ((,) NoFallback <$> T.readFile path)+ readUtf8 = IO.withFile path IO.ReadMode $ \h -> do+ IO.hSetEncoding h IO.utf8_bom+ (,) Utf8Fallback <$> T.hGetContents h+++--------------------------------------------------------------------------------+withHandle :: IO.Handle -> EncodingFallback -> IO a -> IO a+withHandle _ NoFallback mx = mx+withHandle h Utf8Fallback mx = bracket+ (do+ mbOld <- IO.hGetEncoding h+ IO.hSetEncoding h IO.utf8+ pure mbOld)+ (\mbOld -> traverse (IO.hSetEncoding h) mbOld)+ (\_ -> mx)
lib/Patat/Main.hs view
@@ -22,6 +22,7 @@ import qualified Options.Applicative as OA import qualified Options.Applicative.Help.Pretty as OA.PP import Patat.AutoAdvance+import qualified Patat.EncodingFallback as EncodingFallback import qualified Patat.Images as Images import Patat.Presentation import qualified Patat.Presentation.SpeakerNotes as SpeakerNotes@@ -51,6 +52,7 @@ parseOptions = Options <$> (OA.optional $ OA.strArgument $ OA.metavar "FILENAME" <>+ OA.action "file" <> -- For bash file completion OA.help "Input file") <*> (OA.switch $ OA.long "force" <>@@ -145,9 +147,11 @@ withMaybeHandle SpeakerNotes.with (psSpeakerNotes $ pSettings pres) $ \speakerNotes -> - if oDump options- then dumpPresentation pres- else interactiveLoop options images speakerNotes pres+ if oDump options then+ EncodingFallback.withHandle IO.stdout (pEncodingFallback pres) $+ dumpPresentation pres+ else+ interactiveLoop options images speakerNotes pres where interactiveLoop :: Options -> Maybe Images.Handle -> Maybe SpeakerNotes.Handle@@ -168,8 +172,9 @@ let loop :: Presentation -> Maybe String -> IO () loop pres mbError = do- for_ speakerNotes $ \sn ->- SpeakerNotes.write sn $ activeSpeakerNotes pres+ for_ speakerNotes $ \sn -> SpeakerNotes.write sn+ (pEncodingFallback pres)+ (activeSpeakerNotes pres) size <- getDisplaySize pres let display = case mbError of@@ -180,7 +185,9 @@ Ansi.clearScreen Ansi.setCursorPosition 0 0 cleanup <- case display of- DisplayDoc doc -> PP.putDoc doc $> mempty+ DisplayDoc doc -> EncodingFallback.withHandle+ IO.stdout (pEncodingFallback pres) $+ PP.putDoc doc $> mempty DisplayImage path -> case images of Nothing -> do PP.putDoc $ displayPresentationError
lib/Patat/Presentation/Internal.hs view
@@ -45,6 +45,7 @@ import Data.Sequence.Extended (Seq) import qualified Data.Sequence.Extended as Seq import qualified Data.Text as T+import Patat.EncodingFallback (EncodingFallback) import qualified Patat.Presentation.Instruction as Instruction import qualified Patat.Presentation.SpeakerNotes as SpeakerNotes import qualified Patat.Theme as Theme@@ -60,14 +61,15 @@ -------------------------------------------------------------------------------- data Presentation = Presentation- { pFilePath :: !FilePath- , pTitle :: ![Pandoc.Inline]- , pAuthor :: ![Pandoc.Inline]- , pSettings :: !PresentationSettings- , pSlides :: !(Seq Slide)- , pBreadcrumbs :: !(Seq Breadcrumbs) -- One for each slide.- , pActiveFragment :: !Index- , pSyntaxMap :: !Skylighting.SyntaxMap+ { pFilePath :: !FilePath+ , pEncodingFallback :: !EncodingFallback+ , pTitle :: ![Pandoc.Inline]+ , pAuthor :: ![Pandoc.Inline]+ , pSettings :: !PresentationSettings+ , pSlides :: !(Seq Slide)+ , pBreadcrumbs :: !(Seq Breadcrumbs) -- One for each slide.+ , pActiveFragment :: !Index+ , pSyntaxMap :: !Skylighting.SyntaxMap } deriving (Show)
lib/Patat/Presentation/Read.hs view
@@ -24,8 +24,9 @@ import qualified Data.Sequence.Extended as Seq import qualified Data.Text as T import qualified Data.Text.Encoding as T-import qualified Data.Text.IO as T import qualified Data.Yaml as Yaml+import Patat.EncodingFallback (EncodingFallback)+import qualified Patat.EncodingFallback as EncodingFallback import Patat.Eval (eval) import Patat.Presentation.Fragment import qualified Patat.Presentation.Instruction as Instruction@@ -33,8 +34,10 @@ import qualified Patat.Presentation.SpeakerNotes as SpeakerNotes import Prelude import qualified Skylighting as Skylighting-import System.Directory (doesFileExist,- getHomeDirectory)+import System.Directory (XdgDirectory (XdgConfig),+ doesFileExist,+ getHomeDirectory,+ getXdgDirectory) import System.FilePath (splitFileName, takeExtension, (</>)) import qualified Text.Pandoc.Error as Pandoc@@ -45,10 +48,15 @@ readPresentation :: FilePath -> IO (Either String Presentation) readPresentation filePath = runExceptT $ do -- We need to read the settings first.- src <- liftIO $ T.readFile filePath+ (enc, src) <- liftIO $ EncodingFallback.readFile filePath homeSettings <- ExceptT readHomeSettings+ xdgSettings <- ExceptT readXdgSettings metaSettings <- ExceptT $ return $ readMetaSettings src- let settings = metaSettings <> homeSettings <> defaultPresentationSettings+ let settings =+ metaSettings <>+ xdgSettings <>+ homeSettings <>+ defaultPresentationSettings syntaxMap <- ExceptT $ readSyntaxMap $ fromMaybe [] $ psSyntaxDefinitions settings@@ -61,7 +69,7 @@ Right x -> return x pres <- ExceptT $ pure $- pandocToPresentation filePath settings syntaxMap doc+ pandocToPresentation filePath enc settings syntaxMap doc liftIO $ eval pres where ext = takeExtension filePath@@ -112,10 +120,9 @@ -------------------------------------------------------------------------------- pandocToPresentation- :: FilePath -> PresentationSettings -> Skylighting.SyntaxMap- -> Pandoc.Pandoc- -> Either String Presentation-pandocToPresentation pFilePath pSettings pSyntaxMap+ :: FilePath -> EncodingFallback -> PresentationSettings+ -> Skylighting.SyntaxMap -> Pandoc.Pandoc -> Either String Presentation+pandocToPresentation pFilePath pEncodingFallback pSettings pSyntaxMap pandoc@(Pandoc.Pandoc meta _) = do let !pTitle = case Pandoc.docTitle meta of [] -> [Pandoc.Str . T.pack . snd $ splitFileName pFilePath]@@ -162,7 +169,20 @@ readHomeSettings :: IO (Either String PresentationSettings) readHomeSettings = do home <- getHomeDirectory- let path = home </> ".patat.yaml"+ readSettings $ home </> ".patat.yaml"+++--------------------------------------------------------------------------------+-- | Read settings from "$XDG_CONFIG_DIRECTORY/patat/config.yaml".+readXdgSettings :: IO (Either String PresentationSettings)+readXdgSettings =+ getXdgDirectory XdgConfig ("patat" </> "config.yaml") >>= readSettings+++--------------------------------------------------------------------------------+-- | Read settings from the specified path, if it exists.+readSettings :: FilePath -> IO (Either String PresentationSettings)+readSettings path = do exists <- doesFileExist path if not exists then return (Right mempty)
lib/Patat/Presentation/SpeakerNotes.hs view
@@ -25,7 +25,10 @@ import Data.List (intersperse) import qualified Data.Text as T import qualified Data.Text.IO as T+import Patat.EncodingFallback (EncodingFallback)+import qualified Patat.EncodingFallback as EncodingFallback import System.Directory (removeFile)+import qualified System.IO as IO import qualified Text.Pandoc as Pandoc @@ -95,10 +98,12 @@ ---------------------------------------------------------------------------------write :: Handle -> SpeakerNotes -> IO ()-write h sn = do+write :: Handle -> EncodingFallback -> SpeakerNotes -> IO ()+write h encodingFallback sn = do change <- IORef.atomicModifyIORef' (hActive h) $ \old -> (sn, old /= sn)- when change $ T.writeFile (sFile $ hSettings h) $ toText sn+ when change $ IO.withFile (sFile $ hSettings h) IO.WriteMode $ \ioh ->+ EncodingFallback.withHandle ioh encodingFallback $+ T.hPutStr ioh $ toText sn --------------------------------------------------------------------------------
patat.cabal view
@@ -1,5 +1,5 @@ Name: patat-Version: 0.9.0.0+Version: 0.9.2.0 Synopsis: Terminal-based presentations using Pandoc Description: Terminal-based presentations using Pandoc. License: GPL-2@@ -47,7 +47,7 @@ pandoc >= 3.1 && < 3.2, pandoc-types >= 1.23 && < 1.24, process >= 1.6 && < 1.7,- skylighting >= 0.10 && < 0.14,+ skylighting >= 0.10 && < 0.15, terminal-size >= 0.3 && < 0.4, text >= 1.2 && < 2.1, time >= 1.4 && < 1.13,@@ -66,6 +66,7 @@ Exposed-modules: Patat.AutoAdvance Patat.Cleanup+ Patat.EncodingFallback Patat.Eval Patat.Images Patat.Images.Internal