From a3a5764fc1d47927dbdca416411946f5ca423a1b Mon Sep 17 00:00:00 2001 From: Chris Martin Date: Fri, 4 Oct 2024 11:23:56 -0600 Subject: [PATCH] add grouping, masking, and stopping (#8) * remove weeder * add grouping, masking, and stopping * add parent GitHub.Workflow.Command module and docs * release 0.0.1.0 --- .github/workflows/ci.yml | 4 - CHANGELOG.md | 11 ++- README.lhs | 10 +- github-workflow-commands.cabal | 10 +- library/GitHub/Workflow/Command.hs | 99 +++++++++++++++++++ library/GitHub/Workflow/Command/Annotation.hs | 13 +-- .../Command/Annotation/Commands/Debug.hs | 4 + .../Command/Annotation/Commands/Error.hs | 7 ++ .../Command/Annotation/Commands/Notice.hs | 7 ++ .../Command/Annotation/Commands/Warning.hs | 7 ++ library/GitHub/Workflow/Command/Execution.hs | 35 +++++++ library/GitHub/Workflow/Command/Grouping.hs | 42 ++++++++ library/GitHub/Workflow/Command/Masking.hs | 21 ++++ library/GitHub/Workflow/Command/Stopping.hs | 83 ++++++++++++++++ .../GitHub/Workflow/Command/Syntax/Command.hs | 12 ++- package.yaml | 3 +- weeder.toml | 6 -- 17 files changed, 346 insertions(+), 28 deletions(-) create mode 100644 library/GitHub/Workflow/Command.hs create mode 100644 library/GitHub/Workflow/Command/Execution.hs create mode 100644 library/GitHub/Workflow/Command/Grouping.hs create mode 100644 library/GitHub/Workflow/Command/Masking.hs create mode 100644 library/GitHub/Workflow/Command/Stopping.hs delete mode 100644 weeder.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 38d308e..a707826 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,10 +34,6 @@ jobs: uses: freckle/stack-action@v5 with: stack-arguments: --stack-yaml ${{ matrix.stack-yaml }} - - if: ${{ matrix.stack-yaml == 'stack.yaml' }} - uses: freckle/weeder-action@v2 - with: - ghc-version: ${{ steps.stack.outputs.compiler-version }} lint: runs-on: ubuntu-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index db30fce..20996d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,13 @@ -## [_Unreleased_](https://github.com/freckle/github-workflow-commands/compare/v0.0.0.0...main) +## [_Unreleased_](https://github.com/freckle/github-workflow-commands/compare/v0.0.1.0...main) + +## [v0.0.1.0](https://github.com/freckle/github-workflow-commands/compare/v0.0.0.0...v0.0.1.0) + +Additions: + +- Re-exporting overview module `GitHub.Workflow.Command`, which is now the + primary module for users to import and find documentation +- Class `MonadCommand` which is now the recommended way to execute commands +- Support for `group`, `add-mask`, `stop-commands` commands ## [v0.0.0.0](https://github.com/freckle/github-workflow-commands/tree/v0.0.0.0) diff --git a/README.lhs b/README.lhs index 6a36fe4..b912951 100644 --- a/README.lhs +++ b/README.lhs @@ -19,8 +19,8 @@ import Text.Markdown.Unlit () --> ```haskell -import qualified GitHub.Workflow.Command.Annotation as GH -import Control.Lens +import qualified GitHub.Workflow.Command as GH +import Control.Lens ((&), (?~)) ``` An annotation is at minimum just a string. @@ -28,7 +28,7 @@ An annotation is at minimum just a string. ```haskell example1 :: IO () example1 = - GH.printByteStringLn $ + GH.executeCommand $ GH.error "Something failed." ``` @@ -47,7 +47,7 @@ someLocation = ```haskell example2 :: IO () example2 = - GH.printByteStringLn $ + GH.executeCommand $ GH.warning "Something seems amiss here." & GH.location ?~ someLocation ``` @@ -55,7 +55,7 @@ example2 = diff --git a/github-workflow-commands.cabal b/github-workflow-commands.cabal index d4ef1eb..2fb2b56 100644 --- a/github-workflow-commands.cabal +++ b/github-workflow-commands.cabal @@ -5,7 +5,7 @@ cabal-version: 1.18 -- see: https://github.com/sol/hpack name: github-workflow-commands -version: 0.0.0.0 +version: 0.0.1.0 synopsis: GitHub Actions workflow commands description: For printing workflow commands in GitHub Actions. . @@ -29,6 +29,7 @@ source-repository head library exposed-modules: + GitHub.Workflow.Command GitHub.Workflow.Command.Annotation GitHub.Workflow.Command.Annotation.Commands.Debug GitHub.Workflow.Command.Annotation.Commands.Error @@ -43,6 +44,10 @@ library GitHub.Workflow.Command.Annotation.Position.Extent GitHub.Workflow.Command.Annotation.Position.Line GitHub.Workflow.Command.Annotation.Properties + GitHub.Workflow.Command.Execution + GitHub.Workflow.Command.Grouping + GitHub.Workflow.Command.Masking + GitHub.Workflow.Command.Stopping GitHub.Workflow.Command.Syntax GitHub.Workflow.Command.Syntax.Command GitHub.Workflow.Command.Syntax.Key @@ -76,7 +81,8 @@ library TypeFamilies ghc-options: -fwrite-ide-info -Weverything -Wno-all-missed-specialisations -Wno-missed-specialisations -Wno-missing-exported-signatures -Wno-missing-import-lists -Wno-missing-local-signatures -Wno-monomorphism-restriction -Wno-safe -Wno-unsafe build-depends: - base <5 + MonadRandom + , base <5 , bytestring , containers , lens diff --git a/library/GitHub/Workflow/Command.hs b/library/GitHub/Workflow/Command.hs new file mode 100644 index 0000000..191761c --- /dev/null +++ b/library/GitHub/Workflow/Command.hs @@ -0,0 +1,99 @@ +-- | Programs run by GitHub Actions can use workflow commands to communicate with the runner. +-- +-- GitHub documentation: +-- +module GitHub.Workflow.Command + ( -- * Executing commands + MonadCommand (..) + , PrintCommands (..) + + -- * Commands + , ToCommand (..) + + -- ** Setting a debug message + , Debug (..) + , debug + + -- ** Setting a notice message + , Notice (..) + , notice + + -- ** Setting a warning message + , Warning (..) + , warning + + -- ** Setting an error message + , Error (..) + , error + + -- ** Grouping log lines + , group + , GroupStart (..) + , GroupEnd (..) + + -- ** Masking a value in a log + , AddMask (..) + + -- ** Stopping and starting workflow commands + , suspendCommands + , stopCommands + , resumeCommands + , SuspendToken + + -- * Location + , Location (..) + , HasLocationMaybe (..) + + -- ** File + , File (..) + , inFile + , file + + -- ** Position + , Position (..) + , position + , Extent (..) + , extent + , Columns (..) + , line + , startColumn + , endColumn + , Line (..) + , atLine + , Column (..) + , atColumn + + -- * Anatomy of a command + , Command + + -- ** Name + , Name (..) + , HasName (..) + + -- ** Message + , Message (..) + , HasMessage (..) + + -- ** Properties + , Properties + , HasProperties (..) + , Key (..) + , Value (..) + ) where + +import GitHub.Workflow.Command.Annotation.Commands.Debug +import GitHub.Workflow.Command.Annotation.Commands.Error +import GitHub.Workflow.Command.Annotation.Commands.Notice +import GitHub.Workflow.Command.Annotation.Commands.Warning +import GitHub.Workflow.Command.Annotation.File +import GitHub.Workflow.Command.Annotation.Location +import GitHub.Workflow.Command.Annotation.Position +import GitHub.Workflow.Command.Annotation.Position.Column +import GitHub.Workflow.Command.Annotation.Position.Columns +import GitHub.Workflow.Command.Annotation.Position.Extent +import GitHub.Workflow.Command.Annotation.Position.Line +import GitHub.Workflow.Command.Execution +import GitHub.Workflow.Command.Grouping +import GitHub.Workflow.Command.Masking +import GitHub.Workflow.Command.Stopping +import GitHub.Workflow.Command.Syntax diff --git a/library/GitHub/Workflow/Command/Annotation.hs b/library/GitHub/Workflow/Command/Annotation.hs index bb4a1c6..68eb51e 100644 --- a/library/GitHub/Workflow/Command/Annotation.hs +++ b/library/GitHub/Workflow/Command/Annotation.hs @@ -48,6 +48,7 @@ module GitHub.Workflow.Command.Annotation , atColumn -- * Output + , MonadCommand (..) , ToCommand (..) , toCommand , ToByteString (..) @@ -66,11 +67,7 @@ import GitHub.Workflow.Command.Annotation.Position.Columns import GitHub.Workflow.Command.Annotation.Position.Extent import GitHub.Workflow.Command.Annotation.Position.Line import GitHub.Workflow.Command.Annotation.Properties -import GitHub.Workflow.Command.Syntax - ( FromMessage (..) - , Message (..) - , ToByteString (..) - , ToCommand (..) - , printByteStringLn - , toCommand - ) +import GitHub.Workflow.Command.Execution +import GitHub.Workflow.Command.Syntax.Command +import GitHub.Workflow.Command.Syntax.Message +import GitHub.Workflow.Command.Syntax.ToByteString diff --git a/library/GitHub/Workflow/Command/Annotation/Commands/Debug.hs b/library/GitHub/Workflow/Command/Annotation/Commands/Debug.hs index 5c26e37..d0147fc 100644 --- a/library/GitHub/Workflow/Command/Annotation/Commands/Debug.hs +++ b/library/GitHub/Workflow/Command/Annotation/Commands/Debug.hs @@ -16,6 +16,10 @@ import GitHub.Workflow.Command.Syntax ) import GitHub.Workflow.Command.Syntax qualified as Syntax +-- | Prints a debug message to the log +-- +-- GitHub documentation: +-- newtype Debug = Debug { message :: Message } diff --git a/library/GitHub/Workflow/Command/Annotation/Commands/Error.hs b/library/GitHub/Workflow/Command/Annotation/Commands/Error.hs index 706b217..56a796d 100644 --- a/library/GitHub/Workflow/Command/Annotation/Commands/Error.hs +++ b/library/GitHub/Workflow/Command/Annotation/Commands/Error.hs @@ -18,6 +18,13 @@ import GitHub.Workflow.Command.Syntax ) import GitHub.Workflow.Command.Syntax qualified as Syntax +-- | Creates an error message and prints the message to the log +-- +-- The message can be associated with a particular file in your repository, +-- and optionally also a position within the file. See 'HasLocationMaybe'. +-- +-- GitHub documentation: +-- data Error = Error { message :: Message , properties :: Properties diff --git a/library/GitHub/Workflow/Command/Annotation/Commands/Notice.hs b/library/GitHub/Workflow/Command/Annotation/Commands/Notice.hs index babb4f6..cf01e4a 100644 --- a/library/GitHub/Workflow/Command/Annotation/Commands/Notice.hs +++ b/library/GitHub/Workflow/Command/Annotation/Commands/Notice.hs @@ -18,6 +18,13 @@ import GitHub.Workflow.Command.Syntax ) import GitHub.Workflow.Command.Syntax qualified as Syntax +-- | Creates a notice message and prints the message to the log +-- +-- The message can be associated with a particular file in your repository, +-- and optionally also a position within the file. See 'HasLocationMaybe'. +-- +-- GitHub documentation: +-- data Notice = Notice { message :: Message , properties :: Properties diff --git a/library/GitHub/Workflow/Command/Annotation/Commands/Warning.hs b/library/GitHub/Workflow/Command/Annotation/Commands/Warning.hs index c67aacc..a793d4c 100644 --- a/library/GitHub/Workflow/Command/Annotation/Commands/Warning.hs +++ b/library/GitHub/Workflow/Command/Annotation/Commands/Warning.hs @@ -18,6 +18,13 @@ import GitHub.Workflow.Command.Syntax ) import GitHub.Workflow.Command.Syntax qualified as Syntax +-- | Creates a warning message and prints the message to the log +-- +-- The message can be associated with a particular file in your repository, +-- and optionally also a position within the file. See 'HasLocationMaybe'. +-- +-- GitHub documentation: +-- data Warning = Warning { message :: Message , properties :: Properties diff --git a/library/GitHub/Workflow/Command/Execution.hs b/library/GitHub/Workflow/Command/Execution.hs new file mode 100644 index 0000000..9e4c5ef --- /dev/null +++ b/library/GitHub/Workflow/Command/Execution.hs @@ -0,0 +1,35 @@ +module GitHub.Workflow.Command.Execution + ( MonadCommand (..) + , PrintCommands (..) + ) where + +import Control.Applicative (Applicative) +import Control.Monad (Monad) +import Control.Monad.IO.Class (MonadIO, liftIO) +import Data.Function ((.)) +import Data.Functor (Functor) +import GitHub.Workflow.Command.Syntax +import System.IO (IO) + +-- | Monadic context in which GitHub workflow commands may be executed +-- +-- * For the most basic uses, use the 'IO' instance, which prints commands to 'System.IO.stdout'. +-- +-- * For custom monads that support 'MonadIO', you may derive 'MonadCommand' via 'PrintCommands' +-- to get the same behavior that 'IO' exhibits. +-- +-- * A program that wishes to accommodate running in both GitHub and non-GitHub contexts +-- may wish to define a more sophisicated 'MonadCommand' instance that prints GitHub +-- workflow commands only when the @GITHUB_ACTIONS@ environment variable is present, +-- and otherwise takes some other more context-appropriate action. +class Monad m => MonadCommand m where + executeCommand :: ToCommand a => a -> m () + +instance MonadCommand IO where + executeCommand = printByteStringLn . toCommand + +newtype PrintCommands m a = PrintCommands (m a) + deriving newtype (Functor, Applicative, Monad, MonadIO) + +instance MonadIO m => MonadCommand (PrintCommands m) where + executeCommand = liftIO . executeCommand diff --git a/library/GitHub/Workflow/Command/Grouping.hs b/library/GitHub/Workflow/Command/Grouping.hs new file mode 100644 index 0000000..4f2ee83 --- /dev/null +++ b/library/GitHub/Workflow/Command/Grouping.hs @@ -0,0 +1,42 @@ +module GitHub.Workflow.Command.Grouping + ( group + , GroupStart (..) + , GroupEnd (..) + ) where + +import Control.Applicative ((*>), (<*)) +import Control.Lens ((.~)) +import Data.Function ((.)) +import Data.Text (Text) +import GitHub.Workflow.Command.Execution +import GitHub.Workflow.Command.Syntax + +-- | Creates an expandable group in the log +-- +-- GitHub documentation: +-- +group + :: MonadCommand m + => Text + -- ^ Group title + -> m a + -- ^ Anything printed within this action will be + -- nested inside an expandable entry in the log + -> m a +group title x = + executeCommand GroupStart {title} + *> x + <* executeCommand GroupEnd + +-- | Starts a 'group' +newtype GroupStart = GroupStart {title :: Text} + +instance ToCommand GroupStart where + addToCommand GroupStart {title} = + (name .~ "group") . (message .~ Message title) + +-- | Ends a 'group' +data GroupEnd = GroupEnd + +instance ToCommand GroupEnd where + addToCommand GroupEnd = name .~ "endgroup" diff --git a/library/GitHub/Workflow/Command/Masking.hs b/library/GitHub/Workflow/Command/Masking.hs new file mode 100644 index 0000000..649cf53 --- /dev/null +++ b/library/GitHub/Workflow/Command/Masking.hs @@ -0,0 +1,21 @@ +module GitHub.Workflow.Command.Masking + ( AddMask (..) + ) where + +import Control.Lens ((.~)) +import Data.Function ((.)) +import Data.Text (Text) +import GitHub.Workflow.Command.Syntax + +-- | Prevents a string or variable from being printed in the log +-- +-- GitHub documentation: +-- +newtype AddMask = AddMask + { value :: Text + -- ^ An environment variable or string + } + +instance ToCommand AddMask where + addToCommand AddMask {value} = + (name .~ "add-mask") . (message .~ Message value) diff --git a/library/GitHub/Workflow/Command/Stopping.hs b/library/GitHub/Workflow/Command/Stopping.hs new file mode 100644 index 0000000..532a74f --- /dev/null +++ b/library/GitHub/Workflow/Command/Stopping.hs @@ -0,0 +1,83 @@ +module GitHub.Workflow.Command.Stopping + ( -- * Basic usage + suspendCommands + + -- * Stop and resume + , stopCommands + , resumeCommands + , SuspendToken (..) + + -- * Manual token management + , randomSuspendToken + , suspendCommandsWithToken + , stopCommandsWithToken + + -- * Command types + , StopCommands (..) + , ResumeCommands (..) + ) where + +import Control.Applicative ((*>), (<*)) +import Control.Lens ((.~)) +import Control.Monad.Random.Class (MonadRandom, getRandomRs) +import Data.Function ((.)) +import Data.Functor (Functor ((<$)), (<$>)) +import Data.List qualified as List +import Data.Text (Text) +import Data.Text qualified as T +import GitHub.Workflow.Command.Execution +import GitHub.Workflow.Command.Syntax + +-- | Run an action with processing of workflow commands suspended +-- +-- GitHub documentation: +-- +suspendCommands + :: (MonadCommand m, MonadRandom m) + => m a + -- ^ Commands issued by this action will have no effect + -> m a +suspendCommands x = do + token <- randomSuspendToken + suspendCommandsWithToken token x + +suspendCommandsWithToken :: MonadCommand m => SuspendToken -> m a -> m a +suspendCommandsWithToken token x = + stopCommandsWithToken token *> x <* resumeCommands token + +-- | Stops processing any workflow commands +-- +-- This special command allows you to log anything without accidentally running a workflow command. +stopCommands :: (MonadCommand m, MonadRandom m) => m SuspendToken +stopCommands = do + token <- randomSuspendToken + token <$ stopCommandsWithToken token + +stopCommandsWithToken :: MonadCommand m => SuspendToken -> m () +stopCommandsWithToken token = + executeCommand StopCommands {token} + +-- | Resume processing workflow commands +resumeCommands :: MonadCommand m => SuspendToken -> m () +resumeCommands token = executeCommand ResumeCommands {token} + +newtype SuspendToken = SuspendToken Text + +randomSuspendToken :: MonadRandom m => m SuspendToken +randomSuspendToken = SuspendToken . T.pack . List.take 20 <$> getRandomRs ('a', 'z') + +newtype StopCommands = StopCommands + { token :: SuspendToken + } + +instance ToCommand StopCommands where + addToCommand StopCommands {token = SuspendToken t} = + (name .~ "stop-commands") . (message .~ Message t) + +newtype ResumeCommands = ResumeCommands + { token :: SuspendToken + } + +instance ToCommand ResumeCommands where + addToCommand ResumeCommands {token = SuspendToken t} = + name .~ Name t diff --git a/library/GitHub/Workflow/Command/Syntax/Command.hs b/library/GitHub/Workflow/Command/Syntax/Command.hs index be77e7b..80c843e 100644 --- a/library/GitHub/Workflow/Command/Syntax/Command.hs +++ b/library/GitHub/Workflow/Command/Syntax/Command.hs @@ -19,10 +19,20 @@ import GitHub.Workflow.Command.Syntax.Properties qualified as Properties import GitHub.Workflow.Command.Syntax.ToByteString import Prelude (Eq, Maybe (..), Ord, Show, not, (<>)) +-- | A GitHub workflow command +-- +-- A 'Command' consists of: +-- +-- * 'Name' ('HasName') +-- * 'Message' ('HasMessage') +-- * 'Properties' ('HasProperties') +-- +-- Of these, only 'Name' is always required. Some particular types of command require +-- a message or have restrictions on what properties they support or require. data Command = Command { name :: Name - , properties :: Properties , message :: Message + , properties :: Properties } deriving stock (Eq, Ord, Show) diff --git a/package.yaml b/package.yaml index 1c69fbd..04b1a69 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: github-workflow-commands -version: 0.0.0.0 +version: 0.0.1.0 maintainer: Freckle Education category: GitHub github: freckle/github-workflow-commands @@ -72,6 +72,7 @@ library: - bytestring - containers - lens + - MonadRandom - text tests: diff --git a/weeder.toml b/weeder.toml deleted file mode 100644 index c9de85a..0000000 --- a/weeder.toml +++ /dev/null @@ -1,6 +0,0 @@ -roots = [ - "Main.main", - "^Paths_.*", - "GitHub.Workflow.Command.Syntax.ToByteString.printByteStringLn", -] -type-class-roots = true