wip, suckless to support imports + simple web publishing

This commit is contained in:
voidlizard 2025-02-03 12:25:54 +03:00
parent 6a7741e4ae
commit 23e690b302
7 changed files with 285 additions and 122 deletions

35
examples/site/bar.ss Normal file
View File

@ -0,0 +1,35 @@
; это страница, на которую ссылается foo.ss
(define (bar-page)
[html :html [kw]
[html :head [kw]
[html :title [kw] Suckless HTML Page]
[html :meta [kw :charset UTF-8]]
[html :style [kw]
[css body [kw font-family sans-serif margin-left 20px max-width 1024px]]
[css table [kw border-collapse collapse width auto]]
[css (list td th) [kw border [sym (unwords 1px solid #ccc)]
padding 8px
text-align left]]
[css th [kw background-color #f2f2f2 white-space nowrap]]
[css .che [kw margin-right 8px]]
]
]
[html :body [kw]
[html :h1 [kw] Some other page]
[html :h2 [kw] Built with Suckless Script]
[html :p [kw] This is an example page generated using hbs2.]
Just some text
]]
)

96
examples/site/foo.ss Normal file
View File

@ -0,0 +1,96 @@
; это наш "сайт" -- poo.ss
; просто какой-то левый json
[define source [json:file miscellaneous/fuzzy-parse/nix/pkgs.json]]
(define (foo-page bar)
[html :html [kw]
[html :head [kw]
[html :title [kw] Suckless HTML Page]
[html :meta [kw :charset UTF-8]]
[html :style [kw]
[css body [kw font-family sans-serif margin-left 20px max-width 1024px]]
[css table [kw border-collapse collapse width auto]]
[css (list td th) [kw border [sym (unwords 1px solid #ccc)]
padding 8px
text-align left]]
[css th [kw background-color #f2f2f2 white-space nowrap]]
[css .che [kw margin-right 8px]]
]
]
[html :body [kw]
[html :h1 [kw] Super Cool HBS2 Suckless Script Example Page]
[html :h2 [kw] Built with Suckless Script]
[html :p [kw] This is an example page generated using hbs2.]
[html :p [kw] [html :a [kw href [concat ../../tree/ bar]] Referes to bar ] ]
[html :form [kw action # method POST]
[html :label [kw for cb1]
[html :input [kw :type checkbox name checkbox1 :id cb1 :class che]]
I agree with the terms
]
[html :br]
[html :input [kw :type text :name username :placeholder "Enter your name"]]
[html :br]
[html :input [kw :type submit :value Submit]]
]
[html :br]
[html :p [kw]
This text contains
[html :b [kw] bold]
chr:comma
[html :i [kw] italic]
:and
[html :u [kw] :underlined]
styles.
]
[html :br]
; Unicode test section
[html :p [kw] Russian: Привет, мир!]
[html :p [kw] Chinese: 你好世界]
[html :p [kw] Korean: 안녕하세요, 세계!]
[html :br]
[html :table [kw]
[html :thead [kw]
[html :tr [kw]
[html :th [kw] Package]
[html :th [kw] Version]
]
]
[html :tbody [kw]
[map [fn 1 [html :tr [kw] [html :th [kw] [car _1]]
[html :td [kw] [nth 1 _1]] ] ] source]
]
]
[html :br]
[html :p [kw]
For more information, visit
[html :a [kw href http://example.com] our website]
"."
]
]
]
)

44
examples/site/site.ss Normal file
View File

@ -0,0 +1,44 @@
; [eval [cons :begin [top:file bar.ss]]]
(import bar.ss)
(import foo.ss)
(define site-root-ref :4X65y4YvUjRL2gtA9Ec3YDDP4bnxjTGhfjpoah96t3z1)
(define (as-html n) [kw :file-name n :mime-type "text/html; charset=utf-8"]) ; метаданные что бы hbs2-peer отображал как вебстраницу
(define bar.html (bar-page)) ; генерим страничку
(define bar.hash (hbs2:tree:metadata:string [as-html :bar.html] bar.html)) ; сохраняем как дерево с метаданными
(define foo.html (foo-page bar.hash))
(define foo.hash (hbs2:tree:metadata:string [as-html :foo.html] foo.html)) ; сохраняем как дерево с метаданными
(define grove [hbs2:grove:annotated [kw webroot foo.hash] [list foo.hash bar.hash]])
; println :bar.html space "hash:" space bar.hash
println Grove: space grove ; hello.hash
hbs2:lwwref:update site-root-ref grove
; newline
; print [hbs2:lwwref:get site-root-ref]
(define url [sym [join / http://localhost:5000/ref site-root-ref]]) ; вычисляем url
; newline
; print url
; print bar.html
; print foo.html
; print site-root-ref
(call:proc "firefox" url) ; вызываем фарфокс

View File

@ -28,12 +28,14 @@ import Codec.Serialise
import Control.Monad.Trans.Maybe import Control.Monad.Trans.Maybe
import Control.Monad.Trans.Cont import Control.Monad.Trans.Cont
import Data.ByteString.Lazy qualified as LBS import Data.ByteString.Lazy qualified as LBS
import Data.ByteString.Lazy (ByteString)
import Data.Either import Data.Either
import Data.Set qualified as Set import Data.Set qualified as Set
import Data.HashMap.Strict qualified as HM import Data.HashMap.Strict qualified as HM
import Data.Maybe import Data.Maybe
import Data.Text.Encoding qualified as TE import Data.Text.Encoding qualified as TE
import Data.Text qualified as Text import Data.Text qualified as Text
import Data.Text.IO qualified as TIO
import Magic.Data import Magic.Data
import Magic.Init (magicLoadDefault,magicOpen) import Magic.Init (magicLoadDefault,magicOpen)
@ -59,11 +61,14 @@ metaFromSyntax syn =
t x = Text.pack (show $ pretty x) t x = Text.pack (show $ pretty x)
metaDataEntries :: forall c m . ( IsContext c type ForMetadata c m = ( IsContext c
, MonadUnliftIO m , MonadUnliftIO m
, Exception (BadFormException c) , Exception (BadFormException c)
, HasStorage m , HasStorage m
, HasClientAPI StorageAPI UNIX m , HasClientAPI StorageAPI UNIX m
)
metaDataEntries :: forall c m . ( ForMetadata c m
) => MakeDictM c m () ) => MakeDictM c m ()
metaDataEntries = do metaDataEntries = do
@ -199,126 +204,38 @@ file-name: "qqq.txt"
_ -> throwIO (BadFormException @c nil) _ -> throwIO (BadFormException @c nil)
brief "creates a merkle tree with metadata"
$ returns "string" "hash"
$ args [ arg "list-of-options" "..." ]
$ desc ( "options:" <> line
<> indent 4 (
vcat [ opt ":stdin" "read data from stdin"
, opt ":auto" "create metadata from file using libmagic"
, opt "[kw [encrypted group-key-hash]]" "encrypt metadata with given group key"
, opt "dict" "custom metadata dictionary"
, opt "filename : string-like" "file name, ignored if stdin option set"
])
)
$ examples [qc|
Create not encrypted merkle tree for string from stdin without metadata
$ echo TEST | hbs2-cli hbs2:tree:metadata:create :stdin let metadataCreateMan = brief "creates a tree with metadata"
7dGqTtoehsgn7bADcVTyp93tq2FfuQgtBuVvYL46jdyz let kw = arg "kw" "opts"
;; empty metadata metadataCreateMan $ args [kw, arg "string" "filename"] $
entry $ bindMatch "hbs2:tree:metadata:file" $ \case
hbs2-cli hbs2:tree:metadata:get 7dGqTtoehsgn7bADcVTyp93tq2FfuQgtBuVvYL46jdyz [ syn@(ListVal{}), StringLike fn ] -> do
meta0 <- liftIO do
Create merkle tree with custom metadata
$ echo TEST | hbs2-cli hbs2:tree:metadata:create :stdin [kw hello world]
2ASBLBPRUMrHoSkNYsRWwJQiiXuSGDZTaCXAdDTdeJY6
$ hbs2-cli hbs2:tree:metadata:get 2ASBLBPRUMrHoSkNYsRWwJQiiXuSGDZTaCXAdDTdeJY6
hello: "world"
$ hbs2-cli hbs2:tree:metadata:get 7YyWZ44sWpHvrqnFxL8G8HJo4o4p659diusZoHyhXCTx
((mime-type: "text/plain; charset=us-ascii") (file-name: "MetaData.hs"))
$ hbs2-cli hbs2:tree:metadata:create :auto ./lambda.svg
3fv5ym8NhY8zat37NaTvY9PDcwJqMLUD73ewHxtHysWg
Create encrypted tree metadata with a new groupkey
$ hbs2-cli [define pks [list EiwWxY3xwTfnLKJdzzxNMZgA2ZvYAZd9e8B8pFeCtnrn]] \
and [define gk [hbs2:groupkey:store [hbs2:groupkey:create pks]]] \
and [hbs2:tree:metadata:create :auto [kw :encrypted gk] ./lambda.svg]
BFLcbpNEqngsJ8gzx3ps4ETXfpUMGgjEETNEVgR18KG4
Check group key
$ hbs2-cli hbs2:tree:metadata:get-gk BFLcbpNEqngsJ8gzx3ps4ETXfpUMGgjEETNEVgR18KG4y
GixS4wssCD4x7LzvHve2JhFCghW1Hwia2tiGTfTTef1u
Check metadata
$ hbs2-cli hbs2:tree:metadata:get BFLcbpNEqngsJ8gzx3ps4ETXfpUMGgjEETNEVgR18KG4y
mime-type: "image/svg+xml; charset=us-ascii"
file-name: "lambda.svg"
List group key
$ hbs2-cli hbs2:groupkey:list-public-keys [hbs2:groupkey:load GixS4wssCD4x7LzvHve2JhFCghW1Hwia2tiGTfTTef1u]
("EiwWxY3xwTfnLKJdzzxNMZgA2ZvYAZd9e8B8pFeCtnrn")
|]
$ entry $ bindMatch "hbs2:tree:metadata:create" $ \syn -> do
case syn of
args -> do
opts' <- for args $ \case
SymbolVal "stdin" -> pure [Stdin]
SymbolVal "auto" -> pure [Auto]
ListVal [ListVal [SymbolVal "encrypted", StringLike key]]
-> do
pure [Encrypted key]
ListVal ws -> do
pure [MetaDataEntry x y | ListVal [SymbolVal x, StringLike y] <- ws ]
StringLike rest -> do
pure [MetaDataFile rest]
_ -> pure mempty
let opts = mconcat opts' & Set.fromList
let inFile = headMay [ x | MetaDataFile x <- universeBi opts ]
lbs <- case (Set.member Stdin opts, inFile) of
(True, _) -> liftIO LBS.getContents
(False, Just fn) -> liftIO (LBS.readFile fn)
(_, Nothing) -> liftIO LBS.getContents
meta0 <- if not (Set.member Auto opts) || isNothing inFile then
pure (mempty :: HashMap Text Text)
else liftIO do
let fn = fromJust inFile
magic <- magicOpen [MagicMimeType,MagicMime,MagicMimeEncoding] magic <- magicOpen [MagicMimeType,MagicMime,MagicMimeEncoding]
magicLoadDefault magic magicLoadDefault magic
mime <- magicFile magic fn mime <- magicFile magic fn
pure $ HM.fromList [ ("file-name", Text.pack (takeFileName fn)) pure $ HM.fromList [ ("file-name", Text.pack (takeFileName fn))
, ("mime-type", Text.pack mime) , ("mime-type", Text.pack mime)
] ]
doCreateMetadataTree meta0 syn (liftIO $ LBS.readFile fn)
let meta1 = HM.fromList [ (txt n, txt e) | MetaDataEntry n e <- universeBi opts ] _ -> throwIO (BadFormException @c nil)
let enc = headMay [ e | x@(Encrypted e) <- universeBi opts ] metadataCreateMan $ args [kw] $
entry $ bindMatch "hbs2:tree:metadata:stdin" $ \case
[syn@(ListVal{})] -> do
doCreateMetadataTree mempty syn (liftIO LBS.getContents)
gk <- runMaybeT do _ -> throwIO (BadFormException @c nil)
s <- toMPlus enc
g <- lift $ loadGroupKey (fromString s)
toMPlus g
when (isJust enc && isNothing gk) do metadataCreateMan $ args [kw, arg "string" "input"] $
error $ show $ "Can't load group key" <+> pretty enc entry $ bindMatch "hbs2:tree:metadata:string" $ \case
[ syn@(ListVal{}), TextLike content ] -> do
-- liftIO $ TIO.putStr content
doCreateMetadataTree mempty syn (pure $ LBS.fromStrict $ TE.encodeUtf8 content)
sto <- getStorage _ -> throwIO (BadFormException @c nil)
href <- lift (createTreeWithMetadata sto gk (meta0 <> meta1) lbs)
`orDie` "encryption error"
pure $ mkStr (show $ pretty href)
entry $ bindMatch "cbor:base58" $ \case entry $ bindMatch "cbor:base58" $ \case
[ LitStrVal x ] -> do [ LitStrVal x ] -> do
@ -327,3 +244,47 @@ $ hbs2-cli hbs2:groupkey:list-public-keys [hbs2:groupkey:load GixS4wssCD4x7LzvHv
_ -> throwIO (BadFormException @c nil) _ -> throwIO (BadFormException @c nil)
groupKeyFromSyntax :: Syntax c -> Maybe HashRef
groupKeyFromSyntax = \case
ListVal es -> headMay [ v | ListVal [ TextLike "gk", HashLike v ] <- es ]
_ -> Nothing
loadGroupKeyFromSyntax :: ( ForMetadata c m )
=> Syntax c
-> RunM c m (Maybe (GroupKey 'Symm 'HBS2Basic))
loadGroupKeyFromSyntax syn = runMaybeT do
hash <- groupKeyFromSyntax syn & toMPlus
toMPlus =<< lift (loadGroupKey hash)
metadataFromSyntax :: Syntax c -> HashMap Text Text
metadataFromSyntax = \case
ListVal es -> HM.fromList [ (k,v) | ListVal [ TextLike k, TextLike v] <- es, k /= "gk" ]
_ -> mempty
doCreateMetadataTree :: forall c m . ForMetadata c m
=> HashMap Text Text
-> Syntax c
-> m ByteString
-> RunM c m (Syntax c)
doCreateMetadataTree meta0 syn getLbs = do
let meta = metadataFromSyntax syn
let gkh = groupKeyFromSyntax syn
gk <- loadGroupKeyFromSyntax syn
when (isJust gkh && isNothing gk) do
throwIO (GroupKeyNotFound 1)
sto <- getStorage
lbs <- lift getLbs
href <- lift (createTreeWithMetadata sto gk (meta0 <> meta) lbs)
>>= orThrow StorageError
pure $ mkStr (show $ pretty href)

View File

@ -25,6 +25,15 @@ import Data.Text qualified as Text
import Control.Monad.Except import Control.Monad.Except
import Codec.Serialise import Codec.Serialise
pattern GroveHashes :: forall {c}. [HashRef] -> [Syntax c]
pattern GroveHashes hashes <- ( groveHashes -> hashes )
groveHashes :: [Syntax c] -> [HashRef]
groveHashes = \case
[ ListVal (HashLikeList hashes) ] -> hashes
HashLikeList hashes -> hashes
_ -> mempty
treeEntries :: forall c m . ( IsContext c treeEntries :: forall c m . ( IsContext c
, MonadUnliftIO m , MonadUnliftIO m
, Exception (BadFormException c) , Exception (BadFormException c)
@ -65,7 +74,7 @@ It's just an easy way to create a such thing, you may browse it by hbs2 cat -H
_ -> throwIO (BadFormException @c nil) _ -> throwIO (BadFormException @c nil)
entry $ bindMatch "hbs2:grove:annotated" $ \case entry $ bindMatch "hbs2:grove:annotated" $ \case
(ListVal ann : HashLikeList hashes@(x:_)) -> lift do (ListVal ann : GroveHashes hashes) -> lift do
sto <- getStorage sto <- getStorage
let pt = toPTree (MaxSize defHashListChunk) (MaxNum defTreeChildNum) hashes let pt = toPTree (MaxSize defHashListChunk) (MaxNum defTreeChildNum) hashes

View File

@ -227,11 +227,11 @@ getTreeHash sto what'' = void $ flip runContT pure do
] ]
let webroot = headMay [ w let webroot = headMay [ w
| i < 2, ListVal [SymbolVal "webroot", HashLike w] <- meta | i < 3, ListVal [SymbolVal "webroot", HashLike w] <- meta
] ]
case webroot of case webroot of
Just x | i < 2 -> again (x, succ i) Just x | i < 3 -> again (x, succ i)
_ -> do _ -> do

View File

@ -617,7 +617,8 @@ eval' dict0 syn' = handle (handleForm syn') $ do
let dict = dict0 <> dict1 let dict = dict0 <> dict1
-- liftIO $ print $ show $ "TRACE EXP" <+> pretty syn -- liiftIO $ print $ show $ "TRACE EXP" <+> pretty syn
let importDecls = HS.fromList [ "define", "define-macro" :: Id ]
case syn' of case syn' of
@ -653,6 +654,23 @@ eval' dict0 syn' = handle (handleForm syn') $ do
ListVal [ SymbolVal "eval", e ] -> eval e >>= eval ListVal [ SymbolVal "eval", e ] -> eval e >>= eval
ListVal [ SymbolVal "import", StringLike fn ] -> do
-- FIXME: fancy-error-handling
syn <- liftIO (TIO.readFile fn) <&> parseTop >>= either(error.show) pure
let decls = [ fixContext d
| d@(ListVal (SymbolVal what : rest)) <- syn
, what `HS.member` importDecls
]
-- liftIO $ mapM_ (print . pretty) decls
mapM_ eval decls
pure nil
-- error $ show $ "fucked!" <+> pretty fn
ListVal [SymbolVal "define", SymbolVal what, e] -> do ListVal [SymbolVal "define", SymbolVal what, e] -> do
ev <- eval e ev <- eval e
bind what ev>> pure nil bind what ev>> pure nil