init
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/dist-newstyle
|
||||||
30
LICENSE
Normal file
30
LICENSE
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
Copyright (c) 2024, Alexander Foremny
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following
|
||||||
|
disclaimer in the documentation and/or other materials provided
|
||||||
|
with the distribution.
|
||||||
|
|
||||||
|
* Neither the name of Alexander Foremny nor the names of other
|
||||||
|
contributors may be used to endorse or promote products derived
|
||||||
|
from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
38
acms.cabal
Normal file
38
acms.cabal
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
cabal-version: 3.4
|
||||||
|
name: acms
|
||||||
|
version: 0.1.0.0
|
||||||
|
license: BSD-3-Clause
|
||||||
|
license-file: LICENSE
|
||||||
|
maintainer: aforemny@posteo.de
|
||||||
|
author: Alexander Foremny
|
||||||
|
build-type: Simple
|
||||||
|
|
||||||
|
executable acms
|
||||||
|
main-is: Main.hs
|
||||||
|
hs-source-dirs: app
|
||||||
|
default-language: GHC2021
|
||||||
|
default-extensions:
|
||||||
|
BlockArguments LambdaCase OverloadedStrings ViewPatterns
|
||||||
|
OverloadedRecordDot NoFieldSelectors
|
||||||
|
|
||||||
|
ghc-options: -Wall -threaded
|
||||||
|
build-depends:
|
||||||
|
aeson,
|
||||||
|
astore,
|
||||||
|
attoparsec,
|
||||||
|
autotypes,
|
||||||
|
base,
|
||||||
|
bytestring,
|
||||||
|
containers,
|
||||||
|
directory,
|
||||||
|
filepath,
|
||||||
|
gitlib,
|
||||||
|
gitlib-libgit2,
|
||||||
|
hlibgit2,
|
||||||
|
http-types,
|
||||||
|
mtl,
|
||||||
|
optparse-applicative,
|
||||||
|
tagged,
|
||||||
|
utf8-string,
|
||||||
|
wai,
|
||||||
|
warp
|
||||||
132
app/Main.hs
Normal file
132
app/Main.hs
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
module Main where
|
||||||
|
|
||||||
|
import AutoTypes qualified as U
|
||||||
|
import AutoTypes.Unify qualified as U
|
||||||
|
import Control.Applicative ((<**>))
|
||||||
|
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
|
||||||
|
import Data.String (IsString (fromString))
|
||||||
|
import Data.Tagged (Tagged (..))
|
||||||
|
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
|
||||||
|
import System.Directory (setCurrentDirectory)
|
||||||
|
import System.FilePath
|
||||||
|
import Text.Printf (printf)
|
||||||
|
|
||||||
|
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,
|
||||||
|
collections :: [Collection]
|
||||||
|
}
|
||||||
|
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
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = do
|
||||||
|
setCurrentDirectory "./blog"
|
||||||
|
let root = "."
|
||||||
|
ref = "HEAD"
|
||||||
|
repo <- G.openRepository GB.lgFactory G.defaultRepositoryOptions {G.repoPath = root}
|
||||||
|
repo <- G.runRepository GB.lgFactory repo do
|
||||||
|
Just cid <- fmap Tagged <$> G.resolveReference ref
|
||||||
|
c <- G.lookupCommit cid
|
||||||
|
cs <- mapM G.lookupCommit =<< G.listCommits Nothing cid
|
||||||
|
let showCommit c = G.commitLog c
|
||||||
|
fmap Repo . forM cs $ \c -> do
|
||||||
|
let cid = G.commitOid c
|
||||||
|
let tid = G.commitTree c
|
||||||
|
t <- G.lookupTree tid
|
||||||
|
fs <-
|
||||||
|
filter ((== ".json") . takeExtension)
|
||||||
|
. map B.toString
|
||||||
|
. map fst
|
||||||
|
<$> G.listTreeEntries t
|
||||||
|
let cls = M.toList (M.unionsWith (++) (map (\f -> M.singleton (takeDirectory f) [f]) fs))
|
||||||
|
colls <- forM cls $ \(path, (file : files)) -> do
|
||||||
|
schema <-
|
||||||
|
fmap (fromAutoTypes path) . liftIO $ -- TODO read from HEAD
|
||||||
|
U.autoTypes file files
|
||||||
|
pure $ Collection path files schema
|
||||||
|
pure (Commit cid colls)
|
||||||
|
A.execParser (A.info (args <**> A.helper) A.idm) >>= \case
|
||||||
|
Args {cmd = Serve} -> do
|
||||||
|
W.runEnv 8080 $ \req respond -> do
|
||||||
|
case P.parseOnly routeP (W.rawPathInfo req) of
|
||||||
|
Right (SchemaJson path) -> do
|
||||||
|
let [c] = filter ((== path) . (.path)) (head repo.commits).collections
|
||||||
|
respond $ W.responseLBS W.status200 [] (J.encode c.schema)
|
||||||
|
(Debug.Trace.traceShowId -> !_) ->
|
||||||
|
respond $ W.responseLBS W.status200 [] "OK"
|
||||||
|
|
||||||
|
data Route
|
||||||
|
= SchemaJson String
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
routeP :: P.Parser Route
|
||||||
|
routeP =
|
||||||
|
( SchemaJson
|
||||||
|
<$> (P.string "/" *> P.manyTill P.anyChar (P.string ".schema.json"))
|
||||||
|
)
|
||||||
|
<* P.endOfInput
|
||||||
1
autotypes/.gitignore
vendored
Normal file
1
autotypes/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/dist-newstyle
|
||||||
27
autotypes/app/Main.hs
Normal file
27
autotypes/app/Main.hs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
module Main where
|
||||||
|
|
||||||
|
import AutoTypes.Unify as U
|
||||||
|
import Data.Aeson (Value, decodeFileStrict', encode)
|
||||||
|
import qualified Data.ByteString.Lazy as B
|
||||||
|
import System.Environment (getArgs)
|
||||||
|
import System.FilePath (takeFileName)
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = do
|
||||||
|
filePaths <- getArgs
|
||||||
|
types <-
|
||||||
|
mapM
|
||||||
|
( \filePath -> do
|
||||||
|
Just value <- decodeFileStrict' filePath
|
||||||
|
pure (U.fromJson value)
|
||||||
|
)
|
||||||
|
filePaths
|
||||||
|
B.putStr
|
||||||
|
( encode
|
||||||
|
( head
|
||||||
|
( foldr1
|
||||||
|
(\ls rs -> (concat [unify1 l r | l <- ls, r <- rs]))
|
||||||
|
(map (: []) types)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
54
autotypes/autotypes.cabal
Normal file
54
autotypes/autotypes.cabal
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
cabal-version: 2.4
|
||||||
|
name: autotypes
|
||||||
|
version: 0.1.0.0
|
||||||
|
|
||||||
|
-- A short (one-line) description of the package.
|
||||||
|
-- synopsis:
|
||||||
|
|
||||||
|
-- A longer description of the package.
|
||||||
|
-- description:
|
||||||
|
|
||||||
|
-- A URL where users can report bugs.
|
||||||
|
-- bug-reports:
|
||||||
|
|
||||||
|
-- The license under which the package is released.
|
||||||
|
-- license:
|
||||||
|
author: Alexander Foremny
|
||||||
|
maintainer: aforemny@posteo.de
|
||||||
|
|
||||||
|
-- A copyright notice.
|
||||||
|
-- copyright:
|
||||||
|
-- category:
|
||||||
|
extra-source-files: CHANGELOG.md
|
||||||
|
|
||||||
|
library
|
||||||
|
exposed-modules:
|
||||||
|
AutoTypes
|
||||||
|
AutoTypes.Unify
|
||||||
|
|
||||||
|
-- Modules included in this library but not exported.
|
||||||
|
-- other-modules:
|
||||||
|
|
||||||
|
-- LANGUAGE extensions used by modules in this package.
|
||||||
|
-- other-extensions:
|
||||||
|
build-depends:
|
||||||
|
aeson,
|
||||||
|
aeson-qq,
|
||||||
|
base,
|
||||||
|
bytestring,
|
||||||
|
containers,
|
||||||
|
filepath,
|
||||||
|
vector
|
||||||
|
hs-source-dirs: src
|
||||||
|
default-language: Haskell2010
|
||||||
|
|
||||||
|
executable autotypes
|
||||||
|
main-is: Main.hs
|
||||||
|
hs-source-dirs: app
|
||||||
|
default-language: Haskell2010
|
||||||
|
build-depends:
|
||||||
|
aeson,
|
||||||
|
autotypes,
|
||||||
|
base,
|
||||||
|
bytestring,
|
||||||
|
filepath
|
||||||
16
autotypes/default.nix
Normal file
16
autotypes/default.nix
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{ mkDerivation, aeson, aeson-qq, base, bytestring, containers
|
||||||
|
, filepath, lib, vector
|
||||||
|
}:
|
||||||
|
mkDerivation {
|
||||||
|
pname = "autotypes";
|
||||||
|
version = "0.1.0.0";
|
||||||
|
src = ./.;
|
||||||
|
isLibrary = true;
|
||||||
|
isExecutable = true;
|
||||||
|
libraryHaskellDepends = [
|
||||||
|
aeson aeson-qq base bytestring containers filepath vector
|
||||||
|
];
|
||||||
|
executableHaskellDepends = [ aeson base bytestring filepath ];
|
||||||
|
license = "unknown";
|
||||||
|
mainProgram = "autotypes";
|
||||||
|
}
|
||||||
25
autotypes/src/AutoTypes.hs
Normal file
25
autotypes/src/AutoTypes.hs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
module AutoTypes
|
||||||
|
( autoTypes,
|
||||||
|
autoTypes',
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Debug.Trace
|
||||||
|
import qualified AutoTypes.Unify as U
|
||||||
|
import Data.Aeson (Value, decodeFileStrict', encode)
|
||||||
|
import Data.Maybe (fromJust)
|
||||||
|
import System.Environment (getArgs)
|
||||||
|
import System.FilePath (takeFileName)
|
||||||
|
|
||||||
|
autoTypes :: FilePath -> [FilePath] -> IO U.T
|
||||||
|
autoTypes fp fps = autoTypes' <$> go fp <*> mapM go (fp : fps)
|
||||||
|
where go = fmap fromJust . decodeFileStrict'
|
||||||
|
|
||||||
|
autoTypes' :: Value -> [Value] -> U.T
|
||||||
|
autoTypes' t' ts' =
|
||||||
|
let types = map U.fromJson (Debug.Trace.traceShowId (t' : ts'))
|
||||||
|
in head
|
||||||
|
( foldr1
|
||||||
|
(\ls rs -> (concat [U.unify1 l r | l <- ls, r <- rs]))
|
||||||
|
(map (: []) types)
|
||||||
|
)
|
||||||
251
autotypes/src/AutoTypes/Unify.hs
Normal file
251
autotypes/src/AutoTypes/Unify.hs
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
{-# LANGUAGE DeriveTraversable #-}
|
||||||
|
{-# LANGUAGE QuasiQuotes #-}
|
||||||
|
|
||||||
|
module AutoTypes.Unify
|
||||||
|
( T (..),
|
||||||
|
toString,
|
||||||
|
fromJson,
|
||||||
|
unify1,
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Control.Arrow
|
||||||
|
import Control.Exception (Exception, throw)
|
||||||
|
import qualified Data.Aeson as A
|
||||||
|
import qualified Data.Aeson.Key as K
|
||||||
|
import qualified Data.Aeson.KeyMap as KM
|
||||||
|
import Data.Aeson.QQ
|
||||||
|
import Data.List (intercalate, nub)
|
||||||
|
import Data.Map (Map)
|
||||||
|
import qualified Data.Map as M
|
||||||
|
import qualified Data.Map.Merge.Lazy as M
|
||||||
|
import qualified Data.Vector as V
|
||||||
|
import Debug.Trace
|
||||||
|
import Prelude hiding (null)
|
||||||
|
|
||||||
|
data T
|
||||||
|
= List (Maybe T)
|
||||||
|
| Object (Map String T)
|
||||||
|
| Option (Maybe T)
|
||||||
|
| Scalar String
|
||||||
|
| Union [T]
|
||||||
|
deriving (Eq, Show)
|
||||||
|
|
||||||
|
instance A.ToJSON T where
|
||||||
|
toJSON (List t) = A.toJSON t
|
||||||
|
toJSON (Object ts) = A.toJSON ts
|
||||||
|
toJSON (Option t) = A.toJSON t
|
||||||
|
toJSON (Scalar s) = A.toJSON s
|
||||||
|
toJSON (Union ts) = A.toJSON ts
|
||||||
|
|
||||||
|
toString :: T -> String
|
||||||
|
toString = intercalate "\n" . map (\(n, s) -> indent n s) . toString_ 0
|
||||||
|
|
||||||
|
toString_ :: Int -> T -> [(Int, String)]
|
||||||
|
toString_ n (Object kvs) =
|
||||||
|
concat
|
||||||
|
[ [(n, "{")],
|
||||||
|
concat . M.elems $
|
||||||
|
M.mapWithKey (\s t -> (n + 1, s) : toString_ (n + 2) t) kvs,
|
||||||
|
[(n, "}")]
|
||||||
|
]
|
||||||
|
toString_ n (Scalar s) = [(n, s)]
|
||||||
|
toString_ n (Union ts) = concatMap (toString_ n) ts
|
||||||
|
toString_ n (Option Nothing) = [(n, "null")]
|
||||||
|
toString_ n (Option (Just t)) = map (second (++ "?")) (toString_ n t)
|
||||||
|
|
||||||
|
indent n = (++) (replicate (4 * n) ' ')
|
||||||
|
|
||||||
|
union :: [T] -> T
|
||||||
|
union ts =
|
||||||
|
case ts of
|
||||||
|
[t] -> t
|
||||||
|
ts -> Union ts
|
||||||
|
|
||||||
|
unify1 :: T -> T -> [T]
|
||||||
|
unify1 (Scalar n) (Scalar m)
|
||||||
|
| n == m = [Scalar n]
|
||||||
|
| otherwise = [union [Scalar n, Scalar m]]
|
||||||
|
unify1 l@(Object ls) r@(Object rs) =
|
||||||
|
let os =
|
||||||
|
( map Object . traverse id $
|
||||||
|
let f _ (l@(Option _)) = [l]
|
||||||
|
f _ l = [Option (Just l)]
|
||||||
|
in M.merge
|
||||||
|
(M.mapMissing f)
|
||||||
|
(M.mapMissing f)
|
||||||
|
(M.zipWithMatched (\_ l r -> unify1 l r))
|
||||||
|
ls
|
||||||
|
rs
|
||||||
|
)
|
||||||
|
in os
|
||||||
|
++ ( if l `subst` r || r `subst` l
|
||||||
|
then []
|
||||||
|
else [union [l, r]]
|
||||||
|
)
|
||||||
|
unify1 (Option Nothing) (Option Nothing) = [Option Nothing]
|
||||||
|
unify1 (Option (Just l)) (Option Nothing) = [Option (Just l)]
|
||||||
|
unify1 (Option Nothing) (Option (Just r)) = [Option (Just r)]
|
||||||
|
unify1 (Option (Just l)) (Option (Just r)) = map (Option . Just) (unify1 l r)
|
||||||
|
unify1 (Option Nothing) r = [Option (Just r)]
|
||||||
|
unify1 (List Nothing) (List Nothing) = [List Nothing]
|
||||||
|
unify1 (List Nothing) (List (Just t)) = [List (Just t)]
|
||||||
|
unify1 (List (Just t)) (List Nothing) = [List (Just t)]
|
||||||
|
unify1 (List (Just l)) (List (Just r)) =
|
||||||
|
if l == r || r `subst` l
|
||||||
|
then [List (Just l)]
|
||||||
|
else
|
||||||
|
if l `subst` r
|
||||||
|
then [List (Just r)]
|
||||||
|
else [List (Just (union [l, r]))]
|
||||||
|
unify1 l (Option Nothing) = [Option (Just l)]
|
||||||
|
unify1 l (Option (Just r)) = map (Option . Just) (unify1 l r)
|
||||||
|
unify1 (Option (Just l)) r = map (Option . Just) (unify1 l r)
|
||||||
|
unify1 (Union ls) (Union rs) = [union (ls ++ rs)]
|
||||||
|
unify1 (Union ls) r = [union (ls ++ [r])]
|
||||||
|
unify1 l (Union rs) = [union ([l] ++ rs)]
|
||||||
|
unify1 l r = [union [l, r]]
|
||||||
|
|
||||||
|
subst :: T -> T -> Bool
|
||||||
|
subst (Object l) (Object r) =
|
||||||
|
and
|
||||||
|
( map
|
||||||
|
( \(k, t) -> case (t, M.lookup k r) of
|
||||||
|
(t, Just t') -> t `subst` t'
|
||||||
|
_ -> False
|
||||||
|
)
|
||||||
|
(M.toList l)
|
||||||
|
)
|
||||||
|
subst (Option Nothing) (Option _) = True
|
||||||
|
subst l (Option Nothing) = True
|
||||||
|
subst (Option (Just l)) (Option (Just r)) = l `subst` r
|
||||||
|
subst l (Option (Just r)) = l `subst` r
|
||||||
|
subst l r = l == r
|
||||||
|
|
||||||
|
lims :: [T] -> [T]
|
||||||
|
lims ts = nub [t | t <- ts, all (\t' -> not (t `subst` t') || t' == t) ts]
|
||||||
|
|
||||||
|
unify l r =
|
||||||
|
let s =
|
||||||
|
unlines
|
||||||
|
( [ toString l ++ "," ++ toString r,
|
||||||
|
"---"
|
||||||
|
]
|
||||||
|
++ map toString t
|
||||||
|
)
|
||||||
|
t = unify1 l r
|
||||||
|
in Debug.Trace.trace s t
|
||||||
|
|
||||||
|
{-
|
||||||
|
unify :: T -> T -> Either (T, T) T
|
||||||
|
unify (Scalar n) (Scalar m)
|
||||||
|
| n == m = Right (Scalar n)
|
||||||
|
| otherwise = Left (Scalar n, Scalar m)
|
||||||
|
unify (Object ls) (Object rs) =
|
||||||
|
let f _ (l@(Option _)) = Right l
|
||||||
|
f _ l = Right (Option (Just l))
|
||||||
|
in case M.merge
|
||||||
|
(M.mapMissing f)
|
||||||
|
(M.mapMissing f)
|
||||||
|
(M.zipWithMatched (\_ l r -> unify l r))
|
||||||
|
ls
|
||||||
|
rs
|
||||||
|
& M.partition isRight
|
||||||
|
& ( \(rs, ls) ->
|
||||||
|
(M.map (fromRight undefined) rs, M.elems (M.map (fromLeft undefined) ls))
|
||||||
|
) of
|
||||||
|
(_, e : _) -> Left e
|
||||||
|
(lrs, []) -> Right (Object lrs)
|
||||||
|
unify (Option Nothing) (Option Nothing) = Right (Option Nothing)
|
||||||
|
unify (Option (Just l)) (Option Nothing) = Right (Option (Just l))
|
||||||
|
unify (Option Nothing) (Option (Just r)) = Right (Option (Just r))
|
||||||
|
unify (Option (Just l)) (Option (Just r)) = Option . Just <$> unify l r
|
||||||
|
-}
|
||||||
|
|
||||||
|
object :: Map String T -> T
|
||||||
|
object = Object
|
||||||
|
|
||||||
|
list :: Maybe T -> T
|
||||||
|
list = List
|
||||||
|
|
||||||
|
string, number, bool, null :: T
|
||||||
|
string = Scalar "string"
|
||||||
|
number = Scalar "number"
|
||||||
|
bool = Scalar "bool"
|
||||||
|
null = Option Nothing
|
||||||
|
|
||||||
|
data InferException = InferException [T]
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
instance Exception InferException
|
||||||
|
|
||||||
|
fromJson :: A.Value -> T
|
||||||
|
fromJson (A.Object kvs) =
|
||||||
|
object (M.mapKeys K.toString (M.map fromJson (KM.toMap kvs)))
|
||||||
|
fromJson t@(A.Array vs) =
|
||||||
|
let ts = map fromJson (V.toList vs)
|
||||||
|
in case nub ts of
|
||||||
|
[] -> list Nothing
|
||||||
|
[t] -> list (Just t)
|
||||||
|
_ -> throw (InferException ts)
|
||||||
|
fromJson (A.String _) = string
|
||||||
|
fromJson (A.Number _) = number
|
||||||
|
fromJson (A.Bool _) = bool
|
||||||
|
fromJson A.Null = null
|
||||||
|
|
||||||
|
object1 =
|
||||||
|
[aesonQQ|{
|
||||||
|
"firstName": "firstName",
|
||||||
|
"lastName": "lastName"
|
||||||
|
}|]
|
||||||
|
|
||||||
|
object2 =
|
||||||
|
[aesonQQ|{
|
||||||
|
"firstName": "firstName",
|
||||||
|
"lastName": "lastName",
|
||||||
|
"birthDay": null
|
||||||
|
}|]
|
||||||
|
|
||||||
|
object3 =
|
||||||
|
[aesonQQ|{
|
||||||
|
"firstName": "firstName",
|
||||||
|
"lastName": "lastName",
|
||||||
|
"birthDay": "1990-01-01"
|
||||||
|
}|]
|
||||||
|
|
||||||
|
object4 =
|
||||||
|
[aesonQQ|{
|
||||||
|
"firstName": "firstName"
|
||||||
|
}|]
|
||||||
|
|
||||||
|
object5 =
|
||||||
|
[aesonQQ|{
|
||||||
|
"lastName": 42,
|
||||||
|
"birthDay": null
|
||||||
|
}|]
|
||||||
|
|
||||||
|
main =
|
||||||
|
-- fromJson object2 =:= fromJson object3
|
||||||
|
-- unify (fromJson object1) (fromJson object2)
|
||||||
|
putStrLn
|
||||||
|
( intercalate
|
||||||
|
"\n\n"
|
||||||
|
( map
|
||||||
|
toString
|
||||||
|
( foldr1
|
||||||
|
(\ls rs -> (concat [unify1 l r | l <- ls, r <- rs]))
|
||||||
|
( map
|
||||||
|
((: []) . fromJson)
|
||||||
|
[ object1,
|
||||||
|
object2,
|
||||||
|
-- object3,
|
||||||
|
-- object4,
|
||||||
|
object5
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
-- >>= unify (fromJson object2)
|
||||||
|
-- >>= unify (fromJson object4)
|
||||||
1
cabal.project
Normal file
1
cabal.project
Normal file
@@ -0,0 +1 @@
|
|||||||
|
packages: ./*.cabal autotypes/*.cabal
|
||||||
25
default.nix
Normal file
25
default.nix
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{ pkgs ? import sources.nixpkgs { }
|
||||||
|
, sources ? import ./nix/sources.nix
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
haskellPackages = pkgs.haskellPackages.override {
|
||||||
|
overrides = self: super: {
|
||||||
|
acms = self.callCabal2nix "acms" ./. { };
|
||||||
|
astore = self.callCabal2nix "astore" sources.json2sql { };
|
||||||
|
autotypes = self.callCabal2nix "autotypes" ./autotypes { };
|
||||||
|
json2sql = self.callCabal2nix "json2sql" sources.json2sql { };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
rec {
|
||||||
|
inherit (haskellPackages) acms;
|
||||||
|
shell = haskellPackages.shellFor {
|
||||||
|
packages = _: [ acms haskellPackages.autotypes ];
|
||||||
|
buildInputs = [
|
||||||
|
haskellPackages.cabal-install
|
||||||
|
haskellPackages.ormolu
|
||||||
|
];
|
||||||
|
withHoogle = true;
|
||||||
|
withHaddock = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
20
nix/sources.json
Normal file
20
nix/sources.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"json2sql": {
|
||||||
|
"branch": "main",
|
||||||
|
"repo": "git@code.nomath.org:~/json2sql",
|
||||||
|
"rev": "bbe3b75bfd0767c61bcd436e843b9c785efd289f",
|
||||||
|
"type": "git"
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"branch": "nixos-unstable",
|
||||||
|
"description": "Nix Packages collection",
|
||||||
|
"homepage": null,
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "6c43a3495a11e261e5f41e5d7eda2d71dae1b2fe",
|
||||||
|
"sha256": "16f329z831bq7l3wn1dfvbkh95l2gcggdwn6rk3cisdmv2aa3189",
|
||||||
|
"type": "tarball",
|
||||||
|
"url": "https://github.com/NixOS/nixpkgs/archive/6c43a3495a11e261e5f41e5d7eda2d71dae1b2fe.tar.gz",
|
||||||
|
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
|
||||||
|
}
|
||||||
|
}
|
||||||
198
nix/sources.nix
Normal file
198
nix/sources.nix
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
# This file has been generated by Niv.
|
||||||
|
|
||||||
|
let
|
||||||
|
|
||||||
|
#
|
||||||
|
# The fetchers. fetch_<type> fetches specs of type <type>.
|
||||||
|
#
|
||||||
|
|
||||||
|
fetch_file = pkgs: name: spec:
|
||||||
|
let
|
||||||
|
name' = sanitizeName name + "-src";
|
||||||
|
in
|
||||||
|
if spec.builtin or true then
|
||||||
|
builtins_fetchurl { inherit (spec) url sha256; name = name'; }
|
||||||
|
else
|
||||||
|
pkgs.fetchurl { inherit (spec) url sha256; name = name'; };
|
||||||
|
|
||||||
|
fetch_tarball = pkgs: name: spec:
|
||||||
|
let
|
||||||
|
name' = sanitizeName name + "-src";
|
||||||
|
in
|
||||||
|
if spec.builtin or true then
|
||||||
|
builtins_fetchTarball { name = name'; inherit (spec) url sha256; }
|
||||||
|
else
|
||||||
|
pkgs.fetchzip { name = name'; inherit (spec) url sha256; };
|
||||||
|
|
||||||
|
fetch_git = name: spec:
|
||||||
|
let
|
||||||
|
ref =
|
||||||
|
spec.ref or (
|
||||||
|
if spec ? branch then "refs/heads/${spec.branch}" else
|
||||||
|
if spec ? tag then "refs/tags/${spec.tag}" else
|
||||||
|
abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!"
|
||||||
|
);
|
||||||
|
submodules = spec.submodules or false;
|
||||||
|
submoduleArg =
|
||||||
|
let
|
||||||
|
nixSupportsSubmodules = builtins.compareVersions builtins.nixVersion "2.4" >= 0;
|
||||||
|
emptyArgWithWarning =
|
||||||
|
if submodules
|
||||||
|
then
|
||||||
|
builtins.trace
|
||||||
|
(
|
||||||
|
"The niv input \"${name}\" uses submodules "
|
||||||
|
+ "but your nix's (${builtins.nixVersion}) builtins.fetchGit "
|
||||||
|
+ "does not support them"
|
||||||
|
)
|
||||||
|
{ }
|
||||||
|
else { };
|
||||||
|
in
|
||||||
|
if nixSupportsSubmodules
|
||||||
|
then { inherit submodules; }
|
||||||
|
else emptyArgWithWarning;
|
||||||
|
in
|
||||||
|
builtins.fetchGit
|
||||||
|
({ url = spec.repo; inherit (spec) rev; inherit ref; } // submoduleArg);
|
||||||
|
|
||||||
|
fetch_local = spec: spec.path;
|
||||||
|
|
||||||
|
fetch_builtin-tarball = name: throw
|
||||||
|
''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`.
|
||||||
|
$ niv modify ${name} -a type=tarball -a builtin=true'';
|
||||||
|
|
||||||
|
fetch_builtin-url = name: throw
|
||||||
|
''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`.
|
||||||
|
$ niv modify ${name} -a type=file -a builtin=true'';
|
||||||
|
|
||||||
|
#
|
||||||
|
# Various helpers
|
||||||
|
#
|
||||||
|
|
||||||
|
# https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695
|
||||||
|
sanitizeName = name:
|
||||||
|
(
|
||||||
|
concatMapStrings (s: if builtins.isList s then "-" else s)
|
||||||
|
(
|
||||||
|
builtins.split "[^[:alnum:]+._?=-]+"
|
||||||
|
((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
# The set of packages used when specs are fetched using non-builtins.
|
||||||
|
mkPkgs = sources: system:
|
||||||
|
let
|
||||||
|
sourcesNixpkgs =
|
||||||
|
import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; };
|
||||||
|
hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath;
|
||||||
|
hasThisAsNixpkgsPath = <nixpkgs> == ./.;
|
||||||
|
in
|
||||||
|
if builtins.hasAttr "nixpkgs" sources
|
||||||
|
then sourcesNixpkgs
|
||||||
|
else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then
|
||||||
|
import <nixpkgs> { }
|
||||||
|
else
|
||||||
|
abort
|
||||||
|
''
|
||||||
|
Please specify either <nixpkgs> (through -I or NIX_PATH=nixpkgs=...) or
|
||||||
|
add a package called "nixpkgs" to your sources.json.
|
||||||
|
'';
|
||||||
|
|
||||||
|
# The actual fetching function.
|
||||||
|
fetch = pkgs: name: spec:
|
||||||
|
|
||||||
|
if ! builtins.hasAttr "type" spec then
|
||||||
|
abort "ERROR: niv spec ${name} does not have a 'type' attribute"
|
||||||
|
else if spec.type == "file" then fetch_file pkgs name spec
|
||||||
|
else if spec.type == "tarball" then fetch_tarball pkgs name spec
|
||||||
|
else if spec.type == "git" then fetch_git name spec
|
||||||
|
else if spec.type == "local" then fetch_local spec
|
||||||
|
else if spec.type == "builtin-tarball" then fetch_builtin-tarball name
|
||||||
|
else if spec.type == "builtin-url" then fetch_builtin-url name
|
||||||
|
else
|
||||||
|
abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}";
|
||||||
|
|
||||||
|
# If the environment variable NIV_OVERRIDE_${name} is set, then use
|
||||||
|
# the path directly as opposed to the fetched source.
|
||||||
|
replace = name: drv:
|
||||||
|
let
|
||||||
|
saneName = stringAsChars (c: if (builtins.match "[a-zA-Z0-9]" c) == null then "_" else c) name;
|
||||||
|
ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}";
|
||||||
|
in
|
||||||
|
if ersatz == "" then drv else
|
||||||
|
# this turns the string into an actual Nix path (for both absolute and
|
||||||
|
# relative paths)
|
||||||
|
if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}";
|
||||||
|
|
||||||
|
# Ports of functions for older nix versions
|
||||||
|
|
||||||
|
# a Nix version of mapAttrs if the built-in doesn't exist
|
||||||
|
mapAttrs = builtins.mapAttrs or (
|
||||||
|
f: set: with builtins;
|
||||||
|
listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set))
|
||||||
|
);
|
||||||
|
|
||||||
|
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295
|
||||||
|
range = first: last: if first > last then [ ] else builtins.genList (n: first + n) (last - first + 1);
|
||||||
|
|
||||||
|
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257
|
||||||
|
stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1));
|
||||||
|
|
||||||
|
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269
|
||||||
|
stringAsChars = f: s: concatStrings (map f (stringToCharacters s));
|
||||||
|
concatMapStrings = f: list: concatStrings (map f list);
|
||||||
|
concatStrings = builtins.concatStringsSep "";
|
||||||
|
|
||||||
|
# https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331
|
||||||
|
optionalAttrs = cond: as: if cond then as else { };
|
||||||
|
|
||||||
|
# fetchTarball version that is compatible between all the versions of Nix
|
||||||
|
builtins_fetchTarball = { url, name ? null, sha256 }@attrs:
|
||||||
|
let
|
||||||
|
inherit (builtins) lessThan nixVersion fetchTarball;
|
||||||
|
in
|
||||||
|
if lessThan nixVersion "1.12" then
|
||||||
|
fetchTarball ({ inherit url; } // (optionalAttrs (name != null) { inherit name; }))
|
||||||
|
else
|
||||||
|
fetchTarball attrs;
|
||||||
|
|
||||||
|
# fetchurl version that is compatible between all the versions of Nix
|
||||||
|
builtins_fetchurl = { url, name ? null, sha256 }@attrs:
|
||||||
|
let
|
||||||
|
inherit (builtins) lessThan nixVersion fetchurl;
|
||||||
|
in
|
||||||
|
if lessThan nixVersion "1.12" then
|
||||||
|
fetchurl ({ inherit url; } // (optionalAttrs (name != null) { inherit name; }))
|
||||||
|
else
|
||||||
|
fetchurl attrs;
|
||||||
|
|
||||||
|
# Create the final "sources" from the config
|
||||||
|
mkSources = config:
|
||||||
|
mapAttrs
|
||||||
|
(
|
||||||
|
name: spec:
|
||||||
|
if builtins.hasAttr "outPath" spec
|
||||||
|
then
|
||||||
|
abort
|
||||||
|
"The values in sources.json should not have an 'outPath' attribute"
|
||||||
|
else
|
||||||
|
spec // { outPath = replace name (fetch config.pkgs name spec); }
|
||||||
|
)
|
||||||
|
config.sources;
|
||||||
|
|
||||||
|
# The "config" used by the fetchers
|
||||||
|
mkConfig =
|
||||||
|
{ sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null
|
||||||
|
, sources ? if sourcesFile == null then { } else builtins.fromJSON (builtins.readFile sourcesFile)
|
||||||
|
, system ? builtins.currentSystem
|
||||||
|
, pkgs ? mkPkgs sources system
|
||||||
|
}: rec {
|
||||||
|
# The sources, i.e. the attribute set of spec name to spec
|
||||||
|
inherit sources;
|
||||||
|
|
||||||
|
# The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers
|
||||||
|
inherit pkgs;
|
||||||
|
};
|
||||||
|
|
||||||
|
in
|
||||||
|
mkSources (mkConfig { }) // { __functor = _: settings: mkSources (mkConfig settings); }
|
||||||
Reference in New Issue
Block a user