diff --git a/.gitignore b/.gitignore index ebc517e..c6e3e4d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ result result-* *.agdai MAlonzo/** +.shake +dist-newstyle +_build diff --git a/cabal.project b/cabal.project new file mode 100644 index 0000000..edce78a --- /dev/null +++ b/cabal.project @@ -0,0 +1 @@ +packages: shake diff --git a/cubical-experiments.agda-lib b/cubical-experiments.agda-lib index d562779..3c506c0 100644 --- a/cubical-experiments.agda-lib +++ b/cubical-experiments.agda-lib @@ -1,5 +1,5 @@ name: cubical-experiments -include: src +include: src _build depend: cubical 1lab diff --git a/flake.nix b/flake.nix index 0795f13..04b6f2d 100644 --- a/flake.nix +++ b/flake.nix @@ -11,65 +11,34 @@ system = "x86_64-linux"; pkgs = nixpkgs.legacyPackages.${system}; inherit (nixpkgs) lib; + myAgda = pkgs.agda.withPackages (p: with p; [ + cubical + _1lab + ]); + AGDA_LIBRARIES_FILE = pkgs.writeText "libraries" + (lib.concatMapStrings (p: "${p}/${p.libraryFile}\n") (with pkgs.agdaPackages; [ + cubical + _1lab + ])); + shakefile = pkgs.haskellPackages.callCabal2nix "cubical-experiments-shake" ./shake {}; in { + devShells.${system}.shakefile = pkgs.haskellPackages.shellFor { + packages = _: [ shakefile ]; + inherit AGDA_LIBRARIES_FILE; + }; + packages.${system}.default = pkgs.stdenv.mkDerivation { name = "cubical-experiments"; src = self; - nativeBuildInputs = with pkgs; [ - (agda.withPackages (p: with p; [ - cubical - _1lab - ])) - htmlq + nativeBuildInputs = [ + myAgda + shakefile ]; + inherit AGDA_LIBRARIES_FILE; LC_ALL = "C.UTF-8"; - postHead = '' - - - - ''; - preBodyInternal = '' -

- index ∙ - source -

- ''; - preBodyExternal = '' -

index

-

- ⚠️ This module is not part of this project and is only included for reference.
- It is either part of the 1lab, the cubical library, or a built-in Agda module. -

- ''; buildPhase = '' - shopt -s extglob nullglob - for f in src/*.agda src/*.lagda*; do - base=''${f##*/} mod=''${base%%.@(agda|lagda*)} - if grep -qE 'import *(1Lab|Cat)\.' "$f"; then - echo "import $mod" >> Everything-1Lab.agda - else - echo "import $mod" >> Everything.agda - fi - done - agda -i . --html --html-dir="$out" --html-highlight=all --css=style.css --highlight-occurrences Everything.agda - agda -i . --html --html-dir="$out" --html-highlight=all --css=style.css --highlight-occurrences Everything-1Lab.agda - for f in "$out"/*.html; do - base=''${f##*/} mod=''${base%%.html} - printf -v mod %b "''${mod//%/'\x'}" - src=(src/"$mod".@(agda|lagda*)) - if (( ''${#src} )); then - substituteInPlace "$f" \ - --replace-fail '' "$postHead" \ - --replace-fail '' "''${preBodyInternal/@MODULE@/"''${src[0]}"}" - else - substituteInPlace "$f" \ - --replace-fail '' "$postHead" \ - --replace-fail '' "$preBodyExternal" - fi - done - cp ${self}/{index.html,style.css} "$out"/ - modules=$(for e in Everything-1Lab Everything; do htmlq pre.Agda -f "$out/$e.html"; done) - substituteInPlace "$out"/index.html --subst-var-by modules "$modules" + cubical-experiments-shake + mv _build/site "$out" ''; }; }; diff --git a/index.html b/index.html index a2ea5ff..9a591f5 100644 --- a/index.html +++ b/index.html @@ -10,6 +10,7 @@

cubical-experiments

- @modules@ +
+@agda@
diff --git a/module.html b/module.html new file mode 100644 index 0000000..900f19e --- /dev/null +++ b/module.html @@ -0,0 +1,20 @@ + + + + + + + @moduleName@ + + + + + +

+ index ∙ + source +

+
+@agda@
+ + diff --git a/shake/HTML/Backend.hs b/shake/HTML/Backend.hs new file mode 100644 index 0000000..a5c9c1d --- /dev/null +++ b/shake/HTML/Backend.hs @@ -0,0 +1,223 @@ +{-# OPTIONS_GHC -Wunused-imports #-} + +-- | Backend for generating highlighted, hyperlinked HTML from Agda sources. + +module HTML.Backend + ( htmlBackend + , htmlBackend' + , HtmlFlags(..) + , initialHtmlFlags + ) where + +import HTML.Base + +import Prelude hiding ((!!), concatMap) + +import Control.DeepSeq +import Control.Monad.Trans ( MonadIO ) +import Control.Monad.Except ( MonadError(throwError) ) + +import qualified Data.Map as Map +import Data.Map (Map) + +import GHC.Generics (Generic) + +import Agda.Interaction.Options + ( ArgDescr(ReqArg, NoArg) + , OptDescr(..) + , Flag + ) +import Agda.Compiler.Backend (Backend(..), Backend'(..), Recompile(..)) +import Agda.Compiler.Common (IsMain(..), curIF) + +import Agda.Syntax.TopLevelModuleName (TopLevelModuleName, TopLevelModuleName' (TopLevelModuleName)) + +import Agda.TypeChecking.Monad + ( MonadDebug + , ReadTCState + , Definition + , reportS, stModuleToSource, useTC, TCM, getAgdaLibFiles + ) +import Agda.Interaction.Library.Base (_libName, LibName) +import Agda.Utils.FileName (AbsolutePath) +import Agda.Utils.Maybe (listToMaybe) +import Control.Monad ((<=<)) + +-- | Options for HTML generation + +data HtmlFlags = HtmlFlags + { htmlFlagEnabled :: Bool + , htmlFlagDir :: FilePath + , htmlFlagHighlight :: HtmlHighlight + , htmlFlagHighlightOccurrences :: Bool + , htmlFlagCssFile :: Maybe FilePath + } deriving (Eq, Generic) + +instance NFData HtmlFlags + +data HtmlCompileEnv = HtmlCompileEnv + { htmlCompileEnvOpts :: HtmlOptions + , htmlModuleToURL :: ModuleToURL + } + +data HtmlModuleEnv = HtmlModuleEnv + { htmlModEnvCompileEnv :: HtmlCompileEnv + , htmlModEnvName :: TopLevelModuleName + } + +data HtmlModule = HtmlModule +data HtmlDef = HtmlDef + +htmlBackend :: Backend +htmlBackend = Backend htmlBackend' + +htmlBackend' :: Backend' HtmlFlags HtmlCompileEnv HtmlModuleEnv HtmlModule HtmlDef +htmlBackend' = Backend' + { backendName = "HTML" + , backendVersion = Nothing + , options = initialHtmlFlags + , commandLineFlags = htmlFlags + , isEnabled = htmlFlagEnabled + , preCompile = preCompileHtml + , preModule = preModuleHtml + , compileDef = compileDefHtml + , postModule = postModuleHtml + , postCompile = postCompileHtml + -- --only-scope-checking works, but with the caveat that cross-module links + -- will not have their definition site populated. + , scopeCheckingSuffices = True + , mayEraseType = const $ return False + } + +initialHtmlFlags :: HtmlFlags +initialHtmlFlags = HtmlFlags + { htmlFlagEnabled = False + , htmlFlagDir = defaultHTMLDir + , htmlFlagHighlight = HighlightAll + -- Don't enable by default because it causes potential + -- performance problems + , htmlFlagHighlightOccurrences = False + , htmlFlagCssFile = Nothing + } + +htmlOptsOfFlags :: HtmlFlags -> HtmlOptions +htmlOptsOfFlags flags = HtmlOptions + { htmlOptDir = htmlFlagDir flags + , htmlOptHighlight = htmlFlagHighlight flags + , htmlOptHighlightOccurrences = htmlFlagHighlightOccurrences flags + , htmlOptCssFile = htmlFlagCssFile flags + } + +-- | The default output directory for HTML. + +defaultHTMLDir :: FilePath +defaultHTMLDir = "html" + +htmlFlags :: [OptDescr (Flag HtmlFlags)] +htmlFlags = + [ Option [] ["html"] (NoArg htmlFlag) + "generate HTML files with highlighted source code" + , Option [] ["html-dir"] (ReqArg htmlDirFlag "DIR") + ("directory in which HTML files are placed (default: " ++ + defaultHTMLDir ++ ")") + , Option [] ["highlight-occurrences"] (NoArg highlightOccurrencesFlag) + ("highlight all occurrences of hovered symbol in generated " ++ + "HTML files") + , Option [] ["css"] (ReqArg cssFlag "URL") + "the CSS file used by the HTML files (can be relative)" + , Option [] ["html-highlight"] (ReqArg htmlHighlightFlag "[code,all,auto]") + ("whether to highlight only the code parts (code) or " ++ + "the file as a whole (all) or " ++ + "decide by source file type (auto)") + ] + +htmlFlag :: Flag HtmlFlags +htmlFlag o = return $ o { htmlFlagEnabled = True } + +htmlDirFlag :: FilePath -> Flag HtmlFlags +htmlDirFlag d o = return $ o { htmlFlagDir = d } + +cssFlag :: FilePath -> Flag HtmlFlags +cssFlag f o = return $ o { htmlFlagCssFile = Just f } + +highlightOccurrencesFlag :: Flag HtmlFlags +highlightOccurrencesFlag o = return $ o { htmlFlagHighlightOccurrences = True } + +parseHtmlHighlightFlag :: MonadError String m => String -> m HtmlHighlight +parseHtmlHighlightFlag "code" = return HighlightCode +parseHtmlHighlightFlag "all" = return HighlightAll +parseHtmlHighlightFlag "auto" = return HighlightAuto +parseHtmlHighlightFlag opt = throwError $ concat ["Invalid option <", opt, ">, expected , or "] + +htmlHighlightFlag :: String -> Flag HtmlFlags +htmlHighlightFlag opt o = do + flag <- parseHtmlHighlightFlag opt + return $ o { htmlFlagHighlight = flag } + +runLogHtmlWithMonadDebug :: MonadDebug m => LogHtmlT m a -> m a +runLogHtmlWithMonadDebug = runLogHtmlWith $ reportS "html" 1 + +libToURL :: Map LibName String +libToURL = Map.fromList + [ ("agda-builtins", "https://agda.github.io/cubical") + , ("cubical-0.7", "https://agda.github.io/cubical") + , ("1lab", "https://1lab.dev") + ] + +preCompileHtml + :: HtmlFlags + -> TCM HtmlCompileEnv +preCompileHtml flags = do + moduleToSource <- useTC stModuleToSource + moduleToURL <- Map.traverseMaybeWithKey f moduleToSource + runLogHtmlWithMonadDebug $ do + logHtml $ unlines + [ "Warning: HTML is currently generated for ALL files which can be" + , "reached from the given module, including library files." + ] + let opts = htmlOptsOfFlags flags + prepareCommonDestinationAssets opts + return $ HtmlCompileEnv opts moduleToURL + where + f :: TopLevelModuleName -> AbsolutePath -> TCM (Maybe String) + f m p = ((`Map.lookup` libToURL) <=< fmap _libName . listToMaybe) <$> getAgdaLibFiles p m + +preModuleHtml + :: Applicative m + => HtmlCompileEnv + -> IsMain + -> TopLevelModuleName + -> Maybe FilePath + -> m (Recompile HtmlModuleEnv HtmlModule) +preModuleHtml cenv _isMain modName _ifacePath = pure $ Recompile (HtmlModuleEnv cenv modName) + +compileDefHtml + :: Applicative m + => HtmlCompileEnv + -> HtmlModuleEnv + -> IsMain + -> Definition + -> m HtmlDef +compileDefHtml _env _menv _isMain _def = pure HtmlDef + +postModuleHtml + :: (MonadIO m, MonadDebug m, ReadTCState m) + => HtmlCompileEnv + -> HtmlModuleEnv + -> IsMain + -> TopLevelModuleName + -> [HtmlDef] + -> m HtmlModule +postModuleHtml env menv _isMain _modName _defs = do + let generatePage = defaultPageGen (htmlModuleToURL env) . htmlCompileEnvOpts . htmlModEnvCompileEnv $ menv + htmlSrc <- srcFileOfInterface (htmlModEnvName menv) <$> curIF + runLogHtmlWithMonadDebug $ generatePage htmlSrc + return HtmlModule + +postCompileHtml + :: Applicative m + => HtmlCompileEnv + -> IsMain + -> Map TopLevelModuleName HtmlModule + -> m () +postCompileHtml _cenv _isMain _modulesByName = pure () diff --git a/shake/HTML/Base.hs b/shake/HTML/Base.hs new file mode 100644 index 0000000..20d6371 --- /dev/null +++ b/shake/HTML/Base.hs @@ -0,0 +1,441 @@ +{-# OPTIONS_GHC -Wunused-imports #-} + +-- | Function for generating highlighted, hyperlinked HTML from Agda +-- sources. + +module HTML.Base + ( HtmlOptions(..) + , HtmlHighlight(..) + , prepareCommonDestinationAssets + , srcFileOfInterface + , defaultPageGen + , MonadLogHtml(logHtml) + , LogHtmlT + , runLogHtmlWith + , ModuleToURL + ) where + +import Prelude hiding ((!!), concatMap) + +import Control.DeepSeq +import Control.Monad +import Control.Monad.Trans ( MonadIO(..), lift ) +import Control.Monad.Trans.Reader ( ReaderT(runReaderT), ask ) + +import Data.Function ( on ) +import Data.Foldable (toList, concatMap) +import Data.Maybe +import qualified Data.IntMap as IntMap +import qualified Data.Map as Map +import Data.Map (Map) +import Data.List.Split (splitWhen) +import Data.Text.Lazy (Text) +import qualified Data.Text.Lazy as T + +import GHC.Generics (Generic) + +import qualified Network.URI.Encode + +import System.FilePath +import System.Directory + +import Text.Blaze.Html5 + ( preEscapedToHtml + , toHtml + , stringValue + , Html + , (!) + , Attribute + ) +import qualified Text.Blaze.Html5 as Html5 +import qualified Text.Blaze.Html5.Attributes as Attr +import Text.Blaze.Html.Renderer.Text ( renderHtml ) + -- The imported operator (!) attaches an Attribute to an Html value + -- The defined operator (!!) attaches a list of such Attributes + +import Agda.Interaction.Highlighting.Precise hiding (toList) + +import Agda.Syntax.Common +import Agda.Syntax.TopLevelModuleName + +import qualified Agda.TypeChecking.Monad as TCM + ( Interface(..) + ) + +import Agda.Utils.Function +import Agda.Utils.List1 (String1, pattern (:|)) +import qualified Agda.Utils.List1 as List1 +import qualified Agda.Utils.IO.UTF8 as UTF8 +import Agda.Syntax.Common.Pretty + +import Agda.Utils.Impossible +import Agda.Utils.String (rtrim) +import Agda.Main (printAgdaDataDir) + +import System.IO.Silently + +type ModuleToURL = Map TopLevelModuleName String + +getDataFileName :: FilePath -> IO FilePath +getDataFileName name = do + dir <- capture_ printAgdaDataDir -- ... + return (rtrim dir name) + +-- | The Agda data directory containing the files for the HTML backend. + +htmlDataDir :: FilePath +htmlDataDir = "html" + +-- | The name of the default CSS file. + +defaultCSSFile :: FilePath +defaultCSSFile = "Agda.css" + +-- | The name of the occurrence-highlighting JS file. + +occurrenceHighlightJsFile :: FilePath +occurrenceHighlightJsFile = "highlight-hover.js" + +-- | The directive inserted before the rendered code blocks + +rstDelimiter :: String +rstDelimiter = ".. raw:: html\n" + +-- | The directive inserted before rendered code blocks in org + +orgDelimiterStart :: String +orgDelimiterStart = "#+BEGIN_EXPORT html\n
\n"
+
+-- | The directive inserted after rendered code blocks in org
+
+orgDelimiterEnd :: String
+orgDelimiterEnd = "
\n#+END_EXPORT\n" + +-- | Determine how to highlight the file + +data HtmlHighlight = HighlightAll | HighlightCode | HighlightAuto + deriving (Show, Eq, Generic) + +instance NFData HtmlHighlight + +highlightOnlyCode :: HtmlHighlight -> FileType -> Bool +highlightOnlyCode HighlightAll _ = False +highlightOnlyCode HighlightCode _ = True +highlightOnlyCode HighlightAuto AgdaFileType = False +highlightOnlyCode HighlightAuto MdFileType = True +highlightOnlyCode HighlightAuto RstFileType = True +highlightOnlyCode HighlightAuto OrgFileType = True +highlightOnlyCode HighlightAuto TypstFileType = True +highlightOnlyCode HighlightAuto TexFileType = False + +-- | Determine the generated file extension + +highlightedFileExt :: HtmlHighlight -> FileType -> String +highlightedFileExt hh ft + | not $ highlightOnlyCode hh ft = "html" + | otherwise = case ft of + AgdaFileType -> "html" + MdFileType -> "md" + RstFileType -> "rst" + TexFileType -> "tex" + OrgFileType -> "org" + TypstFileType -> "typ" + +-- | Options for HTML generation + +data HtmlOptions = HtmlOptions + { htmlOptDir :: FilePath + , htmlOptHighlight :: HtmlHighlight + , htmlOptHighlightOccurrences :: Bool + , htmlOptCssFile :: Maybe FilePath + } deriving Eq + +-- | Internal type bundling the information related to a module source file + +data HtmlInputSourceFile = HtmlInputSourceFile + { _srcFileModuleName :: TopLevelModuleName + , _srcFileType :: FileType + -- ^ Source file type + , _srcFileText :: Text + -- ^ Source text + , _srcFileHighlightInfo :: HighlightingInfo + -- ^ Highlighting info + } + +-- | Bundle up the highlighting info for a source file + +srcFileOfInterface :: + TopLevelModuleName -> TCM.Interface -> HtmlInputSourceFile +srcFileOfInterface m i = HtmlInputSourceFile m (TCM.iFileType i) (TCM.iSource i) (TCM.iHighlighting i) + +-- | Logging during HTML generation + +type HtmlLogMessage = String +type HtmlLogAction m = HtmlLogMessage -> m () + +class MonadLogHtml m where + logHtml :: HtmlLogAction m + +type LogHtmlT m = ReaderT (HtmlLogAction m) m + +instance Monad m => MonadLogHtml (LogHtmlT m) where + logHtml message = do + doLog <- ask + lift $ doLog message + +runLogHtmlWith :: Monad m => HtmlLogAction m -> LogHtmlT m a -> m a +runLogHtmlWith = flip runReaderT + +renderSourceFile :: ModuleToURL -> HtmlOptions -> HtmlInputSourceFile -> Text +renderSourceFile moduleToURL opts = renderSourcePage + where + cssFile = fromMaybe defaultCSSFile (htmlOptCssFile opts) + highlightOccur = htmlOptHighlightOccurrences opts + htmlHighlight = htmlOptHighlight opts + renderSourcePage (HtmlInputSourceFile moduleName fileType sourceCode hinfo) = + page cssFile highlightOccur onlyCode moduleName pageContents + where + tokens = tokenStream sourceCode hinfo + onlyCode = highlightOnlyCode htmlHighlight fileType + pageContents = code moduleToURL onlyCode fileType tokens + +defaultPageGen :: (MonadIO m, MonadLogHtml m) => ModuleToURL -> HtmlOptions -> HtmlInputSourceFile -> m () +defaultPageGen moduleToURL opts srcFile@(HtmlInputSourceFile moduleName ft _ _) = do + logHtml $ render $ "Generating HTML for" <+> pretty moduleName <+> ((parens (pretty target)) <> ".") + writeRenderedHtml html target + where + ext = highlightedFileExt (htmlOptHighlight opts) ft + target = (htmlOptDir opts) modToFile moduleName ext + html = renderSourceFile moduleToURL opts srcFile + +prepareCommonDestinationAssets :: MonadIO m => HtmlOptions -> m () +prepareCommonDestinationAssets options = liftIO $ do + -- There is a default directory given by 'defaultHTMLDir' + let htmlDir = htmlOptDir options + createDirectoryIfMissing True htmlDir + + -- If the default CSS file should be used, then it is copied to + -- the output directory. + let cssFile = htmlOptCssFile options + when (isNothing $ cssFile) $ do + defCssFile <- getDataFileName $ + htmlDataDir defaultCSSFile + copyFile defCssFile (htmlDir defaultCSSFile) + + let highlightOccurrences = htmlOptHighlightOccurrences options + when highlightOccurrences $ do + highlightJsFile <- getDataFileName $ + htmlDataDir occurrenceHighlightJsFile + copyFile highlightJsFile (htmlDir occurrenceHighlightJsFile) + +-- | Converts module names to the corresponding HTML file names. + +modToFile :: TopLevelModuleName -> String -> FilePath +modToFile m ext = render (pretty m) <.> ext + +-- | Generates a highlighted, hyperlinked version of the given module. + +writeRenderedHtml + :: MonadIO m + => Text -- ^ Rendered page + -> FilePath -- ^ Output path. + -> m () +writeRenderedHtml html target = liftIO $ UTF8.writeTextToFile target html + + +-- | Attach multiple Attributes + +(!!) :: Html -> [Attribute] -> Html +h !! as = h ! mconcat as + +-- | Constructs the web page, including headers. + +page :: FilePath -- ^ URL to the CSS file. + -> Bool -- ^ Highlight occurrences + -> Bool -- ^ Whether to reserve literate + -> TopLevelModuleName -- ^ Module to be highlighted. + -> Html + -> Text +page css + highlightOccurrences + htmlHighlight + modName + pageContent = + renderHtml $ if htmlHighlight + then pageContent + else Html5.docTypeHtml $ hdr <> rest + where + + hdr = Html5.head $ mconcat + [ Html5.meta !! [ Attr.charset "utf-8" ] + , Html5.title (toHtml . render $ pretty modName) + , Html5.link !! [ Attr.rel "stylesheet" + , Attr.href $ stringValue css + ] + , if highlightOccurrences + then Html5.script mempty !! + [ Attr.type_ "text/javascript" + , Attr.src $ stringValue occurrenceHighlightJsFile + ] + else mempty + ] + + rest = Html5.body $ (Html5.pre ! Attr.class_ "Agda") pageContent + +-- | Position, Contents, Infomation + +type TokenInfo = + ( Int + , String1 + , Aspects + ) + +-- | Constructs token stream ready to print. + +tokenStream + :: Text -- ^ The contents of the module. + -> HighlightingInfo -- ^ Highlighting information. + -> [TokenInfo] +tokenStream contents info = + map (\ ((mi, (pos, c)) :| xs) -> + (pos, c :| map (snd . snd) xs, fromMaybe mempty mi)) $ + List1.groupBy ((==) `on` fst) $ + zipWith (\pos c -> (IntMap.lookup pos infoMap, (pos, c))) [1..] (T.unpack contents) + where + infoMap = toMap info + +-- | Constructs the HTML displaying the code. + +code :: ModuleToURL + -> Bool -- ^ Whether to generate non-code contents as-is + -> FileType -- ^ Source file type + -> [TokenInfo] + -> Html +code moduleToURL onlyCode fileType = mconcat . if onlyCode + then case fileType of + -- Explicitly written all cases, so people + -- get compile error when adding new file types + -- when they forget to modify the code here + RstFileType -> map mkRst . splitByMarkup + MdFileType -> map mkMd . splitByMarkup + AgdaFileType -> map mkHtml + OrgFileType -> map mkOrg . splitByMarkup + -- Two useless cases, probably will never used by anyone + TexFileType -> map mkMd . splitByMarkup + TypstFileType -> map mkMd . splitByMarkup + else map mkHtml + where + trd (_, _, a) = a + + splitByMarkup :: [TokenInfo] -> [[TokenInfo]] + splitByMarkup = splitWhen $ (== Just Markup) . aspect . trd + + mkHtml :: TokenInfo -> Html + mkHtml (pos, s, mi) = + -- Andreas, 2017-06-16, issue #2605: + -- Do not create anchors for whitespace. + applyUnless (mi == mempty) (annotate pos mi) $ toHtml $ List1.toList s + + backgroundOrAgdaToHtml :: TokenInfo -> Html + backgroundOrAgdaToHtml token@(_, s, mi) = case aspect mi of + Just Background -> preEscapedToHtml $ List1.toList s + Just Markup -> __IMPOSSIBLE__ + _ -> mkHtml token + + -- Proposed in #3373, implemented in #3384 + mkRst :: [TokenInfo] -> Html + mkRst = mconcat . (toHtml rstDelimiter :) . map backgroundOrAgdaToHtml + + -- The assumption here and in mkOrg is that Background tokens and Agda tokens are always + -- separated by Markup tokens, so these runs only contain one kind. + mkMd :: [TokenInfo] -> Html + mkMd tokens = if containsCode then formatCode else formatNonCode + where + containsCode = any ((/= Just Background) . aspect . trd) tokens + + formatCode = Html5.pre ! Attr.class_ "Agda" $ mconcat $ backgroundOrAgdaToHtml <$> tokens + formatNonCode = mconcat $ backgroundOrAgdaToHtml <$> tokens + + mkOrg :: [TokenInfo] -> Html + mkOrg tokens = mconcat $ if containsCode then formatCode else formatNonCode + where + containsCode = any ((/= Just Background) . aspect . trd) tokens + + startDelimiter = preEscapedToHtml orgDelimiterStart + endDelimiter = preEscapedToHtml orgDelimiterEnd + + formatCode = startDelimiter : foldr (\x -> (backgroundOrAgdaToHtml x :)) [endDelimiter] tokens + formatNonCode = map backgroundOrAgdaToHtml tokens + + -- Put anchors that enable referencing that token. + -- We put a fail safe numeric anchor (file position) for internal references + -- (issue #2756), as well as a heuristic name anchor for external references + -- (issue #2604). + annotate :: Int -> Aspects -> Html -> Html + annotate pos mi = + applyWhen hereAnchor (anchorage nameAttributes mempty <>) . anchorage posAttributes + where + -- Warp an anchor ( tag) with the given attributes around some HTML. + anchorage :: [Attribute] -> Html -> Html + anchorage attrs html = Html5.a html !! attrs + + -- File position anchor (unique, reliable). + posAttributes :: [Attribute] + posAttributes = concat + [ [Attr.id $ stringValue $ show pos ] + , toList $ link <$> definitionSite mi + , Attr.class_ (stringValue $ unwords classes) <$ guard (not $ null classes) + ] + + -- Named anchor (not reliable, but useful in the general case for outside refs). + nameAttributes :: [Attribute] + nameAttributes = [ Attr.id $ stringValue $ fromMaybe __IMPOSSIBLE__ $ mDefSiteAnchor ] + + classes = concat + [ concatMap noteClasses (note mi) + , otherAspectClasses (toList $ otherAspects mi) + , concatMap aspectClasses (aspect mi) + ] + + aspectClasses (Name mKind op) = kindClass ++ opClass + where + kindClass = toList $ fmap showKind mKind + + showKind (Constructor Inductive) = "InductiveConstructor" + showKind (Constructor CoInductive) = "CoinductiveConstructor" + showKind k = show k + + opClass = ["Operator" | op] + aspectClasses a = [show a] + + + otherAspectClasses = map show + + -- Notes are not included. + noteClasses _s = [] + + -- Should we output a named anchor? + -- Only if we are at the definition site now (@here@) + -- and such a pretty named anchor exists (see 'defSiteAnchor'). + hereAnchor :: Bool + hereAnchor = here && isJust mDefSiteAnchor + + mDefinitionSite :: Maybe DefinitionSite + mDefinitionSite = definitionSite mi + + -- Are we at the definition site now? + here :: Bool + here = maybe False defSiteHere mDefinitionSite + + mDefSiteAnchor :: Maybe String + mDefSiteAnchor = maybe __IMPOSSIBLE__ defSiteAnchor mDefinitionSite + + link (DefinitionSite m defPos _here aName) = Attr.href $ stringValue $ + -- If the definition site points to the top of a file, + -- we drop the anchor part and just link to the file. + applyUnless (defPos <= 1) + (++ "#" ++ + -- Use named anchors for external links as they should be more stable(?) + Network.URI.Encode.encode (fromMaybe (show defPos) (aName <* u))) + (maybe id () u $ Network.URI.Encode.encode $ modToFile m "html") + where u = Map.lookup m moduleToURL diff --git a/shake/LICENSE.agda b/shake/LICENSE.agda new file mode 100644 index 0000000..6af9b69 --- /dev/null +++ b/shake/LICENSE.agda @@ -0,0 +1,48 @@ +The files under HTML/ are modified versions of Agda's HTML backend. +Agda is distributed with the following license: + +Copyright (c) 2005-2024 remains with the authors. +Agda 2 was originally written by Ulf Norell, +partially based on code from Agda 1 by Catarina Coquand and Makoto Takeyama, +and from Agdalight by Ulf Norell and Andreas Abel. +Cubical Agda was originally contributed by Andrea Vezzosi. + +Agda 2 is currently actively developed mainly by Andreas Abel, +Guillaume Allais, Liang-Ting Chen, Jesper Cockx, Matthew Daggitt, +Nils Anders Danielsson, Amélia Liao, Ulf Norell, and +Andrés Sicard-Ramírez. + +Further, Agda 2 has received contributions by, amongst others, +Arthur Adjedj, Stevan Andjelkovic, +Marcin Benke, Jean-Philippe Bernardy, Guillaume Brunerie, +James Chapman, Jonathan Coates, +Dominique Devriese, Péter Diviánszky, Robert Estelle, +Olle Fredriksson, Adam Gundry, Daniel Gustafsson, Philipp Hausmann, +Alan Jeffrey, Phil de Joux, +Wolfram Kahl, Wen Kokke, John Leo, Fredrik Lindblad, +Víctor López Juan, Ting-Gan Lua, Francesco Mazzoli, Stefan Monnier, +Guilhem Moulin, Konstantin Nisht, Fredrik Nordvall Forsberg, +Josselin Poiret, Nicolas Pouillard, Jonathan Prieto, Christian Sattler, +Makoto Takeyama, Andrea Vezzosi, Noam Zeilberger, and Tesla Ice Zhang. +The full list of contributors is available at +https://github.com/agda/agda/graphs/contributors or from the git +repository via ``git shortlog -sne``. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/shake/Main.hs b/shake/Main.hs new file mode 100644 index 0000000..70582ba --- /dev/null +++ b/shake/Main.hs @@ -0,0 +1,124 @@ +import Agda.Compiler.Backend hiding (getEnv) +import Agda.Interaction.FindFile +import Agda.Interaction.Imports +import Agda.Interaction.Options +import Agda.TypeChecking.Errors +import Agda.Utils.FileName +import Agda.Utils.Monad + +import Control.Monad.Error.Class + +import Data.Foldable +import Data.Text qualified as T + +import Development.Shake +import Development.Shake.Classes +import Development.Shake.FilePath + +import HTML.Backend +import HTML.Base + +import Text.Regex.TDFA + +newtype ModuleCompileQ = ModuleCompileQ FilePath + deriving (Show, Typeable, Eq, Hashable, Binary, NFData) + +type instance RuleResult ModuleCompileQ = () + +sourceDir, buildDir, htmlDir, siteDir, everythingLoadPrimitives, everythingNoLoadPrimitives :: FilePath +sourceDir = "src" +buildDir = "_build" +htmlDir = buildDir "html" +siteDir = buildDir "site" +everythingLoadPrimitives = buildDir "EverythingLoadPrimitives.agda" +everythingNoLoadPrimitives = buildDir "EverythingNoLoadPrimitives.agda" + +myHtmlBackend :: Backend +myHtmlBackend = Backend htmlBackend' + { options = initialHtmlFlags + { htmlFlagDir = htmlDir + , htmlFlagHighlightOccurrences = True + , htmlFlagCssFile = Just "style.css" + , htmlFlagHighlight = HighlightCode + } + } + +-- | Should this file be compiled with --load-primitives? +-- Modules that depend on 1lab require --no-load-primitives while +-- modules that depend on cubical require --load-primitives, and those +-- flags conflict so we have to split them into separate Everything files... +loadPrimitives :: FilePath -> Action Bool +loadPrimitives f = do + contents <- readFile' (sourceDir f) + pure $ not $ contents =~ ("import *(1Lab|Cat)\\." :: String) + +filenameToModule :: FilePath -> String +filenameToModule f = dropExtension f + +makeEverythingFile :: [FilePath] -> String +makeEverythingFile = unlines . map (\ m -> "import " <> filenameToModule m) + +readFileText :: FilePath -> Action T.Text +readFileText = fmap T.pack . readFile' + +main :: IO () +main = shakeArgs shakeOptions do + compileModule <- addOracle \ (ModuleCompileQ f) -> do + librariesFile <- getEnv "AGDA_LIBRARIES_FILE" + traced "agda" do + sourceFile <- SourceFile <$> absolute f + runTCMTopPrettyErrors do + setCommandLineOptions defaultOptions + { optOverrideLibrariesFile = librariesFile + , optDefaultLibs = False + } + stBackends `setTCLens` [myHtmlBackend] + source <- parseSource sourceFile + checkResult <- typeCheckMain TypeCheck source + callBackend "HTML" IsMain checkResult + + -- I realise this is not how a Shakefile should be structured, but I got + -- bored trying to figure it out and this is enough for my needs. + -- I should probably look into Development.Shake.Forward ... + siteDir "index.html" %> \ index -> do + agdaFiles <- getDirectoryFiles sourceDir ["//*.agda"] + (no, yes) <- partitionM loadPrimitives agdaFiles + writeFile' everythingLoadPrimitives (makeEverythingFile yes) + writeFile' everythingNoLoadPrimitives (makeEverythingFile no) + compileModule (ModuleCompileQ everythingLoadPrimitives) + compileModule (ModuleCompileQ everythingNoLoadPrimitives) + moduleTemplate <- readFileText "module.html" + for_ agdaFiles \ agdaFile -> do + -- Poor man's template engine + agda <- readFileText (htmlDir agdaFile -<.> "html") + writeFile' (siteDir agdaFile -<.> "html") + $ T.unpack + $ T.replace "@agda@" agda + $ T.replace "@moduleName@" (T.pack $ dropExtension agdaFile) + $ T.replace "@filename@" (T.pack agdaFile) + $ moduleTemplate + indexTemplate <- readFileText "index.html" + everything <- (<>) <$> readFileText (htmlDir "EverythingLoadPrimitives.html") + <*> readFileText (htmlDir "EverythingNoLoadPrimitives.html") + writeFile' index + $ T.unpack + $ T.replace "@agda@" everything + $ indexTemplate + copyFile' "style.css" (siteDir "style.css") + copyFile' (htmlDir "highlight-hover.js") (siteDir "highlight-hover.js") + + phony "all" do + need [siteDir "index.html"] + + want ["all"] + +runTCMTopPrettyErrors :: TCM a -> IO a +runTCMTopPrettyErrors tcm = do + r <- runTCMTop' $ (Just <$> tcm) `catchError` \err -> do + warnings <- fmap (map show) . prettyTCWarnings' =<< getAllWarningsOfTCErr err + errors <- show <$> prettyError err + let everything = filter (not . null) $ warnings ++ [errors] + unless (null errors) . liftIO . putStr $ unlines everything + pure Nothing + + maybe (fail "Agda compilation failed") pure r diff --git a/shake/cubical-experiments-shake.cabal b/shake/cubical-experiments-shake.cabal new file mode 100644 index 0000000..a045697 --- /dev/null +++ b/shake/cubical-experiments-shake.cabal @@ -0,0 +1,63 @@ +cabal-version: 3.4 +name: cubical-experiments-shake +version: 0.1.0.0 +license: AGPL-3.0-or-later +author: Naïm Favier +maintainer: n@monade.li +category: Development +build-type: Simple + +executable cubical-experiments-shake + ghc-options: -Wall + main-is: Main.hs + other-modules: HTML.Base, + HTML.Backend, + build-depends: base ^>=4.18.2.1, + Agda, + shake, + blaze-html, + containers, + deepseq, + directory, + filepath, + mtl, + regex-tdfa, + silently, + split, + text, + transformers, + uri-encode, + hs-source-dirs: . + default-language: GHC2021 + default-extensions: + BangPatterns + BlockArguments + ConstraintKinds + DefaultSignatures + DeriveFoldable + DeriveFunctor + DeriveGeneric + DeriveTraversable + DerivingStrategies + ExistentialQuantification + FlexibleContexts + FlexibleInstances + FunctionalDependencies + GADTs + GeneralizedNewtypeDeriving + InstanceSigs + LambdaCase + MultiParamTypeClasses + MultiWayIf + NamedFieldPuns + OverloadedStrings + PatternSynonyms + RankNTypes + RecordWildCards + ScopedTypeVariables + StandaloneDeriving + TupleSections + TypeFamilies + TypeOperators + TypeSynonymInstances + ViewPatterns