commit
6b56dcce6f
11 changed files with 299 additions and 0 deletions
@ -0,0 +1,4 @@ |
|||||
|
.stack-work/ |
||||
|
pullwatch.cabal |
||||
|
*~ |
||||
|
.envrc |
@ -0,0 +1,3 @@ |
|||||
|
# Changelog for pullwatch |
||||
|
|
||||
|
## Unreleased changes |
@ -0,0 +1,30 @@ |
|||||
|
Copyright Wesley Kerfoot (c) 2019 |
||||
|
|
||||
|
All rights reserved. |
||||
|
|
||||
|
Redistribution and use in source and binary forms, with or without |
||||
|
modification, are permitted provided that the following conditions are met: |
||||
|
|
||||
|
* Redistributions of source code must retain the above copyright |
||||
|
notice, this list of conditions and the following disclaimer. |
||||
|
|
||||
|
* Redistributions in binary form must reproduce the above |
||||
|
copyright notice, this list of conditions and the following |
||||
|
disclaimer in the documentation and/or other materials provided |
||||
|
with the distribution. |
||||
|
|
||||
|
* Neither the name of Wesley Kerfoot nor the names of other |
||||
|
contributors may be used to endorse or promote products derived |
||||
|
from this software without specific prior written permission. |
||||
|
|
||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1,11 @@ |
|||||
|
# Pullwatch |
||||
|
A Simple Haskell Daemon that monitors for new pull requests in a list of repos, and notifies you if there is a new one |
||||
|
|
||||
|
## How to Use |
||||
|
* Create a new Github application (see [here](https://github.com/settings/apps)) |
||||
|
* Create a .envrc (or .env if you don't have direnv) with your personal access |
||||
|
token, e.g. `export PERSONAL_ACCESS_TOKEN = "foobarbaz"` |
||||
|
* Source that file |
||||
|
* Modify `Main.hs` (at the moment there is no other entrypoint to this) and |
||||
|
build or run it with stack |
||||
|
* Make sure your desktop environment supports DBus notifications (e.g. [https://wiki.archlinux.org/index.php/Desktop_notifications](https://wiki.archlinux.org/index.php/Desktop_notifications) ) |
@ -0,0 +1,2 @@ |
|||||
|
import Distribution.Simple |
||||
|
main = defaultMain |
@ -0,0 +1,19 @@ |
|||||
|
{-# LANGUAGE ImplicitParams #-} |
||||
|
{-# LANGUAGE OverloadedStrings #-} |
||||
|
module Main where |
||||
|
|
||||
|
import PullWatch.PullWatch (getLatestPRs, getLatest, toNote, prID) |
||||
|
import PullWatch.Environment (getPAT) |
||||
|
|
||||
|
import Control.Applicative |
||||
|
|
||||
|
main :: IO () |
||||
|
main = do |
||||
|
-- Set up authentication token from environment |
||||
|
pat <- getPAT |
||||
|
let ?pat = pat |
||||
|
|
||||
|
prIDs <- getLatestPRs [("racket", "racket")] |
||||
|
print prIDs |
||||
|
|
||||
|
return () |
@ -0,0 +1,48 @@ |
|||||
|
name: pullwatch |
||||
|
version: 0.1.0.0 |
||||
|
github: "weskerfoot/pullwatch" |
||||
|
license: BSD3 |
||||
|
author: "Wesley Kerfoot" |
||||
|
maintainer: "wes@wesk.tech" |
||||
|
copyright: "" |
||||
|
|
||||
|
extra-source-files: |
||||
|
- README.md |
||||
|
- ChangeLog.md |
||||
|
|
||||
|
# Metadata used when publishing your package |
||||
|
# synopsis: Short description of your package |
||||
|
# category: Web |
||||
|
|
||||
|
# To avoid duplicated efforts in documentation and dealing with the |
||||
|
# complications of embedding Haddock markup inside cabal files, it is |
||||
|
# common to point users to the README.md file. |
||||
|
description: Please see the README on GitHub at <https://github.com/weskerfoot/pullwatch#readme> |
||||
|
|
||||
|
dependencies: |
||||
|
- base >= 4.7 && < 5 |
||||
|
|
||||
|
library: |
||||
|
source-dirs: src |
||||
|
|
||||
|
executables: |
||||
|
pullwatch-exe: |
||||
|
main: Main.hs |
||||
|
source-dirs: app |
||||
|
ghc-options: |
||||
|
- -threaded |
||||
|
- -rtsopts |
||||
|
- -with-rtsopts=-N |
||||
|
dependencies: |
||||
|
- pullwatch |
||||
|
|
||||
|
tests: |
||||
|
pullwatch-test: |
||||
|
main: Spec.hs |
||||
|
source-dirs: test |
||||
|
ghc-options: |
||||
|
- -threaded |
||||
|
- -rtsopts |
||||
|
- -with-rtsopts=-N |
||||
|
dependencies: |
||||
|
- pullwatch |
@ -0,0 +1,15 @@ |
|||||
|
{-# LANGUAGE OverloadedStrings #-} |
||||
|
{-# LANGUAGE ImplicitParams #-} |
||||
|
|
||||
|
module PullWatch.Environment |
||||
|
( getPAT |
||||
|
) where |
||||
|
|
||||
|
import System.Environment |
||||
|
|
||||
|
import qualified GitHub.Auth as Auth |
||||
|
import qualified Data.ByteString.Char8 as C |
||||
|
|
||||
|
getPAT = do |
||||
|
(Just pat) <- lookupEnv "PERSONAL_ACCESS_TOKEN" |
||||
|
return $ Just $ Auth.OAuth $ C.pack pat |
@ -0,0 +1,98 @@ |
|||||
|
{-# LANGUAGE NoImplicitPrelude #-} |
||||
|
{-# LANGUAGE OverloadedStrings #-} |
||||
|
{-# LANGUAGE ImplicitParams #-} |
||||
|
|
||||
|
module PullWatch.PullWatch |
||||
|
( getLatest |
||||
|
, getLatestAsync |
||||
|
, getLatestPRs |
||||
|
, prText |
||||
|
, prTitle |
||||
|
, prID |
||||
|
, toNote |
||||
|
) where |
||||
|
|
||||
|
import GitHub.Data.PullRequests |
||||
|
( simplePullRequestId |
||||
|
, simplePullRequestTitle |
||||
|
, simplePullRequestBody |
||||
|
) |
||||
|
|
||||
|
import Prelude.Compat |
||||
|
import GitHub.Data.Id (untagId) |
||||
|
import GitHub.Data.Name (untagName) |
||||
|
import System.Environment (lookupEnv) |
||||
|
import Data.Vector ((!)) |
||||
|
import DBus.Notify |
||||
|
import Control.Concurrent.Async |
||||
|
import Control.Applicative |
||||
|
import Data.Maybe |
||||
|
import Data.Monoid |
||||
|
|
||||
|
import qualified GitHub.Endpoints.PullRequests as PR |
||||
|
import qualified GitHub.Auth as Auth |
||||
|
import qualified Data.Text as T |
||||
|
|
||||
|
data PullRequest = PR { |
||||
|
prText :: T.Text |
||||
|
, prTitle :: T.Text |
||||
|
, prRepo :: T.Text |
||||
|
, prOwner :: T.Text |
||||
|
, prID :: Integer } |
||||
|
|
||||
|
deriving (Show) |
||||
|
|
||||
|
|
||||
|
fiveMinutes = 300000000 |
||||
|
|
||||
|
getPRId = Just . fromIntegral . untagId . simplePullRequestId . (! 0) |
||||
|
|
||||
|
getPRTitle = Just . simplePullRequestTitle . (! 0) |
||||
|
|
||||
|
getPRBody = simplePullRequestBody . (! 0) |
||||
|
|
||||
|
-- Converts a pull request into a dbus notification |
||||
|
toNote :: Maybe PullWatch.PullWatch.PullRequest -> Maybe Note |
||||
|
|
||||
|
toNote Nothing = Nothing |
||||
|
toNote (Just pr) = |
||||
|
Just $ blankNote { |
||||
|
summary = T.unpack $ prRepo pr |
||||
|
, body = (Just $ Text $ T.unpack $ prTitle pr) |
||||
|
, appImage = (Just $ Icon "dialog-information") |
||||
|
} |
||||
|
|
||||
|
getLatest :: (?pat :: (Maybe Auth.Auth)) => |
||||
|
(PR.Name PR.Owner) -> |
||||
|
(PR.Name PR.Repo) -> |
||||
|
IO (Maybe PullRequest) |
||||
|
|
||||
|
getLatest owner repo = do |
||||
|
prs <- PR.pullRequestsFor' ?pat owner repo |
||||
|
let pr = case prs of |
||||
|
(Left _) -> Nothing |
||||
|
(Right pullreqs) -> do |
||||
|
id <- getPRId pullreqs |
||||
|
title <- getPRTitle pullreqs |
||||
|
body <- getPRBody pullreqs |
||||
|
let repoName = untagName repo |
||||
|
let repoOwner = untagName owner |
||||
|
return $ PR body title repoName repoOwner id |
||||
|
return pr |
||||
|
|
||||
|
getLatestAsync owner repo = do |
||||
|
client <- connectSession |
||||
|
|
||||
|
async $ poll client where |
||||
|
poll client = do |
||||
|
latest <- getLatest owner repo |
||||
|
notification <- maybe undefined (notify client) (toNote latest) |
||||
|
return $ prID <$> latest |
||||
|
|
||||
|
getLatestPRs :: (?pat :: (Maybe Auth.Auth)) => |
||||
|
[(PR.Name PR.Owner, PR.Name PR.Repo)] -> |
||||
|
IO [Maybe (T.Text, T.Text, Integer)] |
||||
|
|
||||
|
getLatestPRs repos = do |
||||
|
prs <- mapConcurrently (uncurry getLatest) repos |
||||
|
return $ map ((\pr -> (prRepo pr, prOwner pr, prID pr)) <$>) prs |
@ -0,0 +1,67 @@ |
|||||
|
# This file was automatically generated by 'stack init' |
||||
|
# |
||||
|
# Some commonly used options have been documented as comments in this file. |
||||
|
# For advanced use and comprehensive documentation of the format, please see: |
||||
|
# https://docs.haskellstack.org/en/stable/yaml_configuration/ |
||||
|
|
||||
|
# Resolver to choose a 'specific' stackage snapshot or a compiler version. |
||||
|
# A snapshot resolver dictates the compiler version and the set of packages |
||||
|
# to be used for project dependencies. For example: |
||||
|
# |
||||
|
# resolver: lts-3.5 |
||||
|
# resolver: nightly-2015-09-21 |
||||
|
# resolver: ghc-7.10.2 |
||||
|
# resolver: ghcjs-0.1.0_ghc-7.10.2 |
||||
|
# |
||||
|
# The location of a snapshot can be provided as a file or url. Stack assumes |
||||
|
# a snapshot provided as a file might change, whereas a url resource does not. |
||||
|
# |
||||
|
# resolver: ./custom-snapshot.yaml |
||||
|
# resolver: https://example.com/snapshots/2018-01-01.yaml |
||||
|
resolver: lts-13.14 |
||||
|
|
||||
|
# User packages to be built. |
||||
|
# Various formats can be used as shown in the example below. |
||||
|
# |
||||
|
# packages: |
||||
|
# - some-directory |
||||
|
# - https://example.com/foo/bar/baz-0.0.2.tar.gz |
||||
|
# - location: |
||||
|
# git: https://github.com/commercialhaskell/stack.git |
||||
|
# commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a |
||||
|
# - location: https://github.com/commercialhaskell/stack/commit/e7b331f14bcffb8367cd58fbfc8b40ec7642100a |
||||
|
# subdirs: |
||||
|
# - auto-update |
||||
|
# - wai |
||||
|
packages: |
||||
|
- . |
||||
|
# Dependency packages to be pulled from upstream that are not in the resolver |
||||
|
# using the same syntax as the packages field. |
||||
|
# (e.g., acme-missiles-0.3) |
||||
|
extra-deps: |
||||
|
- fdo-notify-0.3.1 |
||||
|
- github-0.21 |
||||
|
|
||||
|
# Override default flag values for local packages and extra-deps |
||||
|
# flags: {} |
||||
|
|
||||
|
# Extra package databases containing global packages |
||||
|
# extra-package-dbs: [] |
||||
|
|
||||
|
# Control whether we use the GHC we find on the path |
||||
|
# system-ghc: true |
||||
|
# |
||||
|
# Require a specific version of stack, using version ranges |
||||
|
# require-stack-version: -any # Default |
||||
|
# require-stack-version: ">=1.7" |
||||
|
# |
||||
|
# Override the architecture used by stack, especially useful on Windows |
||||
|
# arch: i386 |
||||
|
# arch: x86_64 |
||||
|
# |
||||
|
# Extra directories used by stack for building |
||||
|
# extra-include-dirs: [/path/to/dir] |
||||
|
# extra-lib-dirs: [/path/to/dir] |
||||
|
# |
||||
|
# Allow a newer minor version of GHC than the snapshot specifies |
||||
|
# compiler-check: newer-minor |
@ -0,0 +1,2 @@ |
|||||
|
main :: IO () |
||||
|
main = putStrLn "Test suite not yet implemented" |
Loading…
Reference in new issue