Browse Source

Documentation and small refactoring before first release

tags/v0.2.2.0
Peter J. Jones 4 years ago
parent
commit
e7677e308a

+ 8
- 0
CHANGES View File

@@ -0,0 +1,8 @@
-*- org -*-

#+title: Version History
#+startup: showall

* 0.1.0.0 (May 19, 2015)

- Initial release.

+ 17
- 0
README.md View File

@@ -0,0 +1,17 @@
# Byline -- Library for creating command-line interfaces (colors, menus, etc.)

Byline simplifies writing interactive terminal applications by
building upon [ansi-terminal][] and [haskeline][]. This makes it
possible to print messages and prompts that include terminal escape
sequences such as colors that are automatically disabled when standard
input is a file. It also means that Byline works on both
POSIX-compatible systems and on Windows.

The primary features of Byline include printing messages, prompting
for input, and generating custom menus. It was inspired by the
[highline] Ruby library and the [terminal library][] by Craig Roche.

[ansi-terminal]: http://hackage.haskell.org/package/ansi-terminal
[haskeline]: https://hackage.haskell.org/package/haskeline
[highline]: https://github.com/JEG2/highline
[terminal]: https://github.com/cdxr/terminal

+ 17
- 4
byline.cabal View File

@@ -14,12 +14,23 @@ build-type: Simple
stability: experimental
tested-with: GHC == 7.8.4
cabal-version: >=1.10
description: FIXME
description:
Byline simplifies writing interactive terminal applications by
building upon @ansi-terminal@ and @haskeline@. This makes it
possible to print messages and prompts that include terminal escape
sequences such as colors that are automatically disabled when
standard input is a file. It also means that Byline works on both
POSIX-compatible systems and on Windows.
.
The primary features of Byline include printing messages, prompting
for input, and generating custom menus. It was inspired by the
@highline@ Ruby library and the @terminal@ library by Craig Roche.

--------------------------------------------------------------------------------
extra-source-files:
CHANGES
README.md
examples/*.hs

--------------------------------------------------------------------------------
source-repository head
@@ -42,15 +53,17 @@ flag build-examples
library
exposed-modules:
System.Console.Byline
System.Console.Byline.Color
System.Console.Byline.Completion
System.Console.Byline.Internal.Byline
System.Console.Byline.Internal.Color
System.Console.Byline.Internal.Completion
System.Console.Byline.Internal.Modifiers
System.Console.Byline.Internal.Render
System.Console.Byline.Internal.Stylized
System.Console.Byline.Internal.Types
System.Console.Byline.Menu
System.Console.Byline.Primary
System.Console.Byline.Modifiers
System.Console.Byline.Primitive
System.Console.Byline.Stylized

hs-source-dirs: src
default-language: Haskell2010

+ 65
- 13
src/System/Console/Byline.hs View File

@@ -11,8 +11,52 @@ the LICENSE file.

--------------------------------------------------------------------------------
module System.Console.Byline
( -- * Primary Operations
say
( -- * Introduction
--
-- | Byline provides a monad transformer that allows you to compose
-- interactive terminal actions. When producing output,
-- these actions accept stylized text that can include
-- foreground and background colors, underlined text, and
-- bold text.
--
-- Stylized text can be constructed with string literals
-- (using the @OverloadedStrings@ extension) or using the
-- 'text' function. Attributes such as color can be changed
-- using modifier functions and the @mappend@ operator,
-- @(<>)@.
--
-- Actions that read user input can work with completion
-- functions which are activated when the user presses the
-- tab key. Most input actions also support default values
-- that will be returned when the user presses the enter key
-- without providing any input.
--
-- Example:
--
-- @
-- {-\# LANGUAGE OverloadedStrings \#-}
--
-- ...
--
-- language <- runByline $ do
-- sayLn ("Look mom, " <> ("colors" <> fg blue) <> "!")
--
-- let question = "What's your favorite " <>
-- ("language" <> bold) <> "? "
--
-- ask question Nothing
-- @
--
-- More complete examples can be found in the @examples@
-- directory of the distribution tarball or in the
-- repository.

-- * Executing Interactive Sessions
Byline
, runByline

-- * Primitive Operations
, say
, sayLn
, ask
, askChar
@@ -22,15 +66,28 @@ module System.Console.Byline
, reportLn

-- * Constructing Stylized Text
, Stylized
, text

-- * Modifying Output Text

-- | The 'Stylized' type is an instance of the monoid class.
-- This means you can change attributes of the text by using
-- the following functions along with @mappend@ or the @(<>)@
-- operator.
, fg, bg, bold, underline

-- * Specifying Colors
, Color
, black, red, green, yellow, blue, magenta, cyan, white, rgb

-- * Menus

-- | Menus provide a way to display a small number of list items
-- to the user. The desired list item is selected by typing
-- its index or by typing a unique prefix string. A default
-- completion function is provided to allow the user to
-- select a list item using tab completion.
, Menu
, Choice (..)
, menu
@@ -42,17 +99,12 @@ module System.Console.Byline
, Matcher
, matcher

-- * Executing Terminal IO
, Byline
, runByline

-- * Completion
, CompletionFunc
, Completion
, Completion (..)
, withCompletionFunc

-- * Utility Functions, Operators, and Types
, Stylized
, ReportType (..)
, (<>)
) where
@@ -61,10 +113,10 @@ module System.Console.Byline
import Data.Monoid ((<>))

--------------------------------------------------------------------------------
import System.Console.Byline.Color
import System.Console.Byline.Completion
import System.Console.Byline.Internal.Byline
import System.Console.Byline.Internal.Color
import System.Console.Byline.Internal.Completion
import System.Console.Byline.Internal.Modifiers
import System.Console.Byline.Internal.Stylized
import System.Console.Byline.Menu
import System.Console.Byline.Primary
import System.Console.Byline.Modifiers
import System.Console.Byline.Primitive
import System.Console.Byline.Stylized

+ 69
- 0
src/System/Console/Byline/Color.hs View File

@@ -0,0 +1,69 @@
{-

This file is part of the package byline. It is subject to the license
terms in the LICENSE file found in the top-level directory of this
distribution and at git://pmade.com/byline/LICENSE. No part of the
byline package, including this file, may be copied, modified,
propagated, or distributed except according to the terms contained in
the LICENSE file.

-}


--------------------------------------------------------------------------------
-- | Color type and functions for specifying colors.
module System.Console.Byline.Color
( Color (..)
, black, red, green, yellow, blue, magenta, cyan, white
, rgb
) where

--------------------------------------------------------------------------------
-- Library imports:
import Data.Word
import qualified System.Console.ANSI as ANSI

--------------------------------------------------------------------------------
-- | Opaque type for representing a color.
--
-- A color can be one of the eight standard terminal colors
-- constructed with one of the named color functions (e.g., 'black',
-- 'red', etc.) or using the 'rgb' function.
data Color = ColorCode ANSI.Color | ColorRGB (Word8, Word8, Word8)

--------------------------------------------------------------------------------
-- | Standard ANSI color by name.
black, red, green, yellow, blue, magenta, cyan, white :: Color
black = ColorCode ANSI.Black
red = ColorCode ANSI.Red
green = ColorCode ANSI.Green
yellow = ColorCode ANSI.Yellow
blue = ColorCode ANSI.Blue
magenta = ColorCode ANSI.Magenta
cyan = ColorCode ANSI.Cyan
white = ColorCode ANSI.White

--------------------------------------------------------------------------------
-- | Specify a color using a RGB triplet where each component is in
-- the range @[0 .. 255]@. The actual rendered color will depend on
-- the terminal.
--
-- If the terminal advertises that it supports 256 colors, the color
-- given to this function will be converted to the nearest color in
-- the 216-color pallet supported by the terminal. (216 colors
-- because the first 16 are the standard colors and the last 24 are
-- grayscale entries.)
--
-- However, if the terminal doesn't support extra colors, or doesn't
-- have a @TERMINFO@ entry (e.g., Windows) then the nearest standard
-- color will be chosen.
--
-- Nearest colors are calculated using their CIE distance from one
-- another.
--
-- See also:
--
-- * <http://en.wikipedia.org/wiki/ANSI_escape_code>
-- * <http://en.wikipedia.org/wiki/Color_difference>
rgb :: Word8 -> Word8 -> Word8 -> Color
rgb r g b = ColorRGB (r, g, b)

+ 57
- 0
src/System/Console/Byline/Completion.hs View File

@@ -0,0 +1,57 @@
{-

This file is part of the package byline. It is subject to the license
terms in the LICENSE file found in the top-level directory of this
distribution and at git://pmade.com/byline/LICENSE. No part of the
byline package, including this file, may be copied, modified,
propagated, or distributed except according to the terms contained in
the LICENSE file.

-}

--------------------------------------------------------------------------------
-- | The completion types.
module System.Console.Byline.Completion
( CompletionFunc
, Completion (..)
) where

--------------------------------------------------------------------------------
-- Library imports:
import Data.Text (Text)

--------------------------------------------------------------------------------
-- | A completion function modeled after the one used in Haskeline.
--
-- /Warning:/ If you're familiar with the Haskeline version of the
-- @CompletionFunc@ type please be sure to read this description
-- carefully since the two behave differently.
--
-- The completion function is called when the user presses the tab
-- key. The current input line is split into two parts based on where
-- the cursor is positioned. Text to the left of the cursor will be
-- the first value in the tuple and text to the right of the cursor
-- will be the second value.
--
-- The text returned from the completion function is the text from the
-- left of the cursor which wasn't used in the completion. It should
-- also produce a list of possible 'Completion' values.
--
-- In Haskeline, some of these text values are reversed. This is
-- /not/ the case in Byline.
--
-- /A note about @IO@:/
--
-- Due to the way that Byline uses Haskeline, the completion function
-- is forced to return an @IO@ value. It would be better if it could
-- return a value in the base monad instead. Patches welcome.
type CompletionFunc = (Text, Text) -> IO (Text, [Completion])

--------------------------------------------------------------------------------
-- | A type representing a completion match to the user's input.
data Completion = Completion
{ replacement :: Text -- ^ Text to insert to the right of the cursor.
, display :: Text -- ^ Text to display when listing all completions.
, isFinished :: Bool -- ^ Whether to follow the completed word with a
-- terminating space or close existing quotes.
} deriving (Eq, Ord, Show)

+ 29
- 6
src/System/Console/Byline/Internal/Byline.hs View File

@@ -1,4 +1,5 @@
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# OPTIONS_HADDOCK hide #-}

{-

@@ -12,24 +13,23 @@ the LICENSE file.
-}

--------------------------------------------------------------------------------
-- | Internal module containing the @Byline@ monad transformer.
module System.Console.Byline.Internal.Byline
( Byline (..)
, Env (..)
, eof
, liftOuter
, liftBase
, liftInputT
, runByline
) where


--------------------------------------------------------------------------------
-- Library imports:
import Control.Applicative
import Control.Monad.Catch
import Control.Monad.Reader
import Control.Monad.Trans.Maybe
import Data.IORef
import System.Console.Byline.Internal.Completion
import System.Console.Byline.Internal.Render
import System.Environment (lookupEnv)
import System.IO (Handle, stdout)
import qualified System.Terminfo as Term
@@ -41,6 +41,12 @@ import qualified System.Console.Haskeline as H
import qualified System.Console.Haskeline.IO as H

--------------------------------------------------------------------------------
-- Byline imports:
import System.Console.Byline.Internal.Completion
import System.Console.Byline.Internal.Render

--------------------------------------------------------------------------------
-- | Reader environment for Byline.
data Env = Env
{ sayMode :: RenderMode
, askMode :: RenderMode
@@ -50,11 +56,12 @@ data Env = Env
}

--------------------------------------------------------------------------------
-- | A monad transformer that encapsulates interactive actions.
newtype Byline m a = Byline {unByline :: ReaderT Env (MaybeT m) a}
deriving (Functor, Applicative, Monad, MonadReader Env, MonadIO)

--------------------------------------------------------------------------------
-- | Calculate the default rendering modes based on the terminal type.
defRenderMode :: H.InputT IO (RenderMode, RenderMode)
defRenderMode = do
termHint <- H.haveTerminalUI
@@ -90,12 +97,14 @@ defEnv state (smode, amode) comp =
}

--------------------------------------------------------------------------------
-- | Signal an EOF and terminate all Byline actions.
eof :: (Monad m) => Byline m a
eof = Byline $ lift (MaybeT $ return Nothing)

--------------------------------------------------------------------------------
liftOuter :: (Monad m) => m a -> Byline m a
liftOuter = Byline . lift . lift
-- | Lift an operation in the base monad into Byline.
liftBase :: (Monad m) => m a -> Byline m a
liftBase = Byline . lift . lift

--------------------------------------------------------------------------------
-- | Lift an 'InputT' action into 'Byline'.
@@ -105,6 +114,19 @@ liftInputT input = do
liftIO (H.queryInput state $ H.withInterrupt input)

--------------------------------------------------------------------------------
-- | Execute 'Byline' actions and produce a result within the base monad.
--
-- /A note about EOF:/
--
-- If an End of File (EOF) is encountered during an input action then
-- this function will return @Nothing@. This can occur when the user
-- manually enters an EOF character by pressing @Control-d@ or if
-- standard input is a file.
--
-- This decision was made to simplify the @Byline@ interface for
-- actions that read user input and is a typical strategy for terminal
-- applications. If this isn't desirable, you may want to break your
-- actions up into groups and call 'runByline' multiple times.
runByline :: (MonadIO m, MonadMask m) => Byline m a -> m (Maybe a)
runByline (Byline byline) = do
comp <- liftIO (newIORef Nothing)

+ 5
- 23
src/System/Console/Byline/Internal/Color.hs View File

@@ -1,4 +1,5 @@
{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS_HADDOCK hide #-}

{-

@@ -13,16 +14,16 @@ the LICENSE file.


--------------------------------------------------------------------------------
-- | Internal color operations.
module System.Console.Byline.Internal.Color
( Color (..)
, black, red, green, yellow, blue, magenta, cyan, white
, rgb
, colorAsANSI
, nearestColor
, term256Locations
) where

--------------------------------------------------------------------------------
-- Library imports:
import Control.Arrow (second)
import qualified Data.Colour.CIE as C
import qualified Data.Colour.SRGB as C
@@ -33,45 +34,8 @@ import Data.Word
import qualified System.Console.ANSI as ANSI

--------------------------------------------------------------------------------
data Color = ColorCode ANSI.Color | ColorRGB (Word8, Word8, Word8)

--------------------------------------------------------------------------------
black, red, green, yellow, blue, magenta, cyan, white :: Color
black = ColorCode ANSI.Black
red = ColorCode ANSI.Red
green = ColorCode ANSI.Green
yellow = ColorCode ANSI.Yellow
blue = ColorCode ANSI.Blue
magenta = ColorCode ANSI.Magenta
cyan = ColorCode ANSI.Cyan
white = ColorCode ANSI.White

--------------------------------------------------------------------------------
--
--
--
--
--
rgb :: Word8 -> Word8 -> Word8 -> Color
rgb r g b = ColorRGB (r, g, b)
-- Byline imports:
import System.Console.Byline.Color

--------------------------------------------------------------------------------
-- | Convert a Byline color to an ANSI color.

+ 15
- 11
src/System/Console/Byline/Internal/Completion.hs View File

@@ -1,3 +1,5 @@
{-# OPTIONS_HADDOCK hide #-}

{-

This file is part of the package byline. It is subject to the license
@@ -10,6 +12,7 @@ the LICENSE file.
-}

--------------------------------------------------------------------------------
-- | Internal completion operations.
module System.Console.Byline.Internal.Completion
( CompletionFunc
, Completion (..)
@@ -17,22 +20,17 @@ module System.Console.Byline.Internal.Completion
) where

--------------------------------------------------------------------------------
-- Library imports:
import Data.IORef
import Data.Text (Text)
import qualified Data.Text as Text
import qualified System.Console.Haskeline.Completion as H

--------------------------------------------------------------------------------
data Completion = Completion
{ replacement :: Text
, display :: Text
, isFinished :: Bool
} deriving (Eq, Ord, Show)

--------------------------------------------------------------------------------
type CompletionFunc = (Text, Text) -> IO (Text, [Completion])
-- Byline imports:
import System.Console.Byline.Completion

--------------------------------------------------------------------------------
-- | Convert a Byline completion result into a Haskeline completion result.
convertCompletion :: Completion -> H.Completion
convertCompletion (Completion r d i) =
H.Completion { H.replacement = Text.unpack r
@@ -41,11 +39,17 @@ convertCompletion (Completion r d i) =
}

--------------------------------------------------------------------------------
-- | Helper function that allows Byline to swap out the completion function.
runCompletionFunction :: IORef (Maybe CompletionFunc) -> H.CompletionFunc IO
runCompletionFunction compref (left, right) = do
comp <- readIORef compref

case comp of
Nothing -> H.completeFilename (left, right)
Just f -> do (output, completions) <- f (Text.pack left, Text.pack right)
return (Text.unpack output, map convertCompletion completions)

Just f -> do
(output, completions) <-
f (Text.reverse $ Text.pack left, Text.pack right)

return (Text.unpack $ Text.reverse output,
map convertCompletion completions)

+ 10
- 15
src/System/Console/Byline/Internal/Render.hs View File

@@ -1,4 +1,5 @@
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_HADDOCK hide #-}

{-

@@ -20,16 +21,20 @@ module System.Console.Byline.Internal.Render
) where

--------------------------------------------------------------------------------
-- Library imports:
import Control.Applicative
import Data.Maybe
import Data.Text (Text)
import qualified Data.Text as Text
import Data.Word
import System.Console.ANSI as ANSI
import System.IO (Handle, hPutStr)

--------------------------------------------------------------------------------
-- Byline imports:
import System.Console.Byline.Internal.Color as C
import System.Console.Byline.Internal.Stylized
import System.Console.Byline.Internal.Types
import System.IO (Handle, hPutStr)
import System.Console.Byline.Stylized

--------------------------------------------------------------------------------
-- | How to render stylized text.
@@ -68,20 +73,10 @@ renderText mode stylized = Text.concat $ map go (renderInstructions mode stylize
--------------------------------------------------------------------------------
-- | Internal function to turn stylized text into render instructions.
renderInstructions :: RenderMode -> Stylized -> [RenderInstruction]
renderInstructions mode stylized =
case stylized of
-- Text that should be rendered with a modifier.
StylizedText t m -> renderMod t m

-- No op.
StylizedMod _ -> [ ]

-- Render a list of stylized text.
StylizedList l -> concatMap (renderInstructions mode) l

renderInstructions mode = concat . mapStylized renderMod
where
renderMod :: Text -> Modifier -> [RenderInstruction]
renderMod t m =
renderMod :: (Text, Modifier) -> [RenderInstruction]
renderMod (t, m) =
case mode of
-- Only rendering text.
Plain -> [ RenderText t ]

+ 28
- 3
src/System/Console/Byline/Internal/Types.hs View File

@@ -1,3 +1,5 @@
{-# OPTIONS_HADDOCK hide #-}

{-

This file is part of the package byline. It is subject to the license
@@ -9,17 +11,24 @@ the LICENSE file.

-}


--------------------------------------------------------------------------------
-- | Internal types.
module System.Console.Byline.Internal.Types
( Status (..)
, OnlyOne (..)
( Status (..)
, OnlyOne (..)
, Modifier (..)
) where

--------------------------------------------------------------------------------
-- Library imports:
import Data.Monoid

--------------------------------------------------------------------------------
-- Byline imports:
import System.Console.Byline.Color (Color)

--------------------------------------------------------------------------------
-- | Like @Bool@, but with a different @Monoid@ instance.
data Status = On | Off

--------------------------------------------------------------------------------
@@ -31,6 +40,7 @@ instance Monoid Status where
mappend On Off = On

--------------------------------------------------------------------------------
-- | Like @Maybe@, but with a different @Monoid@ instance.
newtype OnlyOne a = OnlyOne {unOne :: Maybe a}

--------------------------------------------------------------------------------
@@ -38,3 +48,18 @@ instance Monoid (OnlyOne a) where
mempty = OnlyOne Nothing
mappend _ b@(OnlyOne (Just _)) = b
mappend a _ = a

--------------------------------------------------------------------------------
-- | Information about modifications made to stylized text.
data Modifier = Modifier
{ modColorFG :: OnlyOne Color
, modColorBG :: OnlyOne Color
, modBold :: Status
, modUnderline :: Status
}

--------------------------------------------------------------------------------
instance Monoid Modifier where
mempty = Modifier mempty mempty mempty mempty
mappend (Modifier a b c d) (Modifier a' b' c' d') =
Modifier (a <> a') (b <> b') (c <> c') (d <> d')

+ 34
- 16
src/System/Console/Byline/Menu.hs View File

@@ -12,6 +12,7 @@ the LICENSE file.
-}

--------------------------------------------------------------------------------
-- | Functions and types for working with menus.
module System.Console.Byline.Menu
( Menu
, Choice (..)
@@ -26,6 +27,7 @@ module System.Console.Byline.Menu
) where

--------------------------------------------------------------------------------
-- Library imports:
import Control.Applicative
import Control.Monad
import Control.Monad.IO.Class
@@ -37,37 +39,46 @@ import Data.Maybe
import Data.Monoid
import Data.Text (Text)
import qualified Data.Text as Text
import Text.Printf (printf)

--------------------------------------------------------------------------------
-- Byline imports:
import System.Console.Byline.Internal.Byline
import System.Console.Byline.Internal.Completion
import System.Console.Byline.Internal.Render
import System.Console.Byline.Internal.Stylized
import System.Console.Byline.Primary
import Text.Printf (printf)
import System.Console.Byline.Primitive
import System.Console.Byline.Stylized

--------------------------------------------------------------------------------
-- | Opaque type representing a menu containing items of type @a@.
data Menu a = Menu
{ menuItems :: [a]
, menuBanner :: Maybe Stylized
, menuDisplay :: a -> Stylized
, menuItemPrefix :: Int -> Stylized
, menuItemSuffix :: Stylized
, menuBeforePrompt :: Maybe Stylized
, menuMatcher :: Matcher a
{ menuItems :: [a] -- ^ Menu items.
, menuBanner :: Maybe Stylized -- ^ Banner printed before menu.
, menuDisplay :: a -> Stylized -- ^ Stylize a menu item.
, menuItemPrefix :: Int -> Stylized -- ^ Stylize an item's index.
, menuItemSuffix :: Stylized -- ^ Printed after an item's index.
, menuBeforePrompt :: Maybe Stylized -- ^ Printed before the prompt.
, menuMatcher :: Matcher a -- ^ Matcher function.
}

--------------------------------------------------------------------------------
-- | A type representing the choice made by a user while working with
-- a menu.
data Choice a = Match a -- ^ User picked a menu item.
| Other Text -- ^ User entered some text.
data Choice a = Match a -- ^ User picked a menu item.
| Other Text -- ^ User entered text that doesn't match an item.
deriving Show

--------------------------------------------------------------------------------
-- | A function that is given the input from a user while working in a
-- menu and should translate that into a 'Choice'.
--
-- The @Map@ contains the menu item indexes/prefixes (numbers or
-- letters) and the items themselves.
--
-- The default matcher function allows the user to select a menu item
-- by typing its index or part of its textual representation. As long
-- as input from the user is a unique prefix of one of the menu items
-- then that item will be returned.
type Matcher a = Menu a -> Map Text a -> Text -> Choice a

--------------------------------------------------------------------------------
@@ -92,13 +103,15 @@ matchOnPrefix config input = filter prefixCheck (menuItems config)
-- internal @numbered@ function).
defaultMatcher :: Matcher a
defaultMatcher config prefixes input =
case uniquePrefix <|> Map.lookup input prefixes of
case uniquePrefix <|> Map.lookup cleanInput prefixes of
Nothing -> Other input
Just match -> Match match

where
cleanInput = Text.strip input

-- uniquePrefix :: Maybe a
uniquePrefix = let matches = matchOnPrefix config input
uniquePrefix = let matches = matchOnPrefix config cleanInput
in if length matches == 1
then listToMaybe matches
else Nothing
@@ -111,13 +124,13 @@ defaultCompFunc config (left, _) = return ("", completions matches)
-- All matching menu items.
matches = if Text.null left
then menuItems config
else matchOnPrefix config (Text.reverse left)
else matchOnPrefix config left

-- Convert a menu item to a String.
asText i = renderText Plain (menuDisplay config i)

-- Convert menu items into Completion values.
completions = map (\i -> Completion (asText i) (asText i) False)
completions = map (\i -> Completion (asText i) (asText i) True)

--------------------------------------------------------------------------------
-- | Create a 'Menu' by giving a list of menu items and a function
@@ -164,7 +177,7 @@ matcher f m = m {menuMatcher = f}
--------------------------------------------------------------------------------
-- | Ask the user to choose an item from a menu. The menu will only
-- be shown once and the user's choice will be returned in a 'Choice'
-- value.
--
-- If you want to force the user to only choose from the displayed
-- menu items you should use 'askWithMenuRepeatedly' instead.

src/System/Console/Byline/Internal/Modifiers.hs → src/System/Console/Byline/Modifiers.hs View File

@@ -11,7 +11,8 @@ the LICENSE file.


--------------------------------------------------------------------------------
module System.Console.Byline.Internal.Modifiers
-- | Modifiers for the @Stylized@ type.
module System.Console.Byline.Modifiers
( fg
, bg
, bold
@@ -19,23 +20,35 @@ module System.Console.Byline.Internal.Modifiers
) where

--------------------------------------------------------------------------------
-- Library imports:
import Data.Monoid

--------------------------------------------------------------------------------
-- Byline imports:
import System.Console.Byline.Internal.Color
import System.Console.Byline.Internal.Stylized
import System.Console.Byline.Internal.Types
import System.Console.Byline.Stylized

--------------------------------------------------------------------------------
-- | Set the foreground color. For example:
--
-- @
-- "Hello World!" <> fg magenta
-- @
fg :: Color -> Stylized
fg c = StylizedMod (mempty {modColorFG = OnlyOne (Just c)})
fg c = modStylized (mempty {modColorFG = OnlyOne (Just c)})

--------------------------------------------------------------------------------
-- | Set the background color.
bg :: Color -> Stylized
bg c = StylizedMod (mempty {modColorBG = OnlyOne (Just c)})
bg c = modStylized (mempty {modColorBG = OnlyOne (Just c)})

--------------------------------------------------------------------------------
-- | Produce bold text.
bold :: Stylized
bold = StylizedMod (mempty {modBold = On})
bold = modStylized (mempty {modBold = On})

--------------------------------------------------------------------------------
-- | Produce underlined text.
underline :: Stylized
underline = StylizedMod (mempty {modUnderline = On})
underline = modStylized (mempty {modUnderline = On})

src/System/Console/Byline/Primary.hs → src/System/Console/Byline/Primitive.hs View File

@@ -12,7 +12,8 @@ the LICENSE file.
-}

--------------------------------------------------------------------------------
module System.Console.Byline.Primary
-- | Primitive operations such as printing messages and reading input.
module System.Console.Byline.Primitive
( ReportType (..)
, say
, sayLn
@@ -26,6 +27,7 @@ module System.Console.Byline.Primary
) where

--------------------------------------------------------------------------------
-- Library imports:
import Control.Monad.IO.Class
import qualified Control.Monad.Reader as Reader
import Data.IORef
@@ -33,13 +35,16 @@ import Data.Maybe
import Data.Monoid
import Data.Text (Text)
import qualified Data.Text as T
import qualified System.Console.Haskeline as H

--------------------------------------------------------------------------------
--- Byline imports:
import System.Console.Byline.Color
import System.Console.Byline.Internal.Byline
import System.Console.Byline.Internal.Color
import System.Console.Byline.Internal.Completion
import System.Console.Byline.Internal.Modifiers
import System.Console.Byline.Internal.Render
import System.Console.Byline.Internal.Stylized
import qualified System.Console.Haskeline as H
import System.Console.Byline.Modifiers
import System.Console.Byline.Stylized

--------------------------------------------------------------------------------
-- | Report types for the 'report' function.
@@ -61,8 +66,14 @@ sayLn message = say (message <> text "\n")
--------------------------------------------------------------------------------
-- | Read input after printing the given stylized text as a prompt.
ask :: (MonadIO m)
=> Stylized -- ^ The prompt.
-> Maybe Text -- ^ Optional default answer.
=> Stylized
-- ^ The prompt.

-> Maybe Text
-- ^ Optional default answer that will be returned if the user
-- presses return without providing any input (a zero-length
-- string).

-> Byline m Text
ask prompt defans = do
let prompt' = case defans of
@@ -77,8 +88,7 @@ ask prompt defans = do
| otherwise -> return (T.pack s)

--------------------------------------------------------------------------------
-- | Read a single character of input.
askChar :: (MonadIO m)
=> Stylized
-> Byline m Char
@@ -92,8 +102,13 @@ askChar prompt = do
-- | Read a password without echoing it to the terminal. If a masking
-- character is given it will replace each typed character.
askPassword :: (MonadIO m)
=> Stylized -- ^ The prompt.
-> Maybe Char -- ^ Masking character.
=> Stylized
-- ^ The prompt.

-> Maybe Char
-- ^ Optional masking character that will be printed each
-- time the user presses a key.

-> Byline m Text
askPassword prompt maskchr = do
pass <- liftInputT . H.getPassword maskchr =<< renderPrompt prompt
@@ -106,9 +121,9 @@ askPassword prompt maskchr = do
-- returns a valid result.
--
-- The confirmation function receives the output from 'ask' and should
-- return a @Left Stylized@ to produce an error message (printed with
-- 'sayLn'). When an acceptable answer from 'ask' is received, the
-- confirmation function should return it with @Right@.
askUntil :: (MonadIO m)
=> Stylized -- ^ The prompt.
-> Maybe Text -- ^ Optional default answer.
@@ -117,7 +132,7 @@ askUntil :: (MonadIO m)
askUntil prompt defans confirm = go where
go = do
answer <- ask prompt defans
check <- liftOuter (confirm answer)
check <- liftBase (confirm answer)

case check of
Left msg -> sayLn msg >> go

src/System/Console/Byline/Internal/Stylized.hs → src/System/Console/Byline/Stylized.hs View File

@@ -9,47 +9,59 @@ the LICENSE file.

-}


--------------------------------------------------------------------------------
module System.Console.Byline.Internal.Stylized
( Stylized (..)
, Modifier (..)
-- | The stylized type and constructors.
module System.Console.Byline.Stylized
( Stylized
, Modifier
, text
, mapStylized
, modStylized
) where

--------------------------------------------------------------------------------
-- Library imports:
import Data.Monoid
import Data.String
import Data.Text (Text)
import qualified Data.Text as T
import System.Console.Byline.Internal.Color
import System.Console.Byline.Internal.Types

--------------------------------------------------------------------------------
data Modifier = Modifier
{ modColorFG :: OnlyOne Color
, modColorBG :: OnlyOne Color
, modBold :: Status
, modUnderline :: Status
}
-- Byline imports:
import System.Console.Byline.Internal.Types

--------------------------------------------------------------------------------
-- | Stylized text. Construct text with modifiers using string
-- literals and the @OverloadedStrings@ extension and/or the 'text'
-- function.
data Stylized = StylizedText Text Modifier
| StylizedMod Modifier
| StylizedList [Stylized]

--------------------------------------------------------------------------------
-- | Helper function to create stylized text. If you enable the
-- @OverloadedStrings@ extension then you can create stylized text
-- directly without using this function.
--
-- This function is also helpful for producing stylized text from an
-- existing @Text@ value.
text :: Text -> Stylized
text t = StylizedText t mempty

--------------------------------------------------------------------------------
instance Monoid Modifier where
mempty = Modifier mempty mempty mempty mempty
mappend (Modifier a b c d) (Modifier a' b' c' d') =
Modifier (a <> a') (b <> b') (c <> c') (d <> d')
-- | Map a function over stylized text. The 'Modifier' type is
-- opaque so this function might not be very useful outside of the
-- Byline internals.
mapStylized :: ((Text, Modifier) -> a) -> Stylized -> [a]
mapStylized f (StylizedText t m) = [ f (t, m) ]
mapStylized _ (StylizedMod _) = [ ] -- No op.
mapStylized f (StylizedList l) = concatMap (mapStylized f) l

--------------------------------------------------------------------------------
-- | Constructor to modify stylized text. This function is only
-- useful to internal Byline functions.
modStylized :: Modifier -> Stylized
modStylized = StylizedMod

--------------------------------------------------------------------------------
instance Monoid Stylized where

Loading…
Cancel
Save