packages feed

tricorder-0.1.0.0: src/Tricorder/CLI/Render.hs

module Tricorder.CLI.Render
    ( -- * Plain-text formatting
      diagnosticLine
    , diagnosticLineIndexed
    , diagnosticBlock
    , formatDuration
    , renderSourceResults
    ) where

import Atelier.Effects.Console (Console)
import Atelier.Time (Millisecond, toMicroseconds)

import Atelier.Effects.Console qualified as Console
import Data.Text qualified as T

import Tricorder.BuildState
    ( Diagnostic (..)
    , Severity (..)
    )
import Tricorder.GhcPkg.Types (ModuleName (..), PackageId (..))
import Tricorder.SourceLookup (ModuleSourceResult (..), ReExport (..), SourceQuery (..))


formatDuration :: Millisecond -> Text
formatDuration d =
    let ms = toMicroseconds d `div` 1000
    in  if ms < 1000 then
            show ms <> "ms"
        else
            show (ms `div` 1000) <> "." <> show ((ms `mod` 1000) `div` 100) <> "s"


-- | Single-line diagnostic for plain-text / shell output.
--
-- Format: @E src\/Foo\/Bar.hs:42 \`something\` not in scope@
diagnosticLine :: Diagnostic -> Text
diagnosticLine d =
    prefix d.severity <> " " <> toText d.file <> ":" <> show d.line <> " " <> d.title
  where
    prefix SError = "E"
    prefix SWarning = "W"


-- | Like 'diagnosticLine' but prefixed with a 1-based index.
--
-- Format: @[N] E src\/Foo\/Bar.hs:42 \`something\` not in scope@
diagnosticLineIndexed :: Int -> Diagnostic -> Text
diagnosticLineIndexed n d = "[" <> show n <> "] " <> diagnosticLine d


-- | One-liner followed by the full GHC message body (verbose mode).
diagnosticBlock :: Diagnostic -> Text
diagnosticBlock d = diagnosticLine d <> "\n" <> d.text


renderSourceResults :: (Console :> es) => [ModuleSourceResult] -> Eff es ()
renderSourceResults results = mapM_ renderOne results
  where
    renderOne (SourceFound query src reExports) = do
        when (length results > 1) $ Console.putTextLn $ header query
        Console.putText src
        unless (null reExports || isJust query.function)
            $ Console.putTextLn
            $ "\n-- Re-exports: " <> T.intercalate ", " (map renderReExport reExports)
        when (length results > 1) $ Console.putStrLn ""
    renderOne (SourceNotFound query) =
        Console.putTextLn
            $ "Not found: "
                <> unModuleName query.moduleName
                <> " (module not in any installed package)"
    renderOne (SourceNoHaddock query pkgId) =
        Console.putTextLn
            $ "No source available: "
                <> unModuleName query.moduleName
                <> " (package "
                <> unPackageId pkgId
                <> " was built without documentation; try `cabal get "
                <> unPackageId pkgId
                <> "`)"
    renderOne (FunctionNotFound query) =
        Console.putTextLn
            $ "tricorder: "
                <> unModuleName query.moduleName
                <> "#"
                <> fromMaybe "" query.function
                <> ": function not found in module source"

    header query =
        "-- "
            <> unModuleName query.moduleName
            <> maybe "" ("#" <>) query.function

    renderReExport (ReExportModule m) = "module " <> m
    renderReExport (ReExportName name src) = name <> " (from " <> src <> ")"