2024-05-28 22:04:34 +02:00
|
|
|
module Main where
|
|
|
|
|
|
|
|
|
|
import AutoTypes qualified as U
|
|
|
|
|
import AutoTypes.Unify qualified as U
|
|
|
|
|
import Control.Applicative ((<**>))
|
2024-06-05 18:02:33 +02:00
|
|
|
import Control.Concurrent
|
|
|
|
|
import Control.Concurrent.STM
|
2024-05-28 22:04:34 +02:00
|
|
|
import Control.Monad
|
|
|
|
|
import Control.Monad.Trans (liftIO)
|
|
|
|
|
import Data.Aeson qualified as J
|
|
|
|
|
import Data.Attoparsec.Char8 as P
|
|
|
|
|
import Data.ByteString.Char8 qualified as B
|
|
|
|
|
import Data.ByteString.Lazy.Char8 qualified as LB
|
|
|
|
|
import Data.ByteString.Lazy.UTF8 qualified as LB
|
|
|
|
|
import Data.ByteString.UTF8 qualified as B
|
|
|
|
|
import Data.List
|
|
|
|
|
import Data.Map qualified as M
|
2024-06-05 22:47:49 +02:00
|
|
|
import Data.Map.Merge.Strict qualified as M
|
|
|
|
|
import Data.Maybe
|
2024-05-28 22:04:34 +02:00
|
|
|
import Data.String (IsString (fromString))
|
2024-06-05 18:02:33 +02:00
|
|
|
import Data.Tagged (Tagged (..), untag)
|
2024-05-28 22:04:34 +02:00
|
|
|
import Debug.Trace
|
|
|
|
|
import Git qualified as G
|
|
|
|
|
import Git.Libgit2 qualified as GB
|
|
|
|
|
import Network.HTTP.Types.Method qualified as W
|
|
|
|
|
import Network.HTTP.Types.Status qualified as W
|
|
|
|
|
import Network.Wai qualified as W
|
|
|
|
|
import Network.Wai.Handler.Warp qualified as W
|
|
|
|
|
import Options.Applicative qualified as A
|
2024-06-05 22:47:49 +02:00
|
|
|
import Safe
|
2024-06-03 11:22:10 +02:00
|
|
|
import Store qualified as Q
|
2024-05-28 22:04:34 +02:00
|
|
|
import System.Directory (setCurrentDirectory)
|
|
|
|
|
import System.FilePath
|
2024-06-05 18:02:33 +02:00
|
|
|
import System.INotify
|
2024-05-28 22:04:34 +02:00
|
|
|
import Text.Printf (printf)
|
2024-06-05 22:47:49 +02:00
|
|
|
import Version
|
2024-05-28 22:04:34 +02:00
|
|
|
|
|
|
|
|
data Args = Args
|
|
|
|
|
{ cmd :: Cmd
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
args :: A.Parser Args
|
|
|
|
|
args = Args <$> cmd'
|
|
|
|
|
|
|
|
|
|
data Cmd = Serve
|
|
|
|
|
|
|
|
|
|
cmd' :: A.Parser Cmd
|
|
|
|
|
cmd' =
|
|
|
|
|
A.hsubparser . mconcat $
|
|
|
|
|
[ A.command "serve" . A.info serveCmd $
|
|
|
|
|
A.progDesc "Run webserver"
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
serveCmd :: A.Parser Cmd
|
|
|
|
|
serveCmd = pure Serve
|
|
|
|
|
|
|
|
|
|
data Repo = Repo
|
|
|
|
|
{ commits :: [Commit]
|
|
|
|
|
}
|
|
|
|
|
deriving (Show)
|
|
|
|
|
|
|
|
|
|
data Commit = Commit
|
|
|
|
|
{ id :: G.CommitOid GB.LgRepo,
|
2024-06-05 22:47:49 +02:00
|
|
|
collections :: [Collection],
|
|
|
|
|
schemaVersion :: Version
|
2024-05-28 22:04:34 +02:00
|
|
|
}
|
|
|
|
|
deriving (Show)
|
|
|
|
|
|
|
|
|
|
data Collection = Collection
|
|
|
|
|
{ path :: FilePath,
|
|
|
|
|
files :: [FilePath],
|
|
|
|
|
schema :: Schema
|
|
|
|
|
}
|
|
|
|
|
deriving (Show)
|
|
|
|
|
|
|
|
|
|
data Schema = Schema {unSchema :: J.Value}
|
|
|
|
|
deriving (Show)
|
|
|
|
|
|
|
|
|
|
instance J.ToJSON Schema where
|
|
|
|
|
toJSON = J.toJSON . (.unSchema)
|
|
|
|
|
|
|
|
|
|
fromAutoTypes :: String -> U.T -> Schema
|
|
|
|
|
fromAutoTypes path (U.Object ps) =
|
|
|
|
|
Schema $
|
|
|
|
|
J.object
|
|
|
|
|
[ ("$schema", J.toJSON @String "https://json-schema.org/draft/2020-12/schema"),
|
|
|
|
|
("$id", J.toJSON @String (path <> ".schema.json")),
|
|
|
|
|
("title", J.toJSON @String path),
|
|
|
|
|
("type", J.toJSON @String "object"),
|
|
|
|
|
("properties", J.toJSON (M.mapWithKey toProperty ps))
|
|
|
|
|
]
|
|
|
|
|
where
|
|
|
|
|
toProperty k (U.Scalar "string") = "string" :: String
|
|
|
|
|
|
2024-06-05 18:02:33 +02:00
|
|
|
watch :: TMVar Repo -> FilePath -> G.RefName -> IO ()
|
|
|
|
|
watch repoT root ref = do
|
|
|
|
|
i <- initINotify
|
|
|
|
|
qT <- newTQueueIO
|
|
|
|
|
wd <-
|
|
|
|
|
addWatch i [MoveIn] ".git/refs/heads" $ \e ->
|
|
|
|
|
atomically (writeTQueue qT e)
|
|
|
|
|
forever do
|
|
|
|
|
repo <- initRepo root ref
|
|
|
|
|
atomically do putTMVar repoT repo
|
|
|
|
|
_ <- atomically do
|
|
|
|
|
let loop = maybe (pure ()) (const loop) =<< tryReadTQueue qT
|
|
|
|
|
readTQueue qT >> loop
|
|
|
|
|
_ <- atomically do takeTMVar repoT
|
|
|
|
|
pure ()
|
|
|
|
|
|
|
|
|
|
initRepo :: FilePath -> G.RefName -> IO Repo
|
|
|
|
|
initRepo root ref = do
|
2024-05-28 22:04:34 +02:00
|
|
|
repo <- G.openRepository GB.lgFactory G.defaultRepositoryOptions {G.repoPath = root}
|
2024-06-05 18:02:33 +02:00
|
|
|
G.runRepository GB.lgFactory repo do
|
2024-05-28 22:04:34 +02:00
|
|
|
Just cid <- fmap Tagged <$> G.resolveReference ref
|
|
|
|
|
c <- G.lookupCommit cid
|
|
|
|
|
cs <- mapM G.lookupCommit =<< G.listCommits Nothing cid
|
2024-06-05 22:47:49 +02:00
|
|
|
fmap (Repo . reverse) $
|
|
|
|
|
foldM
|
|
|
|
|
( \cs c -> do
|
|
|
|
|
let cid = G.commitOid c
|
|
|
|
|
fs <-
|
|
|
|
|
fmap (filter ((== ".json") . takeExtension)) . liftIO $
|
|
|
|
|
Q.withStore root ref do
|
|
|
|
|
Q.withCommit cid (Q.listFiles "/")
|
|
|
|
|
let cls =
|
|
|
|
|
M.toList . M.unionsWith (++) $
|
|
|
|
|
map (\f -> M.singleton (takeDirectory f) [f]) fs
|
|
|
|
|
colls <- forM cls $ \(path, (file : files)) -> do
|
|
|
|
|
(value : values) <- do
|
|
|
|
|
liftIO $ Q.withStore root ref do
|
|
|
|
|
mapM (Q.withCommit cid . Q.readFile) (file : files)
|
|
|
|
|
let schema = fromAutoTypes path $ U.autoTypes' value values
|
|
|
|
|
pure $ Collection path files schema
|
|
|
|
|
let schemaVersion =
|
|
|
|
|
case lastMay cs of
|
|
|
|
|
Nothing -> Version 1 0 0
|
|
|
|
|
Just c' ->
|
|
|
|
|
let Version major' minor' patch' = c'.schemaVersion
|
|
|
|
|
schemas' =
|
|
|
|
|
M.fromList
|
|
|
|
|
( (\coll -> (coll.path, coll.schema))
|
|
|
|
|
<$> c'.collections
|
|
|
|
|
)
|
|
|
|
|
schemas =
|
|
|
|
|
M.fromList
|
|
|
|
|
( (\coll -> (coll.path, coll.schema))
|
|
|
|
|
<$> c.collections
|
|
|
|
|
)
|
|
|
|
|
in case compareSchemas schemas' schemas of
|
|
|
|
|
Just Major -> Version (major' + 1) 0 0
|
|
|
|
|
Just Minor -> Version major' (minor' + 1) 0
|
|
|
|
|
Just Patch -> Version major' minor' (patch' + 1)
|
|
|
|
|
Nothing -> Version major' minor' patch'
|
|
|
|
|
c = Commit cid colls schemaVersion
|
|
|
|
|
pure (c : cs)
|
|
|
|
|
)
|
|
|
|
|
[]
|
|
|
|
|
cs
|
|
|
|
|
|
|
|
|
|
compareSchemas ::
|
|
|
|
|
M.Map String Schema ->
|
|
|
|
|
M.Map String Schema ->
|
|
|
|
|
Maybe SchemaDifference
|
|
|
|
|
compareSchemas schemas' schemas =
|
|
|
|
|
maximumMay
|
|
|
|
|
. catMaybes
|
|
|
|
|
. M.elems
|
|
|
|
|
. M.map (uncurry compareSchemas')
|
|
|
|
|
$ M.merge
|
|
|
|
|
(M.mapMissing (\_ schema' -> (Just schema', Nothing)))
|
|
|
|
|
(M.mapMissing (\_ schema -> (Nothing, Just schema)))
|
|
|
|
|
(M.zipWithMatched (\_ schema' schema -> (Just schema', Just schema)))
|
|
|
|
|
schemas'
|
|
|
|
|
schemas
|
|
|
|
|
where
|
|
|
|
|
compareSchemas' Nothing (Just _) = Just Patch
|
|
|
|
|
compareSchemas' (Just _) Nothing = Just Patch
|
|
|
|
|
compareSchemas' (Just schema') (Just schema) = compareSchema schema' schema
|
|
|
|
|
|
|
|
|
|
-- TODO
|
|
|
|
|
compareSchema :: Schema -> Schema -> Maybe SchemaDifference
|
|
|
|
|
compareSchema schema' schema = Nothing
|
|
|
|
|
|
|
|
|
|
data SchemaDifference
|
|
|
|
|
= Major
|
|
|
|
|
| Minor
|
|
|
|
|
| Patch
|
|
|
|
|
deriving (Eq, Ord)
|
2024-06-05 18:02:33 +02:00
|
|
|
|
|
|
|
|
main :: IO ()
|
|
|
|
|
main = do
|
|
|
|
|
setCurrentDirectory "../blog"
|
|
|
|
|
let root = "."
|
|
|
|
|
ref = "refs/heads/master"
|
|
|
|
|
repoT <- newEmptyTMVarIO
|
|
|
|
|
_ <- forkIO do watch repoT root ref
|
2024-05-28 22:04:34 +02:00
|
|
|
A.execParser (A.info (args <**> A.helper) A.idm) >>= \case
|
|
|
|
|
Args {cmd = Serve} -> do
|
2024-05-31 10:42:26 +02:00
|
|
|
W.runEnv 8081 $ \req respond -> do
|
2024-05-28 22:04:34 +02:00
|
|
|
case P.parseOnly routeP (W.rawPathInfo req) of
|
|
|
|
|
Right (SchemaJson path) -> do
|
2024-06-05 18:02:33 +02:00
|
|
|
repo <- atomically (readTMVar repoT)
|
|
|
|
|
let [c] = filter ((== path) . (.path)) (last repo.commits).collections
|
2024-05-28 22:04:34 +02:00
|
|
|
respond $ W.responseLBS W.status200 [] (J.encode c.schema)
|
2024-06-03 11:22:10 +02:00
|
|
|
Right Query -> do
|
|
|
|
|
q <-
|
|
|
|
|
fromString @Q.Query . LB.toString
|
|
|
|
|
<$> W.lazyRequestBody req
|
2024-06-05 22:47:49 +02:00
|
|
|
r <- liftIO $ Q.withStore root ref do Q.query q
|
2024-06-03 11:22:10 +02:00
|
|
|
respond . W.responseLBS W.status200 [] $ J.encode r
|
2024-06-05 22:47:49 +02:00
|
|
|
Right SchemaVersion -> do
|
|
|
|
|
repo <- atomically (readTMVar repoT)
|
|
|
|
|
respond $
|
|
|
|
|
W.responseLBS W.status200 [] $
|
|
|
|
|
J.encode (last repo.commits).schemaVersion
|
|
|
|
|
(traceShowId -> !_) ->
|
2024-06-03 11:22:10 +02:00
|
|
|
respond $ W.responseLBS W.status200 [] "not implemented"
|
2024-05-28 22:04:34 +02:00
|
|
|
|
|
|
|
|
data Route
|
|
|
|
|
= SchemaJson String
|
2024-06-03 11:22:10 +02:00
|
|
|
| Query
|
2024-06-05 22:47:49 +02:00
|
|
|
| SchemaVersion
|
2024-05-28 22:04:34 +02:00
|
|
|
deriving (Show)
|
|
|
|
|
|
|
|
|
|
routeP :: P.Parser Route
|
|
|
|
|
routeP =
|
2024-06-03 11:22:10 +02:00
|
|
|
( P.choice
|
|
|
|
|
[ SchemaJson <$> (P.string "/" *> P.manyTill P.anyChar (P.string ".schema.json")),
|
2024-06-05 22:47:49 +02:00
|
|
|
pure SchemaVersion <* P.string "/schemaVersion",
|
|
|
|
|
pure Query <* P.string "/"
|
2024-06-03 11:22:10 +02:00
|
|
|
]
|
2024-05-28 22:04:34 +02:00
|
|
|
)
|
|
|
|
|
<* P.endOfInput
|