-- SPDX-FileCopyrightText: 2024 David Mosbach -- -- SPDX-License-Identifier: AGPL-3.0-or-later {-# Language OverloadedStrings, LambdaCase, TypeApplications #-} import Data.Text (Text) import qualified Data.Text as T import System.Directory import System.Environment import System.IO main :: IO () main = getArgs >>= \case ["--assign", offsetFile] -> parseOffsets offsetFile >>= uncurry nextOffset ["--remove", offset] -> removeOffset offset _ -> fail "unsupported args" parseOffsets :: FilePath -> IO (Int,Int) parseOffsets offsetFile = do user <- T.pack <$> getEnv "USER" let pred x = "//" `T.isPrefixOf` x || T.null (T.strip x) tokenise = map (filter (not . pred) . T.lines) . T.split (=='#') extract = map tail . filter (\u -> not (null u) && user == (T.strip $ head u)) ((extract . tokenise . T.pack) <$> readFile offsetFile) >>= \case [[min,max]] -> return (read $ T.unpack min, read $ T.unpack max) x -> print x >> fail "malformed offset file" nextOffset :: Int -> Int -> IO () nextOffset min max | min > max = nextOffset max min | otherwise = do home <- getEnv "HOME" offset <- findFile [home] ".port-offsets" >>= \case Nothing -> writeFile (home ++ "/.port-offsets") (show min) >> return min Just path -> do used <- (map (read @Int) . filter (not . null) . lines) <$> readFile path o <- next min max used appendFile path ('\n' : show o) return o print offset where next :: Int -> Int -> [Int] -> IO Int next min max used | min > max = fail "all offsets currently in use" | min `elem` used = next (min+1) max used | otherwise = return min removeOffset :: String -> IO () removeOffset offset = do home <- getEnv "HOME" findFile [home] ".port-offsets" >>= \case Nothing -> fail "offset file does not exist" Just path -> do remaining <- (filter (/= offset) . lines) <$> readFile path run <- getEnv "XDG_RUNTIME_DIR" (tempPath, fh) <- openTempFile run ".port-offsets" let out = unlines remaining hPutStr fh $ out case T.null (T.strip $ T.pack out) of True -> removeFile path False -> writeFile path $ out removeFile tempPath