0 | ||| We work a lot with Strings of distinct semantics.
   1 | ||| Since I've been bitten by this more than once, we wrap
   2 | ||| the in single field record types to drastically increase
   3 | ||| type safety.
   4 | module Pack.Core.Types
   5 |
   6 | import public Data.FilePath.File
   7 |
   8 | import Data.Either
   9 | import Data.Maybe
  10 | import Derive.Prelude
  11 | import Idris.Package.Types
  12 | import System.File
  13 |
  14 | %default total
  15 | %language ElabReflection
  16 |
  17 | ----------------------------------------------------------------------------------
  18 | ----          Quoted Strings
  19 | ----------------------------------------------------------------------------------
  20 |
  21 | ||| This puts a value in quotes during interpolation.
  22 | |||
  23 | ||| Note: If the interpolated string contains additional quote
  24 | ||| characters, these will *not* be escaped.
  25 | export
  26 | quote : Interpolation a => a -> String
  27 | quote v = "\"\{v}\""
  28 |
  29 | ----------------------------------------------------------------------------------
  30 | ----          Paths
  31 | ----------------------------------------------------------------------------------
  32 |
  33 | ||| Returns the second path, relative to the first one
  34 | ||| For instance, `relativeTo /foo/bar/baz /foo/quux` will return
  35 | ||| `../../quux`.
  36 | export
  37 | relativeTo : (origin, target : Path Abs) -> Path Rel
  38 | relativeTo (PAbs _ sx) (PAbs _ sy) = PRel $ go (sx <>> []) (sy <>> [])
  39 |
  40 |   where
  41 |     go : (o,t : List Body) -> SnocList Body
  42 |     go [>]       t  = Lin <>< t
  43 |     go xs        [] = Lin <>< xs $> ".."
  44 |     go (x :: xs) (y :: ys) = case x == y of
  45 |       True  => go xs ys
  46 |       False => Lin <>< (((x :: xs) $> "..") ++ (y :: ys))
  47 |
  48 | ||| True if the given file path body ends on `.ipkg`
  49 | export
  50 | isIpkgBody : Body -> Bool
  51 | isIpkgBody = (Just "ipkg" ==) . extension
  52 |
  53 | ||| True if the given file path body ends on `.toml`
  54 | export
  55 | isTomlBody : Body -> Bool
  56 | isTomlBody = (Just "toml" ==) . extension
  57 |
  58 | ||| True if the given file path body ends on `.html`
  59 | export
  60 | isHtmlBody : Body -> Bool
  61 | isHtmlBody = (Just "html" ==) . extension
  62 |
  63 | toRelPath : String -> Path Rel
  64 | toRelPath s = case the FilePath (fromString s) of
  65 |   FP (PAbs _ sx) => PRel sx
  66 |   FP (PRel sx)   => PRel sx
  67 |
  68 | ||| Converts a `FilePath` - which might hold a relative
  69 | ||| or an absolute path - to an absolute path by interpreting
  70 | ||| a relative path being relative to the given parent directory.
  71 | export
  72 | toAbsPath : (parent : Path Abs) -> FilePath -> Path Abs
  73 | toAbsPath parent (FP $ PAbs d sx) = PAbs d sx
  74 | toAbsPath parent (FP $ PRel sx) = parent </> PRel sx
  75 |
  76 | ||| Parses a string, converting it to either a relative
  77 | ||| or absolute path and using `toAbsPath` to convert the result
  78 | ||| to an absolute path.
  79 | export
  80 | parse : String -> (parentDir : Path Abs) -> Path Abs
  81 | parse s parentDir = toAbsPath parentDir (fromString s)
  82 |
  83 | export %inline
  84 | Cast String (Path Rel) where
  85 |   cast = toRelPath
  86 |
  87 | export infixl 5 <//>
  88 |
  89 | ||| More flexible version of `</>` (path concatenation).
  90 | export %inline
  91 | (<//>) : Cast a (Path Rel) => Path t -> a -> Path t
  92 | p <//> v = p </> cast v
  93 |
  94 | export infixl 5 //>
  95 |
  96 | ||| More flexible version of `//>`
  97 | ||| (appending a file path body to an absolute path)
  98 | export %inline
  99 | (//>) : Cast a Body => Path t -> a -> Path t
 100 | p //> v = p /> cast v
 101 |
 102 | export infixl 8 <->
 103 |
 104 | ||| Concatenate two file path bodies with a hyphen inbetween.
 105 | export
 106 | (<->) : Cast a Body => Cast b Body => a -> b -> Body
 107 | x <-> y = the Body (cast x) <+> "-" <+> cast y
 108 |
 109 | ||| Convert a package version to a file path body.
 110 | export
 111 | Cast PkgVersion Body where
 112 |   cast = fromMaybe "0" . parse . show
 113 |
 114 | ||| Convert a package version to a file path body.
 115 | export
 116 | Cast (Maybe PkgVersion) Body where
 117 |   cast Nothing  = "0"
 118 |   cast (Just v) = cast v
 119 |
 120 | ||| Convert a relative file path to an absolute one by appending
 121 | ||| it to the given parent directory.
 122 | export %inline
 123 | toAbsFile : Path Abs -> File Rel -> File Abs
 124 | toAbsFile parentDir (MkF p f) = MkF (parentDir </> p) f
 125 |
 126 | export
 127 | Cast (File t) (Path t) where cast = toPath
 128 |
 129 | ----------------------------------------------------------------------------------
 130 | ----          CurDir, PackDir, and TmpDir
 131 | ----------------------------------------------------------------------------------
 132 |
 133 | ||| The directory where package collections, global user settings,
 134 | ||| and cached `.ipkg` files are stored.
 135 | public export
 136 | record PackDirs where
 137 |   [noHints]
 138 |   constructor PD
 139 |
 140 |   ||| Directory with user settings.
 141 |   |||
 142 |   ||| Depending on what environment variables are set, this defaults to
 143 |   ||| (in decreasing order of priority):
 144 |   |||
 145 |   ||| * `PACK_USER_DIR`
 146 |   ||| * `$XDG_CONFIG_HOME/pack`
 147 |   ||| * `$HOME/.config/pack`
 148 |   user    : Path Abs
 149 |
 150 |   ||| Directory with application state (installed compiler and libraries).
 151 |   |||
 152 |   ||| Depending on what environment variables are set, this defaults to
 153 |   ||| (in decreasing order of priority):
 154 |   |||
 155 |   ||| * `PACK_STATE_DIR`
 156 |   ||| * `$XDG_STATE_HOME/pack`
 157 |   ||| * `$HOME/.local/state/pack`
 158 |   state   : Path Abs
 159 |
 160 |   ||| Directory with cached data such as cached `.ipkg` files, git repos,
 161 |   ||| and temporary files used during installation of libs.
 162 |   |||
 163 |   ||| Depending on what environment variables are set, this defaults to
 164 |   ||| (in decreasing order of priority):
 165 |   |||
 166 |   ||| * `PACK_CACHE_DIR`
 167 |   ||| * `$XDG_CACHE_HOME/pack`
 168 |   ||| * `$HOME/.cache/pack`
 169 |   cache   : Path Abs
 170 |
 171 |   ||| Directory where executables will be installed.
 172 |   |||
 173 |   ||| Depending on what environment variables are set, this defaults to
 174 |   ||| (in decreasing order of priority):
 175 |   |||
 176 |   ||| * `PACK_BIN_DIR`
 177 |   ||| * `$HOME/.local/bin`
 178 |   bin     : Path Abs
 179 |
 180 | ||| The directory from which the pack application was invoked.
 181 | public export
 182 | data CurDir : Type where
 183 |   [noHints]
 184 |   CD : (dir : Path Abs) -> CurDir
 185 |
 186 | export %inline
 187 | Interpolation CurDir where
 188 |   interpolate (CD dir) = interpolate dir
 189 |
 190 | ||| Use this when you need access to the current directory's path with
 191 | ||| only a value of type `CurDir` in scope.
 192 | export %inline
 193 | curDir : (cd : CurDir) => Path Abs
 194 | curDir {cd = CD dir} = dir
 195 |
 196 | ||| The directory where temporary files and git repos will be
 197 | ||| kept.
 198 | public export
 199 | data TmpDir : Type where
 200 |   [noHints]
 201 |   TD : (dir : Path Abs) -> TmpDir
 202 |
 203 | export %inline
 204 | Interpolation TmpDir where
 205 |   interpolate (TD dir) = interpolate dir
 206 |
 207 | ||| Use this when you need access to the `PACK_DIR` path with
 208 | ||| only a value of type `PackDir` in scope.
 209 | export %inline
 210 | tmpDir : (td : TmpDir) => Path Abs
 211 | tmpDir {td = TD dir} = dir
 212 |
 213 | ----------------------------------------------------------------------------------
 214 | ----          TTC-Version
 215 | ----------------------------------------------------------------------------------
 216 |
 217 | ||| The TTC-Version currently used by the Idris compiler
 218 | public export
 219 | record TTCVersion where
 220 |   [noHints]
 221 |   constructor TTCV
 222 |   version : Maybe Body
 223 |
 224 | ||| Use this when you need access to the current TTC version
 225 | ||| only a value of type `TTCVersion` in scope.
 226 | export %inline
 227 | ttcVersion : (ttc : TTCVersion) => Maybe Body
 228 | ttcVersion = ttc.version
 229 |
 230 | ----------------------------------------------------------------------------------
 231 | ----          Interpolation
 232 | ----------------------------------------------------------------------------------
 233 |
 234 | ||| Convenience implementation for printing file errors in string
 235 | ||| interpolation
 236 | export
 237 | Interpolation FileError where interpolate = show
 238 |
 239 | ||| Convenience implementation for printing package versions in string
 240 | ||| interpolation
 241 | export
 242 | Interpolation PkgVersion where interpolate = show
 243 |
 244 | --------------------------------------------------------------------------------
 245 | --          URL
 246 | --------------------------------------------------------------------------------
 247 |
 248 | ||| URL mostly used to represent Git repositories.
 249 | public export
 250 | record URL where
 251 |   constructor MkURL
 252 |   value : String
 253 |
 254 | %runElab derive "URL" [Show,Eq,FromString]
 255 |
 256 | export %inline
 257 | Interpolation URL where interpolate = value
 258 |
 259 | export
 260 | Cast URL (Path Rel) where
 261 |   cast (MkURL s) = case unpack s of
 262 |     'h'::'t'::'t'::'p'::'s'::':'::'/'::'/':: t => cast (pack t)
 263 |     'h'::'t'::'t'::'p'::':'::'/'::'/':: t      => cast (pack t)
 264 |     t                                          => cast (pack t)
 265 |
 266 | --------------------------------------------------------------------------------
 267 | --          Commits and Hashes
 268 | --------------------------------------------------------------------------------
 269 |
 270 | ||| A git commit hash or tag.
 271 | public export
 272 | record Commit where
 273 |   constructor MkCommit
 274 |   value : String
 275 |
 276 | %runElab derive "Commit" [Show,Eq,FromString]
 277 |
 278 | export %inline
 279 | Interpolation Commit where interpolate = value
 280 |
 281 | export %inline
 282 | Cast Commit (Path Rel) where
 283 |   cast = toRelPath . value
 284 |
 285 | ||| A hash generated by pack
 286 | public export
 287 | record Hash where
 288 |   constructor MkHash
 289 |   value : String
 290 |
 291 | %runElab derive "Hash" [Show,Eq,FromString]
 292 |
 293 | export %inline
 294 | Interpolation Hash where interpolate = value
 295 |
 296 | export %inline
 297 | Cast Hash (Path Rel) where
 298 |   cast = toRelPath . value
 299 |
 300 | --------------------------------------------------------------------------------
 301 | --          Branches
 302 | --------------------------------------------------------------------------------
 303 |
 304 | ||| A branch in a git repo.
 305 | public export
 306 | record Branch where
 307 |   constructor MkBranch
 308 |   value : String
 309 |
 310 | %runElab derive "Branch" [Show,Eq,FromString]
 311 |
 312 | export %inline
 313 | Interpolation Branch where interpolate = value
 314 |
 315 | export %inline
 316 | Cast Branch (Path Rel) where
 317 |   cast = toRelPath . value
 318 |
 319 | --------------------------------------------------------------------------------
 320 | --          Package Name
 321 | --------------------------------------------------------------------------------
 322 |
 323 | ||| Name of an Idris package
 324 | public export
 325 | record PkgName where
 326 |   constructor MkPkgName
 327 |   value : String
 328 |
 329 | %runElab derive "PkgName" [Show,Eq,Ord,FromString]
 330 |
 331 | export %inline
 332 | Interpolation PkgName where interpolate = value
 333 |
 334 | export %inline
 335 | Cast PkgName (Path Rel) where
 336 |   cast = toRelPath . value
 337 |
 338 | --------------------------------------------------------------------------------
 339 | --          PkgOrIpkg
 340 | --------------------------------------------------------------------------------
 341 |
 342 | ||| Several pack commands operat either on a pack package or a local
 343 | ||| `.ipkg` file. This data type represents such command line arguments.
 344 | public export
 345 | data PkgOrIpkg : Type where
 346 |   Pkg :  PkgName -> PkgOrIpkg
 347 |   Ipkg : File Abs -> PkgOrIpkg
 348 |
 349 | --------------------------------------------------------------------------------
 350 | --          Package Type
 351 | --------------------------------------------------------------------------------
 352 |
 353 | ||| Type of an Idris package (either a library or a binary).
 354 | public export
 355 | data PkgType = PLib | PApp
 356 |
 357 | export
 358 | Interpolation PkgType where
 359 |   interpolate PLib = "lib"
 360 |   interpolate PApp = "app"
 361 |
 362 | export
 363 | Eq PkgType where
 364 |   PLib == PLib = True
 365 |   PApp == PApp = True
 366 |   _    == _    = False
 367 |
 368 | export
 369 | Ord PkgType where
 370 |   compare PLib PApp = LT
 371 |   compare PApp PLib = GT
 372 |   compare _    _    = EQ
 373 |
 374 | --------------------------------------------------------------------------------
 375 | --          Install Type
 376 | --------------------------------------------------------------------------------
 377 |
 378 | ||| What we want to install: A library, an application to
 379 | ||| run it from within pack, or an application, which should
 380 | ||| be available via the `PATH` variable.
 381 | public export
 382 | data InstallType : Type where
 383 |   Library : InstallType
 384 |   App     : (withWrapperScript : Bool) -> InstallType
 385 |
 386 | export %inline
 387 | Interpolation InstallType where
 388 |   interpolate Library = "library"
 389 |   interpolate (App b) = "app"
 390 |
 391 | export
 392 | Eq InstallType where
 393 |   Library == Library = True
 394 |   App b1  == App b2  = b1 == b2
 395 |   _       == _       = False
 396 |
 397 | export
 398 | Ord InstallType where
 399 |   compare Library  (App _)  = LT
 400 |   compare (App _)  Library  = GT
 401 |   compare Library  Library  = EQ
 402 |   compare (App b1) (App b2) = compare b1 b2
 403 |
 404 | --------------------------------------------------------------------------------
 405 | --          DBName
 406 | --------------------------------------------------------------------------------
 407 |
 408 | ||| Name of a package collection. This must be a valid
 409 | ||| file path body.
 410 | public export
 411 | record DBName where
 412 |   constructor MkDBName
 413 |   value : Body
 414 |
 415 | export %inline
 416 | Eq DBName where (==) = (==) `on` value
 417 |
 418 | export %inline
 419 | Ord DBName where compare = compare `on` value
 420 |
 421 | export %inline
 422 | Interpolation DBName where interpolate = interpolate . value
 423 |
 424 | export %inline
 425 | Cast DBName Body where
 426 |   cast = value
 427 |
 428 | export %inline
 429 | Head : DBName
 430 | Head = MkDBName "HEAD"
 431 |
 432 | export %inline
 433 | All : DBName
 434 | All = MkDBName "all"
 435 |
 436 | --------------------------------------------------------------------------------
 437 | --          Desc
 438 | --------------------------------------------------------------------------------
 439 |
 440 | ||| A tagged package desc. We use the tag mainly to make sure that
 441 | ||| the package desc in question has been checked for safety issues.
 442 | ||| Since the tag is parameterized by a `PkgDesc`, we make sure
 443 | ||| we will not inadvertently use a tag for a `PkgDesc` different to
 444 | ||| the one we wrapped.
 445 | public export
 446 | record Desc (t : PkgDesc -> Type) where
 447 |   constructor MkDesc
 448 |   ||| The parsed package desc
 449 |   desc : PkgDesc
 450 |
 451 |   ||| String content of the `.ipkg` file used when reading the package desc
 452 |   cont : String
 453 |
 454 |   ||| Path to the file use when reading the package desc
 455 |   path : File Abs
 456 |
 457 |   ||| Security tag. See `Pack.Runner.Database.check`
 458 |   0 tag : t desc
 459 |
 460 | ||| This is used as a tag for unchecked `Desc`s.
 461 | public export
 462 | 0 U : PkgDesc -> Type
 463 | U d = Unit
 464 |
 465 | namespace PkgDesc
 466 |   ||| Lists the dependencies of a package.
 467 |   export
 468 |   dependencies : PkgDesc -> List PkgName
 469 |   dependencies d = map (MkPkgName . pkgname) $ d.depends
 470 |
 471 | ||| Lists the dependencies of a package.
 472 | export
 473 | dependencies : Desc t -> List PkgName
 474 | dependencies d = dependencies d.desc
 475 |
 476 | --------------------------------------------------------------------------------
 477 | --          CmdArg
 478 | --------------------------------------------------------------------------------
 479 |
 480 | ||| A command line argument of a call to the system shell.
 481 | public export
 482 | data CmdArg : Type where
 483 |
 484 |   ||| Escapable alternative represents a string that should be understood as a
 485 |   ||| single argument disregarding its contents, i.e. the whole string should
 486 |   ||| be passed as an argument even if it contains spaces or characters that
 487 |   ||| are normally understood specially by shell.
 488 |   Escapable : String -> CmdArg
 489 |
 490 |   ||| No escape alternative represents a raw string that is used "as is" at the
 491 |   ||| shell call, thus allowing to pass commands to the shell itself.
 492 |   ||| Be aware that spaces inside these strings would form several actual
 493 |   ||| arguments and quotation marks and backslash symbols may interfere with
 494 |   ||| escaping of other arguments.
 495 |   NoEscape : String -> CmdArg
 496 |
 497 | ||| Interface that is used to mark which types can be used as a source for
 498 | ||| command line arguments when building lists of such.
 499 | ||| This allows to be able to pass different literals and expressions without
 500 | ||| explicit wrapping them into the `CmdArg` type.
 501 | public export
 502 | interface CmdArgable a where
 503 |   toCmdArg : a -> CmdArg
 504 |
 505 | export %inline
 506 | CmdArgable CmdArg where
 507 |   toCmdArg = id
 508 |
 509 | ||| Implementation that allows any type implementing `Interpolation` interface
 510 | ||| to be used as an escapable command line argument.
 511 | export %inline
 512 | Interpolation a => CmdArgable a where
 513 |   toCmdArg = Escapable . interpolate
 514 |
 515 | ||| A list of command line arguments.
 516 | |||
 517 | ||| This type is meant to look syntacitcally as a simple list, however
 518 | ||| containing possibly values of different types which can form a command
 519 | ||| line argument.
 520 | |||
 521 | ||| For example, a call to some command with redirection may look like this:
 522 | ||| `sys ["echo", "a", NoEscape ">", file]`,
 523 | ||| when, say, `file` is a value of type `File Abs`.
 524 | public export
 525 | data CmdArgList : Type where
 526 |   Nil  : CmdArgList
 527 |   (::) : CmdArgable a => a -> CmdArgList -> CmdArgList
 528 |
 529 | cmdArgList : CmdArgList -> List CmdArg
 530 | cmdArgList []      = []
 531 | cmdArgList (x::xs) = toCmdArg x :: cmdArgList xs
 532 |
 533 | ||| Converts a list of command line arguments to a single string
 534 | ||| while putting spaces between arguments and escaping appropriate ones.
 535 | |||
 536 | ||| For example, call `escapeCmd ["echo", "&&", NoEscape "&&", "echo", "yes"]`
 537 | ||| would return a string equivalent to the literal
 538 | ||| `#""echo" "&&" && "echo" "yes""#`.
 539 | export
 540 | escapeCmd : CmdArgList -> String
 541 | escapeCmd = unwords . map manageArg . cmdArgList where
 542 |   manageArg : CmdArg -> String
 543 |   manageArg $ Escapable s = escapeArg s
 544 |   manageArg $ NoEscape s  = s
 545 |
 546 | namespace CmdArg
 547 |
 548 |   ||| Concatenate two command line arguments into one.
 549 |   |||
 550 |   ||| This operation respects meaning of the contents of each argument,
 551 |   ||| whether they should be escaped or not.
 552 |   ||| This allows, say, to form an argument which partially contains something
 553 |   ||| escapable and partially contains a special shell argument, for example
 554 |   ||| `sys ["cp", "-r", Escapable "\{dirName}/" ++ NoEscape "*", dest]` would
 555 |   ||| list files with shell's `*` even if `dirName` contains spaces.
 556 |   export
 557 |   (++) : CmdArg -> CmdArg -> CmdArg
 558 |   Escapable x ++ Escapable y = Escapable $ x ++ y
 559 |   Escapable x ++ NoEscape y  = NoEscape $ escapeArg x ++ y
 560 |   NoEscape x  ++ Escapable y = NoEscape $ x ++ escapeArg y
 561 |   NoEscape x  ++ NoEscape y  = NoEscape $ x ++ y
 562 |
 563 | namespace CmdArgList
 564 |
 565 |   ||| Concatenation operation for command line argument lists.
 566 |   export
 567 |   (++) : CmdArgList -> CmdArgList -> CmdArgList
 568 |   []      ++ ys = ys
 569 |   (x::xs) ++ ys = x :: xs ++ ys
 570 |
 571 |   export
 572 |   Semigroup CmdArgList where
 573 |     (<+>) = (++)
 574 |
 575 |   export
 576 |   Monoid CmdArgList where
 577 |     neutral = []
 578 |
 579 |   ||| Specialised version of `concatMap` from `Foldable` for `CmdArgList`,
 580 |   ||| since `CmdArgList` cannot implement `Foldable`.
 581 |   export
 582 |   concatMap : Monoid m => (CmdArg -> m) -> CmdArgList -> m
 583 |   concatMap f []      = neutral
 584 |   concatMap f (x::xs) = f (toCmdArg x) <+> concatMap f xs
 585 |
 586 |   ||| Function that forms a command line arguments list from a raw list of
 587 |   ||| strings, treating each string as a single escapable argument.
 588 |   export
 589 |   fromStrList : List String -> CmdArgList
 590 |   fromStrList = foldr (\x, xs => x::xs) Nil
 591 |
 592 | --------------------------------------------------------------------------------
 593 | --          LineBufferingCmd
 594 | --------------------------------------------------------------------------------
 595 |
 596 | ||| A wrapper for a command list of a shell command which allows to do
 597 | ||| line buffering.
 598 | public export
 599 | record LineBufferingCmd where
 600 |   [noHints]
 601 |   constructor MkLineBufferingCmd
 602 |   lineBufferingCmd : CmdArgList
 603 |
 604 | --------------------------------------------------------------------------------
 605 | --          Logging
 606 | --------------------------------------------------------------------------------
 607 |
 608 | ||| Level used during logging.
 609 | public export
 610 | data LogLevel : Type where
 611 |   [noHints]
 612 |   Debug   : LogLevel
 613 |   Build   : LogLevel
 614 |   Info    : LogLevel
 615 |   Cache   : LogLevel
 616 |   Warning : LogLevel
 617 |   Silence : LogLevel
 618 |
 619 | llToNat : LogLevel -> Nat
 620 | llToNat Debug   = 0
 621 | llToNat Build   = 1
 622 | llToNat Info    = 2
 623 | llToNat Cache   = 3
 624 | llToNat Warning = 4
 625 | llToNat Silence = 5
 626 |
 627 | export
 628 | Eq LogLevel where (==) = (==) `on` llToNat
 629 |
 630 | export
 631 | Ord LogLevel where compare = compare `on` llToNat
 632 |
 633 | export
 634 | Interpolation LogLevel where
 635 |   interpolate Debug   = "debug"
 636 |   interpolate Build   = "build"
 637 |   interpolate Info    = "info"
 638 |   interpolate Cache   = "cache"
 639 |   interpolate Warning = "warning"
 640 |   interpolate Silence = ""
 641 |
 642 | export
 643 | logLevels : List (String, LogLevel)
 644 | logLevels =
 645 |   [ ("debug"  , Debug  )
 646 |   , ("build"  , Build  )
 647 |   , ("info"   , Info   )
 648 |   , ("cache"  , Cache  )
 649 |   , ("warning", Warning)
 650 |   , ("silence", Silence)
 651 |   ]
 652 |
 653 | ||| Reference `LogLevel` to be used as an auto implicit
 654 | public export
 655 | record LogRef where
 656 |   [noHints]
 657 |   constructor MkLogRef
 658 |   level : LogLevel
 659 |
 660 | --------------------------------------------------------------------------------
 661 | --          Errors
 662 | --------------------------------------------------------------------------------
 663 |
 664 | ||| Error during marshalling from TOML to an Idris type.
 665 | public export
 666 | data TOMLErr : Type where
 667 |   ||| A mandatory key/value pair in a toml file is
 668 |   ||| missing
 669 |   MissingKey : (path : List String) -> TOMLErr
 670 |
 671 |   ||| A value in a toml file has the wrong type
 672 |   WrongType : (path  : List String) -> (type  : String) -> TOMLErr
 673 |
 674 | tomlPath : List String -> String
 675 | tomlPath = concat . intersperse "."
 676 |
 677 | printTOMLErr : TOMLErr -> String
 678 | printTOMLErr (MissingKey path) = "Missing toml key: \{tomlPath path}."
 679 | printTOMLErr (WrongType path type) =
 680 |   "Wrong type at \{tomlPath path}. Expect \{type}."
 681 |
 682 | ||| Prefix the given TOML key to an error message. This allows us to
 683 | ||| specify exactly where in a TOML structure things went wrong.
 684 | export
 685 | prefixKey : (key : String) -> Either TOMLErr a -> Either TOMLErr a
 686 | prefixKey k = mapFst $ \case
 687 |   MissingKey p => MissingKey (k :: p)
 688 |   WrongType p t => WrongType (k :: p) t
 689 |
 690 | ||| Errors that can occur when running pack programs.
 691 | public export
 692 | data PackErr : Type where
 693 |   ||| Failed to get the path of the current directory.
 694 |   NoCurDir   : PackErr
 695 |
 696 |   ||| Failed to get package directory path
 697 |   NoPackDir  : PackErr
 698 |
 699 |   ||| Failed to create temporary directory
 700 |   NoTmpDir  : PackErr
 701 |
 702 |   ||| Failed to create the given directory
 703 |   MkDir      : (path : Path Abs) -> (err : FileError) -> PackErr
 704 |
 705 |   ||| Failed to read the given file
 706 |   ReadFile   : (path : File Abs) -> (err : FileError) -> PackErr
 707 |
 708 |   ||| Failed to write to the given file
 709 |   WriteFile  : (path : File Abs) -> (err : FileError) -> PackErr
 710 |
 711 |   ||| Failed to read the content of a directory
 712 |   DirEntries : (path : Path Abs) -> (err : FileError) -> PackErr
 713 |
 714 |   ||| Error when running the given system command
 715 |   Sys        : (cmd : CmdArgList) -> (err : Int) -> PackErr
 716 |
 717 |   ||| Error when changing into the given directory
 718 |   ChangeDir  : (path : Path Abs) -> PackErr
 719 |
 720 |   ||| The given package is not in the package data base
 721 |   UnknownPkg : (name : PkgName) -> PackErr
 722 |
 723 |   ||| Not enough data has been given for this package
 724 |   IncompletePkg : (name : PkgName) -> PackErr
 725 |
 726 |   ||| The given package is not a local package
 727 |   NotLocalPkg : (name : PkgName) -> PackErr
 728 |
 729 |   ||| The given package is not an applicatio
 730 |   ||| (No executable name set in the `.ipkg` file)
 731 |   NoApp      : (rep : PkgName) -> PackErr
 732 |
 733 |   ||| The given package is not an application
 734 |   ||| (No executable name set in the `.ipkg` file)
 735 |   NoAppIpkg  : (path : File Abs) -> PackErr
 736 |
 737 |   ||| An erroneous package description in a package DB file
 738 |   InvalidPackageDesc : (s : String) -> PackErr
 739 |
 740 |   ||| The package database is empty (no header)
 741 |   EmptyPkgDB : PackErr
 742 |
 743 |   ||| Invalid package database header
 744 |   InvalidDBHeader : (s : String) -> PackErr
 745 |
 746 |   ||| Invalid package database header
 747 |   InvalidDBName : (s : String) -> PackErr
 748 |
 749 |   ||| Invalid package type
 750 |   InvalidPkgType : (s : String) -> PackErr
 751 |
 752 |   ||| Invalid package version
 753 |   InvalidPkgVersion : (s : String) -> PackErr
 754 |
 755 |   ||| Invalid log level
 756 |   InvalidLogLevel : (s : String) -> PackErr
 757 |
 758 |   ||| Failed to parse an .ipkg file
 759 |   InvalidIpkgFile  : (path : File Abs) -> PackErr
 760 |
 761 |   ||| Invalid file path body
 762 |   InvalidBody  : (s : String) -> PackErr
 763 |
 764 |   ||| Failed to parse a file path
 765 |   NoFilePath : (str : String) -> PackErr
 766 |
 767 |   ||| The given core package (base, contrib, etc.)
 768 |   ||| is missing from the Idris installation.
 769 |   MissingCorePackage :
 770 |        (name    : PkgName)
 771 |     -> (version : PkgVersion)
 772 |     -> (commit  : Commit)
 773 |     -> PackErr
 774 |
 775 |   ||| Unknown command line argument
 776 |   UnknownArg : (arg : String) -> PackErr
 777 |
 778 |   ||| Invalid command line arguments (in micropack)
 779 |   InvalidArgs : (args : List String) -> PackErr
 780 |
 781 |   ||| Erroneous command line argument
 782 |   ErroneousArg : (err : String) -> PackErr
 783 |
 784 |   ||| Trying to run zero or more than one local package
 785 |   ||| (or something that isn't a local package).
 786 |   BuildMany : List Body -> PackErr
 787 |
 788 |   ||| Unknown pack command
 789 |   UnknownCommand : String -> (usage : String) -> PackErr
 790 |
 791 |   ||| Unknown pack command
 792 |   InvalidCmdArgs :
 793 |        (cmd   : String)
 794 |     -> (args  : List String)
 795 |     -> (usage : String)
 796 |     -> PackErr
 797 |
 798 |   ||| Trying to clone a repository into an existing
 799 |   ||| directory.
 800 |   DirExists : Path Abs -> PackErr
 801 |
 802 |   ||| Error in a toml file.
 803 |   TOMLFile :  (file : File Abs) -> (err : TOMLErr) -> PackErr
 804 |
 805 |   ||| Error in a toml file.
 806 |   TOMLParse : (err : String) -> PackErr
 807 |
 808 |   ||| Number of failures when building packages.
 809 |   BuildFailures : Nat -> PackErr
 810 |
 811 |   ||| User tried to manually install the pack application
 812 |   ManualInstallPackApp : PackErr
 813 |
 814 |   ||| User aborted installation of a lib/app with custom build hooks.
 815 |   SafetyAbort : PackErr
 816 |
 817 |   CyclicDeps : List PkgName -> PackErr
 818 |
 819 | ||| Prints an error that occured during program execution.
 820 | export
 821 | printErr : PackErr -> String
 822 | printErr NoCurDir = "Failed to get current directory."
 823 |
 824 | printErr NoPackDir = """
 825 |   Failed to figure out package directory.
 826 |   This means, that neither environment variable \"PACK_DIR\"
 827 |   nor environment varaible \"HOME\" was set.
 828 |   """
 829 |
 830 | printErr NoTmpDir = """
 831 |   Failed to create temporary directory.
 832 |   Please check directory `PACK_DIR` and make sure to remove
 833 |   all `.tmpXY` directories you no longer need.
 834 |   """
 835 |
 836 | printErr (MkDir path err) =
 837 |   "Error when creating directory \{quote path}: \{err}."
 838 |
 839 | printErr (ReadFile path err) =
 840 |   "Error when reading file \{quote path}: \{err}."
 841 |
 842 | printErr (WriteFile path err) =
 843 |   "Error when writing to file \{quote path}: \{err}."
 844 |
 845 | printErr (DirEntries path err) =
 846 |   "Error when reading directory \{quote path}: \{err}."
 847 |
 848 | printErr (Sys cmd err) = """
 849 |   Error when executing system command.
 850 |   Command: \{escapeCmd cmd}
 851 |   Error code: \{show err}
 852 |   """
 853 |
 854 | printErr (ChangeDir path) =
 855 |   "Failed to change to directory \{quote path}."
 856 |
 857 | printErr (InvalidPackageDesc s) = """
 858 |   Invalid package description: \{quote s}.
 859 |   This should be of the format \"name,url,commit hash,ipkg file\".
 860 |   """
 861 |
 862 | printErr (InvalidDBHeader s) = """
 863 |   Invalid data base header: \{quote s}.
 864 |   This should be of the format \"idris2 commit hash,idris2 version\".
 865 |   """
 866 |
 867 | printErr (InvalidDBName s) = """
 868 |   Invalid data collection name: \{quote s}.
 869 |   This should be a non-empty string without path separators.
 870 |   """
 871 |
 872 | printErr (InvalidBody s) = "Invalid file path body: \{quote s}."
 873 |
 874 | printErr (InvalidPkgType s) = """
 875 |   Invalid package type: \{quote s}.
 876 |   Valid types are `lib` and `bin`.
 877 |   """
 878 |
 879 | printErr (InvalidPkgVersion s) = "Invalid package version: \{quote s}."
 880 |
 881 | printErr (InvalidLogLevel s) = """
 882 |   Invalid log level: \{quote s}. Valid values are
 883 |   \{joinBy "\n" $ ("- " ++) . fst <$> logLevels}
 884 |   """
 885 |
 886 | printErr (UnknownPkg name) = "Unknown package: \{name}"
 887 |
 888 | printErr (IncompletePkg name) = "Incomplete data for custom package: \{name}"
 889 |
 890 | printErr (NotLocalPkg name) = "Not a local package: \{name}"
 891 |
 892 | printErr (NoApp rep) = "Package \{rep} is not an application"
 893 |
 894 | printErr (NoAppIpkg p) = "Package \{p} is not an application"
 895 |
 896 | printErr EmptyPkgDB = "Empty package data base"
 897 |
 898 | printErr (InvalidIpkgFile path) =
 899 |   "Failed to parse .ipkg file: \{path}"
 900 |
 901 | printErr (MissingCorePackage nm v c) =
 902 |   "Core package \{quote nm} missing for Idris2 version \{v} (commit: \{c})"
 903 |
 904 | printErr (UnknownArg arg) = "Unknown command line arg: \{arg}"
 905 |
 906 | printErr (InvalidArgs args) = "Invalid command line args: \{unwords args}"
 907 |
 908 | printErr (ErroneousArg err) = err
 909 |
 910 | printErr (UnknownCommand cmd usage) =
 911 |   """
 912 |   Unknown command: "\{cmd}"
 913 |
 914 |   \{usage}
 915 |   """
 916 |
 917 | printErr (InvalidCmdArgs cmd args usage) =
 918 |   """
 919 |   Invalid argument(s) for command `\{cmd}`.
 920 |
 921 |   \{usage}
 922 |   """
 923 |
 924 | printErr (BuildMany []) = "No local `.ipkg` files found."
 925 | printErr (BuildMany fs@(_::_)) = """
 926 |   Ambiguous `.ipkg` files:
 927 |   \{joinBy "\n" $ ("- " ++) . interpolate <$> fs}
 928 |   Please choose only one.
 929 |   """
 930 |
 931 | printErr (NoFilePath s) = "Not a file path : \{s}"
 932 |
 933 | printErr (DirExists path) = """
 934 |   Failed to clone Git repository into \{path}.
 935 |   Directory already exists.
 936 |   """
 937 |
 938 | printErr (TOMLFile file err) =
 939 |   "Error in file \{file}: \{printTOMLErr err}."
 940 |
 941 | printErr (TOMLParse err) = err
 942 |
 943 | printErr (BuildFailures 1) = "1 package failed to build."
 944 |
 945 | printErr (BuildFailures n) = "\{show n} packages failed to build."
 946 |
 947 | printErr ManualInstallPackApp = """
 948 |   You are not supposed to manually install or remove the pack
 949 |   application. In order to update pack to the latest version on
 950 |   GitHub, run `pack update`.
 951 |
 952 |   Note: If you didn't run `pack install-app pack` or a similar
 953 |   operation, "pack" might be listed as an auto-install application
 954 |   in one of your pack.toml files. Please remove it from there.
 955 |   """
 956 |
 957 | printErr (CyclicDeps ps) =
 958 |  let cycle = fastConcat $ intersperse " -> " (map value ps)
 959 |   in """
 960 |      Dependency cylce detected:
 961 |      \{cycle}
 962 |      """
 963 |
 964 | printErr SafetyAbort = "Aborted."
 965 |
 966 | ||| Tries to convert a string to a `DBName` by first converting
 967 | ||| it to a valid file path body.
 968 | export
 969 | readDBName : String -> Either PackErr DBName
 970 | readDBName s = case Body.parse s of
 971 |   Just b  => Right $ MkDBName b
 972 |   Nothing => Left (InvalidDBName s)
 973 |
 974 | ||| Tries to convert a string to a file path body.
 975 | export
 976 | readBody : String -> Either PackErr Body
 977 | readBody s = case Body.parse s of
 978 |   Just b  => Right b
 979 |   Nothing => Left (InvalidBody s)
 980 |
 981 | ||| Tries to convert a string to a package type.
 982 | export
 983 | readPkgType : String -> Either PackErr PkgType
 984 | readPkgType "lib" = Right PLib
 985 | readPkgType "bin" = Right PApp
 986 | readPkgType "app" = Right PApp
 987 | readPkgType s     = Left (InvalidPkgType s)
 988 |
 989 | ||| Tries to convert a string representing a relative or absolute
 990 | ||| file path. This uses `toAbsPath` internally, so it is somewhat
 991 | ||| tolerant w.r.t. to dubious file paths. It fails, however, if the
 992 | ||| given path does not contain at least one file path body.
 993 | export
 994 | readAbsFile : (curdir : Path Abs) -> String -> Either PackErr (File Abs)
 995 | readAbsFile cd s = case split $ toAbsPath cd (fromString s) of
 996 |   Just (p,b) => Right $ MkF p b
 997 |   Nothing    => Left (NoFilePath s)
 998 |
 999 | export
1000 | readLogLevel : String -> Either PackErr LogLevel
1001 | readLogLevel str = maybeToEither (InvalidLogLevel str) $ lookup str logLevels
1002 |