1
1
mirror of https://github.com/anoma/juvix.git synced 2024-12-13 19:49:20 +03:00
juvix/app/Commands/Init.hs

138 lines
4.0 KiB
Haskell
Raw Normal View History

2022-08-12 00:05:38 +03:00
module Commands.Init where
import Data.Text qualified as Text
import Data.Versions
import Data.Yaml (encodeFile)
import Juvix.Compiler.Pipeline.Package
import Juvix.Data.Effect.Fail.Extra qualified as Fail
import Juvix.Extra.Paths
import Juvix.Prelude
import Juvix.Prelude.Pretty
import Text.Megaparsec (Parsec)
import Text.Megaparsec qualified as P
import Text.Megaparsec.Char qualified as P
type Err = Text
parse :: Parsec Void Text a -> Text -> Either Err a
parse p t = mapLeft ppErr (P.runParser p "<stdin>" t)
ppErr :: P.ParseErrorBundle Text Void -> Text
ppErr = pack . errorBundlePretty
init :: forall r. (Members '[Embed IO] r) => Sem r ()
2022-08-12 00:05:38 +03:00
init = do
checkNotInProject
say "✨ Your next Juvix adventure is about to begin! ✨"
say "I will help you set it up"
pkg <- getPackage
2022-12-20 15:05:40 +03:00
say ("creating " <> pack (toFilePath juvixYamlFile))
embed (encodeFile (toFilePath juvixYamlFile) (rawPackage pkg))
2022-08-12 00:05:38 +03:00
say "you are all set"
checkNotInProject :: forall r. (Members '[Embed IO] r) => Sem r ()
2022-08-12 00:05:38 +03:00
checkNotInProject =
2022-12-20 15:05:40 +03:00
whenM (doesFileExist juvixYamlFile) err
2022-08-12 00:05:38 +03:00
where
err :: Sem r ()
err = do
say "You are already in a Juvix project"
embed exitFailure
2023-02-10 19:53:23 +03:00
getPackage :: forall r. Members '[Embed IO] r => Sem r Package
2022-08-12 00:05:38 +03:00
getPackage = do
tproj <- getProjName
2023-02-10 19:53:23 +03:00
say "Write the version of your project [leave empty for 0.0.0]"
2022-12-20 15:05:40 +03:00
tversion :: SemVer <- getVersion
2022-08-12 00:05:38 +03:00
return
Package
2022-12-20 15:05:40 +03:00
{ _packageName = tproj,
_packageVersion = tversion,
_packageBuildDir = Nothing,
_packageMain = Nothing,
_packageDependencies = [defaultStdlibDep]
2022-08-12 00:05:38 +03:00
}
getProjName :: forall r. (Members '[Embed IO] r) => Sem r Text
2022-08-12 00:05:38 +03:00
getProjName = do
d <- getDefault
let defMsg :: Text
defMsg = case d of
Nothing -> mempty
Just d' -> " [leave empty for '" <> d' <> "']"
say
2023-02-10 19:53:23 +03:00
( "Write the name of your project"
2022-08-12 00:05:38 +03:00
<> defMsg
<> " (lower case letters, numbers and dashes are allowed): "
)
readName d
where
getDefault :: Sem r (Maybe Text)
getDefault = runFail $ do
2022-12-20 15:05:40 +03:00
dir <- map toLower . dropTrailingPathSeparator . toFilePath . dirname <$> getCurrentDir
2022-08-12 00:05:38 +03:00
Fail.fromRight (parse projectNameParser (pack dir))
readName :: Maybe Text -> Sem r Text
readName def = go
where
go :: Sem r Text
go = do
txt <- embed getLine
if
| Text.null txt, Just def' <- def -> return def'
| otherwise ->
case parse projectNameParser txt of
Right p
| Text.length p <= projextNameMaxLength -> return p
| otherwise -> do
say ("The project name cannot exceed " <> prettyText projextNameMaxLength <> " characters")
retry
Left err -> do
say err
retry
where
retry :: Sem r Text
retry = do
tryAgain
go
say :: (Members '[Embed IO] r) => Text -> Sem r ()
2022-08-12 00:05:38 +03:00
say = embed . putStrLn
tryAgain :: (Members '[Embed IO] r) => Sem r ()
2022-08-12 00:05:38 +03:00
tryAgain = say "Please, try again:"
getVersion :: forall r. (Members '[Embed IO] r) => Sem r SemVer
2022-08-12 00:05:38 +03:00
getVersion = do
txt <- embed getLine
if
| Text.null txt -> return mempty
| otherwise -> case parse semver' txt of
Right r -> return r
Left err -> do
say err
say "The version must follow the 'Semantic Versioning 2.0.0' specification"
retry
where
retry :: Sem r SemVer
retry = do
tryAgain
getVersion
projextNameMaxLength :: Int
projextNameMaxLength = 100
projectNameParser :: Parsec Void Text Text
projectNameParser = do
h <- P.satisfy validFirstChar
t <- P.takeWhileP (Just "project name character") validChar
P.hspace
P.eof
return (Text.cons h t)
where
validFirstChar :: Char -> Bool
validFirstChar c =
isAscii c
&& (isLower c || isNumber c)
validChar :: Char -> Bool
validChar c = c == '-' || validFirstChar c