reflex-libtelnet 0.1.0.0 → 0.2.0.0
raw patch · 4 files changed
+184/−186 lines, 4 filesdep −data-defaultdep −lensdep ~basedep ~bytestringdep ~dependent-map
Dependencies removed: data-default, lens
Dependency ranges changed: base, bytestring, dependent-map, dependent-sum, dependent-sum-template, libtelnet, reflex
Files
- CHANGELOG.md +21/−0
- README.md +15/−6
- reflex-libtelnet.cabal +37/−32
- src/Reflex/LibTelnet.hs +111/−148
CHANGELOG.md view
@@ -1,5 +1,26 @@ # Revision history for reflex-libtelnet +## 0.2.0.0 -- 2022-05-21++* Remove `instance Reflex t => Default (TelnetConfig t)`; use+ `newTelnetConfig` instead.++* New instances:+ - `instance Generic (TelnetConfig t)`+ - `instance Generic (TelnetEvents t)`++* Remove the dependency on `lens`:+ - As GHC is now better at dealing with duplicate record fields, we+ can give them nice unprefixed names.+ - Use+ [`generic-lens`](https://hackage.haskell.org/package/generic-lens)+ or+ [`generic-optics`](https://hackage.haskell.org/package/generic-optics)+ to get lenses for your preferred flavour of optic. Make sure to+ use the `field' @"fieldName"` version (i.e. not `#fieldName` or+ un-primed `field`), otherwise GHC will struggle to infer the type+ of `t` when setting fields in `ReflexConfig t`.+ ## 0.1.0.0 -- 2019-12-03 * First version. Released on an unsuspecting world.
README.md view
@@ -14,17 +14,26 @@ 1. Assemble a `config :: TelnetConfig t`, which holds the event streams that feed into a telnet state tracker. The easiest way to- do this is to call `def` (there is an `instance Reflex t => Default- (TelnetConfig t)`) and overwrite the fields you care about using- record updates or lenses. You will almost certainly want to- overwrite `_cRecv` to be the stream of incoming data from a socket,- and `_cSend` to be the stream of outgoing data from your application.+ do this is to call `newTelnetConfig` and overwrite the fields you+ care about using record updates or lenses. You will almost+ certainly want to overwrite `recv` to be the stream of incoming+ data from a socket, and `send` to be the stream of outgoing data+ from your application. + * If you want to use lenses, use the+ [`generic-lens`](https://hackage.haskell.org/package/generic-lens)+ or+ [`generic-optics`](https://hackage.haskell.org/package/generic-optics)+ to get your preferred flavour of optic. Make sure to use the+ `field' @"fieldName"` version (i.e. not `#fieldName` or un-primed+ `field`), otherwise GHC will struggle to infer the type of `t`+ when setting fields in `ReflexConfig t`.+ 2. Call `telnet config`, and save the resulting `TelnetEvents t`. This structure holds the output event streams from a single telnet state tracker. -3. Wire the ouput events into your application.+3. Wire the output events into your application. ## Other Resources
reflex-libtelnet.cabal view
@@ -1,37 +1,42 @@-cabal-version: 2.2-name: reflex-libtelnet-version: 0.1.0.0-synopsis: Reflex bindings for libtelnet-description: Wraps https://git.sr.ht/~jack/libtelnet-haskell , so you- can handle telnet data streams using reflex- 'Reflex.Event's. See @README.md@ or- "Reflex.LibTelnet" to get started.-homepage: https://git.sr.ht/~jack/reflex-libtelnet-bug-reports: https://todo.sr.ht/~jack/reflex-libtelnet-license: GPL-3.0-or-later-license-file: LICENSE-author: Jack Kelly-maintainer: jack@jackkelly.name-copyright: (c) 2019 Jack Kelly-category: Network-build-type: Simple-extra-source-files: CHANGELOG.md, README.md-tested-with: GHC == 8.6.5+cabal-version: 2.2+name: reflex-libtelnet+version: 0.2.0.0+synopsis: Reflex bindings for libtelnet+description:+ Wraps https://git.sr.ht/~jack/libtelnet-haskell , so you+ can handle telnet data streams using reflex+ 'Reflex.Event's. See @README.md@ or+ "Reflex.LibTelnet" to get started. +homepage: https://git.sr.ht/~jack/reflex-libtelnet+bug-reports: https://todo.sr.ht/~jack/reflex-libtelnet+license: GPL-3.0-or-later+license-file: LICENSE+author: Jack Kelly+maintainer: jack@jackkelly.name+copyright: (c) 2019, 2022 Jack Kelly+category: Network+build-type: Simple+extra-source-files:+ CHANGELOG.md+ README.md++tested-with: GHC ==8.8.4 || ==8.10.7 || ==9.0.2+ library- ghc-options: -Wall- exposed-modules: Reflex.LibTelnet- build-depends: base >= 4.11 && < 4.13- , bytestring >= 0.10.8.2 && < 0.11- , data-default >= 0.7.1.1 && < 0.8- , dependent-map >= 0.3 && < 0.4- , dependent-sum >= 0.6.2.0 && <= 0.7- , dependent-sum-template >= 0.1 && < 0.2- , lens >= 4.16.1 && < 4.18- , libtelnet >= 0.1 && < 0.2- , reflex >= 0.5 && < 0.7- hs-source-dirs: src- default-language: Haskell2010+ ghc-options: -Wall+ exposed-modules: Reflex.LibTelnet+ build-depends:+ , base >=4.11 && <4.16+ , bytestring >=0.10.8.2 && <0.12+ , dependent-map >=0.3 && <0.5+ , dependent-sum >=0.6.2.0 && <=0.8+ , dependent-sum-template ^>=0.1+ , libtelnet ^>=0.1+ , reflex >=0.5 && <0.9++ hs-source-dirs: src+ default-language: Haskell2010 source-repository head type: git
src/Reflex/LibTelnet.hs view
@@ -1,14 +1,16 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-} {-| Module : Reflex.LibTelnet Description : Reflex wrapper around libtelnet-Copyright : (c) 2019 Jack Kelly+Copyright : (c) 2019, 2022 Jack Kelly License : GPL-3.0-or-later Maintainer : jack@jackkelly.name Stability : experimental@@ -16,8 +18,8 @@ How to run a libtelnet state tracker off Reflex 'Event's: -1. Construct a 'TelnetConfig' by using 'def' to get an empty config,- and then fill it out using record updates or lenses.+1. Construct a 'TelnetConfig' by using 'newTelnetConfig' to get an+ empty config, and then fill it out using record updates or lenses. 2. Start a state tracker by calling 'telnet' on your config. @@ -30,58 +32,21 @@ -- * Input Events , TelnetConfig(..)+ , newTelnetConfig -- * Output Events , TelnetEvents(..)-- -- * Lenses- -- ** TelnetConfig- , cOptions- , cFlags- , cRecv- , cSend- , cIac- , cNegotiate- , cSubnegotiation- , cBeginCompress2- , cNewEnvironSend- , cNewEnviron- , cTTypeSend- , cTTypeIs- , cSendZmp- , cSendMssp-- -- ** TelnetEvents- , eReceived- , eSend- , eWarning- , eError- , eIac- , eWill- , eWont- , eDo- , eDont- , eSubnegotiation- , eZmp- , eTerminalTypeSend- , eTerminalTypeIs- , eCompress- , eEnvironSend- , eEnviron- , eMssp- , eException ) where import Control.Exception (catch)-import Control.Lens (Lens', (^.), makeLenses) import Control.Monad.IO.Class (MonadIO(..)) import Data.ByteString (ByteString)-import Data.Default (Default(..)) import qualified Data.Dependent.Map as DMap import Data.Dependent.Sum ((==>)) import Data.Functor ((<&>)) import Data.GADT.Compare.TH (deriveGCompare, deriveGEq) import Data.GADT.Show.TH (deriveGShow)+import GHC.Generics (Generic) import Network.Telnet.LibTelnet (Telnet) import qualified Network.Telnet.LibTelnet as Telnet import Network.Telnet.LibTelnet.Iac (Iac)@@ -94,126 +59,124 @@ -- event" describes all events the state tracker listens to; it -- doesn't care whether the events are coming from "above" or "below" -- itself in the application stack. Data arriving on the socket--- ('_cRecv') is an "input event" from "below"; data pasing through--- libtelnet on its way out to the socket ('_cSend') is also an "input+-- (@recv@) is an "input event" from "below"; data pasing through+-- libtelnet on its way out to the socket (@send@) is also an "input -- event", but from "above". -- -- You will almost certainly want to: ----- 1. use 'def' to get an empty 'TelnetConfig';--- 2. replace the '_cRecv' event with incoming socket data; and--- 3. replace the '_cSend' event with outgoing data from your application.+-- 1. use 'newTelnetConfig' to get an empty 'TelnetConfig';+-- 2. replace the @recv@ event with incoming socket data; and+-- 3. replace the @send@ event with outgoing data from your application. -- -- @since 0.1.0.0 data TelnetConfig t = TelnetConfig- { _cOptions :: [Telnet.OptionSpec]+ { options :: [Telnet.OptionSpec] -- ^ Passed to 'Telnet.telnetInit'- , _cFlags :: [Telnet.Flag]+ , flags :: [Telnet.Flag] -- ^ Passed to 'Telnet.telnetInit'- , _cRecv :: Event t ByteString+ , recv :: Event t ByteString -- ^ 'Telnet.telnetRecv' - "I just received this data, please decode it"- , _cSend :: Event t ByteString+ , send :: Event t ByteString -- ^ 'Telnet.telnetSend' - "I want to send this data out, please encode it"- , _cIac :: Event t Iac+ , iac :: Event t Iac -- ^ 'Telnet.telnetIac'- , _cNegotiate :: Event t (Iac, Option)+ , negotiate :: Event t (Iac, Option) -- ^ 'Telnet.telnetNegotiate'- , _cSubnegotiation :: Event t (Option, ByteString)+ , subnegotiation :: Event t (Option, ByteString) -- ^ 'Telnet.telnetSubnegotiation'- , _cBeginCompress2 :: Event t ()+ , beginCompress2 :: Event t () -- ^ 'Telnet.telnetBeginCompress2'- , _cNewEnvironSend :: Event t [(Telnet.Var, ByteString)]+ , newEnvironSend :: Event t [(Telnet.Var, ByteString)] -- ^ 'Telnet.telnetNewEnvironSend'- , _cNewEnviron :: Event t ( Telnet.IsInfo+ , newEnviron :: Event t ( Telnet.IsInfo , [(Telnet.Var, ByteString, ByteString)] ) -- ^ 'Telnet.telnetNewEnviron'- , _cTTypeSend :: Event t ()+ , tTypeSend :: Event t () -- ^ 'Telnet.telnetTTypeSend'- , _cTTypeIs :: Event t ByteString+ , tTypeIs :: Event t ByteString -- ^ 'Telnet.telnetTTypeIs'- , _cSendZmp :: Event t [ByteString]+ , sendZmp :: Event t [ByteString] -- ^ 'Telnet.telnetSendZmp'- , _cSendMssp :: Event t [(ByteString, [ByteString])]+ , sendMssp :: Event t [(ByteString, [ByteString])] -- ^ 'Telnet.telnetSendMssp' }--$(makeLenses ''TelnetConfig)+ deriving Generic -- ^ @since 0.2.0.0 -- | No options set and all events are 'never'. ----- @since 0.1.0.0-instance Reflex t => Default (TelnetConfig t) where- def = TelnetConfig- { _cOptions = []- , _cFlags = []- , _cRecv = never- , _cSend = never- , _cIac = never- , _cNegotiate = never- , _cSubnegotiation = never- , _cBeginCompress2 = never- , _cNewEnvironSend = never- , _cNewEnviron = never- , _cTTypeSend = never- , _cTTypeIs = never- , _cSendZmp = never- , _cSendMssp = never- }+-- @since 0.2.0.0+newTelnetConfig :: Reflex t => TelnetConfig t+newTelnetConfig = TelnetConfig+ { options = []+ , flags = []+ , recv = never+ , send = never+ , iac = never+ , negotiate = never+ , subnegotiation = never+ , beginCompress2 = never+ , newEnvironSend = never+ , newEnviron = never+ , tTypeSend = never+ , tTypeIs = never+ , sendZmp = never+ , sendMssp = never+ } -- | The libtelnet 'Telnet.Event' type is fanned out into a set of -- individual "output events". An "output event" describes all events -- that should be listened to, regardless of whether they are going--- "up" or "down" the application stack: parsed data ('_eReceived') is+-- "up" or "down" the application stack: parsed data (@received@) is -- an "output event" that should be listened to by the layer "above";--- encoded data that should go to a socket ('_eSend') is also an+-- encoded data that should go to a socket (@send@) is also an -- "output event", but listened to by the layer below. -- -- @since 0.1.0.0 data TelnetEvents t = TelnetEvents- { _eReceived :: Event t ByteString+ { received :: Event t ByteString -- ^ 'Telnet.Received' - "Here is some decoded data, please send -- it up to the application"- , _eSend :: Event t ByteString+ , send :: Event t ByteString -- ^ 'Telnet.Send' - "Here is some encoded data, please send it -- out on the socket"- , _eWarning :: Event t Telnet.Err+ , warning :: Event t Telnet.Err -- ^ 'Telnet.Warning'- , _eError :: Event t Telnet.Err+ , error :: Event t Telnet.Err -- ^ 'Telnet.Error'- , _eIac :: Event t Iac+ , iac :: Event t Iac -- ^ 'Telnet.Iac'- , _eWill :: Event t Option+ , will :: Event t Option -- ^ 'Telnet.Will'- , _eWont :: Event t Option+ , wont :: Event t Option -- ^ 'Telnet.Wont'- , _eDo :: Event t Option+ , do_ :: Event t Option -- ^ 'Telnet.Do'- , _eDont :: Event t Option+ , dont :: Event t Option -- ^ 'Telnet.Dont'- , _eSubnegotiation :: Event t (Option, ByteString)+ , subnegotiation :: Event t (Option, ByteString) -- ^ 'Telnet.Subnegotiation'- , _eZmp :: Event t [ByteString]+ , zmp :: Event t [ByteString] -- ^ 'Telnet.Zmp'- , _eTerminalTypeSend :: Event t ()+ , terminalTypeSend :: Event t () -- ^ 'Telnet.TerminalTypeSend'- , _eTerminalTypeIs :: Event t ByteString+ , terminalTypeIs :: Event t ByteString -- ^ 'Telnet.TerminalTypeIs'- , _eCompress :: Event t Bool+ , compress :: Event t Bool -- ^ 'Telnet.Compress'- , _eEnvironSend :: Event t [(Telnet.Var, ByteString)]+ , environSend :: Event t [(Telnet.Var, ByteString)] -- ^ 'Telnet.EnvironSend'- , _eEnviron :: Event t (Telnet.IsInfo, [(Telnet.Var, ByteString, ByteString)])+ , environ :: Event t (Telnet.IsInfo, [(Telnet.Var, ByteString, ByteString)]) -- ^ 'Telnet.Environ'- , _eMssp :: Event t [(ByteString, [ByteString])]+ , mssp :: Event t [(ByteString, [ByteString])] -- ^ 'Telnet.Mssp'- , _eException :: Event t Telnet.TelnetException+ , exception :: Event t Telnet.TelnetException -- ^ Exceptions thrown by the binding are caught and emitted -- here. Protocol errors and warnings are emitted on the- -- '_eWarning' and '_eError' events.+ -- @warning@ and @error@ events. }--$(makeLenses ''TelnetEvents)+ deriving Generic -- ^ @since 0.2.0.0 data EventKey a where Received :: EventKey ByteString@@ -239,9 +202,9 @@ $(deriveGShow ''EventKey) -- | Process telnet streams using--- <https://github.com/seanmiddleditch/libtelnet libtelnet>. The--- easiest way to get a @'TelnetConfig' t@ is through its 'Default'--- instance; see 'TelnetConfig' for details.+-- <https://github.com/seanmiddleditch/libtelnet libtelnet>. Use+-- 'newTelnetConfig' to get a @'TelnetConfig' t@, and update it as+-- required. -- -- @since 0.1.0.0 telnet@@ -256,31 +219,31 @@ (telnetE, fireTelnetE) <- newTriggerEvent (telnetExcE, fireTelnetExcE) <- newTriggerEvent t <- liftIO $ Telnet.telnetInit- (config ^. cOptions)- (config ^. cFlags)+ (options config)+ (flags config) (const fireTelnetE) let perform :: (Telnet -> a -> IO ())- -> Lens' (TelnetConfig t) (Event t a)+ -> (TelnetConfig t -> Event t a) -> m ()- perform f l = performEvent_ $ action <$> config ^. l where- action x = liftIO $ f t x `catch` \(ex :: Telnet.TelnetException) ->+ perform act get = performEvent_ $ action <$> get config where+ action x = liftIO $ act t x `catch` \(ex :: Telnet.TelnetException) -> fireTelnetExcE ex - perform Telnet.telnetRecv cRecv- perform Telnet.telnetSend cSend- perform Telnet.telnetIac cIac- perform (uncurry . Telnet.telnetNegotiate) cNegotiate- perform (uncurry . Telnet.telnetSubnegotiation) cSubnegotiation- perform (const . Telnet.telnetBeginCompress2) cBeginCompress2- perform Telnet.telnetNewEnvironSend cNewEnvironSend- perform (uncurry . Telnet.telnetNewEnviron) cNewEnviron- perform (const . Telnet.telnetTTypeSend) cTTypeSend- perform Telnet.telnetTTypeIs cTTypeIs- perform Telnet.telnetSendZmp cSendZmp- perform Telnet.telnetSendMssp cSendMssp+ perform Telnet.telnetRecv recv+ perform Telnet.telnetSend send+ perform Telnet.telnetIac iac+ perform (uncurry . Telnet.telnetNegotiate) negotiate+ perform (uncurry . Telnet.telnetSubnegotiation) subnegotiation+ perform (const . Telnet.telnetBeginCompress2) beginCompress2+ perform Telnet.telnetNewEnvironSend newEnvironSend+ perform (uncurry . Telnet.telnetNewEnviron) newEnviron+ perform (const . Telnet.telnetTTypeSend) tTypeSend+ perform Telnet.telnetTTypeIs tTypeIs+ perform Telnet.telnetSendZmp sendZmp+ perform Telnet.telnetSendMssp sendMssp let selector :: EventSelector t EventKey@@ -304,22 +267,22 @@ Telnet.Mssp msg -> Mssp ==> msg pure $ TelnetEvents- { _eReceived = selector `select` Received- , _eSend = selector `select` Send- , _eWarning = selector `select` Warning- , _eError = selector `select` Error- , _eIac = selector `select` Iac- , _eWill = selector `select` Will- , _eWont = selector `select` Wont- , _eDo = selector `select` Do- , _eDont = selector `select` Dont- , _eSubnegotiation = selector `select` Subnegotiation- , _eZmp = selector `select` Zmp- , _eTerminalTypeSend = selector `select` TerminalTypeSend- , _eTerminalTypeIs = selector `select` TerminalTypeIs- , _eCompress = selector `select` Compress- , _eEnvironSend = selector `select` EnvironSend- , _eEnviron = selector `select` Environ- , _eMssp = selector `select` Mssp- , _eException = telnetExcE+ { received = selector `select` Received+ , send = selector `select` Send+ , warning = selector `select` Warning+ , error = selector `select` Error+ , iac = selector `select` Iac+ , will = selector `select` Will+ , wont = selector `select` Wont+ , do_ = selector `select` Do+ , dont = selector `select` Dont+ , subnegotiation = selector `select` Subnegotiation+ , zmp = selector `select` Zmp+ , terminalTypeSend = selector `select` TerminalTypeSend+ , terminalTypeIs = selector `select` TerminalTypeIs+ , compress = selector `select` Compress+ , environSend = selector `select` EnvironSend+ , environ = selector `select` Environ+ , mssp = selector `select` Mssp+ , exception = telnetExcE }