Compare commits

..

20 commits

Author SHA1 Message Date
q9i
48de041a44 docs(workflow): rename main.yml
Some checks failed
Cache binaries / tests (push) Has been cancelled
2024-08-16 20:22:15 -07:00
7342a97eb4 fix: accept flake config for nix develop 2024-08-16 20:22:15 -07:00
49169b914a deps: add suntheme cachix to flake 2024-08-16 20:22:15 -07:00
cc418491bc fix: allow flake binary cache and substituters 2024-08-16 20:22:15 -07:00
Youwen Wu
e96156a936 ci: add cachix tests 2024-08-16 20:22:15 -07:00
q9i
4ea4e5e716 fix: optimize external dependency specifications
remove busybox (pure bloat) and unnecessary documentation
2024-08-16 20:22:15 -07:00
q9i
42af799d0f docs: formalize readme
in preparation for merge
2024-08-16 20:22:15 -07:00
853fcc790a fix: correctly gitignore nix result 2024-08-16 20:22:15 -07:00
bb35415ed0 deps: add all required dependencies to flake 2024-08-16 20:22:15 -07:00
e6dc490044 chore: add .envrc for automatic dev env loading 2024-08-16 20:22:15 -07:00
c40d3b8d4a docs: add development instructions 2024-08-16 20:22:15 -07:00
f79b895cc3 docs: add readme 2024-08-16 20:22:15 -07:00
61895a9abb fix: delete unused Setup.hs file 2024-08-16 20:22:15 -07:00
012a42a7d3 deps(dev): switch to nix 2024-08-16 20:22:15 -07:00
q9i
3a8fb784b8 refactor: complete modularization
introduce Workers, Cache modules and shuffle around some existing functions
2024-08-16 20:22:15 -07:00
q9i
13188fe64d refactor: create getters module
group helper funcs for creating directory names and fetching network resources into a single module
2024-08-16 20:22:15 -07:00
q9i
b9750dafea refactor: create sugar module
move IO abstractions and desugaring processes into a separate module
2024-08-16 20:22:15 -07:00
q9i
1ed2a75f6c refactor: create time module
move time-related functions from `Main.hs` to a new `Time` module for better
organization and code separation
2024-08-16 20:22:15 -07:00
q9i
cc1935f92c refactor: create pure module
`Pure` contains functions for processing data and building commands. The module includes functions for reading lines of input, formatting time, and constructing a command with a given script and time.
2024-08-16 20:22:15 -07:00
q9i
6cf30685ad refactor: create types and const modules 2024-08-16 20:22:15 -07:00
16 changed files with 1120 additions and 226 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
use flake

18
.github/workflows/main.yml vendored Normal file
View file

@ -0,0 +1,18 @@
name: "Cache binaries"
on:
pull_request:
push:
jobs:
tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@v25
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v14
with:
name: suntheme
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- run: nix build --accept-flake-config
- run: nix develop --accept-flake-config

2
.gitignore vendored
View file

@ -21,3 +21,5 @@ cabal.project.local
cabal.project.local~
.HTF/
.ghc.environment.*
result
.direnv/

57
README.md Normal file
View file

@ -0,0 +1,57 @@
# Suntheme
Runs a script on sunrise and sunset, written in pure Haskell
---
You may be wondering how a program written in Haskell, the purely functional
programming language, could possibly act on the real world by running a so-called "script."
It's simple. We take in the entire World as an input to a pure function, the IO Monad.
It then maps the original World to a new (generated) World, with our desired IO actions carefully applied with mathematical precision.
> From the second perspective, an IO action transforms the whole world. IO actions are actually pure, because they receive a unique world as an argument and then return the changed world.
See [this](https://lean-lang.org/functional_programming_in_lean/monads/io.html) for more information.
## Hacking on suntheme
It's trivial to get started with suntheme development thanks to [Nix](https://nixos.org/), the purely functional package manager.
Naturally, we leverage it as our primary package manager, both for Hackage and development tools like language servers and the like.
First, install Nix through your preferred avenue or local system administrator. If unsure, we recommend [the Determinate Nix Installer](https://github.com/DeterminateSystems/nix-installer).
Make sure flakes and nix-command are enabled (the Determinate Installer will enable them by default).
Once you have `nix`, simply type:
```bash
nix develop
```
Say yes to any prompts asking you to allow substituters or trust public keys.
Nix will fetch all of the required packages, such as GHC and Hackage dependencies.
Additionally, you will have access to `hlint` and the `haskell-language-server`.
To create a build, type
```bash
nix build
```
A binary will be produced in `result/bin/suntheme`.
To make `suntheme` available in the shell without outputting to `result`, use
```bash
nix shell
```
This will build a binary just like `nix build` but add it temporarily to the PATH, so you can just type `suntheme`.
To build and run `suntheme` immediately without adding it to the PATH, use
```bash
nix run
```
This will build a binary just like `nix shell` but immediately execute it.

51
app/Cache.hs Normal file
View file

@ -0,0 +1,51 @@
module Cache where
import Const (cacheFile, logFile, prog)
import Sugar (killall, failure)
import Data.List.Extra ((!?))
import System.FilePath ((</>), takeDirectory)
import System.Directory (
XdgDirectory(XdgCache, XdgConfig),
createDirectoryIfMissing, doesFileExist, getXdgDirectory
)
import Control.Exception (catch)
pathToCache :: String -> IO String
pathToCache str = (</> str) <$> getXdgDirectory XdgCache prog
pathToConfig :: String -> IO String
pathToConfig str = (</> str) <$> getXdgDirectory XdgConfig prog
readCache :: IO (Maybe (Double, Double, String))
readCache = do
cache <- pathToCache cacheFile
contents <- readFile cache
let entries = lines contents
case (entries !? 0, entries !? 1, entries !? 2) of
(Just lat, Just lon, Just tz) -> return (Just (read lat, read lon, tz))
_ -> return Nothing
dumpCache :: Double -> Double -> String -> IO (Double, Double)
dumpCache lat lon tz = do
cache <- pathToCache cacheFile
catch (writer cache) failure
return (lat, lon)
where writer dir = writeFile dir (show lat ++ "\n" ++ show lon ++ "\n" ++ tz)
start :: IO ()
start = do
logs <- pathToCache logFile
existsLog <- doesFileExist logs
if existsLog then do
contents <- readFile logs
(sequence_ . killall . lines) contents
else createDirectoryIfMissing True (takeDirectory logs)
finish :: String -> IO ()
finish queue = do
cache <- pathToCache logFile
catch (writeFile cache num) failure
where
getId = head . words . last . lines
num = getId queue

19
app/Const.hs Normal file
View file

@ -0,0 +1,19 @@
module Const where
query :: String
query = "http://ip-api.com/line/?fields=status,lat,lon,timezone"
prog :: String
prog = "suntheme"
cacheFile :: String
cacheFile = "data.txt"
logFile :: String
logFile = "log.txt"
lightModeScript :: String
lightModeScript = "light.sh"
darkModeScript :: String
darkModeScript = "dark.sh"

7
app/Getters.hs Normal file
View file

@ -0,0 +1,7 @@
module Getters where
import Control.Exception (SomeException, try)
import Network.HTTP.Request (Response, get)
fetch :: String -> IO (Either SomeException Response)
fetch = try . get

View file

@ -1,128 +1,35 @@
-- requires 'at' for running a command at a certain time
-- requires 'date' for converting unix time to human readable time
-- run on boot and at noon and midnight
-- don't add commands to at while this script is running (monadic purity must be preserved)
-- don't add commands to `at` while this script is running (monadic purity must be preserved)
module Main where
import Data.Time (ZonedTime, getCurrentTime)
import Data.Time.Solar (Location(Location), sunrise, sunset)
import Data.Time.RFC3339 (formatTimeRFC3339)
import Data.Time.LocalTime (getTimeZone, utcToZonedTime, zonedTimeToUTC)
import Data.List.Extra ((!?))
import Pure (readLines)
import Cache (dumpCache, start)
import Const (query)
import Sugar (continue, destruct)
import Types (ResponseCode(..), ResponseMsg(..), genericErr, toResponseMsg)
import Getters (fetch)
import Workers (backupRunner, prepareScripts)
import Data.ByteString.Char8 (unpack)
import System.Exit (ExitCode(ExitFailure), exitWith)
import System.Process (readProcess)
import System.FilePath ((</>), takeDirectory)
import System.Directory (XdgDirectory(XdgCache, XdgConfig), createDirectoryIfMissing, doesFileExist, getXdgDirectory)
import Control.Exception (SomeException, catch, try)
import Network.HTTP.Request (Response(responseBody, responseStatus), get)
import Network.HTTP.Request (Response(responseBody, responseStatus))
-- error handling
-- refactor into modules
-- clean up jank (reduce lines in functions, more atomic/functional, etc)
-- clean up jank (
-- create types,
-- more pure functions,
-- reduce lines in functions,
-- more atomic/functional,
-- etc
-- )
-- eliminate as many do blocks as possible
-- introduce liquid types and checking
-- whitepaper!
class Status s where
ok :: s -> Bool
ok = const False
disp :: s -> String
disp = const "encountered bad response (expected OK)"
newtype ResponseCode = ResponseCode Int
instance Status ResponseCode where
ok (ResponseCode code) = code == 200
disp (ResponseCode code) =
"encountered bad response code: "
++ show code
++ " (expected 200 'OK')"
newtype ResponseMsg = ResponseMsg String
instance Status ResponseMsg where
ok (ResponseMsg msg) = msg == "success"
disp (ResponseMsg msg) =
"encountered bad response message: "
++ msg
++ " (expected 'success')"
toResponseMsg :: SomeException -> ResponseMsg
toResponseMsg = ResponseMsg . show
query :: String
query = "http://ip-api.com/line/?fields=status,lat,lon,timezone"
genericErr :: ResponseMsg
genericErr = ResponseMsg "encountered unknown error"
prog :: String
prog = "suntheme"
lightModeScript :: String
lightModeScript = "light.sh"
darkModeScript :: String
darkModeScript = "dark.sh"
now :: IO ZonedTime
now = do
utcTime <- getCurrentTime
timeZone <- getTimeZone utcTime
return (utcToZonedTime timeZone utcTime)
sunriseNow :: Double -> Double -> IO ZonedTime
sunriseNow lat lon = do
time <- now
return (sunrise time here)
where here = Location lat lon
sunsetNow :: Double -> Double -> IO ZonedTime
sunsetNow lat lon = do
time <- now
return (sunset time here)
where here = Location lat lon
pathToCache :: String -> IO String
pathToCache str = do
dir <- getXdgDirectory XdgCache prog
return (dir </> str)
pathToConfig :: String -> IO String
pathToConfig str = do
dir <- getXdgDirectory XdgConfig prog
return (dir </> str)
fetch :: String -> IO (Either SomeException Response)
fetch = try . get
cont :: (Status s) => s -> IO () -> IO ()
cont e err = (putStrLn . disp) e >> err
destruct :: (Status s) => s -> IO () -> IO () -> IO ()
destruct status success failure
| ok status = success
| otherwise = cont status failure
ping :: (Response -> IO ()) -> IO () -> IO ()
ping run err = do
res <- fetch query
case res of
Left e -> cont (toResponseMsg e) err
Right r ->
let status = (ResponseCode . responseStatus) r
runner = run r
in destruct status runner err
readLines :: [String] -> Maybe (String, Double, Double, String)
readLines [msg, latStr, lonStr, tz] = Just (msg, read latStr, read lonStr, tz)
readLines _ = Nothing
process :: Response -> (Double -> Double -> String -> IO ()) -> IO () -> IO ()
process r run err =
case info of
Nothing -> cont genericErr err
Nothing -> continue genericErr err
Just (msg, lat, lon, tz) ->
let status = ResponseMsg msg
runner = run lat lon tz
@ -132,114 +39,22 @@ process r run err =
parts = (lines . unpack) body
info = (readLines . take 4) parts
dumpCache :: Double -> Double -> String -> IO ()
dumpCache lat lon tz = do
cache <- pathToCache "data.txt"
catch (writer cache) failure
prepareScripts lat lon
where
writer dir = writeFile dir (show lat ++ "\n" ++ show lon ++ "\n" ++ tz)
failure = print :: SomeException -> IO ()
readCache :: IO (Maybe (Double, Double, String))
readCache = do
cache <- pathToCache "data.txt"
contents <- readFile cache
let entries = lines contents
case (entries !? 0, entries !? 1, entries !? 2) of
(Just lat, Just lon, Just tz) -> return (Just (read lat, read lon, tz))
_ -> return Nothing
processRunner :: Response -> IO ()
processRunner r = process r dumpCache backupRunner
processRunner r = process r run backupRunner
where run lat lon tz = dumpCache lat lon tz >>= prepareScripts
crash :: IO ()
crash = exitWith (ExitFailure 1)
backupRunner :: IO ()
backupRunner = do
contents <- readCache
case contents of
Nothing -> do
putStrLn "Failed to read cache after IP location failed"
crash
Just (lat, lon, _) -> prepareScripts lat lon
exec :: String -> (SomeException -> IO String) -> IO String
exec cmd err = do catch (readProcess "bash" ["-c", cmd] "") err
kill :: String -> String
kill = (++) "atrm "
killall :: [String] -> IO ()
killall = foldr ((>>) . dispatch . kill) (return ())
where
dispatch cmd = exec cmd failure
failure :: SomeException -> IO String
failure e = print e >> return []
start :: IO () -> IO ()
start act = do
logFile <- pathToCache "log.txt"
existsLog <- doesFileExist logFile
if existsLog then do
contents <- readFile logFile
let entries = lines contents
killall entries
else createDirectoryIfMissing True (takeDirectory logFile)
act
finish :: String -> IO ()
finish queue = do
cache <- pathToCache "log.txt"
catch (writeFile cache num) failure
where
num = getId queue
getId = head . words . last . lines
failure = print :: SomeException -> IO ()
after :: (Eq a) => a -> [a] -> [a]
after c = drop 1 . dropWhile (/= c)
formatTime :: ZonedTime -> String
formatTime = take 5 . after 'T' . formatTimeRFC3339
buildCmd :: String -> ZonedTime -> String
buildCmd script time = "echo \"" ++ script ++ "\" | at " ++ formatTime time
activateOnSunrise :: ZonedTime -> ZonedTime -> IO Bool
activateOnSunrise sunriseTime sunsetTime = do
timeNow <- now
let utcTimeNow = zonedTimeToUTC timeNow
utcSunrise = zonedTimeToUTC sunriseTime
utcSunset = zonedTimeToUTC sunsetTime
if utcTimeNow < utcSunrise || utcTimeNow > utcSunset then return True
else return False
prepareScripts :: Double -> Double -> IO ()
prepareScripts lat lon = do
sunriseTime <- sunriseNow lat lon
sunsetTime <- sunsetNow lat lon
lightMode <- activateOnSunrise sunriseTime sunsetTime
_ <- if lightMode then do
putStr "Light mode activation script scheduled for "
print sunriseTime
script <- pathToConfig lightModeScript
exec (buildCmd script sunriseTime) terminate
else do
putStrLn "Dark mode activation script scheduled for "
print sunsetTime
script <- pathToConfig darkModeScript
exec (buildCmd script sunsetTime) terminate
queue <- exec "atq" noQueue
finish queue
where
terminate _ = print "Scheduling process failed" >> return ""
noQueueMsg = "Scheduled process could not be retrieved (try rerunning if 'atq' fails)"
noQueue _ = print noQueueMsg >> return ""
ping :: (Response -> IO ()) -> IO () -> IO ()
ping run err = do
res <- fetch query
case res of
Left e -> continue (toResponseMsg e) err
Right r ->
let status = (ResponseCode . responseStatus) r
runner = run r
in destruct status runner err
routine :: IO ()
routine = ping processRunner backupRunner
main :: IO ()
main = start routine
main = start >> routine

28
app/Pure.hs Normal file
View file

@ -0,0 +1,28 @@
module Pure where
import Data.Time (ZonedTime)
import Data.Time.RFC3339 (formatTimeRFC3339)
import Data.Time.LocalTime (zonedTimeToUTC)
readLines :: [String] -> Maybe (String, Double, Double, String)
readLines [msg, latStr, lonStr, tz] = Just (msg, read latStr, read lonStr, tz)
readLines _ = Nothing
kill :: String -> String
kill = (++) "atrm "
after :: (Eq a) => a -> [a] -> [a]
after c = drop 1 . dropWhile (/= c)
formatTime :: ZonedTime -> String
formatTime = take 5 . after 'T' . formatTimeRFC3339
buildCmd :: String -> ZonedTime -> String
buildCmd script time = "echo \"" ++ script ++ "\" | at " ++ formatTime time
sunriseNext :: ZonedTime -> ZonedTime -> ZonedTime -> Bool
sunriseNext sunriseTime sunsetTime time =
let utcTimeNow = zonedTimeToUTC time
utcSunrise = zonedTimeToUTC sunriseTime
utcSunset = zonedTimeToUTC sunsetTime
in utcTimeNow < utcSunrise || utcTimeNow > utcSunset

31
app/Sugar.hs Normal file
View file

@ -0,0 +1,31 @@
module Sugar where
import Pure (kill)
import Types (Status(..))
import System.Exit (ExitCode(ExitFailure), exitWith)
import System.Process (readProcess)
import Control.Exception (SomeException, catch)
throw :: (Monoid m) => SomeException -> IO m
throw e = print e >> return mempty
continue :: (Status s) => s -> IO () -> IO ()
continue e err = (putStrLn . disp) e >> err
failure :: SomeException -> IO ()
failure = print
destruct :: (Status s) => s -> IO () -> IO () -> IO ()
destruct status success unsuccessful
| ok status = success
| otherwise = continue status unsuccessful
crash :: IO ()
crash = (exitWith . ExitFailure) 1
exec :: String -> (SomeException -> IO String) -> IO String
exec cmd = catch (readProcess "bash" ["-c", cmd] "")
killall :: [String] -> [IO String]
killall = map (dispatch . kill) where dispatch cmd = exec cmd throw

44
app/Time.hs Normal file
View file

@ -0,0 +1,44 @@
module Time where
import Pure (buildCmd, sunriseNext)
import Cache (pathToConfig)
import Const (darkModeScript, lightModeScript)
import Sugar (exec)
import Data.Time (ZonedTime, getCurrentTime)
import Data.Time.Solar (Location(Location), sunrise, sunset)
import Data.Time.LocalTime (getTimeZone, utcToZonedTime)
now :: IO ZonedTime
now = do
utcTime <- getCurrentTime
timeZone <- getTimeZone utcTime
return (utcToZonedTime timeZone utcTime)
sunriseNow :: Double -> Double -> IO ZonedTime
sunriseNow lat lon = do
time <- now
return (sunrise time here)
where here = Location lat lon
sunsetNow :: Double -> Double -> IO ZonedTime
sunsetNow lat lon = do
time <- now
return (sunset time here)
where here = Location lat lon
activateOnSunrise :: ZonedTime -> ZonedTime -> IO Bool
activateOnSunrise sunriseTime sunsetTime = sunriseNext sunriseTime sunsetTime <$> now
activate :: String -> ZonedTime -> String -> IO String
activate msg time script = do
putStr msg
print time
scriptPath <- pathToConfig script
exec (buildCmd scriptPath time) terminate
where terminate _ = print "Scheduling process failed" >> return ""
chooseActivation :: Bool -> ZonedTime -> ZonedTime -> IO String
chooseActivation lightMode sunriseTime sunsetTime
| lightMode = activate "Light mode activation script scheduled for " sunriseTime lightModeScript
| otherwise = activate "Dark mode activation script scheduled for " sunsetTime darkModeScript

32
app/Types.hs Normal file
View file

@ -0,0 +1,32 @@
module Types where
import Control.Exception (SomeException)
class Status s where
ok :: s -> Bool
ok = const False
disp :: s -> String
disp = const "encountered bad response (expected OK)"
newtype ResponseCode = ResponseCode Int
instance Status ResponseCode where
ok (ResponseCode code) = code == 200
disp (ResponseCode code) =
"encountered bad response code: "
++ show code
++ " (expected 200 'OK')"
newtype ResponseMsg = ResponseMsg String
instance Status ResponseMsg where
ok (ResponseMsg msg) = msg == "success"
disp (ResponseMsg msg) =
"encountered bad response message: "
++ msg
++ " (expected 'success')"
toResponseMsg :: SomeException -> ResponseMsg
toResponseMsg = ResponseMsg . show
genericErr :: ResponseMsg
genericErr = ResponseMsg "encountered unknown error"

26
app/Workers.hs Normal file
View file

@ -0,0 +1,26 @@
module Workers where
import Time (chooseActivation, activateOnSunrise, sunriseNow, sunsetNow)
import Cache (finish, readCache)
import Sugar (crash, exec)
prepareScripts :: (Double, Double) -> IO ()
prepareScripts (lat, lon) = do
sunriseTime <- sunriseNow lat lon
sunsetTime <- sunsetNow lat lon
lightMode <- activateOnSunrise sunriseTime sunsetTime
_ <- chooseActivation lightMode sunriseTime sunsetTime
queue <- exec "atq" noQueue
finish queue
where
noQueueMsg = "Scheduled process could not be retrieved (try rerunning if 'atq' fails)"
noQueue _ = print noQueueMsg >> return ""
backupRunner :: IO ()
backupRunner = do
contents <- readCache
case contents of
Nothing -> do
putStrLn "Failed to read cache after IP location failed"
crash
Just (lat, lon, _) -> prepareScripts (lat, lon)

707
flake.lock Normal file
View file

@ -0,0 +1,707 @@
{
"nodes": {
"HTTP": {
"flake": false,
"locked": {
"lastModified": 1451647621,
"narHash": "sha256-oHIyw3x0iKBexEo49YeUDV1k74ZtyYKGR2gNJXXRxts=",
"owner": "phadej",
"repo": "HTTP",
"rev": "9bc0996d412fef1787449d841277ef663ad9a915",
"type": "github"
},
"original": {
"owner": "phadej",
"repo": "HTTP",
"type": "github"
}
},
"cabal-32": {
"flake": false,
"locked": {
"lastModified": 1603716527,
"narHash": "sha256-X0TFfdD4KZpwl0Zr6x+PLxUt/VyKQfX7ylXHdmZIL+w=",
"owner": "haskell",
"repo": "cabal",
"rev": "48bf10787e27364730dd37a42b603cee8d6af7ee",
"type": "github"
},
"original": {
"owner": "haskell",
"ref": "3.2",
"repo": "cabal",
"type": "github"
}
},
"cabal-34": {
"flake": false,
"locked": {
"lastModified": 1645834128,
"narHash": "sha256-wG3d+dOt14z8+ydz4SL7pwGfe7SiimxcD/LOuPCV6xM=",
"owner": "haskell",
"repo": "cabal",
"rev": "5ff598c67f53f7c4f48e31d722ba37172230c462",
"type": "github"
},
"original": {
"owner": "haskell",
"ref": "3.4",
"repo": "cabal",
"type": "github"
}
},
"cabal-36": {
"flake": false,
"locked": {
"lastModified": 1669081697,
"narHash": "sha256-I5or+V7LZvMxfbYgZATU4awzkicBwwok4mVoje+sGmU=",
"owner": "haskell",
"repo": "cabal",
"rev": "8fd619e33d34924a94e691c5fea2c42f0fc7f144",
"type": "github"
},
"original": {
"owner": "haskell",
"ref": "3.6",
"repo": "cabal",
"type": "github"
}
},
"cardano-shell": {
"flake": false,
"locked": {
"lastModified": 1608537748,
"narHash": "sha256-PulY1GfiMgKVnBci3ex4ptk2UNYMXqGjJOxcPy2KYT4=",
"owner": "input-output-hk",
"repo": "cardano-shell",
"rev": "9392c75087cb9a3d453998f4230930dea3a95725",
"type": "github"
},
"original": {
"owner": "input-output-hk",
"repo": "cardano-shell",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1672831974,
"narHash": "sha256-z9k3MfslLjWQfnjBtEtJZdq3H7kyi2kQtUThfTgdRk0=",
"owner": "input-output-hk",
"repo": "flake-compat",
"rev": "45f2638735f8cdc40fe302742b79f248d23eb368",
"type": "github"
},
"original": {
"owner": "input-output-hk",
"ref": "hkm/gitlab-fix",
"repo": "flake-compat",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"ghc-8.6.5-iohk": {
"flake": false,
"locked": {
"lastModified": 1600920045,
"narHash": "sha256-DO6kxJz248djebZLpSzTGD6s8WRpNI9BTwUeOf5RwY8=",
"owner": "input-output-hk",
"repo": "ghc",
"rev": "95713a6ecce4551240da7c96b6176f980af75cae",
"type": "github"
},
"original": {
"owner": "input-output-hk",
"ref": "release/8.6.5-iohk",
"repo": "ghc",
"type": "github"
}
},
"hackage": {
"flake": false,
"locked": {
"lastModified": 1723768183,
"narHash": "sha256-YG61ZL5YZvSWPvdusFsS8EYDJ/MW+EgX8fjNMWU18GQ=",
"owner": "input-output-hk",
"repo": "hackage.nix",
"rev": "37977e3b6201db1e0f3c62d9c55ec20dfcee10a2",
"type": "github"
},
"original": {
"owner": "input-output-hk",
"repo": "hackage.nix",
"type": "github"
}
},
"haskellNix": {
"inputs": {
"HTTP": "HTTP",
"cabal-32": "cabal-32",
"cabal-34": "cabal-34",
"cabal-36": "cabal-36",
"cardano-shell": "cardano-shell",
"flake-compat": "flake-compat",
"ghc-8.6.5-iohk": "ghc-8.6.5-iohk",
"hackage": "hackage",
"hls-1.10": "hls-1.10",
"hls-2.0": "hls-2.0",
"hls-2.2": "hls-2.2",
"hls-2.3": "hls-2.3",
"hls-2.4": "hls-2.4",
"hls-2.5": "hls-2.5",
"hls-2.6": "hls-2.6",
"hls-2.7": "hls-2.7",
"hls-2.8": "hls-2.8",
"hls-2.9": "hls-2.9",
"hpc-coveralls": "hpc-coveralls",
"hydra": "hydra",
"iserv-proxy": "iserv-proxy",
"nixpkgs": [
"haskellNix",
"nixpkgs-unstable"
],
"nixpkgs-2003": "nixpkgs-2003",
"nixpkgs-2105": "nixpkgs-2105",
"nixpkgs-2111": "nixpkgs-2111",
"nixpkgs-2205": "nixpkgs-2205",
"nixpkgs-2211": "nixpkgs-2211",
"nixpkgs-2305": "nixpkgs-2305",
"nixpkgs-2311": "nixpkgs-2311",
"nixpkgs-2405": "nixpkgs-2405",
"nixpkgs-unstable": "nixpkgs-unstable",
"old-ghc-nix": "old-ghc-nix",
"stackage": "stackage"
},
"locked": {
"lastModified": 1723769442,
"narHash": "sha256-dGhNJril+hILFro59PdYWwKZYJ6/2A1hozTanaAu4lE=",
"owner": "input-output-hk",
"repo": "haskell.nix",
"rev": "517ceaa97e38ae211d9c311952cc8be4a4a026cd",
"type": "github"
},
"original": {
"owner": "input-output-hk",
"repo": "haskell.nix",
"type": "github"
}
},
"hls-1.10": {
"flake": false,
"locked": {
"lastModified": 1680000865,
"narHash": "sha256-rc7iiUAcrHxwRM/s0ErEsSPxOR3u8t7DvFeWlMycWgo=",
"owner": "haskell",
"repo": "haskell-language-server",
"rev": "b08691db779f7a35ff322b71e72a12f6e3376fd9",
"type": "github"
},
"original": {
"owner": "haskell",
"ref": "1.10.0.0",
"repo": "haskell-language-server",
"type": "github"
}
},
"hls-2.0": {
"flake": false,
"locked": {
"lastModified": 1687698105,
"narHash": "sha256-OHXlgRzs/kuJH8q7Sxh507H+0Rb8b7VOiPAjcY9sM1k=",
"owner": "haskell",
"repo": "haskell-language-server",
"rev": "783905f211ac63edf982dd1889c671653327e441",
"type": "github"
},
"original": {
"owner": "haskell",
"ref": "2.0.0.1",
"repo": "haskell-language-server",
"type": "github"
}
},
"hls-2.2": {
"flake": false,
"locked": {
"lastModified": 1693064058,
"narHash": "sha256-8DGIyz5GjuCFmohY6Fa79hHA/p1iIqubfJUTGQElbNk=",
"owner": "haskell",
"repo": "haskell-language-server",
"rev": "b30f4b6cf5822f3112c35d14a0cba51f3fe23b85",
"type": "github"
},
"original": {
"owner": "haskell",
"ref": "2.2.0.0",
"repo": "haskell-language-server",
"type": "github"
}
},
"hls-2.3": {
"flake": false,
"locked": {
"lastModified": 1695910642,
"narHash": "sha256-tR58doOs3DncFehHwCLczJgntyG/zlsSd7DgDgMPOkI=",
"owner": "haskell",
"repo": "haskell-language-server",
"rev": "458ccdb55c9ea22cd5d13ec3051aaefb295321be",
"type": "github"
},
"original": {
"owner": "haskell",
"ref": "2.3.0.0",
"repo": "haskell-language-server",
"type": "github"
}
},
"hls-2.4": {
"flake": false,
"locked": {
"lastModified": 1699862708,
"narHash": "sha256-YHXSkdz53zd0fYGIYOgLt6HrA0eaRJi9mXVqDgmvrjk=",
"owner": "haskell",
"repo": "haskell-language-server",
"rev": "54507ef7e85fa8e9d0eb9a669832a3287ffccd57",
"type": "github"
},
"original": {
"owner": "haskell",
"ref": "2.4.0.1",
"repo": "haskell-language-server",
"type": "github"
}
},
"hls-2.5": {
"flake": false,
"locked": {
"lastModified": 1701080174,
"narHash": "sha256-fyiR9TaHGJIIR0UmcCb73Xv9TJq3ht2ioxQ2mT7kVdc=",
"owner": "haskell",
"repo": "haskell-language-server",
"rev": "27f8c3d3892e38edaef5bea3870161815c4d014c",
"type": "github"
},
"original": {
"owner": "haskell",
"ref": "2.5.0.0",
"repo": "haskell-language-server",
"type": "github"
}
},
"hls-2.6": {
"flake": false,
"locked": {
"lastModified": 1705325287,
"narHash": "sha256-+P87oLdlPyMw8Mgoul7HMWdEvWP/fNlo8jyNtwME8E8=",
"owner": "haskell",
"repo": "haskell-language-server",
"rev": "6e0b342fa0327e628610f2711f8c3e4eaaa08b1e",
"type": "github"
},
"original": {
"owner": "haskell",
"ref": "2.6.0.0",
"repo": "haskell-language-server",
"type": "github"
}
},
"hls-2.7": {
"flake": false,
"locked": {
"lastModified": 1708965829,
"narHash": "sha256-LfJ+TBcBFq/XKoiNI7pc4VoHg4WmuzsFxYJ3Fu+Jf+M=",
"owner": "haskell",
"repo": "haskell-language-server",
"rev": "50322b0a4aefb27adc5ec42f5055aaa8f8e38001",
"type": "github"
},
"original": {
"owner": "haskell",
"ref": "2.7.0.0",
"repo": "haskell-language-server",
"type": "github"
}
},
"hls-2.8": {
"flake": false,
"locked": {
"lastModified": 1715153580,
"narHash": "sha256-Vi/iUt2pWyUJlo9VrYgTcbRviWE0cFO6rmGi9rmALw0=",
"owner": "haskell",
"repo": "haskell-language-server",
"rev": "dd1be1beb16700de59e0d6801957290bcf956a0a",
"type": "github"
},
"original": {
"owner": "haskell",
"ref": "2.8.0.0",
"repo": "haskell-language-server",
"type": "github"
}
},
"hls-2.9": {
"flake": false,
"locked": {
"lastModified": 1718469202,
"narHash": "sha256-THXSz+iwB1yQQsr/PY151+2GvtoJnTIB2pIQ4OzfjD4=",
"owner": "haskell",
"repo": "haskell-language-server",
"rev": "40891bccb235ebacce020b598b083eab9dda80f1",
"type": "github"
},
"original": {
"owner": "haskell",
"ref": "2.9.0.0",
"repo": "haskell-language-server",
"type": "github"
}
},
"hpc-coveralls": {
"flake": false,
"locked": {
"lastModified": 1607498076,
"narHash": "sha256-8uqsEtivphgZWYeUo5RDUhp6bO9j2vaaProQxHBltQk=",
"owner": "sevanspowell",
"repo": "hpc-coveralls",
"rev": "14df0f7d229f4cd2e79f8eabb1a740097fdfa430",
"type": "github"
},
"original": {
"owner": "sevanspowell",
"repo": "hpc-coveralls",
"type": "github"
}
},
"hydra": {
"inputs": {
"nix": "nix",
"nixpkgs": [
"haskellNix",
"hydra",
"nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1671755331,
"narHash": "sha256-hXsgJj0Cy0ZiCiYdW2OdBz5WmFyOMKuw4zyxKpgUKm4=",
"owner": "NixOS",
"repo": "hydra",
"rev": "f48f00ee6d5727ae3e488cbf9ce157460853fea8",
"type": "github"
},
"original": {
"id": "hydra",
"type": "indirect"
}
},
"iserv-proxy": {
"flake": false,
"locked": {
"lastModified": 1717479972,
"narHash": "sha256-7vE3RQycHI1YT9LHJ1/fUaeln2vIpYm6Mmn8FTpYeVo=",
"owner": "stable-haskell",
"repo": "iserv-proxy",
"rev": "2ed34002247213fc435d0062350b91bab920626e",
"type": "github"
},
"original": {
"owner": "stable-haskell",
"ref": "iserv-syms",
"repo": "iserv-proxy",
"type": "github"
}
},
"lowdown-src": {
"flake": false,
"locked": {
"lastModified": 1633514407,
"narHash": "sha256-Dw32tiMjdK9t3ETl5fzGrutQTzh2rufgZV4A/BbxuD4=",
"owner": "kristapsdz",
"repo": "lowdown",
"rev": "d2c2b44ff6c27b936ec27358a2653caaef8f73b8",
"type": "github"
},
"original": {
"owner": "kristapsdz",
"repo": "lowdown",
"type": "github"
}
},
"nix": {
"inputs": {
"lowdown-src": "lowdown-src",
"nixpkgs": "nixpkgs",
"nixpkgs-regression": "nixpkgs-regression"
},
"locked": {
"lastModified": 1661606874,
"narHash": "sha256-9+rpYzI+SmxJn+EbYxjGv68Ucp22bdFUSy/4LkHkkDQ=",
"owner": "NixOS",
"repo": "nix",
"rev": "11e45768b34fdafdcf019ddbd337afa16127ff0f",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "2.11.0",
"repo": "nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1657693803,
"narHash": "sha256-G++2CJ9u0E7NNTAi9n5G8TdDmGJXcIjkJ3NF8cetQB8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "365e1b3a859281cf11b94f87231adeabbdd878a2",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-22.05-small",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-2003": {
"locked": {
"lastModified": 1620055814,
"narHash": "sha256-8LEHoYSJiL901bTMVatq+rf8y7QtWuZhwwpKE2fyaRY=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "1db42b7fe3878f3f5f7a4f2dc210772fd080e205",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-20.03-darwin",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-2105": {
"locked": {
"lastModified": 1659914493,
"narHash": "sha256-lkA5X3VNMKirvA+SUzvEhfA7XquWLci+CGi505YFAIs=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "022caabb5f2265ad4006c1fa5b1ebe69fb0c3faf",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-21.05-darwin",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-2111": {
"locked": {
"lastModified": 1659446231,
"narHash": "sha256-hekabNdTdgR/iLsgce5TGWmfIDZ86qjPhxDg/8TlzhE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "eabc38219184cc3e04a974fe31857d8e0eac098d",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-21.11-darwin",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-2205": {
"locked": {
"lastModified": 1685573264,
"narHash": "sha256-Zffu01pONhs/pqH07cjlF10NnMDLok8ix5Uk4rhOnZQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "380be19fbd2d9079f677978361792cb25e8a3635",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-22.05-darwin",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-2211": {
"locked": {
"lastModified": 1688392541,
"narHash": "sha256-lHrKvEkCPTUO+7tPfjIcb7Trk6k31rz18vkyqmkeJfY=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "ea4c80b39be4c09702b0cb3b42eab59e2ba4f24b",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-22.11-darwin",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-2305": {
"locked": {
"lastModified": 1705033721,
"narHash": "sha256-K5eJHmL1/kev6WuqyqqbS1cdNnSidIZ3jeqJ7GbrYnQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a1982c92d8980a0114372973cbdfe0a307f1bdea",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-23.05-darwin",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-2311": {
"locked": {
"lastModified": 1719957072,
"narHash": "sha256-gvFhEf5nszouwLAkT9nWsDzocUTqLWHuL++dvNjMp9I=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "7144d6241f02d171d25fba3edeaf15e0f2592105",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-23.11-darwin",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-2405": {
"locked": {
"lastModified": 1720122915,
"narHash": "sha256-Nby8WWxj0elBu1xuRaUcRjPi/rU3xVbkAt2kj4QwX2U=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "835cf2d3f37989c5db6585a28de967a667a75fb1",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-24.05-darwin",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-regression": {
"locked": {
"lastModified": 1643052045,
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
"type": "github"
}
},
"nixpkgs-unstable": {
"locked": {
"lastModified": 1720181791,
"narHash": "sha256-i4vJL12/AdyuQuviMMd1Hk2tsGt02hDNhA0Zj1m16N8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "4284c2b73c8bce4b46a6adf23e16d9e2ec8da4bb",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"old-ghc-nix": {
"flake": false,
"locked": {
"lastModified": 1631092763,
"narHash": "sha256-sIKgO+z7tj4lw3u6oBZxqIhDrzSkvpHtv0Kki+lh9Fg=",
"owner": "angerman",
"repo": "old-ghc-nix",
"rev": "af48a7a7353e418119b6dfe3cd1463a657f342b8",
"type": "github"
},
"original": {
"owner": "angerman",
"ref": "master",
"repo": "old-ghc-nix",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"haskellNix": "haskellNix",
"nixpkgs": [
"haskellNix",
"nixpkgs-unstable"
]
}
},
"stackage": {
"flake": false,
"locked": {
"lastModified": 1723594352,
"narHash": "sha256-cQVhF1M1et3/XNE1sclwH39prxIDMUojTdnW61t3YrM=",
"owner": "input-output-hk",
"repo": "stackage.nix",
"rev": "c077da02c56031a78adc4bf0cf2b182effc895ed",
"type": "github"
},
"original": {
"owner": "input-output-hk",
"repo": "stackage.nix",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

56
flake.nix Normal file
View file

@ -0,0 +1,56 @@
{
description = "Suntheme's development and build environment";
inputs.haskellNix.url = "github:input-output-hk/haskell.nix";
inputs.nixpkgs.follows = "haskellNix/nixpkgs-unstable";
inputs.flake-utils.url = "github:numtide/flake-utils";
outputs = {
self,
nixpkgs,
flake-utils,
haskellNix,
}: let
supportedSystems = [
"x86_64-linux"
"x86_64-darwin"
"aarch64-linux"
"aarch64-darwin"
];
in
flake-utils.lib.eachSystem supportedSystems (system: let
overlays = [
haskellNix.overlay
(final: prev: {
suntheme = final.haskell-nix.project' {
src = ./.;
compiler-nix-name = "ghc982";
shell.tools = {
cabal = {};
hlint = {};
haskell-language-server = {};
};
shell.buildInputs = with pkgs; [
at
];
};
})
];
pkgs = import nixpkgs {
inherit system overlays;
inherit (haskellNix) config;
};
flake = pkgs.suntheme.flake {};
in
flake
// {
formatter = pkgs.alejandra;
packages.default = flake.packages."suntheme:exe:suntheme";
});
nixConfig = {
extra-substituters = ["https://cache.iog.io" "https://suntheme.cachix.org"];
extra-trusted-public-keys = ["hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ=" "suntheme.cachix.org-1:fHjlz7YAmMUcLp3tsZis8g9wIsDS6HvECGR3uZETGRo="];
allow-import-from-derivation = "true";
};
}

View file

@ -62,13 +62,13 @@ executable suntheme
main-is: Main.hs
-- Modules included in this executable, other than Main.
-- other-modules:
other-modules: Types, Const, Pure, Time, Sugar, Getters, Cache, Workers
-- LANGUAGE extensions used by modules in this package.
-- other-extensions:
-- Other library packages from which modules are imported.
build-depends: base ^>=4.17.2.1,
build-depends: base ^>=4.19.1.0,
request ^>=0.2.2.0,
process ^>=1.6.18.0,
bytestring ^>=0.11.5.3,