{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
module Hledger.Cli.Commands.Demo (
demomode
,demo
) where
import Text.Printf
import Control.Concurrent (threadDelay)
import System.Process (callProcess)
import System.IO.Error (catchIOError)
import Safe (readMay, atMay, headMay)
import Data.List (isPrefixOf, find, findIndex, isInfixOf, dropWhileEnd)
import Control.Applicative ((<|>))
import Data.ByteString as B (ByteString)
import Data.Maybe
import Data.ByteString.Char8 qualified as B
import Safe (tailMay)
import System.IO.Temp (withSystemTempFile)
import System.IO (hClose)
import System.Console.CmdArgs.Explicit (flagReq)
import Hledger
import Hledger.Cli.CliOptions
import System.Directory (findExecutable)
import Control.Monad (when)
demos :: [Demo]
demos :: [Demo]
demos = (ByteString -> Demo) -> [ByteString] -> [Demo]
forall a b. (a -> b) -> [a] -> [b]
map ByteString -> Demo
readDemo [
$(embedFileRelative "embeddedfiles/add.cast"),
$(embedFileRelative "embeddedfiles/print.cast"),
$(embedFileRelative "embeddedfiles/balance.cast"),
$(embedFileRelative "embeddedfiles/install.cast")
]
data Demo = Demo {
Demo -> [Char]
dtitle :: String,
Demo -> ByteString
_dcontent :: ByteString
}
demomode :: Mode RawOpts
demomode = [Char]
-> [Flag RawOpts]
-> [([Char], [Flag RawOpts])]
-> [Flag RawOpts]
-> ([Arg RawOpts], Maybe (Arg RawOpts))
-> Mode RawOpts
hledgerCommandMode
$(embedFileRelative "Hledger/Cli/Commands/Demo.txt")
[
[[Char]] -> Update RawOpts -> [Char] -> [Char] -> Flag RawOpts
forall a. [[Char]] -> Update a -> [Char] -> [Char] -> Flag a
flagReq [[Char]
"speed",[Char]
"s"] (\[Char]
s RawOpts
opts -> RawOpts -> Either [Char] RawOpts
forall a b. b -> Either a b
Right (RawOpts -> Either [Char] RawOpts)
-> RawOpts -> Either [Char] RawOpts
forall a b. (a -> b) -> a -> b
$ [Char] -> [Char] -> RawOpts -> RawOpts
setopt [Char]
"speed" [Char]
s RawOpts
opts) [Char]
"SPEED"
([Char]
"playback speed (1 is original speed, .5 is half, 2 is double, etc (default: 2))")
]
[([Char], [Flag RawOpts])
generalflagsgroup3]
[]
([], Arg RawOpts -> Maybe (Arg RawOpts)
forall a. a -> Maybe a
Just (Arg RawOpts -> Maybe (Arg RawOpts))
-> Arg RawOpts -> Maybe (Arg RawOpts)
forall a b. (a -> b) -> a -> b
$ [Char] -> Arg RawOpts
argsFlag [Char]
optsstr)
optsstr :: [Char]
optsstr = [Char]
"[NUM|PREFIX|SUBSTR]"
usagestr :: [Char]
usagestr = [Char]
"Usage: hledger demo " [Char] -> [Char] -> [Char]
forall a. Semigroup a => a -> a -> a
<> [Char]
optsstr
demo :: CliOpts -> Journal -> IO ()
demo :: CliOpts -> Journal -> IO ()
demo CliOpts{rawopts_ :: CliOpts -> RawOpts
rawopts_=RawOpts
rawopts, reportspec_ :: CliOpts -> ReportSpec
reportspec_=ReportSpec{_rsQuery :: ReportSpec -> Query
_rsQuery=Query
_query}} Journal
_j = do
case [Char] -> RawOpts -> [[Char]]
listofstringopt [Char]
"args" RawOpts
rawopts of
[] -> [Char] -> IO ()
putStrLn [Char]
usagestr IO () -> IO () -> IO ()
forall a b. IO a -> IO b -> IO b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> [Char] -> IO ()
putStr [Char]
listDemos
([Char]
a:[[Char]]
as) ->
case [Demo] -> [Char] -> Maybe Demo
findDemo [Demo]
demos [Char]
a of
Maybe Demo
Nothing -> [Char] -> IO ()
forall a. [Char] -> a
error' ([Char] -> IO ()) -> [Char] -> IO ()
forall a b. (a -> b) -> a -> b
$ [[Char]] -> [Char]
unlines
[[Char]
"No demo \"" [Char] -> [Char] -> [Char]
forall a. Semigroup a => a -> a -> a
<> [Char]
a [Char] -> [Char] -> [Char]
forall a. Semigroup a => a -> a -> a
<> [Char]
"\" was found."
,[Char]
usagestr
,[Char]
listDemos
]
Just (Demo [Char]
t ByteString
c) -> do
masciinema <- [Char] -> IO (Maybe [Char])
findExecutable [Char]
"asciinema"
when (isNothing masciinema) $ error' "Could not find 'asciinema'; please install that first."
let
defidlelimit = Float
10
defspeed = Float
2
speed =
case [Char] -> RawOpts -> Maybe [Char]
maybestringopt [Char]
"speed" RawOpts
rawopts of
Maybe [Char]
Nothing -> Float
defspeed
Just [Char]
s -> Float -> Maybe Float -> Float
forall a. a -> Maybe a -> a
fromMaybe Float
forall {a}. a
err (Maybe Float -> Float) -> Maybe Float -> Float
forall a b. (a -> b) -> a -> b
$ [Char] -> Maybe Float
forall a. Read a => [Char] -> Maybe a
readMay [Char]
s
where err :: a
err = [Char] -> a
forall a. [Char] -> a
error' ([Char] -> a) -> [Char] -> a
forall a b. (a -> b) -> a -> b
$ [Char]
"could not parse --speed " [Char] -> [Char] -> [Char]
forall a. Semigroup a => a -> a -> a
<> [Char]
s [Char] -> [Char] -> [Char]
forall a. Semigroup a => a -> a -> a
<> [Char]
", numeric argument expected"
idx = Int -> (Int -> Int) -> Maybe Int -> Int
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Int
0 (Int
1Int -> Int -> Int
forall a. Num a => a -> a -> a
+) (Maybe Int -> Int) -> Maybe Int -> Int
forall a b. (a -> b) -> a -> b
$ (Demo -> Bool) -> [Demo] -> Maybe Int
forall a. (a -> Bool) -> [a] -> Maybe Int
findIndex (\(Demo [Char]
t2 ByteString
_) -> [Char]
t2 [Char] -> [Char] -> Bool
forall a. Eq a => a -> a -> Bool
== [Char]
t) [Demo]
demos
mw <- getTerminalWidth
let line = [Char] -> [Char]
red' ([Char] -> [Char]) -> [Char] -> [Char]
forall a b. (a -> b) -> a -> b
$ Int -> Char -> [Char]
forall a. Int -> a -> [a]
replicate Int
w Char
'.' where w :: Int
w = Int -> Maybe Int -> Int
forall a. a -> Maybe a -> a
fromMaybe ([Char] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Char]
t) Maybe Int
mw
printf "playing: %d) %s\nspace to pause, . to step, ctrl-c to quit\n" idx (bold' t)
putStrLn line
putStrLn ""
threadDelay 1000000
runAsciinemaPlay speed defidlelimit c as
putStrLn ""
putStrLn line
readDemo :: ByteString -> Demo
readDemo :: ByteString -> Demo
readDemo ByteString
content = [Char] -> ByteString -> Demo
Demo [Char]
title ByteString
content
where
title :: [Char]
title = [Char] -> (ByteString -> [Char]) -> Maybe ByteString -> [Char]
forall b a. b -> (a -> b) -> Maybe a -> b
maybe [Char]
"" ([Char] -> [Char]
readTitle ([Char] -> [Char])
-> (ByteString -> [Char]) -> ByteString -> [Char]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> [Char]
B.unpack) (Maybe ByteString -> [Char]) -> Maybe ByteString -> [Char]
forall a b. (a -> b) -> a -> b
$ [ByteString] -> Maybe ByteString
forall a. [a] -> Maybe a
headMay ([ByteString] -> Maybe ByteString)
-> [ByteString] -> Maybe ByteString
forall a b. (a -> b) -> a -> b
$ ByteString -> [ByteString]
B.lines ByteString
content
where
readTitle :: [Char] -> [Char]
readTitle [Char]
s
| [Char]
"\"title\":" [Char] -> [Char] -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` [Char]
s = (Char -> Bool) -> [Char] -> [Char]
forall a. (a -> Bool) -> [a] -> [a]
takeWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/=Char
'"') ([Char] -> [Char]) -> [Char] -> [Char]
forall a b. (a -> b) -> a -> b
$ Int -> [Char] -> [Char]
forall a. Int -> [a] -> [a]
drop Int
1 ([Char] -> [Char]) -> [Char] -> [Char]
forall a b. (a -> b) -> a -> b
$ [Char] -> [Char]
lstrip ([Char] -> [Char]) -> [Char] -> [Char]
forall a b. (a -> b) -> a -> b
$ Int -> [Char] -> [Char]
forall a. Int -> [a] -> [a]
drop Int
8 [Char]
s
| Bool
otherwise = [Char] -> ([Char] -> [Char]) -> Maybe [Char] -> [Char]
forall b a. b -> (a -> b) -> Maybe a -> b
maybe [Char]
"" [Char] -> [Char]
readTitle (Maybe [Char] -> [Char]) -> Maybe [Char] -> [Char]
forall a b. (a -> b) -> a -> b
$ [Char] -> Maybe [Char]
forall a. [a] -> Maybe [a]
tailMay [Char]
s
findDemo :: [Demo] -> String -> Maybe Demo
findDemo :: [Demo] -> [Char] -> Maybe Demo
findDemo [Demo]
ds [Char]
s =
([Char] -> Maybe Int
forall a. Read a => [Char] -> Maybe a
readMay [Char]
s Maybe Int -> (Int -> Maybe Demo) -> Maybe Demo
forall a b. Maybe a -> (a -> Maybe b) -> Maybe b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= [Demo] -> Int -> Maybe Demo
forall a. [a] -> Int -> Maybe a
atMay [Demo]
ds (Int -> Maybe Demo) -> (Int -> Int) -> Int -> Maybe Demo
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> Int -> Int
forall a. Num a => a -> a -> a
subtract Int
1)
Maybe Demo -> Maybe Demo -> Maybe Demo
forall a. Maybe a -> Maybe a -> Maybe a
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> (Demo -> Bool) -> [Demo] -> Maybe Demo
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find (([Char]
sl [Char] -> [Char] -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf`)([Char] -> Bool) -> (Demo -> [Char]) -> Demo -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
.[Char] -> [Char]
lowercase([Char] -> [Char]) -> (Demo -> [Char]) -> Demo -> [Char]
forall b c a. (b -> c) -> (a -> b) -> a -> c
.Demo -> [Char]
dtitle) [Demo]
ds
Maybe Demo -> Maybe Demo -> Maybe Demo
forall a. Maybe a -> Maybe a -> Maybe a
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> (Demo -> Bool) -> [Demo] -> Maybe Demo
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find (([Char]
sl [Char] -> [Char] -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf`) ([Char] -> Bool) -> (Demo -> [Char]) -> Demo -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
.[Char] -> [Char]
lowercase([Char] -> [Char]) -> (Demo -> [Char]) -> Demo -> [Char]
forall b c a. (b -> c) -> (a -> b) -> a -> c
.Demo -> [Char]
dtitle) [Demo]
ds
where
sl :: [Char]
sl = [Char] -> [Char]
lowercase [Char]
s
listDemos :: String
listDemos :: [Char]
listDemos = [[Char]] -> [Char]
unlines ([[Char]] -> [Char]) -> [[Char]] -> [Char]
forall a b. (a -> b) -> a -> b
$
[Char]
"Demos:" [Char] -> [[Char]] -> [[Char]]
forall a. a -> [a] -> [a]
:
[Int -> [Char]
forall a. Show a => a -> [Char]
show Int
i [Char] -> [Char] -> [Char]
forall a. Semigroup a => a -> a -> a
<> [Char]
") " [Char] -> [Char] -> [Char]
forall a. Semigroup a => a -> a -> a
<> [Char] -> [Char]
bold' [Char]
t | (Int
i, Demo [Char]
t ByteString
_) <- [Int] -> [Demo] -> [(Int, Demo)]
forall a b. [a] -> [b] -> [(a, b)]
zip [(Int
1::Int)..] [Demo]
demos]
runAsciinemaPlay :: Float -> Float -> ByteString -> [String] -> IO ()
runAsciinemaPlay :: Float -> Float -> ByteString -> [[Char]] -> IO ()
runAsciinemaPlay Float
speed Float
idlelimit ByteString
content [[Char]]
args = do
[Char] -> ([Char] -> Handle -> IO ()) -> IO ()
forall (m :: * -> *) a.
(MonadIO m, MonadMask m) =>
[Char] -> ([Char] -> Handle -> m a) -> m a
withSystemTempFile [Char]
"hledger-cast" (([Char] -> Handle -> IO ()) -> IO ())
-> ([Char] -> Handle -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ \[Char]
f Handle
h -> do
Handle -> ByteString -> IO ()
B.hPutStr Handle
h ByteString
content IO () -> IO () -> IO ()
forall a b. IO a -> IO b -> IO b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> Handle -> IO ()
hClose Handle
h
[Char] -> [[Char]] -> IO ()
callProcess [Char]
"asciinema" (([[Char]] -> [Char]) -> [[Char]] -> [[Char]]
forall a. (a -> [Char]) -> a -> a
dbg8With (([Char]
"asciinema: "[Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++)([Char] -> [Char]) -> ([[Char]] -> [Char]) -> [[Char]] -> [Char]
forall b c a. (b -> c) -> (a -> b) -> a -> c
.[[Char]] -> [Char]
unwords) ([[Char]] -> [[Char]]) -> [[Char]] -> [[Char]]
forall a b. (a -> b) -> a -> b
$ [[[Char]]] -> [[Char]]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [
[[Char]
"play"]
,[[Char]
"-s"[Char] -> [Char] -> [Char]
forall a. Semigroup a => a -> a -> a
<> Float -> [Char]
showwithouttrailingzero Float
speed]
,if Float
idlelimit Float -> Float -> Bool
forall a. Eq a => a -> a -> Bool
== Float
0 then [] else [[Char]
"-i"[Char] -> [Char] -> [Char]
forall a. Semigroup a => a -> a -> a
<>Float -> [Char]
showwithouttrailingzero Float
idlelimit]
,[[Char]
f]
,[[Char]]
args
])
IO () -> (IOError -> IO ()) -> IO ()
forall a. IO a -> (IOError -> IO a) -> IO a
`catchIOError` \IOError
err -> do
[Char] -> IO ()
printError ([Char] -> IO ()) -> [Char] -> IO ()
forall a b. (a -> b) -> a -> b
$ [[Char]] -> [Char]
unlines
[[Char]
""
,IOError -> [Char]
forall a. Show a => a -> [Char]
show IOError
err
,[Char]
"Running asciinema failed. Trying 'asciinema --version':"
]
[Char] -> [[Char]] -> IO ()
callProcess [Char]
"asciinema" [[Char]
"--version"]
IO () -> (IOError -> IO ()) -> IO ()
forall a. IO a -> (IOError -> IO a) -> IO a
`catchIOError` \IOError
_ -> [Char] -> IO ()
forall a. [Char] -> a
error' [Char]
"This also failed."
where
showwithouttrailingzero :: Float -> [Char]
showwithouttrailingzero = (Char -> Bool) -> [Char] -> [Char]
forall a. (a -> Bool) -> [a] -> [a]
dropWhileEnd (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
==Char
'.') ([Char] -> [Char]) -> (Float -> [Char]) -> Float -> [Char]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Char -> Bool) -> [Char] -> [Char]
forall a. (a -> Bool) -> [a] -> [a]
dropWhileEnd (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
==Char
'0') ([Char] -> [Char]) -> (Float -> [Char]) -> Float -> [Char]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Float -> [Char]
forall a. Show a => a -> [Char]
show