{-# LANGUAGE RecordWildCards #-} ----------------------------------------------------------------------------- -- | -- License : BSD-3-Clause -- Maintainer : Oleg Grenrus -- -- Module with modifiers for pull requests' and issues' listings. module GitHub.Data.Options ( -- * Common modifiers stateOpen, stateClosed, stateAll, sortAscending, sortDescending, sortByCreated, sortByUpdated, -- * Pull Requests PullRequestMod, prModToQueryString, optionsBase, optionsNoBase, optionsHead, optionsNoHead, sortByPopularity, sortByLongRunning, -- * Issues IssueMod, issueModToQueryString, sortByComments, optionsLabels, optionsSince, optionsSinceAll, optionsAssignedIssues, optionsCreatedIssues, optionsMentionedIssues, optionsSubscribedIssues, optionsAllIssues, -- * Repo issues IssueRepoMod, issueRepoModToQueryString, optionsIrrelevantMilestone, optionsAnyMilestone, optionsNoMilestone, optionsIrrelevantAssignee, optionsAnyAssignee, optionsNoAssignee, -- * Data IssueState (..), MergeableState (..), -- * Internal HasState, HasDirection, HasCreatedUpdated, HasComments, HasLabels, HasSince, ) where import GitHub.Data.Definitions import GitHub.Data.Id (Id, untagId) import GitHub.Data.Milestone (Milestone) import GitHub.Data.Name (Name, untagName) import GitHub.Internal.Prelude import Prelude () import qualified Data.Text as T import qualified Data.Text.Encoding as TE ------------------------------------------------------------------------------- -- Data ------------------------------------------------------------------------------- -- | 'GitHub.Data.Issues.Issue' or 'GitHub.Data.PullRequests.PullRequest' state data IssueState = StateOpen | StateClosed deriving (Eq, Ord, Show, Enum, Bounded, Generic, Typeable, Data) instance ToJSON IssueState where toJSON StateOpen = String "open" toJSON StateClosed = String "closed" instance FromJSON IssueState where parseJSON (String "open") = pure StateOpen parseJSON (String "closed") = pure StateClosed parseJSON v = typeMismatch "IssueState" v instance NFData IssueState where rnf = genericRnf instance Binary IssueState -- | 'GitHub.Data.PullRequests.PullRequest' mergeable_state data MergeableState = StateUnknown | StateClean | StateDirty | StateUnstable | StateBlocked deriving (Eq, Ord, Show, Enum, Bounded, Generic, Typeable, Data) instance ToJSON MergeableState where toJSON StateUnknown = String "unknown" toJSON StateClean = String "clean" toJSON StateDirty = String "dirty" toJSON StateUnstable = String "unstable" toJSON StateBlocked = String "blocked" instance FromJSON MergeableState where parseJSON (String "unknown") = pure StateUnknown parseJSON (String "clean") = pure StateClean parseJSON (String "dirty") = pure StateDirty parseJSON (String "unstable") = pure StateUnstable parseJSON (String "blocked") = pure StateBlocked parseJSON v = typeMismatch "MergeableState" v instance NFData MergeableState where rnf = genericRnf instance Binary MergeableState data SortDirection = SortAscending | SortDescending deriving (Eq, Ord, Show, Enum, Bounded, Generic, Typeable, Data) instance NFData SortDirection where rnf = genericRnf instance Binary SortDirection -- PR data SortPR = SortPRCreated | SortPRUpdated | SortPRPopularity | SortPRLongRunning deriving (Eq, Ord, Show, Enum, Bounded, Generic, Typeable, Data) instance NFData SortPR where rnf = genericRnf instance Binary SortPR -- Issue data IssueFilter = IssueFilterAssigned | IssueFilterCreated | IssueFilterMentioned | IssueFilterSubscribed | IssueFilterAll deriving (Eq, Ord, Show, Enum, Bounded, Generic, Typeable, Data) instance NFData IssueFilter where rnf = genericRnf instance Binary IssueFilter data SortIssue = SortIssueCreated | SortIssueUpdated | SortIssueComments deriving (Eq, Ord, Show, Enum, Bounded, Generic, Typeable, Data) instance NFData SortIssue where rnf = genericRnf instance Binary SortIssue data FilterBy a = FilterAny | FilterNone | FilterBy a | FilterNotSpecified -- ^ e.g. for milestones "any" means "any milestone". -- I.e. won't show issues without mileston specified deriving (Eq, Ord, Show, Generic, Typeable, Data) ------------------------------------------------------------------------------- -- Classes ------------------------------------------------------------------------------- class HasState mod where state :: Maybe IssueState -> mod stateOpen :: HasState mod => mod stateOpen = state (Just StateOpen) stateClosed :: HasState mod => mod stateClosed = state (Just StateClosed) stateAll :: HasState mod => mod stateAll = state Nothing instance HasState PullRequestMod where state s = PRMod $ \opts -> opts { pullRequestOptionsState = s } instance HasState IssueMod where state s = IssueMod $ \opts -> opts { issueOptionsState = s } instance HasState IssueRepoMod where state s = IssueRepoMod $ \opts -> opts { issueRepoOptionsState = s } class HasDirection mod where sortDir :: SortDirection -> mod sortAscending :: HasDirection mod => mod sortAscending = sortDir SortAscending sortDescending :: HasDirection mod => mod sortDescending = sortDir SortDescending instance HasDirection PullRequestMod where sortDir x = PRMod $ \opts -> opts { pullRequestOptionsDirection = x } instance HasDirection IssueMod where sortDir x = IssueMod $ \opts -> opts { issueOptionsDirection = x } instance HasDirection IssueRepoMod where sortDir x = IssueRepoMod $ \opts -> opts { issueRepoOptionsDirection = x } class HasCreatedUpdated mod where sortByCreated :: mod sortByUpdated :: mod instance HasCreatedUpdated PullRequestMod where sortByCreated = PRMod $ \opts -> opts { pullRequestOptionsSort = SortPRCreated } sortByUpdated = PRMod $ \opts -> opts { pullRequestOptionsSort = SortPRUpdated } instance HasCreatedUpdated IssueMod where sortByCreated = IssueMod $ \opts -> opts { issueOptionsSort = SortIssueCreated } sortByUpdated = IssueMod $ \opts -> opts { issueOptionsSort = SortIssueUpdated } instance HasCreatedUpdated IssueRepoMod where sortByCreated = IssueRepoMod $ \opts -> opts { issueRepoOptionsSort = SortIssueCreated } sortByUpdated = IssueRepoMod $ \opts -> opts { issueRepoOptionsSort = SortIssueUpdated } ------------------------------------------------------------------------------- -- Pull Request ------------------------------------------------------------------------------- -- | See . data PullRequestOptions = PullRequestOptions { pullRequestOptionsState :: !(Maybe IssueState) , pullRequestOptionsHead :: !(Maybe Text) , pullRequestOptionsBase :: !(Maybe Text) , pullRequestOptionsSort :: !SortPR , pullRequestOptionsDirection :: !SortDirection } deriving (Eq, Ord, Show, Generic, Typeable, Data) defaultPullRequestOptions :: PullRequestOptions defaultPullRequestOptions = PullRequestOptions { pullRequestOptionsState = Just StateOpen , pullRequestOptionsHead = Nothing , pullRequestOptionsBase = Nothing , pullRequestOptionsSort = SortPRCreated , pullRequestOptionsDirection = SortDescending } -- | See . newtype PullRequestMod = PRMod (PullRequestOptions -> PullRequestOptions) instance Semigroup PullRequestMod where PRMod f <> PRMod g = PRMod (g . f) instance Monoid PullRequestMod where mempty = PRMod id mappend = (<>) toPullRequestOptions :: PullRequestMod -> PullRequestOptions toPullRequestOptions (PRMod f) = f defaultPullRequestOptions prModToQueryString :: PullRequestMod -> QueryString prModToQueryString = pullRequestOptionsToQueryString . toPullRequestOptions pullRequestOptionsToQueryString :: PullRequestOptions -> QueryString pullRequestOptionsToQueryString (PullRequestOptions st head_ base sort dir) = [ mk "state" state' , mk "sort" sort' , mk "direction" direction' ] ++ catMaybes [ mk "head" <$> head' , mk "base" <$> base' ] where mk k v = (k, Just v) state' = case st of Nothing -> "all" Just StateOpen -> "open" Just StateClosed -> "closed" sort' = case sort of SortPRCreated -> "created" SortPRUpdated -> "updated" SortPRPopularity -> "popularity" SortPRLongRunning -> "long-running" direction' = case dir of SortDescending -> "desc" SortAscending -> "asc" head' = fmap TE.encodeUtf8 head_ base' = fmap TE.encodeUtf8 base ------------------------------------------------------------------------------- -- Pull request modifiers ------------------------------------------------------------------------------- optionsBase :: Text -> PullRequestMod optionsBase x = PRMod $ \opts -> opts { pullRequestOptionsBase = Just x } optionsNoBase :: PullRequestMod optionsNoBase = PRMod $ \opts -> opts { pullRequestOptionsBase = Nothing } optionsHead :: Text -> PullRequestMod optionsHead x = PRMod $ \opts -> opts { pullRequestOptionsHead = Just x } optionsNoHead :: PullRequestMod optionsNoHead = PRMod $ \opts -> opts { pullRequestOptionsHead = Nothing } sortByPopularity :: PullRequestMod sortByPopularity = PRMod $ \opts -> opts { pullRequestOptionsSort = SortPRPopularity } sortByLongRunning :: PullRequestMod sortByLongRunning = PRMod $ \opts -> opts { pullRequestOptionsSort = SortPRLongRunning } ------------------------------------------------------------------------------- -- Issues ------------------------------------------------------------------------------- -- | See . data IssueOptions = IssueOptions { issueOptionsFilter :: !IssueFilter , issueOptionsState :: !(Maybe IssueState) , issueOptionsLabels :: ![Name IssueLabel] -- TODO: change to newtype , issueOptionsSort :: !SortIssue , issueOptionsDirection :: !SortDirection , issueOptionsSince :: !(Maybe UTCTime) } deriving (Eq, Ord, Show, Generic, Typeable, Data) defaultIssueOptions :: IssueOptions defaultIssueOptions = IssueOptions { issueOptionsFilter = IssueFilterAssigned , issueOptionsState = Just StateOpen , issueOptionsLabels = [] , issueOptionsSort = SortIssueCreated , issueOptionsDirection = SortDescending , issueOptionsSince = Nothing } -- | See . newtype IssueMod = IssueMod (IssueOptions -> IssueOptions) instance Semigroup IssueMod where IssueMod f <> IssueMod g = IssueMod (g . f) instance Monoid IssueMod where mempty = IssueMod id mappend = (<>) toIssueOptions :: IssueMod -> IssueOptions toIssueOptions (IssueMod f) = f defaultIssueOptions issueModToQueryString :: IssueMod -> QueryString issueModToQueryString = issueOptionsToQueryString . toIssueOptions issueOptionsToQueryString :: IssueOptions -> QueryString issueOptionsToQueryString (IssueOptions filt st labels sort dir since) = [ mk "state" state' , mk "sort" sort' , mk "direction" direction' , mk "filter" filt' ] ++ catMaybes [ mk "labels" <$> labels' , mk "since" <$> since' ] where mk k v = (k, Just v) filt' = case filt of IssueFilterAssigned -> "assigned" IssueFilterCreated -> "created" IssueFilterMentioned -> "mentioned" IssueFilterSubscribed -> "subscribed" IssueFilterAll -> "all" state' = case st of Nothing -> "all" Just StateOpen -> "open" Just StateClosed -> "closed" sort' = case sort of SortIssueCreated -> "created" SortIssueUpdated -> "updated" SortIssueComments -> "comments" direction' = case dir of SortDescending -> "desc" SortAscending -> "asc" since' = fmap (TE.encodeUtf8 . T.pack . show) since labels' = TE.encodeUtf8 . T.intercalate "," . fmap untagName <$> nullToNothing labels nullToNothing :: Foldable f => f a -> Maybe (f a) nullToNothing xs | null xs = Nothing | otherwise = Just xs ------------------------------------------------------------------------------- -- Issues modifiers ------------------------------------------------------------------------------- class HasComments mod where sortByComments :: mod instance HasComments IssueMod where sortByComments = IssueMod $ \opts -> opts { issueOptionsSort = SortIssueComments } instance HasComments IssueRepoMod where sortByComments = IssueRepoMod $ \opts -> opts { issueRepoOptionsSort = SortIssueComments } class HasLabels mod where optionsLabels :: Foldable f => f (Name IssueLabel) -> mod instance HasLabels IssueMod where optionsLabels lbls = IssueMod $ \opts -> opts { issueOptionsLabels = toList lbls } instance HasLabels IssueRepoMod where optionsLabels lbls = IssueRepoMod $ \opts -> opts { issueRepoOptionsLabels = toList lbls } class HasSince mod where optionsSince :: UTCTime -> mod optionsSinceAll :: mod instance HasSince IssueMod where optionsSince since = IssueMod $ \opts -> opts { issueOptionsSince = Just since } optionsSinceAll = IssueMod $ \opts -> opts { issueOptionsSince = Nothing } instance HasSince IssueRepoMod where optionsSince since = IssueRepoMod $ \opts -> opts { issueRepoOptionsSince = Just since } optionsSinceAll = IssueRepoMod $ \opts -> opts { issueRepoOptionsSince = Nothing } ------------------------------------------------------------------------------- -- Only issues modifiers ------------------------------------------------------------------------------- optionsAssignedIssues, optionsCreatedIssues, optionsMentionedIssues, optionsSubscribedIssues, optionsAllIssues :: IssueMod optionsAssignedIssues = issueFilter IssueFilterAssigned optionsCreatedIssues = issueFilter IssueFilterCreated optionsMentionedIssues = issueFilter IssueFilterMentioned optionsSubscribedIssues = issueFilter IssueFilterSubscribed optionsAllIssues = issueFilter IssueFilterAll issueFilter :: IssueFilter -> IssueMod issueFilter f = IssueMod $ \opts -> opts { issueOptionsFilter = f } ------------------------------------------------------------------------------- -- Issues repo ------------------------------------------------------------------------------- data IssueRepoOptions = IssueRepoOptions { issueRepoOptionsMilestone :: !(FilterBy (Id Milestone)) , issueRepoOptionsState :: !(Maybe IssueState) , issueRepoOptionsAssignee :: !(FilterBy (Name User)) , issueRepoOptionsCreator :: !(Maybe (Name User)) , issueRepoOptionsMentioned :: !(Maybe (Name User)) , issueRepoOptionsLabels :: ![Name IssueLabel] , issueRepoOptionsSort :: !SortIssue , issueRepoOptionsDirection :: !SortDirection , issueRepoOptionsSince :: !(Maybe UTCTime) } deriving (Eq, Ord, Show, Generic, Typeable, Data) defaultIssueRepoOptions :: IssueRepoOptions defaultIssueRepoOptions = IssueRepoOptions { issueRepoOptionsMilestone = FilterNotSpecified , issueRepoOptionsState = (Just StateOpen) , issueRepoOptionsAssignee = FilterNotSpecified , issueRepoOptionsCreator = Nothing , issueRepoOptionsMentioned = Nothing , issueRepoOptionsLabels = [] , issueRepoOptionsSort = SortIssueCreated , issueRepoOptionsDirection = SortDescending , issueRepoOptionsSince = Nothing } -- | See . newtype IssueRepoMod = IssueRepoMod (IssueRepoOptions -> IssueRepoOptions) instance Semigroup IssueRepoMod where IssueRepoMod f <> IssueRepoMod g = IssueRepoMod (g . f) instance Monoid IssueRepoMod where mempty = IssueRepoMod id mappend = (<>) toIssueRepoOptions :: IssueRepoMod -> IssueRepoOptions toIssueRepoOptions (IssueRepoMod f) = f defaultIssueRepoOptions issueRepoModToQueryString :: IssueRepoMod -> QueryString issueRepoModToQueryString = issueRepoOptionsToQueryString . toIssueRepoOptions issueRepoOptionsToQueryString :: IssueRepoOptions -> QueryString issueRepoOptionsToQueryString IssueRepoOptions {..} = [ mk "state" state' , mk "sort" sort' , mk "direction" direction' ] ++ catMaybes [ mk "milestone" <$> milestone' , mk "assignee" <$> assignee' , mk "labels" <$> labels' , mk "since" <$> since' , mk "creator" <$> creator' , mk "mentioned" <$> mentioned' ] where mk k v = (k, Just v) filt f x = case x of FilterAny -> Just "*" FilterNone -> Just "none" FilterBy x' -> Just $ TE.encodeUtf8 $ f x' FilterNotSpecified -> Nothing milestone' = filt (T.pack . show . untagId) issueRepoOptionsMilestone assignee' = filt untagName issueRepoOptionsAssignee state' = case issueRepoOptionsState of Nothing -> "all" Just StateOpen -> "open" Just StateClosed -> "closed" sort' = case issueRepoOptionsSort of SortIssueCreated -> "created" SortIssueUpdated -> "updated" SortIssueComments -> "comments" direction' = case issueRepoOptionsDirection of SortDescending -> "desc" SortAscending -> "asc" since' = TE.encodeUtf8 . T.pack . show <$> issueRepoOptionsSince labels' = TE.encodeUtf8 . T.intercalate "," . fmap untagName <$> nullToNothing issueRepoOptionsLabels creator' = TE.encodeUtf8 . untagName <$> issueRepoOptionsCreator mentioned' = TE.encodeUtf8 . untagName <$> issueRepoOptionsMentioned ------------------------------------------------------------------------------- -- Issues repo modifiers ------------------------------------------------------------------------------- -- | Don't care about milestones. -- -- 'optionsAnyMilestone' means there should be some milestone, but it can be any. -- -- See optionsIrrelevantMilestone :: IssueRepoMod optionsIrrelevantMilestone = IssueRepoMod $ \opts -> opts { issueRepoOptionsMilestone = FilterNotSpecified } optionsAnyMilestone :: IssueRepoMod optionsAnyMilestone = IssueRepoMod $ \opts -> opts { issueRepoOptionsMilestone = FilterAny } optionsNoMilestone :: IssueRepoMod optionsNoMilestone = IssueRepoMod $ \opts -> opts { issueRepoOptionsMilestone = FilterNone } optionsIrrelevantAssignee :: IssueRepoMod optionsIrrelevantAssignee = IssueRepoMod $ \opts -> opts { issueRepoOptionsAssignee = FilterNotSpecified } optionsAnyAssignee :: IssueRepoMod optionsAnyAssignee = IssueRepoMod $ \opts -> opts { issueRepoOptionsAssignee = FilterAny } optionsNoAssignee :: IssueRepoMod optionsNoAssignee = IssueRepoMod $ \opts -> opts { issueRepoOptionsAssignee = FilterNone }