This commit is contained in:
Dmitry Zuikov 2024-07-17 10:10:25 +03:00
parent 94ecc947ee
commit 7ab1f829f2
1 changed files with 217 additions and 22 deletions

View File

@ -4,28 +4,72 @@ module Main where
import HBS2.Prelude.Plated
import HBS2.OrDie
import HBS2.Misc.PrettyStuff as All
import HBS2.System.Logger.Simple.ANSI as All
import HBS2.Peer.CLI.Detect
import HBS2.Peer.RPC.Client.Unix
import HBS2.Peer.RPC.API.Peer
import HBS2.Peer.Proto hiding (request)
import HBS2.Peer.Proto.RefLog
import HBS2.Base58
import HBS2.Net.Auth.Credentials
import HBS2.Net.Auth.Schema()
import HBS2.Net.Auth.Credentials
import HBS2.Net.Auth.Credentials.Sigil
import HBS2.Data.Types.SignedBox
import HBS2.KeyMan.Keys.Direct
import HBS2.KeyMan.State
import HBS2.KeyMan.App.Types
import HBS2.Misc.PrettyStuff
import Data.Coerce
import Data.Config.Suckless
import Data.HashMap.Strict (HashMap)
import Data.HashMap.Strict qualified as HM
import Data.Kind
import Data.List (isPrefixOf)
import Data.ByteString qualified as BS
import Data.ByteString (ByteString)
import Data.Text qualified as Text
import Data.Text.Encoding qualified as TE
import Data.Maybe
import Codec.Serialise
import Control.Monad.Reader
import Control.Monad.Writer
import Control.Monad.Identity
import UnliftIO
import System.Environment
import System.IO (hPrint)
import Streaming.Prelude qualified as S
import Prettyprinter
data BindAction c ( m :: Type -> Type) = BindAction { getAction :: [Syntax c] -> RunM c m (Syntax c) }
type RefLogId = PubKey 'Sign 'HBS2Basic
pattern StringLike :: forall {c} . String -> Syntax c
pattern StringLike e <- (stringLike -> Just e)
pattern StringLikeList :: forall {c} . [String] -> [Syntax c]
pattern StringLikeList e <- (stringLikeList -> e)
stringLike :: Syntax c -> Maybe String
stringLike = \case
LitStrVal s -> Just $ Text.unpack s
SymbolVal (Id s) -> Just $ Text.unpack s
_ -> Nothing
stringLikeList :: [Syntax c] -> [String]
stringLikeList syn = [ stringLike s | s <- syn ] & takeWhile isJust & catMaybes
data BindAction c ( m :: Type -> Type) =
BindLambda { fromLambda :: [Syntax c] -> RunM c m (Syntax c) }
| BindValue (Syntax c)
data Bind c ( m :: Type -> Type) = Bind
{ bindAction :: BindAction c m
@ -39,8 +83,28 @@ newtype NameNotBoundException = NameNotBound Id
deriving stock Show
deriving newtype (Generic,Typeable)
newtype NotLambda = NotLambda Id
deriving stock Show
deriving newtype (Generic,Typeable)
instance Exception NotLambda
data BadFormException c = BadFormException (Syntax c)
newtype BadValueException = BadValueException String
deriving stock Show
deriving newtype (Generic,Typeable)
instance Exception NameNotBoundException
instance IsContext c => Show (BadFormException c) where
show (BadFormException sy) = show $ "BadFormException" <+> pretty sy
instance Exception (BadFormException C)
instance Exception BadValueException
newtype Dict c m = Dict { fromDict :: HashMap Id (Bind c m) }
deriving newtype (Semigroup, Monoid)
@ -59,77 +123,208 @@ splitForms s0 = runIdentity $ S.toList_ (go mempty s0)
where
go acc ( "then" : rest ) = emit acc >> go mempty rest
go acc ( "and" : rest ) = emit acc >> go mempty rest
go acc ( x : rest ) | isPrefixOf "-" x = go ( x : acc ) rest
go acc ( x : rest ) | isPrefixOf "--" x = go ( x : acc ) rest
go acc ( x : rest ) = go ( x : acc ) rest
go acc [] = emit acc
emit = S.yield . reverse
run :: forall c m . (IsContext c, MonadIO m) => Dict c m -> [Syntax c] -> m ()
run :: forall c m . ( IsContext c
, MonadUnliftIO m
, Exception (BadFormException c)
) => Dict c m -> [Syntax c] -> m (Syntax c)
run d sy = do
tvd <- newTVarIO d
runReaderT (fromRunM (mapM_ runExpr sy)) tvd
lastDef nil <$> runReaderT (fromRunM (mapM runExpr sy)) tvd
where
runExpr :: Syntax c -> RunM c m (Syntax c)
runExpr = \case
runExpr syn = handle (handleForm syn) $ case syn of
ListVal (SymbolVal name : args') -> do
what <- ask >>= readTVarIO <&> HM.lookup name . fromDict
case bindAction <$> what of
Just (BindAction e) -> mapM runExpr args' >>= e
Just (BindLambda e) -> mapM runExpr args' >>= e
Just (BindValue v) -> throwIO (NotLambda name)
Nothing -> throwIO (NameNotBound name)
SymbolVal (Id s) | Text.isPrefixOf ":" s -> do
pure (mkSym @c (Text.drop 1 s))
SymbolVal name -> do
what <- ask >>= readTVarIO
<&> HM.lookup name . fromDict
<&> maybe (BindValue (mkSym name)) bindAction
case what of
BindValue e -> pure e
BindLambda e -> pure $ mkForm "lambda" [mkSym "..."]
e -> pure e
bindOne :: Id -> ([Syntax c] -> RunM c m (Syntax c)) -> Dict c m
bindOne n fn = Dict (HM.singleton n (Bind (BindAction fn) n ""))
handleForm syn (BadFormException _ :: BadFormException c) = do
throwIO (BadFormException syn)
-- nil = List noContext []
bindMatch :: Id -> ([Syntax c] -> RunM c m (Syntax c)) -> Dict c m
bindMatch n fn = Dict (HM.singleton n (Bind (BindLambda fn) n ""))
nil :: forall c . IsContext c => Syntax c
nil = List noContext []
nil_ :: (IsContext c, MonadIO m) => (a -> RunM c m b) -> a -> RunM c m (Syntax c)
nil_ m w = m w >> pure (List noContext [])
bind :: (MonadUnliftIO m, IsContext c) => Id -> Syntax c -> RunM c m (Syntax c)
bind name expr = do
tv <- ask -- >>= readTVarIO
atomically do
w@(Dict x) <- readTVar tv
writeTVar tv w
pure nil
setupLogger :: MonadIO m => m ()
setupLogger = do
setLogging @DEBUG $ toStderr . logPrefix "[debug] "
setLogging @ERROR $ toStderr . logPrefix "[error] "
setLogging @WARN $ toStderr . logPrefix "[warn] "
setLogging @NOTICE $ toStdout . logPrefix ""
pure ()
flushLoggers :: MonadIO m => m ()
flushLoggers = do
silence
silence :: MonadIO m => m ()
silence = do
setLoggingOff @DEBUG
setLoggingOff @ERROR
setLoggingOff @WARN
setLoggingOff @NOTICE
display :: (MonadIO m, Pretty a) => a -> m ()
display = liftIO . print . pretty
display_ :: (MonadIO m, Show a) => a -> m ()
display_ = liftIO . print
{- HLINT ignore "Functor law" -}
class IsContext c => MkSym c a where
mkSym :: a -> Syntax c
instance IsContext c => MkSym c String where
mkSym s = Symbol noContext (Id $ Text.pack s)
instance IsContext c => MkSym c Text where
mkSym s = Symbol noContext (Id s)
instance IsContext c => MkSym c Id where
mkSym = Symbol noContext
mkStr :: forall c . IsContext c => String -> Syntax c
mkStr s = Literal noContext $ LitStr (Text.pack s)
mkForm :: forall c . IsContext c => String -> [Syntax c] -> Syntax c
mkForm s sy = List noContext ( mkSym s : sy )
mkList :: [Syntax C] -> Syntax C
mkList = List noContext
getCredentialsForReflog :: MonadUnliftIO m => String -> m (PeerCredentials 'HBS2Basic)
getCredentialsForReflog reflog = do
puk <- orThrow (BadValueException reflog) (fromStringMay @(RefLogKey HBS2Basic) reflog)
runKeymanClient (loadCredentials puk)
>>= orThrowUser "credentials not found"
mkRefLogUpdateFrom :: MonadUnliftIO m => m ByteString -> String -> m (Syntax C)
mkRefLogUpdateFrom mbs reflog = do
what <- getCredentialsForReflog reflog
let puk = view peerSignPk what
let privk = view peerSignSk what
txraw <- mbs
w <- makeRefLogUpdate @L4Proto @'HBS2Basic (coerce puk) privk txraw
let s = show $ pretty $ AsBase58 (serialise w)
pure $ mkForm "cbor:base58" [ mkStr s ]
main :: IO ()
main = do
cli <- getArgs <&> unlines . mconcat . splitForms
setupLogger
cli <- getArgs <&> unlines . fmap unwords . splitForms
>>= either (error.show) pure . parseTop
let dict = execWriter do
tell $ bindOne "help" $ nil_ $ \case
tell $ bindMatch "help" $ nil_ \case
[] -> do
d <- ask >>= readTVarIO <&> fromDict
liftIO $ mapM_ (print.pretty.bindName) d
mapM_ (display.bindName) d
_ -> pure ()
tell $ bindOne "internal:show-cli" $ nil_ $ \case
_ -> liftIO (print $ pretty cli)
tell $ bindMatch "concat" $ \case
StringLikeList xs@(_:_) -> do
pure $ mkStr ( mconcat xs )
_ -> throwIO (BadFormException @C nil)
tell $ bindOne "hbs2:peer:detect" $ nil_ $ \case
tell $ bindMatch "lookup" $ \case
[StringLike s, ListVal (SymbolVal "dict" : es) ] -> do
let val = headDef nil [ v | ListVal [StringLike k, v] <- es, k == s ]
pure val
_ -> throwIO (BadFormException @C nil)
tell $ bindMatch "display" $ nil_ \case
[ sy ] -> display sy
ss -> display (mkList ss)
tell $ bindMatch "internal:show-cli" $ nil_ \case
_ -> display cli
tell $ bindMatch "hbs2:peer:detect" $ nil_ \case
_ -> do
so <- detectRPC
liftIO (print $ pretty so)
display so
tell $ bindOne "hbs2:peer:poke" $ nil_ $ \case
tell $ bindMatch "hbs2:peer:poke" $ \case
_ -> do
so <- detectRPC `orDie` "hbs2-peer not found"
r <- newTVarIO nil
withRPC2 @PeerAPI @UNIX so $ \caller -> do
what <- callRpcWaitMay @RpcPoke (TimeoutSec 1) caller ()
liftIO $ print $ pretty what
tell $ bindOne "hbs2:keyman:list" $ nil_ $ \case
what <- callRpcWaitMay @RpcPoke (TimeoutSec 1) caller ()
<&> fromMaybe ""
<&> parseTop
<&> either (const nil) (mkForm "dict")
atomically $ writeTVar r what
readTVarIO r
tell $ bindMatch "hbs2:keyman:list" $ nil_ \case
_ -> do
void $ runKeymanClient $ KeyManClient $ do
k <- listKeys
liftIO $ print $ vcat (fmap pretty k)
display_ $ vcat (fmap pretty k)
tell $ bindMatch "hbs2:reflog:tx:create-raw" $ \case
[SymbolVal "stdin", StringLike reflog] -> do
mkRefLogUpdateFrom ( liftIO BS.getContents ) reflog
[LitStrVal s, StringLike reflog] -> do
mkRefLogUpdateFrom ( pure (TE.encodeUtf8 s) ) reflog
_ -> throwIO (BadFormException @C nil)
case cli of
[ListVal [SymbolVal "stdin"]] -> do
what <- getContents
>>= either (error.show) pure . parseTop
run dict what
void $ run dict what
_ -> do
run dict cli
void $ run dict cli