extract doc generator to separate executable (#1671)

Closes #1443.

Also added `-Wunused-packages` to clean up dependencies.

## Demo

This still works as usual:

    stack run

Output editor keywords:

    stack run swarm-docs -- editors --emacs
This commit is contained in:
Karl Ostmo 2023-12-03 19:45:07 -08:00 committed by GitHub
parent 437f70418c
commit b8d37a9364
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 181 additions and 130 deletions

View File

@ -7,16 +7,13 @@
module Main where
import Data.Foldable qualified
import Data.Maybe (fromMaybe)
import Data.Text (Text, pack)
import Data.Text qualified as T
import Data.Text.IO qualified as Text
import GitHash (GitInfo, giBranch, giHash, tGitInfoCwdTry)
import Options.Applicative
import Prettyprinter
import Prettyprinter.Render.Text qualified as RT
import Swarm.App (appMain)
import Swarm.Doc.Gen (EditorType (..), GenerateDocs (..), PageAddress (..), SheetType (..), generateDocs)
import Swarm.Game.Scenario.Topography.Area (AreaDimensions (..))
import Swarm.Game.World.Render (OuputFormat (..), RenderOpts (..), doRenderCmd)
import Swarm.Language.LSP (lspMain)
@ -45,7 +42,6 @@ type Width = Int
data CLI
= Run AppOpts
| Format Input (Maybe Width)
| DocGen GenerateDocs
| RenderMap FilePath RenderOpts
| LSP
| Version
@ -55,7 +51,6 @@ cliParser =
subparser
( mconcat
[ command "format" (info (Format <$> format <*> optional widthOpt <**> helper) (progDesc "Format a file"))
, command "generate" (info (DocGen <$> docgen <**> helper) (progDesc "Generate docs"))
, command "map" (info (render <**> helper) (progDesc "Render a scenario world map."))
, command "lsp" (info (pure LSP) (progDesc "Start the LSP"))
, command "version" (info (pure Version) (progDesc "Get current and upstream version."))
@ -97,46 +92,6 @@ cliParser =
widthOpt :: Parser Width
widthOpt = option auto (long "width" <> metavar "COLUMNS" <> help "Use layout with maximum width")
docgen :: Parser GenerateDocs
docgen =
subparser . mconcat $
[ command "recipes" (info (pure RecipeGraph) $ progDesc "Output graphviz dotfile of entity dependencies based on recipes")
, command "editors" (info (EditorKeywords <$> editor <**> helper) $ progDesc "Output editor keywords")
, command "keys" (info (pure SpecialKeyNames) $ progDesc "Output list of recognized special key names")
, command "cheatsheet" (info (CheatSheet <$> address <*> cheatsheet <**> helper) $ progDesc "Output nice Wiki tables")
, command "pedagogy" (info (pure TutorialCoverage) $ progDesc "Output tutorial coverage")
, command "endpoints" (info (pure WebAPIEndpoints) $ progDesc "Generate markdown Web API documentation.")
]
editor :: Parser (Maybe EditorType)
editor =
Data.Foldable.asum
[ pure Nothing
, Just VSCode <$ switch (long "code" <> help "Generate for the VS Code editor")
, Just Emacs <$ switch (long "emacs" <> help "Generate for the Emacs editor")
]
address :: Parser PageAddress
address =
let replace a b = T.unpack . T.replace a b . T.pack
opt n =
fmap (fromMaybe "") . optional $
option
str
( long n
<> metavar "ADDRESS"
<> help ("Set the address of " <> replace "-" " " n <> ". Default no link.")
)
in PageAddress <$> opt "entities-page" <*> opt "commands-page" <*> opt "capabilities-page" <*> opt "recipes-page"
cheatsheet :: Parser (Maybe SheetType)
cheatsheet =
Data.Foldable.asum
[ pure Nothing
, Just Entities <$ switch (long "entities" <> help "Generate entities page (uses data from entities.yaml)")
, Just Recipes <$ switch (long "recipes" <> help "Generate recipes page (uses data from recipes.yaml)")
, Just Capabilities <$ switch (long "capabilities" <> help "Generate capabilities page (uses entity map)")
, Just Commands <$ switch (long "commands" <> help "Generate commands page (uses constInfo, constCaps and inferConst)")
, Just Scenario <$ switch (long "scenario" <> help "Generate scenario schema page")
]
seed :: Parser (Maybe Int)
seed = optional $ option auto (long "seed" <> short 's' <> metavar "INT" <> help "Seed to use for world generation")
webPort :: Parser (Maybe Int)
@ -218,7 +173,6 @@ main = do
cli <- execParser cliInfo
case cli of
Run opts -> appMain opts
DocGen g -> generateDocs g
Format fo w -> formatFile fo w
RenderMap mapPath opts -> doRenderCmd opts mapPath
LSP -> lspMain

66
app/doc/Main.hs Normal file
View File

@ -0,0 +1,66 @@
{-# LANGUAGE OverloadedStrings #-}
-- |
-- SPDX-License-Identifier: BSD-3-Clause
module Main where
import Data.Foldable qualified
import Data.Maybe (fromMaybe)
import Data.Text qualified as T
import Options.Applicative
import Swarm.Doc.Gen (GenerateDocs (..), PageAddress (..), SheetType (..), generateDocs)
import Swarm.Doc.Keyword (EditorType (..))
cliParser :: Parser GenerateDocs
cliParser =
subparser $
mconcat
[ command "recipes" (info (pure RecipeGraph) $ progDesc "Output graphviz dotfile of entity dependencies based on recipes")
, command "editors" (info (EditorKeywords <$> editor <**> helper) $ progDesc "Output editor keywords")
, command "keys" (info (pure SpecialKeyNames) $ progDesc "Output list of recognized special key names")
, command "cheatsheet" (info (CheatSheet <$> address <*> cheatsheet <**> helper) $ progDesc "Output nice Wiki tables")
, command "pedagogy" (info (pure TutorialCoverage) $ progDesc "Output tutorial coverage")
, command "endpoints" (info (pure WebAPIEndpoints) $ progDesc "Generate markdown Web API documentation.")
]
where
editor :: Parser (Maybe EditorType)
editor =
Data.Foldable.asum
[ pure Nothing
, Just VSCode <$ switch (long "code" <> help "Generate for the VS Code editor")
, Just Emacs <$ switch (long "emacs" <> help "Generate for the Emacs editor")
]
address :: Parser PageAddress
address =
let replace a b = T.unpack . T.replace a b . T.pack
opt n =
fmap (fromMaybe "") . optional $
option
str
( long n
<> metavar "ADDRESS"
<> help ("Set the address of " <> replace "-" " " n <> ". Default no link.")
)
in PageAddress <$> opt "entities-page" <*> opt "commands-page" <*> opt "capabilities-page" <*> opt "recipes-page"
cheatsheet :: Parser (Maybe SheetType)
cheatsheet =
Data.Foldable.asum
[ pure Nothing
, Just Entities <$ switch (long "entities" <> help "Generate entities page (uses data from entities.yaml)")
, Just Recipes <$ switch (long "recipes" <> help "Generate recipes page (uses data from recipes.yaml)")
, Just Capabilities <$ switch (long "capabilities" <> help "Generate capabilities page (uses entity map)")
, Just Commands <$ switch (long "commands" <> help "Generate commands page (uses constInfo, constCaps and inferConst)")
, Just Scenario <$ switch (long "scenario" <> help "Generate scenario schema page")
]
cliInfo :: ParserInfo GenerateDocs
cliInfo =
info
(cliParser <**> helper)
( header "Swarm docs"
<> progDesc "Generate swarm documentation."
<> fullDesc
)
main :: IO ()
main = generateDocs =<< execParser cliInfo

View File

@ -1,4 +1,5 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
-- |
@ -9,15 +10,7 @@ module Swarm.Doc.Gen (
-- ** Main document generation function + types
generateDocs,
GenerateDocs (..),
EditorType (..),
SheetType (..),
loadStandaloneScenario,
-- ** Formatted keyword lists
keywordsCommands,
keywordsDirections,
operatorNames,
builtinFunctionList,
-- ** Wiki pages
PageAddress (..),
@ -36,6 +29,7 @@ import Data.Text (Text, unpack)
import Data.Text qualified as T
import Data.Text.IO qualified as T
import Data.Tuple (swap)
import Swarm.Doc.Keyword
import Swarm.Doc.Pedagogy
import Swarm.Doc.Util
import Swarm.Doc.Wiki.Cheatsheet
@ -47,8 +41,7 @@ import Swarm.Game.Scenario (loadStandaloneScenario)
import Swarm.Game.World.Gen (extractEntities)
import Swarm.Game.World.Typecheck (Some (..), TTerm)
import Swarm.Language.Key (specialKeyNames)
import Swarm.Language.Syntax qualified as Syntax
import Swarm.Util (both, listEnums, quote)
import Swarm.Util (both, listEnums)
import Swarm.Util.Effect (simpleErrorHandle)
import Swarm.Web (swarmApiMarkdown)
import Text.Dot (Dot, NodeId, (.->.))
@ -78,11 +71,6 @@ data GenerateDocs where
WebAPIEndpoints :: GenerateDocs
deriving (Eq, Show)
-- | An enumeration of the editors supported by Swarm (currently,
-- Emacs and VS Code).
data EditorType = Emacs | VSCode | Vim
deriving (Eq, Show, Enum, Bounded)
-- | Generate the requested kind of documentation to stdout.
generateDocs :: GenerateDocs -> IO ()
generateDocs = \case
@ -133,34 +121,6 @@ generateEditorKeywords = \case
putStr "\nsyn keyword Direction "
T.putStrLn $ keywordsDirections Vim
builtinFunctionList :: EditorType -> Text
builtinFunctionList e = editorList e $ map constSyntax builtinFunctions
editorList :: EditorType -> [Text] -> Text
editorList = \case
Emacs -> T.unlines . map ((" " <>) . quote)
VSCode -> T.intercalate "|"
Vim -> T.intercalate " "
-- | Get formatted list of basic functions/commands.
keywordsCommands :: EditorType -> Text
keywordsCommands e = editorList e $ map constSyntax commands
-- | Get formatted list of directions.
keywordsDirections :: EditorType -> Text
keywordsDirections e = editorList e $ map Syntax.directionSyntax Syntax.allDirs
-- | A list of the names of all the operators in the language.
operatorNames :: Text
operatorNames = T.intercalate "|" $ map (escape . constSyntax) operators
where
special :: String
special = "*+$[]|^"
slashNotComment = \case
'/' -> "/(?![/|*])"
c -> T.singleton c
escape = T.concatMap (\c -> if c `elem` special then T.snoc "\\\\" c else slashNotComment c)
-- ----------------------------------------------------------------------------
-- GENERATE SPECIAL KEY NAMES
-- ----------------------------------------------------------------------------

View File

@ -1,3 +1,4 @@
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
-- |

View File

@ -1,3 +1,4 @@
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
-- |

View File

@ -26,7 +26,7 @@
(setq swarm-font-lock-keywords
(let* (
;; Generate the current keywords with:
;; cabal run swarm:swarm -- generate editors --emacs
;; cabal run swarm:swarm-docs -- editors --emacs
(x-keywords '("def" "end" "let" "in" "require"))
(x-builtins '(
"self"

View File

@ -28,11 +28,11 @@
### Updating the syntax highlighting
Whenever swarm language adds new features, the highlighing needs to be updated.
Whenever swarm language adds new features, the highlighting needs to be updated.
To save some time, get the current reserved words by running `swarm generate`:
To save some time, get the current reserved words by running `swarm-docs`:
```bash
cabal run swarm:swarm -- generate editors
cabal run swarm:swarm-docs -- editors
```
You still have to add for example types manually.

55
src/Swarm/Doc/Keyword.hs Normal file
View File

@ -0,0 +1,55 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedStrings #-}
-- |
-- SPDX-License-Identifier: BSD-3-Clause
--
-- Collect keywords for documentation generation and testing.
module Swarm.Doc.Keyword (
EditorType (..),
-- ** Formatted keyword lists
keywordsCommands,
keywordsDirections,
operatorNames,
builtinFunctionList,
) where
import Data.Text (Text)
import Data.Text qualified as T
import Swarm.Doc.Util
import Swarm.Language.Syntax qualified as Syntax
import Swarm.Util (quote)
-- | An enumeration of the editors supported by Swarm (currently,
-- Emacs and VS Code).
data EditorType = Emacs | VSCode | Vim
deriving (Eq, Show, Enum, Bounded)
builtinFunctionList :: EditorType -> Text
builtinFunctionList e = editorList e $ map constSyntax builtinFunctions
editorList :: EditorType -> [Text] -> Text
editorList = \case
Emacs -> T.unlines . map ((" " <>) . quote)
VSCode -> T.intercalate "|"
Vim -> T.intercalate " "
-- | Get formatted list of basic functions/commands.
keywordsCommands :: EditorType -> Text
keywordsCommands e = editorList e $ map constSyntax commands
-- | Get formatted list of directions.
keywordsDirections :: EditorType -> Text
keywordsDirections e = editorList e $ map Syntax.directionSyntax Syntax.allDirs
-- | A list of the names of all the operators in the language.
operatorNames :: Text
operatorNames = T.intercalate "|" $ map (escape . constSyntax) operators
where
special :: String
special = "*+$[]|^"
slashNotComment = \case
'/' -> "/(?![/|*])"
c -> T.singleton c
escape = T.concatMap (\c -> if c `elem` special then T.snoc "\\\\" c else slashNotComment c)

View File

@ -17,13 +17,12 @@ import Data.Tuple.Extra (both)
import Data.Vector qualified as V
import Graphics.Vty.Attributes.Color240
import Linear (V2 (..))
import Swarm.Doc.Gen (loadStandaloneScenario)
import Swarm.Game.Display (Attribute (AWorld), defaultChar, displayAttr)
import Swarm.Game.Entity.Cosmetic
import Swarm.Game.Entity.Cosmetic.Assignment (terrainAttributes)
import Swarm.Game.Location
import Swarm.Game.ResourceLoading (initNameGenerator, readAppData)
import Swarm.Game.Scenario (Scenario, area, scenarioCosmetics, scenarioWorlds, ul, worldName)
import Swarm.Game.Scenario (Scenario, area, loadStandaloneScenario, scenarioCosmetics, scenarioWorlds, ul, worldName)
import Swarm.Game.Scenario.Status (seedLaunchParams)
import Swarm.Game.Scenario.Topography.Area (AreaDimensions (..), getAreaDimensions, isEmpty, upperLeftToBottomRight)
import Swarm.Game.Scenario.Topography.Cell

View File

@ -70,6 +70,7 @@ common stan-config
-- Harmless extensions from GHC2021
common ghc2021-extensions
ghc-options: -Wprepositive-qualified-module
-Wunused-packages
default-extensions:
BangPatterns
DeriveAnyClass
@ -100,15 +101,9 @@ library
Data.BoolExpr.Simplify
Swarm.App
Swarm.Constant
Swarm.Doc.Gen
Swarm.Doc.Keyword
Swarm.Doc.Pedagogy
Swarm.Doc.Schema.Arrangement
Swarm.Doc.Schema.Parse
Swarm.Doc.Schema.Refined
Swarm.Doc.Schema.Render
Swarm.Doc.Schema.SchemaType
Swarm.Doc.Util
Swarm.Doc.Wiki.Cheatsheet
Swarm.Game.Failure
Swarm.Game.Achievement.Attainment
Swarm.Game.Achievement.Definitions
@ -269,7 +264,6 @@ library
aeson >= 2 && < 2.2,
array >= 0.5.4 && < 0.6,
astar >= 0.3 && < 0.3.1,
blaze-html >= 0.9.1 && < 0.9.2,
boolexpr >= 0.2 && < 0.3,
brick >= 2.1.1 && < 2.2,
bytestring >= 0.10 && < 0.12,
@ -279,7 +273,6 @@ library
commonmark-extensions >= 0.2 && < 0.3,
containers >= 0.6.2 && < 0.7,
directory >= 1.3 && < 1.4,
dotgen >= 0.4 && < 0.5,
either >= 5.0 && < 5.1,
extra >= 1.7 && < 1.8,
filepath >= 1.4 && < 1.5,
@ -300,8 +293,6 @@ library
minimorph >= 0.3 && < 0.4,
transformers >= 0.5 && < 0.7,
mtl >= 2.2.2 && < 2.4,
pandoc >= 3.0 && < 3.2,
pandoc-types >= 1.23 && < 1.24,
murmur3 >= 1.0.4 && < 1.1,
natural-sort >= 0.1.2 && < 0.2,
nonempty-containers >= 0.3.4 && < 0.3.5,
@ -309,15 +300,12 @@ library
parser-combinators >= 1.2 && < 1.4,
prettyprinter >= 1.7.0 && < 1.8,
random >= 1.2.0 && < 1.3,
scientific >= 0.3.6 && < 0.3.8,
wai-app-static >= 3.1.8 && < 3.1.9,
servant >= 0.19 && < 0.21,
servant-docs >= 0.12 && < 0.14,
servant-server >= 0.19 && < 0.21,
SHA >= 1.6.4 && < 1.6.5,
simple-enumeration >= 0.2 && < 0.3,
split >= 0.2.3 && < 0.3,
stm >= 2.5.0 && < 2.6,
syb >= 0.7 && < 0.8,
tagged >= 0.8 && < 0.9,
template-haskell >= 2.16 && < 2.21,
@ -330,7 +318,6 @@ library
unordered-containers >= 0.2.14 && < 0.3,
vector >= 0.12 && < 0.14,
vty >= 6.0 && < 6.1,
vty-crossplatform >= 0.2.0.0 && < 0.3,
wai >= 3.2 && < 3.3,
warp >= 3.2 && < 3.4,
witch >= 1.1.1.0 && < 1.3,
@ -354,6 +341,8 @@ executable swarm
build-depends: optparse-applicative >= 0.16 && < 0.19,
githash >= 0.1.6 && < 0.2,
terminal-size >= 0.3 && < 1.0,
blaze-html >= 0.9.1 && < 0.9.2,
servant >= 0.19 && < 0.21,
-- Imports shared with the library don't need bounds
base,
text,
@ -364,6 +353,40 @@ executable swarm
ghc-options: -threaded
default-extensions: ImportQualifiedPost
executable swarm-docs
import: stan-config, common, ghc2021-extensions
main-is: Main.hs
other-modules: Swarm.Doc.Gen
Swarm.Doc.Schema.Arrangement
Swarm.Doc.Schema.Parse
Swarm.Doc.Schema.Refined
Swarm.Doc.Schema.Render
Swarm.Doc.Schema.SchemaType
Swarm.Doc.Wiki.Cheatsheet
build-depends: optparse-applicative >= 0.16 && < 0.19,
dotgen >= 0.4 && < 0.5,
pandoc >= 3.0 && < 3.2,
pandoc-types >= 1.23 && < 1.24,
scientific >= 0.3.6 && < 0.3.8,
-- Imports shared with the library don't need bounds
base,
aeson,
containers,
directory,
extra,
filepath,
fused-effects,
lens,
mtl,
swarm,
text,
transformers,
vector,
hs-source-dirs: app/doc
default-language: Haskell2010
ghc-options: -threaded
default-extensions: ImportQualifiedPost
test-suite swarm-unit
import: stan-config, common, ghc2021-extensions
main-is: Main.hs
@ -394,7 +417,6 @@ test-suite swarm-unit
filepath,
hashable,
lens,
linear,
mtl,
swarm,
text,
@ -412,19 +434,15 @@ test-suite swarm-integration
build-depends: tasty >= 0.10 && < 1.6,
tasty-hunit >= 0.10 && < 0.11,
tasty-expected-failure >= 0.12 && < 0.13,
-- Imports shared with the library don't need bounds
base,
containers,
directory,
filepath,
fused-effects,
lens,
linear,
mtl,
swarm,
text,
transformers,
witch,
yaml
hs-source-dirs: test/integration
@ -439,11 +457,8 @@ benchmark benchmark
build-depends: tasty-bench >= 0.3.1 && < 0.4,
base,
lens,
linear,
mtl,
random,
swarm,
text,
containers,
default-language: Haskell2010
ghc-options: -threaded

View File

@ -26,8 +26,8 @@ import Data.Text (Text)
import Data.Text qualified as T
import Data.Text.IO qualified as T
import Data.Yaml (ParseException, prettyPrintParseException)
import Swarm.Doc.Gen (EditorType (..))
import Swarm.Doc.Gen qualified as DocGen
import Swarm.Doc.Keyword (EditorType (..))
import Swarm.Doc.Keyword qualified as Keyword
import Swarm.Effect (runTimeIO)
import Swarm.Game.Achievement.Definitions (GameplayAchievement (..))
import Swarm.Game.CESK (emptyStore, getTickNumber, initMachine)
@ -525,22 +525,22 @@ testEditorFiles =
"editors"
[ testGroup
"VS Code"
[ testTextInVSCode "operators" (const DocGen.operatorNames)
, testTextInVSCode "builtin" DocGen.builtinFunctionList
, testTextInVSCode "commands" DocGen.keywordsCommands
, testTextInVSCode "directions" DocGen.keywordsDirections
[ testTextInVSCode "operators" (const Keyword.operatorNames)
, testTextInVSCode "builtin" Keyword.builtinFunctionList
, testTextInVSCode "commands" Keyword.keywordsCommands
, testTextInVSCode "directions" Keyword.keywordsDirections
]
, testGroup
"Emacs"
[ testTextInEmacs "builtin" DocGen.builtinFunctionList
, testTextInEmacs "commands" DocGen.keywordsCommands
, testTextInEmacs "directions" DocGen.keywordsDirections
[ testTextInEmacs "builtin" Keyword.builtinFunctionList
, testTextInEmacs "commands" Keyword.keywordsCommands
, testTextInEmacs "directions" Keyword.keywordsDirections
]
, testGroup
"Vim"
[ testTextInVim "builtin" DocGen.builtinFunctionList
, testTextInVim "commands" DocGen.keywordsCommands
, testTextInVim "directions" DocGen.keywordsDirections
[ testTextInVim "builtin" Keyword.builtinFunctionList
, testTextInVim "commands" Keyword.keywordsCommands
, testTextInVim "directions" Keyword.keywordsDirections
]
]
where