packages feed

Z-IO 0.1.5.2 → 0.1.6.0

raw patch · 30 files changed

+3619/−201 lines, 30 filesdep +unix-timedep ~Z-DataPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: unix-time

Dependency ranges changed: Z-Data

API changes (from Hackage documentation)

- Z.IO.Logger: [loggerShowSourceLoc] :: LoggerConfig -> Bool
- Z.IO.Logger: [loggerShowTS] :: LoggerConfig -> Bool
- Z.IO.Logger: getStdLogger :: IO Logger
- Z.IO.Logger: setStdLogger :: Logger -> IO ()
- Z.IO.Logger: withStdLogger :: IO () -> IO ()
- Z.IO.Resource: statPool :: Pool a -> IO PoolState
- Z.IO.UV.FFI: type UVFD = CInt
+ Z.IO.Buffered: bufInput :: BufferedInput i -> i
+ Z.IO.Buffered: bufOutput :: BufferedOutput o -> o
+ Z.IO.Environment: PassWD :: CBytes -> UID -> GID -> CBytes -> CBytes -> PassWD
+ Z.IO.Environment: [passwd_gid] :: PassWD -> GID
+ Z.IO.Environment: [passwd_homedir] :: PassWD -> CBytes
+ Z.IO.Environment: [passwd_shell] :: PassWD -> CBytes
+ Z.IO.Environment: [passwd_uid] :: PassWD -> UID
+ Z.IO.Environment: [passwd_username] :: PassWD -> CBytes
+ Z.IO.Environment: chDir :: HasCallStack => CBytes -> IO ()
+ Z.IO.Environment: data GID
+ Z.IO.Environment: data PassWD
+ Z.IO.Environment: data UID
+ Z.IO.Environment: getCWD :: HasCallStack => IO CBytes
+ Z.IO.Environment: getHomeDir :: HasCallStack => IO CBytes
+ Z.IO.Environment: getPassWD :: IO PassWD
+ Z.IO.Environment: getTempDir :: HasCallStack => IO CBytes
+ Z.IO.FileSystem: chown :: HasCallStack => CBytes -> UID -> GID -> IO ()
+ Z.IO.FileSystem: fchown :: HasCallStack => File -> UID -> GID -> IO ()
+ Z.IO.FileSystem: lchown :: HasCallStack => CBytes -> UID -> GID -> IO ()
+ Z.IO.FileSystem.FilePath: UnixStyle :: PathStyle
+ Z.IO.FileSystem.FilePath: WindowsStyle :: PathStyle
+ Z.IO.FileSystem.FilePath: absolute :: CBytes -> CBytes -> IO CBytes
+ Z.IO.FileSystem.FilePath: changeBaseName :: CBytes -> CBytes -> IO CBytes
+ Z.IO.FileSystem.FilePath: changeExtension :: CBytes -> CBytes -> IO CBytes
+ Z.IO.FileSystem.FilePath: changeRoot :: CBytes -> CBytes -> IO CBytes
+ Z.IO.FileSystem.FilePath: concat :: [CBytes] -> IO CBytes
+ Z.IO.FileSystem.FilePath: data PathStyle
+ Z.IO.FileSystem.FilePath: extensionSeparator :: Word8
+ Z.IO.FileSystem.FilePath: getPathStyle :: IO PathStyle
+ Z.IO.FileSystem.FilePath: getSearchPath :: IO [CBytes]
+ Z.IO.FileSystem.FilePath: instance GHC.Classes.Eq Z.IO.FileSystem.FilePath.PathStyle
+ Z.IO.FileSystem.FilePath: instance GHC.Classes.Ord Z.IO.FileSystem.FilePath.PathStyle
+ Z.IO.FileSystem.FilePath: instance GHC.Generics.Generic Z.IO.FileSystem.FilePath.PathStyle
+ Z.IO.FileSystem.FilePath: instance GHC.Show.Show Z.IO.FileSystem.FilePath.PathStyle
+ Z.IO.FileSystem.FilePath: instance Z.Data.JSON.Base.EncodeJSON Z.IO.FileSystem.FilePath.PathStyle
+ Z.IO.FileSystem.FilePath: instance Z.Data.JSON.Base.FromValue Z.IO.FileSystem.FilePath.PathStyle
+ Z.IO.FileSystem.FilePath: instance Z.Data.JSON.Base.ToValue Z.IO.FileSystem.FilePath.PathStyle
+ Z.IO.FileSystem.FilePath: instance Z.Data.Text.ShowT.ShowT Z.IO.FileSystem.FilePath.PathStyle
+ Z.IO.FileSystem.FilePath: intersection :: CBytes -> CBytes -> IO CBytes
+ Z.IO.FileSystem.FilePath: isAbsolute :: CBytes -> IO Bool
+ Z.IO.FileSystem.FilePath: isRelative :: CBytes -> IO Bool
+ Z.IO.FileSystem.FilePath: join :: CBytes -> CBytes -> IO CBytes
+ Z.IO.FileSystem.FilePath: normalize :: CBytes -> IO CBytes
+ Z.IO.FileSystem.FilePath: pathSeparator :: IO [Word8]
+ Z.IO.FileSystem.FilePath: pathStyle :: CBytes -> IO PathStyle
+ Z.IO.FileSystem.FilePath: relative :: CBytes -> CBytes -> IO CBytes
+ Z.IO.FileSystem.FilePath: searchPathSeparator :: IO Word8
+ Z.IO.FileSystem.FilePath: setPathStyle :: PathStyle -> IO ()
+ Z.IO.FileSystem.FilePath: splitBaseName :: CBytes -> IO (CBytes, CBytes)
+ Z.IO.FileSystem.FilePath: splitExtension :: CBytes -> IO (CBytes, CBytes)
+ Z.IO.FileSystem.FilePath: splitRoot :: CBytes -> IO (CBytes, CBytes)
+ Z.IO.FileSystem.FilePath: splitSegments :: CBytes -> IO (CBytes, [CBytes], CBytes)
+ Z.IO.FileSystem.Threaded: chown :: HasCallStack => CBytes -> UID -> GID -> IO ()
+ Z.IO.FileSystem.Threaded: fchown :: HasCallStack => FD -> UID -> GID -> IO ()
+ Z.IO.FileSystem.Threaded: lchown :: HasCallStack => CBytes -> UID -> GID -> IO ()
+ Z.IO.Logger: coloredFmt :: Bool -> LogFormatter
+ Z.IO.Logger: defaultLoggerConfig :: LoggerConfig
+ Z.IO.Logger: flushDefaultLogger :: IO ()
+ Z.IO.Logger: getDefaultLogger :: IO Logger
+ Z.IO.Logger: newColoredLogger :: LoggerConfig -> IO Logger
+ Z.IO.Logger: setDefaultLogger :: Logger -> IO ()
+ Z.IO.Logger: withDefaultLogger :: IO () -> IO ()
+ Z.IO.Resource: poolInUse :: Pool a -> IO Int
+ Z.IO.Resource: poolStat :: Pool a -> IO PoolState
+ Z.IO.StdStream.Ansi: Black :: AnsiColor
+ Z.IO.StdStream.Ansi: Blue :: AnsiColor
+ Z.IO.StdStream.Ansi: Cyan :: AnsiColor
+ Z.IO.StdStream.Ansi: Green :: AnsiColor
+ Z.IO.StdStream.Ansi: Magenta :: AnsiColor
+ Z.IO.StdStream.Ansi: Red :: AnsiColor
+ Z.IO.StdStream.Ansi: White :: AnsiColor
+ Z.IO.StdStream.Ansi: Yellow :: AnsiColor
+ Z.IO.StdStream.Ansi: blinkOff :: Builder ()
+ Z.IO.StdStream.Ansi: bold :: Builder () -> Builder ()
+ Z.IO.StdStream.Ansi: boldIntensity :: Builder ()
+ Z.IO.StdStream.Ansi: clearFromCursorToLineBeginning :: Builder ()
+ Z.IO.StdStream.Ansi: clearFromCursorToLineEnd :: Builder ()
+ Z.IO.StdStream.Ansi: clearFromCursorToScreenBeginning :: Builder ()
+ Z.IO.StdStream.Ansi: clearFromCursorToScreenEnd :: Builder ()
+ Z.IO.StdStream.Ansi: clearLine :: Builder ()
+ Z.IO.StdStream.Ansi: clearScreen :: Builder ()
+ Z.IO.StdStream.Ansi: color :: AnsiColor -> Builder () -> Builder ()
+ Z.IO.StdStream.Ansi: color' :: AnsiColor -> AnsiColor -> Builder () -> Builder ()
+ Z.IO.StdStream.Ansi: colorToCode :: AnsiColor -> Word8
+ Z.IO.StdStream.Ansi: conceal :: Builder ()
+ Z.IO.StdStream.Ansi: csi :: [Int] -> Builder () -> Builder ()
+ Z.IO.StdStream.Ansi: cursorBackward :: Int -> Builder ()
+ Z.IO.StdStream.Ansi: cursorDown :: Int -> Builder ()
+ Z.IO.StdStream.Ansi: cursorDownLine :: Int -> Builder ()
+ Z.IO.StdStream.Ansi: cursorForward :: Int -> Builder ()
+ Z.IO.StdStream.Ansi: cursorUp :: Int -> Builder ()
+ Z.IO.StdStream.Ansi: cursorUpLine :: Int -> Builder ()
+ Z.IO.StdStream.Ansi: data AnsiColor
+ Z.IO.StdStream.Ansi: doubleUnderline :: Builder ()
+ Z.IO.StdStream.Ansi: faintIntensity :: Builder ()
+ Z.IO.StdStream.Ansi: hideCursor :: Builder ()
+ Z.IO.StdStream.Ansi: instance GHC.Classes.Eq Z.IO.StdStream.Ansi.AnsiColor
+ Z.IO.StdStream.Ansi: instance GHC.Classes.Ord Z.IO.StdStream.Ansi.AnsiColor
+ Z.IO.StdStream.Ansi: instance GHC.Enum.Bounded Z.IO.StdStream.Ansi.AnsiColor
+ Z.IO.StdStream.Ansi: instance GHC.Enum.Enum Z.IO.StdStream.Ansi.AnsiColor
+ Z.IO.StdStream.Ansi: instance GHC.Generics.Generic Z.IO.StdStream.Ansi.AnsiColor
+ Z.IO.StdStream.Ansi: instance GHC.Read.Read Z.IO.StdStream.Ansi.AnsiColor
+ Z.IO.StdStream.Ansi: instance GHC.Show.Show Z.IO.StdStream.Ansi.AnsiColor
+ Z.IO.StdStream.Ansi: instance Z.Data.Text.ShowT.ShowT Z.IO.StdStream.Ansi.AnsiColor
+ Z.IO.StdStream.Ansi: invert :: Builder ()
+ Z.IO.StdStream.Ansi: invertOff :: Builder ()
+ Z.IO.StdStream.Ansi: italicize :: Builder () -> Builder ()
+ Z.IO.StdStream.Ansi: italicized :: Builder ()
+ Z.IO.StdStream.Ansi: noItalicized :: Builder ()
+ Z.IO.StdStream.Ansi: noUnderline :: Builder ()
+ Z.IO.StdStream.Ansi: palette :: PaletteColor -> Builder () -> Builder ()
+ Z.IO.StdStream.Ansi: palette' :: PaletteColor -> PaletteColor -> Builder () -> Builder ()
+ Z.IO.StdStream.Ansi: rapidBlink :: Builder ()
+ Z.IO.StdStream.Ansi: reset :: Builder ()
+ Z.IO.StdStream.Ansi: resetIntensity :: Builder ()
+ Z.IO.StdStream.Ansi: restoreCursor :: Builder ()
+ Z.IO.StdStream.Ansi: reveal :: Builder ()
+ Z.IO.StdStream.Ansi: rgb :: RGBColor -> Builder () -> Builder ()
+ Z.IO.StdStream.Ansi: rgb' :: RGBColor -> RGBColor -> Builder () -> Builder ()
+ Z.IO.StdStream.Ansi: saveCursor :: Builder ()
+ Z.IO.StdStream.Ansi: scrollPageDown :: Int -> Builder ()
+ Z.IO.StdStream.Ansi: scrollPageUp :: Int -> Builder ()
+ Z.IO.StdStream.Ansi: setBackground :: AnsiColor -> Builder ()
+ Z.IO.StdStream.Ansi: setBrightBackground :: AnsiColor -> Builder ()
+ Z.IO.StdStream.Ansi: setBrightForeground :: AnsiColor -> Builder ()
+ Z.IO.StdStream.Ansi: setCursorColumn :: Int -> Builder ()
+ Z.IO.StdStream.Ansi: setCursorPosition :: Int -> Int -> Builder ()
+ Z.IO.StdStream.Ansi: setDefaultBackground :: Builder ()
+ Z.IO.StdStream.Ansi: setDefaultForeground :: Builder ()
+ Z.IO.StdStream.Ansi: setForeground :: AnsiColor -> Builder ()
+ Z.IO.StdStream.Ansi: setPaletteBackground :: PaletteColor -> Builder ()
+ Z.IO.StdStream.Ansi: setPaletteForeground :: PaletteColor -> Builder ()
+ Z.IO.StdStream.Ansi: setRGBBackground :: RGBColor -> Builder ()
+ Z.IO.StdStream.Ansi: setRGBForeground :: RGBColor -> Builder ()
+ Z.IO.StdStream.Ansi: setTitle :: Text -> Builder ()
+ Z.IO.StdStream.Ansi: sgr :: [Word8] -> Builder ()
+ Z.IO.StdStream.Ansi: showCursor :: Builder ()
+ Z.IO.StdStream.Ansi: singleUnderline :: Builder ()
+ Z.IO.StdStream.Ansi: slowBlink :: Builder ()
+ Z.IO.StdStream.Ansi: type PaletteColor = Word8
+ Z.IO.StdStream.Ansi: type RGBColor = (Word8, Word8, Word8)
+ Z.IO.StdStream.Ansi: underline :: Builder () -> Builder ()
+ Z.IO.Time: MkSystemTime :: {-# UNPACK #-} !Int64 -> {-# UNPACK #-} !Word32 -> SystemTime
+ Z.IO.Time: [systemNanoseconds] :: SystemTime -> {-# UNPACK #-} !Word32
+ Z.IO.Time: [systemSeconds] :: SystemTime -> {-# UNPACK #-} !Int64
+ Z.IO.Time: data SystemTime
+ Z.IO.Time: formatSystemTime :: TimeFormat -> SystemTime -> IO CBytes
+ Z.IO.Time: formatSystemTimeGMT :: TimeFormat -> SystemTime -> CBytes
+ Z.IO.Time: getSystemTime :: IO SystemTime
+ Z.IO.Time: mailDateFormat :: TimeFormat
+ Z.IO.Time: parseSystemTime :: TimeFormat -> CBytes -> IO SystemTime
+ Z.IO.Time: parseSystemTimeGMT :: TimeFormat -> CBytes -> SystemTime
+ Z.IO.Time: simpleDateFormat :: TimeFormat
+ Z.IO.Time: type TimeFormat = CBytes
+ Z.IO.Time: webDateFormat :: TimeFormat
+ Z.IO.UV.FFI: PassWD :: CBytes -> UID -> GID -> CBytes -> CBytes -> PassWD
+ Z.IO.UV.FFI: [passwd_gid] :: PassWD -> GID
+ Z.IO.UV.FFI: [passwd_homedir] :: PassWD -> CBytes
+ Z.IO.UV.FFI: [passwd_shell] :: PassWD -> CBytes
+ Z.IO.UV.FFI: [passwd_uid] :: PassWD -> UID
+ Z.IO.UV.FFI: [passwd_username] :: PassWD -> CBytes
+ Z.IO.UV.FFI: data PassWD
+ Z.IO.UV.FFI: getPassWD :: IO PassWD
+ Z.IO.UV.FFI: hs_uv_fs_chown :: BA# Word8 -> UID -> GID -> IO Int
+ Z.IO.UV.FFI: hs_uv_fs_chown_threaded :: BA# Word8 -> UID -> GID -> Ptr UVLoop -> IO UVSlotUnsafe
+ Z.IO.UV.FFI: hs_uv_fs_fchown :: FD -> UID -> GID -> IO Int
+ Z.IO.UV.FFI: hs_uv_fs_fchown_threaded :: FD -> UID -> GID -> Ptr UVLoop -> IO UVSlotUnsafe
+ Z.IO.UV.FFI: hs_uv_fs_lchown :: BA# Word8 -> UID -> GID -> IO Int
+ Z.IO.UV.FFI: hs_uv_fs_lchown_threaded :: BA# Word8 -> UID -> GID -> Ptr UVLoop -> IO UVSlotUnsafe
+ Z.IO.UV.FFI: hs_uv_udp_check_start :: Ptr UVHandle -> IO CInt
+ Z.IO.UV.FFI: instance GHC.Classes.Eq Z.IO.UV.FFI.PassWD
+ Z.IO.UV.FFI: instance GHC.Classes.Ord Z.IO.UV.FFI.PassWD
+ Z.IO.UV.FFI: instance GHC.Generics.Generic Z.IO.UV.FFI.PassWD
+ Z.IO.UV.FFI: instance GHC.Num.Num Z.IO.UV.FFI.GID
+ Z.IO.UV.FFI: instance GHC.Num.Num Z.IO.UV.FFI.UID
+ Z.IO.UV.FFI: instance GHC.Show.Show Z.IO.UV.FFI.PassWD
+ Z.IO.UV.FFI: instance Z.Data.JSON.Base.EncodeJSON Z.IO.UV.FFI.PassWD
+ Z.IO.UV.FFI: instance Z.Data.JSON.Base.EncodeJSON Z.IO.UV.FFI.ProcessOptions
+ Z.IO.UV.FFI: instance Z.Data.JSON.Base.EncodeJSON Z.IO.UV.FFI.ProcessStdStream
+ Z.IO.UV.FFI: instance Z.Data.JSON.Base.FromValue Z.IO.UV.FFI.PassWD
+ Z.IO.UV.FFI: instance Z.Data.JSON.Base.FromValue Z.IO.UV.FFI.ProcessOptions
+ Z.IO.UV.FFI: instance Z.Data.JSON.Base.FromValue Z.IO.UV.FFI.ProcessStdStream
+ Z.IO.UV.FFI: instance Z.Data.JSON.Base.ToValue Z.IO.UV.FFI.PassWD
+ Z.IO.UV.FFI: instance Z.Data.JSON.Base.ToValue Z.IO.UV.FFI.ProcessOptions
+ Z.IO.UV.FFI: instance Z.Data.JSON.Base.ToValue Z.IO.UV.FFI.ProcessStdStream
+ Z.IO.UV.FFI: instance Z.Data.Text.ShowT.ShowT Z.IO.UV.FFI.PassWD
+ Z.IO.UV.FFI: type FD = CInt
+ Z.IO.UV.FFI: uv_chdir :: BA# Word8 -> IO CInt
+ Z.IO.UV.FFI: uv_cwd :: MBA# Word8 -> MBA# CSize -> IO CInt
+ Z.IO.UV.FFI: uv_os_free_passwd :: MBA# PassWD -> IO ()
+ Z.IO.UV.FFI: uv_os_get_passwd :: MBA# PassWD -> IO CInt
+ Z.IO.UV.FFI: uv_os_homedir :: MBA# Word8 -> MBA# CSize -> IO CInt
+ Z.IO.UV.FFI: uv_os_tmpdir :: MBA# Word8 -> MBA# CSize -> IO CInt
- Z.IO.Environment: OSName :: Text -> Text -> Text -> Text -> OSName
+ Z.IO.Environment: OSName :: CBytes -> CBytes -> CBytes -> CBytes -> OSName
- Z.IO.Environment: [os_machine] :: OSName -> Text
+ Z.IO.Environment: [os_machine] :: OSName -> CBytes
- Z.IO.Environment: [os_release] :: OSName -> Text
+ Z.IO.Environment: [os_release] :: OSName -> CBytes
- Z.IO.Environment: [os_sysname] :: OSName -> Text
+ Z.IO.Environment: [os_sysname] :: OSName -> CBytes
- Z.IO.Environment: [os_version] :: OSName -> Text
+ Z.IO.Environment: [os_version] :: OSName -> CBytes
- Z.IO.FileSystem: getFileFD :: File -> IO UVFD
+ Z.IO.FileSystem: getFileFD :: File -> IO FD
- Z.IO.FileSystem.Threaded: getFileTFD :: FileT -> IO UVFD
+ Z.IO.FileSystem.Threaded: getFileTFD :: FileT -> IO FD
- Z.IO.Logger: Logger :: (Builder () -> IO ()) -> IO () -> IO () -> IO (Maybe (Builder ())) -> LogFormatter -> Logger
+ Z.IO.Logger: Logger :: (Builder () -> IO ()) -> IO () -> IO () -> IO (Builder ()) -> LogFormatter -> Logger
- Z.IO.Logger: LoggerConfig :: {-# UNPACK #-} !Int -> {-# UNPACK #-} !Int -> Bool -> Bool -> Bool -> LoggerConfig
+ Z.IO.Logger: LoggerConfig :: {-# UNPACK #-} !Int -> {-# UNPACK #-} !Int -> Bool -> LoggerConfig
- Z.IO.Logger: [loggerTSCache] :: Logger -> IO (Maybe (Builder ()))
+ Z.IO.Logger: [loggerTSCache] :: Logger -> IO (Builder ())
- Z.IO.Logger: otherLevel :: HasCallStack => Builder () -> Bool -> Builder () -> IO ()
+ Z.IO.Logger: otherLevel :: HasCallStack => Level -> Bool -> Builder () -> IO ()
- Z.IO.Logger: otherLevelTo :: HasCallStack => Logger -> Builder () -> Bool -> Builder () -> IO ()
+ Z.IO.Logger: otherLevelTo :: HasCallStack => Logger -> Level -> Bool -> Builder () -> IO ()
- Z.IO.Logger: type LogFormatter = Maybe (Builder ()) " data/time string" -> Builder () " log level" -> Builder () " log content" -> CallStack " call stack trace" -> Builder ()
+ Z.IO.Logger: type LogFormatter = Builder () " data/time string" -> Level " log level" -> Builder () " log content" -> CallStack " call stack trace" -> Builder ()
- Z.IO.Process: ProcessInherit :: UVFD -> ProcessStdStream
+ Z.IO.Process: ProcessInherit :: FD -> ProcessStdStream
- Z.IO.StdStream: getStdStreamFD :: StdStream -> IO UVFD
+ Z.IO.StdStream: getStdStreamFD :: StdStream -> IO FD
- Z.IO.UV.FFI: OSName :: Text -> Text -> Text -> Text -> OSName
+ Z.IO.UV.FFI: OSName :: CBytes -> CBytes -> CBytes -> CBytes -> OSName
- Z.IO.UV.FFI: ProcessInherit :: UVFD -> ProcessStdStream
+ Z.IO.UV.FFI: ProcessInherit :: FD -> ProcessStdStream
- Z.IO.UV.FFI: [os_machine] :: OSName -> Text
+ Z.IO.UV.FFI: [os_machine] :: OSName -> CBytes
- Z.IO.UV.FFI: [os_release] :: OSName -> Text
+ Z.IO.UV.FFI: [os_release] :: OSName -> CBytes
- Z.IO.UV.FFI: [os_sysname] :: OSName -> Text
+ Z.IO.UV.FFI: [os_sysname] :: OSName -> CBytes
- Z.IO.UV.FFI: [os_version] :: OSName -> Text
+ Z.IO.UV.FFI: [os_version] :: OSName -> CBytes
- Z.IO.UV.FFI: hs_uv_fileno :: Ptr UVHandle -> IO UVFD
+ Z.IO.UV.FFI: hs_uv_fileno :: Ptr UVHandle -> IO FD
- Z.IO.UV.FFI: hs_uv_fs_close :: UVFD -> IO Int
+ Z.IO.UV.FFI: hs_uv_fs_close :: FD -> IO Int
- Z.IO.UV.FFI: hs_uv_fs_close_threaded :: UVFD -> Ptr UVLoop -> IO UVSlotUnsafe
+ Z.IO.UV.FFI: hs_uv_fs_close_threaded :: FD -> Ptr UVLoop -> IO UVSlotUnsafe
- Z.IO.UV.FFI: hs_uv_fs_fchmod :: UVFD -> FileMode -> IO Int
+ Z.IO.UV.FFI: hs_uv_fs_fchmod :: FD -> FileMode -> IO Int
- Z.IO.UV.FFI: hs_uv_fs_fchmod_threaded :: UVFD -> FileMode -> Ptr UVLoop -> IO UVSlotUnsafe
+ Z.IO.UV.FFI: hs_uv_fs_fchmod_threaded :: FD -> FileMode -> Ptr UVLoop -> IO UVSlotUnsafe
- Z.IO.UV.FFI: hs_uv_fs_fdatasync :: UVFD -> IO Int
+ Z.IO.UV.FFI: hs_uv_fs_fdatasync :: FD -> IO Int
- Z.IO.UV.FFI: hs_uv_fs_fdatasync_threaded :: UVFD -> Ptr UVLoop -> IO UVSlotUnsafe
+ Z.IO.UV.FFI: hs_uv_fs_fdatasync_threaded :: FD -> Ptr UVLoop -> IO UVSlotUnsafe
- Z.IO.UV.FFI: hs_uv_fs_fstat :: UVFD -> Ptr FStat -> IO Int
+ Z.IO.UV.FFI: hs_uv_fs_fstat :: FD -> Ptr FStat -> IO Int
- Z.IO.UV.FFI: hs_uv_fs_fstat_threaded :: UVFD -> Ptr FStat -> Ptr UVLoop -> IO UVSlotUnsafe
+ Z.IO.UV.FFI: hs_uv_fs_fstat_threaded :: FD -> Ptr FStat -> Ptr UVLoop -> IO UVSlotUnsafe
- Z.IO.UV.FFI: hs_uv_fs_fsync :: UVFD -> IO Int
+ Z.IO.UV.FFI: hs_uv_fs_fsync :: FD -> IO Int
- Z.IO.UV.FFI: hs_uv_fs_fsync_threaded :: UVFD -> Ptr UVLoop -> IO UVSlotUnsafe
+ Z.IO.UV.FFI: hs_uv_fs_fsync_threaded :: FD -> Ptr UVLoop -> IO UVSlotUnsafe
- Z.IO.UV.FFI: hs_uv_fs_ftruncate :: UVFD -> Int64 -> IO Int
+ Z.IO.UV.FFI: hs_uv_fs_ftruncate :: FD -> Int64 -> IO Int
- Z.IO.UV.FFI: hs_uv_fs_ftruncate_threaded :: UVFD -> Int64 -> Ptr UVLoop -> IO UVSlotUnsafe
+ Z.IO.UV.FFI: hs_uv_fs_ftruncate_threaded :: FD -> Int64 -> Ptr UVLoop -> IO UVSlotUnsafe
- Z.IO.UV.FFI: hs_uv_fs_futime :: UVFD -> Double -> Double -> IO Int
+ Z.IO.UV.FFI: hs_uv_fs_futime :: FD -> Double -> Double -> IO Int
- Z.IO.UV.FFI: hs_uv_fs_futime_threaded :: UVFD -> Double -> Double -> Ptr UVLoop -> IO UVSlotUnsafe
+ Z.IO.UV.FFI: hs_uv_fs_futime_threaded :: FD -> Double -> Double -> Ptr UVLoop -> IO UVSlotUnsafe
- Z.IO.UV.FFI: hs_uv_fs_open :: BA# Word8 -> FileFlag -> FileMode -> IO UVFD
+ Z.IO.UV.FFI: hs_uv_fs_open :: BA# Word8 -> FileFlag -> FileMode -> IO FD
- Z.IO.UV.FFI: hs_uv_fs_read :: UVFD -> Ptr Word8 -> Int -> Int64 -> IO Int
+ Z.IO.UV.FFI: hs_uv_fs_read :: FD -> Ptr Word8 -> Int -> Int64 -> IO Int
- Z.IO.UV.FFI: hs_uv_fs_read_threaded :: UVFD -> Ptr Word8 -> Int -> Int64 -> Ptr UVLoop -> IO UVSlotUnsafe
+ Z.IO.UV.FFI: hs_uv_fs_read_threaded :: FD -> Ptr Word8 -> Int -> Int64 -> Ptr UVLoop -> IO UVSlotUnsafe
- Z.IO.UV.FFI: hs_uv_fs_write :: UVFD -> Ptr Word8 -> Int -> Int64 -> IO Int
+ Z.IO.UV.FFI: hs_uv_fs_write :: FD -> Ptr Word8 -> Int -> Int64 -> IO Int
- Z.IO.UV.FFI: hs_uv_fs_write_threaded :: UVFD -> Ptr Word8 -> Int -> Int64 -> Ptr UVLoop -> IO UVSlotUnsafe
+ Z.IO.UV.FFI: hs_uv_fs_write_threaded :: FD -> Ptr Word8 -> Int -> Int64 -> Ptr UVLoop -> IO UVSlotUnsafe
- Z.IO.UV.FFI: hs_uv_pipe_open :: Ptr UVHandle -> UVFD -> IO CInt
+ Z.IO.UV.FFI: hs_uv_pipe_open :: Ptr UVHandle -> FD -> IO CInt
- Z.IO.UV.FFI: hs_uv_spawn :: Ptr UVLoop -> MBA# ProcessOptions -> BA# Word8 -> BA# Word8 -> CSize -> BA# Word8 -> CSsize -> BA# Word8 -> MBA# ProcessStdStream -> IO Int
+ Z.IO.UV.FFI: hs_uv_spawn :: Ptr UVLoop -> MBA# ProcessOptions -> BA# Word8 -> BAArray# Word8 -> Int -> BAArray# Word8 -> Int -> BA# Word8 -> MBA# ProcessStdStream -> IO Int
- Z.IO.UV.FFI: hs_uv_tcp_open :: Ptr UVHandle -> UVFD -> IO CInt
+ Z.IO.UV.FFI: hs_uv_tcp_open :: Ptr UVHandle -> FD -> IO CInt
- Z.IO.UV.FFI: hs_uv_udp_check_alloc :: Ptr UVHandle -> IO (Ptr UVHandle)
+ Z.IO.UV.FFI: hs_uv_udp_check_alloc :: IO (Ptr UVHandle)
- Z.IO.UV.FFI: hs_uv_udp_check_init :: Ptr UVHandle -> IO CInt
+ Z.IO.UV.FFI: hs_uv_udp_check_init :: Ptr UVHandle -> Ptr UVHandle -> IO CInt
- Z.IO.UV.FFI: uv_guess_handle :: UVFD -> IO UVHandleType
+ Z.IO.UV.FFI: uv_guess_handle :: FD -> IO UVHandleType
- Z.IO.UV.FFI: uv_udp_open :: Ptr UVHandle -> UVFD -> IO CInt
+ Z.IO.UV.FFI: uv_udp_open :: Ptr UVHandle -> FD -> IO CInt
- Z.IO.UV.UVStream: getUVStreamFD :: HasCallStack => UVStream -> IO UVFD
+ Z.IO.UV.UVStream: getUVStreamFD :: HasCallStack => UVStream -> IO FD

Files

ChangeLog.md view
@@ -1,5 +1,15 @@ # Revision history for Z-IO +## 0.1.6.0  -- 2020-10-17++* Fix a bug affects udp exception raising(simliar to the one fixed in 0.1.5.2).+* Add `Z.IO.StdStream.Ansi` module, add a default colored logger.+* Add `Z.IO.Time` module, for fast time parsing and formatting.+* Add `Z.IO.FileSystem.FilePath` module for file path manipulations.+* Add `getCWD`, `chDir`, `getHomeDir`, `getTempDir`, `getPassWD` to `Z.IO.Environment`.+* Add `chown`, `fchown`, `lchown` to `Z.IO.FileSystem` and `Z.IO.FileSystem.Threaded`.+* Rename `UVFD` to `FD` accross module.+ ## 0.1.5.2  -- 2020-10-13  * Fix windows dist(add `fs-fd-hash-inl.h` to other-source-files).
LICENSE view
@@ -100,3 +100,27 @@   - android-ifaddrs.h, android-ifaddrs.c, copyright Berkeley Software Design     Inc, Kenneth MacKay and Emergya (Cloud4all, FP7/2007-2013, grant agreement     n° 289016). Three clause BSD license.++--------------------------------------------------------------------------------++MIT License++Copyright (c) 2020 Leonard Iklé++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
README.md view
@@ -16,7 +16,6 @@ ## Requirements  * A working haskell compiler system, GHC(>=8.6), cabal-install(>=2.4), hsc2hs.- * Tests need  [hspec-discover](https://hackage.haskell.org/package/hspec-discover).  ## Example usage
Z-IO.cabal view
@@ -1,6 +1,6 @@ cabal-version:              2.4 name:                       Z-IO-version:                    0.1.5.2+version:                    0.1.6.0 synopsis:                   Simple and high performance IO toolkit for Haskell description:                Simple and high performance IO toolkit for Haskell, including                             file system, network, ipc and more!@@ -30,6 +30,9 @@                             third_party/zlib/zutil.h                             third_party/zlib/gzguts.h +                            -- cwalk header+                            third_party/cwalk/include/cwalk.h+                             -- libuv C sources                             third_party/libuv/include/uv.h                             third_party/libuv/include/uv/aix.h@@ -76,13 +79,14 @@     location: git://github.com/haskell-Z/Z-IO.git  flag no-pkg-config-    description: Don't use pkg-config to check for library dependences+    description: Don't use pkg-config to check for library dependences(on platforms other than linux, win and osx)     default: False     manual: True  library     exposed-modules:        Z.IO                             Z.IO.FileSystem+                            Z.IO.FileSystem.FilePath                             Z.IO.FileSystem.Threaded                             Z.IO.Network.SocketAddr                             Z.IO.Network.DNS@@ -97,6 +101,8 @@                             Z.IO.Process                             Z.IO.Resource                             Z.IO.StdStream+                            Z.IO.StdStream.Ansi+                            Z.IO.Time                              Z.IO.UV.Errno                             Z.IO.UV.FFI@@ -108,11 +114,12 @@                              Z.Compression.Zlib -    build-depends:          base                    >= 4.12 && <5.0-                          , Z-Data                  >= 0.1.6.1 && <0.1.7+    build-depends:          base                    >= 4.12 && < 5.0+                          , Z-Data                  >= 0.1.7.2 && < 0.1.8                           , primitive               >= 0.7.1 && < 0.7.2                           , exceptions              == 0.10.*                           , time                    >= 1.8 && < 2.0+                          , unix-time               >= 0.4.7 && < 0.5                           , stm                     == 2.5.*      default-language:       Haskell2010@@ -157,14 +164,17 @@      include-dirs:           include                             third_party/zlib+                            third_party/cwalk/include      includes:               hs_uv.h                             zlib.h                             zconf.h+                            hs_cwalk.h      install-includes:       hs_uv.h                              zlib.h                             zconf.h+                            hs_cwalk.h      cc-options:             -march=native     c-sources:              cbits/hs_uv_base.c@@ -174,6 +184,9 @@                             cbits/hs_uv_process.c                             cbits/hs_uv_udp.c                             cbits/hs_zlib.c+                            cbits/hs_cwalk.c++                            third_party/cwalk/src/cwalk.c                              third_party/zlib/adler32.c                             third_party/zlib/compress.c
Z/IO/Buffered.hs view
@@ -7,7 +7,7 @@ Stability   : experimental Portability : non-portable -This module provide buffered IO interface.+This module provides buffered IO interface and basic streaming utilities.  -} @@ -15,7 +15,7 @@   ( -- * Input & Output device     Input(..), Output(..)     -- * Buffered Input-  , BufferedInput+  , BufferedInput, bufInput   , newBufferedInput   , newBufferedInput'   , readBuffer@@ -26,7 +26,7 @@   , readLine, readLine'   , readAll, readAll'     -- * Buffered Output-  , BufferedOutput+  , BufferedOutput, bufOutput   , newBufferedOutput   , newBufferedOutput'   , writeBuffer
Z/IO/Environment.hs view
@@ -7,7 +7,7 @@ Stability   : experimental Portability : non-portable -This module provide methods for retrieving various environment infomation. There's no encoding guarantee about these information, if you want textual representation, use UTF8 assumption is recommended. i.e. use "Z.Data.Text.validate".+This module provide methods for retrieving various environment infomation. There's no encoding guarantee about these information, if you want textual representation, UTF8 assumption is recommended. i.e. use 'Z.Data.Text.validate'.  -} module Z.IO.Environment@@ -18,6 +18,7 @@   , getEnv, getEnv'   , setEnv, unsetEnv     -- * other environment infos+  , getCWD, chDir, getHomeDir, getTempDir   , getResUsage   , getResidentSetMemory   , getUpTime@@ -26,6 +27,7 @@   , getPID, getPPID   , getHostname   , getOSName, OSName(..)+  , getPassWD, PassWD(..), UID, GID   , getRandom, getRandomT   ) where @@ -177,6 +179,67 @@         uvm <- getUVManager         withUVRequest_ uvm (hs_uv_random_threaded p (fromIntegral siz) 0)     return v++-- | Gets the current working directory.+--+getCWD :: HasCallStack => IO CBytes+getCWD = go 512+  where+    go siz = do+        (siz', (v, r))<- withPrimUnsafe siz $ \ p_siz ->+            allocCBytesUnsafe siz $ \ p_v ->+                uv_cwd p_v p_siz+        case r of+            UV_ENOBUFS -> go siz'+            _ -> do+                throwUVIfMinus_ (return r)+                return v++-- | Changes the current working directory.+--+chDir :: HasCallStack => CBytes -> IO ()+chDir p = throwUVIfMinus_ (withCBytesUnsafe p $ \ pp -> uv_chdir pp)++-- | Gets the current user’s home directory.+--+-- On Windows, first checks the USERPROFILE environment variable using GetEnvironmentVariableW().+-- If USERPROFILE is not set, GetUserProfileDirectoryW() is called.+-- On all other operating systems, first checks the HOME environment variable using getenv(3).+-- If HOME is not set, getpwuid_r(3) is called.+--+-- Warning 'getHomeDir' is not thread safe.+getHomeDir :: HasCallStack => IO CBytes+getHomeDir = go 512+  where+    go siz = do+        (siz', (v, r))<- withPrimUnsafe siz $ \ p_siz ->+            allocCBytesUnsafe siz $ \ p_v ->+                uv_os_homedir p_v p_siz+        case r of+            UV_ENOBUFS -> go siz'+            _ -> do+                throwUVIfMinus_ (return r)+                return v++-- | Gets the temp directory.+--+-- On Windows, uses GetTempPathW(). On all other operating systems,+-- uses the first environment variable found in the ordered list TMPDIR, TMP, TEMP, and TEMPDIR.+-- If none of these are found, the path @\/tmp@ is used, or, on Android, @\/data\/local\/tmp@ is used.+--+-- Warning 'getHomeDir' is not thread safe.+getTempDir :: HasCallStack => IO CBytes+getTempDir = go 512+  where+    go siz = do+        (siz', (v, r))<- withPrimUnsafe siz $ \ p_siz ->+            allocCBytesUnsafe siz $ \ p_v ->+                uv_os_tmpdir p_v p_siz+        case r of+            UV_ENOBUFS -> go siz'+            _ -> do+                throwUVIfMinus_ (return r)+                return v  -------------------------------------------------------------------------------- 
Z/IO/FileSystem.hs view
@@ -36,6 +36,7 @@   , utime, futime, lutime   , link, symlink   , readlink, realpath+  , chown, fchown, lchown   -- * opening constant   -- ** AccessMode   , AccessMode@@ -118,17 +119,17 @@ -- Implict offset interface is provided by 'Input' \/ 'Output' instances. -- Explict offset interface is provided by 'readFile' \/ 'writeFile'. ---data File =  File  {-# UNPACK #-} !UVFD      -- ^ the file+data File =  File  {-# UNPACK #-} !FD      -- ^ the file                    {-# UNPACK #-} !(IORef Bool)  -- ^ closed flag  -- | Return File fd.-getFileFD :: File -> IO UVFD+getFileFD :: File -> IO FD getFileFD (File fd closedRef) = do     closed <- readIORef closedRef     if closed then throwECLOSED else return fd  -- | If fd is -1 (closed), throw 'ResourceVanished' ECLOSED.-checkFileClosed :: HasCallStack => File -> (UVFD -> IO a) -> IO a+checkFileClosed :: HasCallStack => File -> (FD -> IO a) -> IO a checkFileClosed (File fd closedRef) f = do     closed <- readIORef closedRef     if closed then throwECLOSED else f fd@@ -466,3 +467,15 @@                 throwUVIfMinus (hs_uv_fs_realpath p p'))         (hs_uv_fs_readlink_cleanup . fst)         (fromCString . fst)++-- | Equivalent to <http://linux.die.net/man/2/chown chown(2)>.+chown :: HasCallStack => CBytes -> UID -> GID -> IO ()+chown path uid gid = throwUVIfMinus_ . withCBytesUnsafe path $ \ p -> hs_uv_fs_chown p uid gid++-- | Equivalent to <http://linux.die.net/man/2/fchown fchown(2)>.+fchown :: HasCallStack => File -> UID -> GID -> IO ()+fchown uvf uid gid = checkFileClosed uvf $ \ fd -> throwUVIfMinus_ $ hs_uv_fs_fchown fd uid gid++-- | Equivalent to <http://linux.die.net/man/2/lchown lchown(2)>.+lchown :: HasCallStack => CBytes -> UID -> GID -> IO ()+lchown path uid gid = throwUVIfMinus_ . withCBytesUnsafe path $ \ p -> hs_uv_fs_lchown p uid gid
+ Z/IO/FileSystem/FilePath.hsc view
@@ -0,0 +1,628 @@+{-|+Module      : Z.IO.FileSystem.FilePath+Description : file path toolbox+Copyright   : (c) Dong Han, 2017~2019+License     : BSD+Maintainer  : winterland1989@gmail.com+Stability   : experimental+Portability : non-portable++This module provides file path manipulations using <https://likle.github.io/cwalk/ cwalk>,+both unix and window style path is accepted. Default style is choosen during compile time,+but can be changed during runtime.+-}++module Z.IO.FileSystem.FilePath+ ( -- * Paths+    splitBaseName, changeBaseName+  , splitRoot, changeRoot+  , splitSegments+  , isAbsolute+  , isRelative+  , join+  , concat+  , normalize+  , intersection+  , absolute+  , relative+  -- * Extensions+  , splitExtension, changeExtension+  -- * Path Style+  , PathStyle(..)+  , pathStyle+  , getPathStyle, setPathStyle+  , pathSeparator, searchPathSeparator, extensionSeparator+  -- * Search path+  , getSearchPath+ ) where+++import           Data.Word+import qualified Data.List as List+import           GHC.Generics+import           Z.Data.CBytes (CBytes(CB), allocCBytesUnsafe, withCBytesUnsafe, withCBytesListUnsafe)+import qualified Z.Data.CBytes as CB+import qualified Z.Data.Text.ShowT as T+import           Z.Data.JSON (FromValue, ToValue, EncodeJSON)+import qualified Z.Data.Vector.Base as V+import qualified Z.Data.Vector      as V+import           Z.Foreign+import           Z.IO.Environment   (getEnv')+import Prelude hiding (concat)++#include "hs_cwalk.h"+-- \+#define BACKSLASH 92+-- /+#define SLASH 47+-- "+#define DOUBLE_QUOTE 34+-- :+#define COLON 58+-- ;+#define SEMICOLON 59+-- .+#define DOT 46++#define BUF_EXT_SIZ  4++data PathStyle = WindowsStyle   -- ^ Use backslashes as a separator and volume for the root.+               | UnixStyle      -- ^ Use slashes as a separator and a slash for the root.+    deriving (Show, Eq, Ord, Generic)+    deriving anyclass (T.ShowT, FromValue, ToValue, EncodeJSON)++enumToPathStyle_ :: CInt -> PathStyle+enumToPathStyle_ (#const CWK_STYLE_WINDOWS) = WindowsStyle+enumToPathStyle_ _ = UnixStyle++pathStyleToEnum_ :: PathStyle -> CInt+pathStyleToEnum_ WindowsStyle = (#const CWK_STYLE_WINDOWS)+pathStyleToEnum_ _ = (#const CWK_STYLE_UNIX)++-- | Guesses the path style.+--+-- This function guesses the path style based on a submitted path-string.+-- The guessing will look at the root and the type of slashes contained in the path+-- and return the style which is more likely used in the path. The algorithm checks the following:+--+-- * If the root is longer than 1 character      -> WINDOWS+-- * If the first separator is a backslash       -> WINDOWS+-- * If the first separator is a slash           -> UNIX+-- * If the last segment starts with a dot       -> UNIX+-- * If the last segment contains a dot          -> WINDOWS+-- * If nothing was found to determine the style -> UNIX+--+pathStyle :: CBytes -> IO PathStyle+pathStyle p = enumToPathStyle_ <$> withCBytesUnsafe p cwk_path_guess_style++-- | Gets the path style currently using.+getPathStyle :: IO PathStyle+getPathStyle = enumToPathStyle_ <$> cwk_path_get_style++-- | Configures which path style is used afterwards.+--+-- This function configures which path style is used.+-- call to this function is only required if a non-native behaviour is required. +-- The style defaults to 'WindowsStyle' on windows builds and to 'UnixStyle' otherwise.+setPathStyle :: PathStyle -> IO ()+setPathStyle = cwk_path_set_style . pathStyleToEnum_++-- | Get the character that separates directories. +pathSeparator :: IO [Word8]+pathSeparator = do+    s <- getPathStyle+    case s of UnixStyle -> return [(#const SLASH)]+              _         -> return [(#const SLASH), (#const BACKSLASH)]++-- | The character that is used to separate the entries in the $PATH environment variable.+--+-- * Windows: searchPathSeparator is ASCII @;@+-- * Unix:    searchPathSeparator is ASCII @:@+--+searchPathSeparator :: IO Word8+searchPathSeparator = do+    s <- getPathStyle+    case s of UnixStyle -> return (#const COLON)+              _         -> return (#const SEMICOLON)++-- | File extension character+--+-- ExtSeparator is ASCII @.@+extensionSeparator :: Word8+extensionSeparator = #const DOT++-- | Get the basename of a file path.+--+-- The basename is the last segment of a path. For instance, @logs@ is the basename of the path @\/var\/logs@.+--+-- +--------------------------+---------------++-- |     Path                 |   Basename    |+-- +--------------------------+---------------++-- | \/my\/path.txt           | path.txt      |+-- +--------------------------+---------------++-- | \/my\/path.txt\/         | path.txt      |+-- +--------------------------+---------------++-- | \/my\/path.txt\/\/\/\/   | path.txt      |+-- +--------------------------+---------------++-- | file_name                | file_name     |+-- +--------------------------+---------------++-- | ..                       | ..            |+-- +--------------------------+---------------++-- | .                        | .             |+-- +--------------------------+---------------++-- | \/                       | ""            |+-- +--------------------------+---------------++-- | C:\path\test.txt         | test.txt      |+-- +--------------------------+---------------++--+splitBaseName :: CBytes+              -> IO (CBytes, CBytes)    -- ^ return (dir, basename)+{-# INLINABLE splitBaseName #-}+splitBaseName p = do+    (off, len) <- withCBytesUnsafe p $ \ pp ->+        allocPrimUnsafe $ \ poff ->+        hs_cwk_path_get_basename pp poff+    if len == 0+    then return (p, CB.empty)+    else return ( CB (V.PrimVector (CB.rawPrimArray p) 0 off)+                , CB (V.PrimVector (CB.rawPrimArray p) off len))+++-- | Changes the basename of a file path.+--+-- @+-- > changeBaseName  "foo\/bar.txt" "qux.png"+-- "foo\/qux.png"+-- @+--+changeBaseName :: CBytes+               -> CBytes   -- ^ new base name+               -> IO CBytes+changeBaseName p b = do+    let l = CB.length p + CB.length b + (#const BUF_EXT_SIZ)+    (p', _) <- withCBytesUnsafe p $ \ pp ->+        withCBytesUnsafe b $ \ pb ->+            allocCBytesUnsafe l $ \ pbuf ->+                cwk_path_change_basename pp pb pbuf (fromIntegral l)+    return p'++-- | Determines the root of a path.+--+-- This function determines the root of a path by finding it’s length.+-- The root comes before the first segment of the path.+-- For example, @C:\\@ is the root of @C:\\folder\\file.txt@.+-- It always starts at the submitted path. If the path has no root, 'CB.empty' will be returned.+--+-- +---------+--------------------------+----------------------++-- | Style   | Path                     | Root                 |+-- +---------+--------------------------+----------------------++-- | UNIX    | \/test\/                 | \/                   |+-- +---------+--------------------------+----------------------++-- | UNIX    | test.txt                 | ""                   |+-- +---------+--------------------------+----------------------++-- | UNIX    | C:\\test.txt             | ""                   |+-- +---------+--------------------------+----------------------++-- | UNIX    | \\folder\\               | ""                   |+-- +---------+--------------------------+----------------------++-- | WINDOWS | \/test.txt               | \/                   |+-- +---------+--------------------------+----------------------++-- | WINDOWS | \\test.txt               | \\                   |+-- +---------+--------------------------+----------------------++-- | WINDOWS | C:\\test.txt             | C:\\                 |+-- +---------+--------------------------+----------------------++-- | WINDOWS | \\\\server\\folder\\data | \\\\server\\folder\\ |+-- +---------+--------------------------+----------------------++-- | WINDOWS | \\\\.\\folder\\data      | \\\\.\\              |+-- +---------+--------------------------+----------------------++-- | WINDOWS | \\\\?\\folder\\data      | \\\\?\\              |+-- +---------+--------------------------+----------------------++-- | WINDOWS | C:test.txt               | C:                   |+-- +---------+--------------------------+----------------------++-- | WINDOWS | ..\\hello\\world.txt     | ""                   |+-- +---------+--------------------------+----------------------++--+splitRoot :: CBytes +          -> IO (CBytes, CBytes) -- ^ return (root, rest path)+{-# INLINABLE splitRoot #-}+splitRoot p = do+    off <- withCBytesUnsafe p hs_cwk_path_get_root+    if off == 0+    then return (CB.empty, p)+    else return ( CB (V.PrimVector (CB.rawPrimArray p) 0 off)+                , CB (V.PrimVector (CB.rawPrimArray p) off (CB.length p - off)))++-- | Changes the root of a file path.+--+-- @+-- > changeBaseName "C:\\\\test.txt" "D:\\\\"    -- windows style+-- "D:\\test.txt"+-- @+--+changeRoot :: CBytes+           -> CBytes   -- ^ new base name+           -> IO CBytes+changeRoot p r = do+    let l = CB.length p + CB.length r + (#const BUF_EXT_SIZ)+    (p', _) <- withCBytesUnsafe p $ \ pp ->+        withCBytesUnsafe r $ \ pr ->+            allocCBytesUnsafe l $ \ pbuf ->+                cwk_path_change_root pp pr pbuf (fromIntegral l)+    return p'++-- | Split file path into (root, segments, basename) tuple.+--+-- Root and basename may be empty, segments are separated by 'pathSeparator' and never be empty if any.+splitSegments :: CBytes+              -> IO (CBytes, [CBytes], CBytes) -- ^ return (root, segments, basename)+{-# INLINABLE splitSegments #-}+splitSegments p = do+    (root, rest) <- splitRoot p+    (CB seg, basename) <- splitBaseName rest+    sty <- getPathStyle+    let segs = case sty of+            UnixStyle ->+                V.splitWith (== (#const SLASH)) seg+            _ ->+                V.splitWith (\ w -> w == (#const SLASH) || w == (#const BACKSLASH)) seg+    return (root, (map CB (filter (not . V.null) segs)), basename)++-- | Determine whether the path is absolute or not.+--+-- This function checks whether the path is an absolute (fully qualified) path or not.+-- A path is considered to be absolute if the root ends with a separator.+--+-- +---------+--------------------------+-----------++-- | Style   | Path                     | Result    |+-- +---------+--------------------------+-----------++-- | UNIX    | \/test\/                 | True      |+-- +---------+--------------------------+-----------++-- | UNIX    | test.txt                 | False     |+-- +---------+--------------------------+-----------++-- | UNIX    | C:\\test.txt             | False     |+-- +---------+--------------------------+-----------++-- | UNIX    | \\folder\\               | False     |+-- +---------+--------------------------+-----------++-- | WINDOWS | \/test.txt               | True      |+-- +---------+--------------------------+-----------++-- | WINDOWS | \\test.txt               | True      |+-- +---------+--------------------------+-----------++-- | WINDOWS | C:\\test.txt             | True      |+-- +---------+--------------------------+-----------++-- | WINDOWS | \\\\server\\folder\\data | True      |+-- +---------+--------------------------+-----------++-- | WINDOWS | \\\\.\\folder\\data      | True      |+-- +---------+--------------------------+-----------++-- | WINDOWS | \\\\?\\folder\\data      | True      |+-- +---------+--------------------------+-----------++-- | WINDOWS | C:test.txt               | False     |+-- +---------+--------------------------+-----------++-- | WINDOWS | ..\\hello\\world.txt     | False     |+-- +---------+--------------------------+-----------++--+isAbsolute :: CBytes -> IO Bool+isAbsolute p = (/=0) <$> withCBytesUnsafe p cwk_path_is_absolute++-- | Determine whether the path is relative or not.+--+-- This function checks whether the path is a relative path or not.+-- A path is considered to be relative if the root does not end with a separator.+--+-- +---------+--------------------------+-----------++-- | Style   | Path                     | Result    |+-- +---------+--------------------------+-----------++-- | UNIX    | \/test\/                 | False     |+-- +---------+--------------------------+-----------++-- | UNIX    | test.txt                 | True      |+-- +---------+--------------------------+-----------++-- | UNIX    | C:\\test.txt             | True      |+-- +---------+--------------------------+-----------++-- | UNIX    | \\folder\\               | True      |+-- +---------+--------------------------+-----------++-- | WINDOWS | \/test.txt               | False     |+-- +---------+--------------------------+-----------++-- | WINDOWS | \\test.txt               | False     |+-- +---------+--------------------------+-----------++-- | WINDOWS | C:\\test.txt             | False     |+-- +---------+--------------------------+-----------++-- | WINDOWS | \\\\server\\folder\\data | False     |+-- +---------+--------------------------+-----------++-- | WINDOWS | \\\\.\\folder\\data      | False     |+-- +---------+--------------------------+-----------++-- | WINDOWS | \\\\?\\folder\\data      | False     |+-- +---------+--------------------------+-----------++-- | WINDOWS | C:test.txt               | True      |+-- +---------+--------------------------+-----------++-- | WINDOWS | ..\\hello\\world.txt     | True      |+-- +---------+--------------------------+-----------++--+isRelative :: CBytes -> IO Bool+isRelative p = (/=0) <$> withCBytesUnsafe p cwk_path_is_relative++-- | Joins two paths together.+--+-- +---------+-----------------------+---------------------------+--------------------------------------++-- | Style   | Path A                | Path B                    | Result                               |+-- +---------+-----------------------+---------------------------+--------------------------------------++-- | UNIX    | hello\/there          | ..\/world                 | hello\/world                         |+-- +---------+-----------------------+---------------------------+--------------------------------------++-- | UNIX    | \/first               | \/second                  | \/first\/second                      |+-- +---------+-----------------------+---------------------------+--------------------------------------++-- | UNIX    | hello                 | ..                        | .                                    |+-- +---------+-----------------------+---------------------------+--------------------------------------++-- | UNIX    | hello\/there          | ..                        | hello                                |+-- +---------+-----------------------+---------------------------+--------------------------------------++-- | UNIX    | hello                 | there                     | hello\/there                         |+-- +---------+-----------------------+---------------------------+--------------------------------------++-- | WINDOWS | this\\                | C:\\..\\..\\is\\a\\test\\ | is\\a\\test                          |+-- +---------+-----------------------+---------------------------+--------------------------------------++-- | WINDOWS | C:\\this\\path        | C:\\is\\a\\test\\         | C:\\this\\path\\C:\\is\\a\\test      |+-- +---------+-----------------------+---------------------------+--------------------------------------++-- | WINDOWS | C:\\this\\path        | C:\\..\\is\\a\\test\\     | C:\\this\\path\\is\\a\\test          |+-- +---------+-----------------------+---------------------------+--------------------------------------++-- | WINDOWS | \\\\s1\\unc\\path     | \\\\s2\\unc\\pa           | \\\\s1\\unc\\pa\\s2\\unc\\path       |+-- +---------+-----------------------+---------------------------+--------------------------------------++--+join :: CBytes -> CBytes -> IO CBytes+join p p2 = do+    let l = CB.length p + CB.length p2 + (#const BUF_EXT_SIZ)+    (p', _) <- withCBytesUnsafe p $ \ pp ->+        withCBytesUnsafe p2 $ \ pp2 ->+            allocCBytesUnsafe l $ \ pbuf ->+                cwk_path_join pp pp2 pbuf (fromIntegral l)+    return p'++-- | Joins multiple paths together.+--+-- This function generates a new path by joining multiple paths together.+-- It will remove double separators, and unlike 'getAbsolute',+-- it permits the use of multiple relative paths to combine.+concat :: [CBytes] -> IO CBytes+concat ps = do+    (p', _) <- withCBytesListUnsafe ps $ \ pp l -> do+        let l' = sum (List.map (\ p -> CB.length p + (#const BUF_EXT_SIZ)) ps)+        allocCBytesUnsafe l' $ \ pbuf ->+            hs_cwk_path_join_multiple pp l pbuf (fromIntegral l')+    return p'++-- | Creates a normalized version of the path.+-- The following will be true for the normalized path:+--+-- * "..\/" will be resolved.+-- * ".\/" will be removed.+-- * double separators will be fixed with a single separator.+-- * separator suffixes will be removed.+--+-- +--------------------------------------------------+-------------------++-- | Input                                            | Output            |+-- +--------------------------------------------------+-------------------++-- | \/var                                            | \/var             |+-- +--------------------------------------------------+-------------------++-- | \/var\/logs\/test\/..\/..\/                      | \/var             |+-- +--------------------------------------------------+-------------------++-- | \/var\/logs\/test\/..\/..\/..\/..\/..\/..\/      | \/                |+-- +--------------------------------------------------+-------------------++-- | rel\/..\/..\/                                    | ..                |+-- +--------------------------------------------------+-------------------++-- | \/var\/\/\/\/logs\/\/test\/                      | \/var\/logs\/test |+-- +--------------------------------------------------+-------------------++-- | \/var\/.\/.\/.\/.\/                              | \/var             |+-- +--------------------------------------------------+-------------------++-- | \/var\/.\/logs\/.\/\/test\/..\/\/..\/\/\/\/\/\/  | \/var             |+-- +--------------------------------------------------+-------------------++--+normalize :: CBytes -> IO CBytes+normalize p = do+    let l = CB.length p + (#const BUF_EXT_SIZ)+    (p', _) <- withCBytesUnsafe p $ \ pp ->+        allocCBytesUnsafe l $ \ pbuf ->+            cwk_path_normalize pp pbuf (fromIntegral l)+    return p'++-- | Finds common portions in two paths.+--+-- +---------+---------------------------+---------------------------+----------------------++-- | Style   | Base                      | Other                     | Result               |+-- +---------+---------------------------+---------------------------+----------------------++-- | UNIX    | \/test\/abc\/..\/foo\/bar | \/test\/foo\/har          | \/test\/abc\/..\/foo |+-- +---------+---------------------------+---------------------------+----------------------++-- | UNIX    | \/test\/foo\/har          | \/test\/abc\/..\/foo\/bar | \/test\/foo          |+-- +---------+---------------------------+---------------------------+----------------------++-- | UNIX    | \/test\/abc.txt           | test\/abc.txt             | ""                   |+-- +---------+---------------------------+---------------------------+----------------------++-- | UNIX    | \/                        | ""                        | ""                   |+-- +---------+---------------------------+---------------------------+----------------------++-- | UNIX    | \/this\/\/\/is\/a\/\/test | \/this\/\/is\/a\/\/\/file | \/this\/\/\/is\/a    |+-- +---------+---------------------------+---------------------------+----------------------++-- | UNIX    | \/this\/is\/a\/test       | \/this\/is\/a\/           | \/this\/is\/a        |+-- +---------+---------------------------+---------------------------+----------------------++-- | UNIX    | \/this\/is\/a\/test       | \/this\/is\/a             | \/this\/is\/a        |+-- +---------+---------------------------+---------------------------+----------------------++-- | UNIX    | \/this\/is\/a\/test       | \/this\/is\/a\/string     | \/this\/is\/a        |+-- +---------+---------------------------+---------------------------+----------------------++-- | WINDOWS | C:\/abc\/test.txt         | C:\/                      | C:\/                 |+-- +---------+---------------------------+---------------------------+----------------------++-- | WINDOWS | C:\/abc\/test.txt         | C:\/def\/test.txt         | C:\/                 |+-- +---------+---------------------------+---------------------------+----------------------++-- | WINDOWS | C:\/test\/abc.txt         | D:\/test\/abc.txt         | ""                   |+-- +---------+---------------------------+---------------------------+----------------------++--+intersection :: CBytes      -- ^ The base path which will be compared with the other path.+             -> CBytes      -- ^ The other path which will compared with the base path.+             -> IO CBytes+intersection p1 p2 = do+    len <- withCBytesUnsafe p1 $ \ pp1 ->+        withCBytesUnsafe p2 $ \ pp2 ->+            cwk_path_get_intersection pp1 pp2+    if len == 0+    then return CB.empty+    else return (CB (V.PrimVector (CB.rawPrimArray p1) 0 (fromIntegral len)))++-- | Generates an absolute path based on a base.+--+-- This function generates an absolute path based on a base path and another path.+-- It is guaranteed to return an absolute path.+-- If the second submitted path is absolute, it will override the base path.+--+-- +----------------------+----------------------+-----------------------++-- | Base                 | Path                 | Result                |+-- +----------------------+----------------------+-----------------------++-- | \/hello\/there       | ..\/..\/..\/..\/..\/ | \/                    |+-- +----------------------+----------------------+-----------------------++-- | \/hello\/\/..\/there | test\/\/thing        | \/there\/test\/thing  |+-- +----------------------+----------------------+-----------------------++-- | hello\/there         | \/test               | \/test                |+-- +----------------------+----------------------+-----------------------++-- | hello\/there         | test                 | \/hello\/there\/test  |+-- +----------------------+----------------------+-----------------------++-- | \/hello\/there       | \/test               | \/test                |+-- +----------------------+----------------------+-----------------------++-- | \/hello\/there       | ..                   | \/hello               |+-- +----------------------+----------------------+-----------------------++--+absolute :: CBytes  -- ^ The absolute base path on which the relative path will be applied.+         -> CBytes  -- ^ The relative path which will be applied on the base path.+         -> IO CBytes+absolute p p2 = do+    let l = CB.length p + CB.length p2 + (#const BUF_EXT_SIZ)+    (p', _) <- withCBytesUnsafe p $ \ pp ->+        withCBytesUnsafe p2 $ \ pp2 ->+            allocCBytesUnsafe l $ \ pbuf ->+                cwk_path_get_absolute pp pp2 pbuf (fromIntegral l)+    return p'++-- | Generates a relative path based on a base.+--+-- This function generates a relative path based on a base path and another path.+-- It determines how to get to the submitted path, starting from the base directory.+--+-- +---------+--------------------------+--------------------------+-----------------++-- | Style   | Base                     | Path                     | Result          |+-- +---------+--------------------------+--------------------------+-----------------++-- | UNIX    | \/..\/..\/               | \/..\/..\/               | .               |+-- +---------+--------------------------+--------------------------+-----------------++-- | UNIX    | \/path\/same             | \/path\/not_same\/ho\/.. | ..\/not_same    |+-- +---------+--------------------------+--------------------------+-----------------++-- | UNIX    | \/path\/not_same\/ho\/.. | \/path\/same             | ..\/same        |+-- +---------+--------------------------+--------------------------+-----------------++-- | UNIX    | \/path\/same             | \/path\/same\/ho\/..     | .               |+-- +---------+--------------------------+--------------------------+-----------------++-- | UNIX    | \/path\/same\/ho\/..     | \/path\/same             | .               |+-- +---------+--------------------------+--------------------------+-----------------++-- | UNIX    | \/path\/same             | \/path\/same             | .               |+-- +---------+--------------------------+--------------------------+-----------------++-- | UNIX    | \/path\/long\/one        | \/path\/long\/one\/two   | two             |+-- +---------+--------------------------+--------------------------+-----------------++-- | UNIX    | \/path\/long\/one\/two   | \/path\/long\/one        | ..              |+-- +---------+--------------------------+--------------------------+-----------------++-- | UNIX    | .\/this\/is\/path_one    | .\/this\/is\/path_two    | ..\/path_two    |+-- +---------+--------------------------+--------------------------+-----------------++-- | UNIX    | \/this\/is\/path_one     | \/this\/is\/path_two     | ..\/path_two    |+-- +---------+--------------------------+--------------------------+-----------------++-- | WINDOWS | C:\/path\/same           | D:\/path\/same           | ""              |+-- +---------+--------------------------+--------------------------+-----------------++--+relative :: CBytes  -- ^ The base path from which the relative path will start.+         -> CBytes  -- ^ The target path where the relative path will point to.+         -> IO CBytes+relative p p2 = do+    let l = CB.length p + CB.length p2 + (#const BUF_EXT_SIZ)+    (p', _) <- withCBytesUnsafe p $ \ pp ->+        withCBytesUnsafe p2 $ \ pp2 ->+            allocCBytesUnsafe l $ \ pbuf ->+                cwk_path_get_relative pp pp2 pbuf (fromIntegral l)+    return p'++-- | Split the extension of a file path.+--+-- This function extracts the extension portion of a file path.+--+-- +----------------------------+------------++-- | Path                       | Result     |+-- +----------------------------+------------++-- | \/my\/path.txt             | .txt       |+-- +----------------------------+------------++-- | \/my\/path                 | ""         |+-- +----------------------------+------------++-- | \/my\/.path                | .path      |+-- +----------------------------+------------++-- | \/my\/path.                | .          |+-- +----------------------------+------------++-- | \/my\/path.abc.txt.tests   | .tests     |+-- +----------------------------+------------++--+splitExtension :: CBytes+               -> IO (CBytes, CBytes) -- ^ return (file, ext)+{-# INLINABLE splitExtension #-}+splitExtension p = do+    (len ,off) <- withCBytesUnsafe p $ \ pp ->+        allocPrimUnsafe $ \ plen ->+            hs_cwk_path_get_extension pp plen+    if off == -1+    then return (p, CB.empty)+    else return ( CB (V.PrimVector (CB.rawPrimArray p) 0 off)+                , CB (V.PrimVector (CB.rawPrimArray p) off len))++-- | Changes the extension of a file path.+--+-- This function changes the extension of a file name.+-- The function will append an extension if the basename does not have an extension,+-- or use the extension as a basename if the path does not have a basename.+--+-- Note:+--+-- * This function does not normalize the resulting path. You can use 'normalize' to do so.+-- * If the new_extension parameter starts with a dot,+--   the first dot will be ignored when the new extension is appended.+--+-- @+-- > changeExtension  "foo\/bar.txt" "png"+-- "foo\/bar.png"+-- @+--+changeExtension :: CBytes  -- ^ The path which will be used to make the change.+                -> CBytes  -- ^ The extension which will be placed within the new path.+                -> IO CBytes+changeExtension p p2 = do+    let l = CB.length p + CB.length p2 + (#const BUF_EXT_SIZ)+    (p', _) <- withCBytesUnsafe p $ \ pp ->+        withCBytesUnsafe p2 $ \ pp2 ->+            allocCBytesUnsafe l $ \ pbuf ->+                cwk_path_change_extension pp pp2 pbuf (fromIntegral l)+    return p'++--------------------------------------------------------------------------------++-- | Get a list of paths in the @$PATH@ variable.+getSearchPath :: IO [CBytes]+getSearchPath = do+    s <- getEnv' "PATH"+    sp <- searchPathSeparator+    return (splitSearchPath s sp)+  where+    splitSearchPath (CB bs) sp = go bs sp+    go bs sp = case V.break (== sp) bs of+        (p, rest)+            | V.null rest -> []+            | otherwise -> g p : go (V.drop 1 rest) sp+    g bs = if V.null bs then CB.singleton (#const DOT) else CB bs++--------------------------------------------------------------------------------++foreign import ccall unsafe hs_cwk_path_get_basename :: BA## Word8 -> MBA## Int -> IO Int+foreign import ccall unsafe cwk_path_change_basename :: BA## Word8 -> BA## Word8 -> MBA## Word8 -> CSize -> IO CSize+-- foreign import ccall unsafe hs_cwk_path_get_dirname :: BA## Word8 -> IO Int+foreign import ccall unsafe hs_cwk_path_get_root :: BA## Word8 -> IO Int+foreign import ccall unsafe cwk_path_change_root :: BA## Word8 -> BA## Word8 -> MBA## Word8 -> CSize -> IO CSize+foreign import ccall unsafe cwk_path_is_absolute :: BA## Word8 -> IO CBool+foreign import ccall unsafe cwk_path_is_relative :: BA## Word8 -> IO CBool+foreign import ccall unsafe cwk_path_join :: BA## Word8 -> BA## Word8 -> MBA## Word8 -> CSize -> IO CSize+foreign import ccall unsafe hs_cwk_path_join_multiple :: BAArray## Word8 -> Int -> MBA## Word8 -> CSize -> IO CSize+foreign import ccall unsafe cwk_path_normalize :: BA## Word8 -> MBA## Word8 -> CSize -> IO CSize+foreign import ccall unsafe cwk_path_get_intersection :: BA## Word8 -> BA## Word8 -> IO CSize+foreign import ccall unsafe cwk_path_get_absolute :: BA## Word8 -> BA## Word8 -> MBA## Word8 -> CSize -> IO CSize+foreign import ccall unsafe cwk_path_get_relative :: BA## Word8 -> BA## Word8 -> MBA## Word8 -> CSize -> IO CSize+foreign import ccall unsafe hs_cwk_path_get_extension :: BA## Word8 -> MBA## CSize -> IO Int+foreign import ccall unsafe cwk_path_change_extension :: BA## Word8 -> BA## Word8 -> MBA## Word8 -> CSize -> IO CSize+foreign import ccall unsafe cwk_path_guess_style :: BA## Word8 -> IO CInt+foreign import ccall unsafe cwk_path_get_style :: IO CInt+foreign import ccall unsafe cwk_path_set_style :: CInt -> IO ()
Z/IO/FileSystem/Threaded.hs view
@@ -7,7 +7,7 @@ Stability   : experimental Portability : non-portable -This module provide IO operations related to filesystem, operations are implemented using libuv's threadpool to achieve non-block behavior (non-block here meaning won't block other haskell threads), which should be prefered when the operations' estimated time is long enough(>1ms) or running with a non-threaded haskell runtime, such as accessing network filesystem or scan a very large directory. Otherwise you may block RTS's capability thus all the other haskell threads live on it.+This module provides IO operations related to filesystem, operations are implemented using libuv's threadpool to achieve non-block behavior (non-block here meaning won't block other haskell threads), which should be prefered when the operations' estimated time is long enough(>1ms) or running with a non-threaded haskell runtime, such as accessing network filesystem or scan a very large directory. Otherwise you may block RTS's capability thus all the other haskell threads live on it.  The threadpool version operations have overheads similar to safe FFI, but provide same adventages: @@ -42,6 +42,7 @@   , utime, futime, lutime   , link, symlink   , readlink, realpath+  , chown, fchown, lchown   -- * opening constant   -- ** AccessMode   , AccessMode@@ -127,17 +128,17 @@ -- Implict offset interface is provided by 'Input' \/ 'Output' instances. -- Explict offset interface is provided by 'readFileT' \/ 'writeFileT'. ---data FileT =  FileT  {-# UNPACK #-} !UVFD      -- ^ the file+data FileT =  FileT  {-# UNPACK #-} !FD      -- ^ the file                      {-# UNPACK #-} !(IORef Bool)  -- ^ closed flag  -- | Return FileT fd.-getFileTFD :: FileT -> IO UVFD+getFileTFD :: FileT -> IO FD getFileTFD (FileT fd closedRef) = do     closed <- readIORef closedRef     if closed then throwECLOSED else return fd  -- | If fd is -1 (closed), throw 'ResourceVanished' ECLOSED.-checkFileTClosed :: HasCallStack => FileT -> (UVFD -> IO a) -> IO a+checkFileTClosed :: HasCallStack => FileT -> (FD -> IO a) -> IO a checkFileTClosed (FileT fd closedRef) f = do     closed <- readIORef closedRef     if closed then throwECLOSED else f fd@@ -540,3 +541,23 @@                     (\ _ -> hs_uv_fs_readlink_extra_cleanup p'))         (hs_uv_fs_readlink_cleanup . fst)         (fromCString . fst)++-- | Equivalent to <http://linux.die.net/man/2/chown chown(2)>.+chown :: HasCallStack => CBytes -> UID -> GID -> IO ()+chown path uid gid = do+    uvm <- getUVManager+    withCBytesUnsafe path $ \ p ->+        withUVRequest_ uvm (hs_uv_fs_chown_threaded p uid gid)++-- | Equivalent to <http://linux.die.net/man/2/fchown fchown(2)>.+fchown :: HasCallStack => FD -> UID -> GID -> IO ()+fchown fd uid gid = do+    uvm <- getUVManager+    withUVRequest_ uvm (hs_uv_fs_fchown_threaded fd uid gid)++-- | Equivalent to <http://linux.die.net/man/2/lchown lchown(2)>.+lchown :: HasCallStack => CBytes -> UID -> GID -> IO ()+lchown path uid gid = do+    uvm <- getUVManager+    withCBytesUnsafe path $ \ p ->+        withUVRequest_ uvm (hs_uv_fs_lchown_threaded p uid gid)
Z/IO/Logger.hs view
@@ -10,7 +10,7 @@ Simple, high performance logger. The design choice of this logger is biased towards simplicity instead of generlization:      * All log functions lives in 'IO'.-    * By default this logger is connected to stderr, use 'setStdLogger' to customize.+    * By default this logger is connected to stderr, use 'setDefaultLogger' to customize.     * When logging each thread will build log 'Builder's into a small 'V.Bytes' with line buffer       instead of leaving all 'Builder's to the flushing thread:         * Logger won't keep heap data for too long simply because they're referenced by log's 'Builder'.@@ -18,13 +18,13 @@           which reduces contention.         * Each log call is atomic, Logging order is preserved under concurrent settings. -Flushing is automatic and throttled for 'debug', 'info', 'warn' to boost performance, but a 'fatal' log will always flush logger's buffer.  This could lead to a problem that if main thread exits too early logs may missed, to add a flushing when program exits, use 'withStdLogger' like:+Flushing is automatic and throttled for 'debug', 'info', 'warn' to boost performance, but a 'fatal' log will always flush logger's buffer.  This could lead to a problem that if main thread exits too early logs may missed, to add a flushing when program exits, use 'withDefaultLogger' like:  @ import Z.IO.Logger  main :: IO ()-main = withStdLogger $ do+main = withDefaultLogger $ do     ....     debug "..."   -- So that this log won't be missed     ...@@ -35,10 +35,13 @@   ( -- * A simple Logger type     Logger(..)   , LoggerConfig(..)-  , setStdLogger-  , getStdLogger-  , withStdLogger+  , defaultLoggerConfig+  , setDefaultLogger+  , getDefaultLogger+  , flushDefaultLogger+  , withDefaultLogger   , newLogger+  , newColoredLogger     -- * logging functions   , debug   , info@@ -54,7 +57,7 @@     -- * Helper to write new logger   , defaultTSCache   , defaultFmtCallStack-  , LogFormatter, defaultFmt+  , LogFormatter, defaultFmt, coloredFmt   , flushLog   ) where @@ -62,26 +65,28 @@ import Z.Data.Vector.Base as V import Z.IO.LowResTimer import Z.IO.StdStream+import Z.IO.StdStream.Ansi  (color, AnsiColor(..)) import Z.IO.Buffered import System.IO.Unsafe (unsafePerformIO) import Z.IO.Exception+import Z.IO.Time import Data.IORef import Control.Concurrent.MVar import GHC.Stack import qualified Z.Data.Builder as B-import qualified Data.Time as Time+import qualified Z.Data.CBytes as CB -type LogFormatter = Maybe (B.Builder ())     -- ^ data/time string-                    -> B.Builder ()          -- ^ log level-                    -> B.Builder ()          -- ^ log content-                    -> CallStack             -- ^ call stack trace-                    -> B.Builder ()+type LogFormatter = B.Builder ()            -- ^ data/time string+                  -> Level                  -- ^ log level+                  -> B.Builder ()           -- ^ log content+                  -> CallStack              -- ^ call stack trace+                  -> B.Builder ()  data Logger = Logger     { loggerPushBuilder     :: B.Builder () -> IO () -- ^ push log into buffer     , flushLogger           :: IO ()                -- ^ flush logger's buffer to output device     , flushLoggerThrottled  :: IO ()                -- ^ throttled flush, e.g. use 'throttleTrailing_' from "Z.IO.LowResTimer"-    , loggerTSCache         :: IO (Maybe (B.Builder ())) -- ^ A IO action return a formatted date/time string+    , loggerTSCache         :: IO (B.Builder ())    -- ^ A IO action return a formatted date/time string     , loggerFmt             :: LogFormatter     } @@ -89,8 +94,6 @@     { loggerMinFlushInterval :: {-# UNPACK #-} !Int -- ^ Minimal flush interval, see Notes on 'debug'     , loggerLineBufSize      :: {-# UNPACK #-} !Int -- ^ Buffer size to build each log/line     , loggerShowDebug        :: Bool                -- ^ Set to 'False' to filter debug logs-    , loggerShowTS           :: Bool                -- ^ Set to 'False' to disable auto data/time string prepending-    , loggerShowSourceLoc    :: Bool                -- ^ Set to 'True' to enable source location line     }  -- | A default logger config with@@ -103,7 +106,7 @@ --   * don't show source location --   * buffer size equals to 'V.defaultChunkSize'. defaultLoggerConfig :: LoggerConfig-defaultLoggerConfig = LoggerConfig 1 128 True True True+defaultLoggerConfig = LoggerConfig 1 128 True  -- | A default timestamp cache with format @%Y-%m-%dT%H:%M:%S%Z@ --@@ -112,9 +115,8 @@ {-# NOINLINE defaultTSCache #-} defaultTSCache = unsafePerformIO $ do     throttle 1 $ do-        t <- Time.getCurrentTime-        return . B.string8 $-            Time.formatTime Time.defaultTimeLocale "%Y-%m-%dT%H:%M:%S%Z" t+        t <- getSystemTime+        CB.toBuilder <$> formatSystemTime simpleDateFormat t  -- | Use this function to implement a simple 'IORef' based concurrent logger. --@@ -133,32 +135,66 @@         flushBuffer o  -- | Make a new simple logger.+-- newLogger :: Output o           => LoggerConfig           -> MVar (BufferedOutput o)           -> IO Logger-newLogger config oLock = do+newLogger LoggerConfig{..} oLock = do     bList <- newIORef []     let flush = flushLog oLock bList-    throttledFlush <- throttleTrailing_ (loggerMinFlushInterval config) flush-    return $ Logger (pushLog bList) flush throttledFlush tsCache (defaultFmt (loggerShowSourceLoc config))+    throttledFlush <- throttleTrailing_ loggerMinFlushInterval flush+    return $ Logger (pushLog bList) flush throttledFlush defaultTSCache+        (defaultFmt loggerShowDebug)   where-    tsCache = if (loggerShowTS config) then Just <$> defaultTSCache else pure Nothing     pushLog bList b = do-        let !bs = B.buildBytesWith (loggerLineBufSize config) b+        let !bs = B.buildBytesWith loggerLineBufSize b         atomicModifyIORef' bList (\ bss -> (bs:bss, ())) +-- | Make a new colored logger connected to stderr.+--+-- This logger will output colorized log if stderr is connected to TTY.+newColoredLogger :: LoggerConfig -> IO Logger+newColoredLogger LoggerConfig{..} = do+    bList <- newIORef []+    let flush = flushLog stderrBuf bList+    throttledFlush <- throttleTrailing_ loggerMinFlushInterval flush+    return $ Logger (pushLog bList) flush throttledFlush defaultTSCache+        (if isStdStreamTTY stderr then coloredFmt loggerShowDebug+                                  else defaultFmt loggerShowDebug)++  where+    pushLog bList b = do+        let !bs = B.buildBytesWith loggerLineBufSize b+        atomicModifyIORef' bList (\ bss -> (bs:bss, ()))+ -- | A default log formatter -- -- @ [DEBUG][2020-10-09T07:44:14UTC][<interactive>:7:1]This a debug message@-defaultFmt :: Bool          -- ^ show call stack info?+defaultFmt :: Bool  -- ^ show DEGUG?            -> LogFormatter-defaultFmt showcstack maybeTS level content cstack = do-    B.square level-    forM_ maybeTS $ \ ts -> B.square ts-    when showcstack (B.square $ defaultFmtCallStack cstack)+defaultFmt showdebug ts level content cstack = when (showdebug || level /= "DEBUG") $ do+    B.square (CB.toBuilder level)+    B.square ts+    B.square $ defaultFmtCallStack cstack     content +-- | A default colored log formatter+--+-- DEBUG level is 'Cyan', WARN level is 'Yellow', FATAL level is 'Red'.+coloredFmt :: Bool  -- ^ show DEBUG?+           -> LogFormatter+coloredFmt showdebug ts level content cstack = when (showdebug || level /= "DEBUG") $ do+    let blevel = CB.toBuilder level+    B.square (case level of+        "DEBUG" -> color Cyan blevel+        "WARN"  -> color Yellow blevel+        "FATAL" -> color Red blevel+        _       -> blevel)+    B.square ts+    B.square $ defaultFmtCallStack cstack+    content+ -- | Default stack formatter which fetch the logging source and location. defaultFmtCallStack :: CallStack -> B.Builder () defaultFmtCallStack cs =@@ -174,26 +210,28 @@ globalLogger :: IORef Logger {-# NOINLINE globalLogger #-} globalLogger = unsafePerformIO $-    newIORef =<< newLogger defaultLoggerConfig stderrBuf+    newIORef =<< newColoredLogger defaultLoggerConfig  -- | Change the global logger.-setStdLogger :: Logger -> IO ()-setStdLogger !logger = atomicWriteIORef globalLogger logger+setDefaultLogger :: Logger -> IO ()+setDefaultLogger !logger = atomicWriteIORef globalLogger logger  -- | Get the global logger.-getStdLogger :: IO Logger-getStdLogger = readIORef globalLogger+getDefaultLogger :: IO Logger+getDefaultLogger = readIORef globalLogger  -- | Manually flush stderr logger. flushDefaultLogger :: IO ()-flushDefaultLogger = getStdLogger >>= flushLogger+flushDefaultLogger = getDefaultLogger >>= flushLogger  -- | Flush stderr logger when program exits.-withStdLogger :: IO () -> IO ()-withStdLogger = (`finally` flushDefaultLogger)+withDefaultLogger :: IO () -> IO ()+withDefaultLogger = (`finally` flushDefaultLogger)  -------------------------------------------------------------------------------- +type Level = CB.CBytes+ debug :: HasCallStack => B.Builder () -> IO () debug = otherLevel_ "DEBUG" False callStack @@ -207,15 +245,15 @@ fatal = otherLevel_ "FATAL" True callStack  otherLevel :: HasCallStack-           => B.Builder ()      -- ^ log level+           => Level             -- ^ log level            -> Bool              -- ^ flush immediately?            -> B.Builder ()      -- ^ log content            -> IO () otherLevel level flushNow bu = otherLevel_ level flushNow callStack bu -otherLevel_ :: B.Builder () -> Bool -> CallStack -> B.Builder () -> IO ()+otherLevel_ :: Level -> Bool -> CallStack -> B.Builder () -> IO () otherLevel_ level flushNow cstack bu = do-    logger <- getStdLogger+    logger <- getDefaultLogger     otherLevelTo_ level flushNow cstack logger bu  --------------------------------------------------------------------------------@@ -234,14 +272,14 @@  otherLevelTo :: HasCallStack              => Logger-             -> B.Builder ()      -- ^ log level+             -> Level             -- ^ log level              -> Bool              -- ^ flush immediately?              -> B.Builder ()      -- ^ log content              -> IO () otherLevelTo logger level flushNow =     otherLevelTo_ level flushNow callStack logger -otherLevelTo_ :: B.Builder () -> Bool -> CallStack -> Logger -> B.Builder () -> IO ()+otherLevelTo_ :: Level -> Bool -> CallStack -> Logger -> B.Builder () -> IO () otherLevelTo_ level flushNow cstack logger bu = do     ts <- loggerTSCache logger     (loggerPushBuilder logger) $ (loggerFmt logger) ts level bu cstack
Z/IO/Network/IPC.hs view
@@ -120,7 +120,7 @@ -- The buffer passing of accept is a litte complicated here, to get maximum performance, -- we do batch accepting. i.e. recv multiple client inside libuv's event loop: ----- we poke uvmanager's buffer table as a Ptr Word8, with byte size (backLog*sizeof(UVFD))+-- we poke uvmanager's buffer table as a Ptr Word8, with byte size (backLog*sizeof(FD)) -- inside libuv event loop, we cast the buffer back to int32_t* pointer. -- each accept callback push a new socket fd to the buffer, and increase a counter(buffer_size_table). -- backLog should be large enough(>128), so under windows we can't possibly filled it up within one@@ -134,7 +134,7 @@ -- we allocate a buffer to hold accepted FDs, pass it just like a normal reading buffer. -- then we can start listening.                 acceptBuf <- newPinnedPrimArray backLog-                let acceptBufPtr = coerce (mutablePrimArrayContents acceptBuf :: Ptr UVFD)+                let acceptBufPtr = coerce (mutablePrimArrayContents acceptBuf :: Ptr FD)                  withUVManager' serverUVManager $ do                     -- We use buffersize as accepted fd count(count backwards)
Z/IO/Network/TCP.hs view
@@ -131,7 +131,7 @@ -- The buffer passing of accept is a litte complicated here, to get maximum performance, -- we do batch accepting. i.e. recv multiple client inside libuv's event loop: ----- we poke uvmanager's buffer table as a Ptr Word8, with byte size (backLog*sizeof(UVFD))+-- we poke uvmanager's buffer table as a Ptr Word8, with byte size (backLog*sizeof(FD)) -- inside libuv event loop, we cast the buffer back to int32_t* pointer. -- each accept callback push a new socket fd to the buffer, and increase a counter(buffer_size_table). -- backLog should be large enough(>128), so under windows we can't possibly filled it up within one@@ -146,8 +146,8 @@ -- then we can start listening.                 acceptBuf <- newPinnedPrimArray backLog                 -- https://stackoverflow.com/questions/1953639/is-it-safe-to-cast-socket-to-int-under-win64-                -- UVFD is 32bit CInt, it's large enough to hold uv_os_sock_t-                let acceptBufPtr = coerce (mutablePrimArrayContents acceptBuf :: Ptr UVFD)+                -- FD is 32bit CInt, it's large enough to hold uv_os_sock_t+                let acceptBufPtr = coerce (mutablePrimArrayContents acceptBuf :: Ptr FD)                  withUVManager' serverUVManager $ do                     -- We use buffersize as accepted fd count(count backwards)
Z/IO/Network/UDP.hs view
@@ -373,14 +373,16 @@             -> IO () recvUDPLoop (UDPRecvConfig bufSiz bufArrSiz) udp@(UDP hdl slot uvm _ _) worker = do     bracket-        (throwOOMIfNull $ hs_uv_udp_check_alloc hdl)+        (do check <- throwOOMIfNull $ hs_uv_udp_check_alloc+            throwUVIfMinus_ (hs_uv_udp_check_init check hdl)+            return check)         hs_uv_udp_check_close $         \ check -> do             buf@(_, rbufArr) <- newRecvBuf bufSiz bufArrSiz             withMutablePrimArrayContents rbufArr $ \ p -> do                 pokeBufferTable uvm slot (castPtr p) (bufArrSiz-1)                 -- init uv_check_t must come after poking buffer-                throwUVIfMinus_ $ hs_uv_udp_check_init check+                throwUVIfMinus_ $ hs_uv_udp_check_start check             forever $ do                 msgs <- recvUDPWith udp check buf bufSiz                 withMutablePrimArrayContents rbufArr $ \ p ->@@ -393,14 +395,16 @@ recvUDP :: HasCallStack => UDPRecvConfig -> UDP -> IO [(Maybe SocketAddr, Bool, V.Bytes)] recvUDP (UDPRecvConfig bufSiz bufArrSiz) udp@(UDP hdl slot uvm _ _)  = do     bracket-        (throwOOMIfNull $ hs_uv_udp_check_alloc hdl)+        (do check <- throwOOMIfNull $ hs_uv_udp_check_alloc+            throwUVIfMinus_ (hs_uv_udp_check_init check hdl)+            return check)         hs_uv_udp_check_close $         \ check -> do             buf@(_, rbufArr) <- newRecvBuf bufSiz bufArrSiz             withMutablePrimArrayContents rbufArr $ \ p -> do                 pokeBufferTable uvm slot (castPtr p) (bufArrSiz-1)                 -- init uv_check_t must come after poking buffer-                throwUVIfMinus_ $ hs_uv_udp_check_init check+                throwUVIfMinus_ $ hs_uv_udp_check_start check             recvUDPWith udp check buf bufSiz  recvUDPWith :: HasCallStack
Z/IO/Process.hsc view
@@ -12,6 +12,9 @@ @ import Control.Concurrent.MVar import Z.IO.Process++> readProcessText defaultProcessOptions{processFile = "cat"} "hello world"+("hello world","",ExitSuccess) @  -}@@ -239,22 +242,18 @@             return (Just uvs2)         _ -> return Nothing -    -- concat args-    let allArgs = V.intercalateElem 0 (List.map CBytes.toPrimArray processArgs ++ [mempty])  -        argsLen = fromIntegral $ List.length processArgs-    -- concat env-        mkEnv (k, v) = CBytes.toPrimArray (k <> "=" <> v)+    let mkEnv (k, v) = CBytes.concat [k, "=", v]         allEnv = case processEnv of-            Just e -> V.intercalateElem 0 (List.map mkEnv e ++ [mempty])  -            _ -> V.singleton 0 +            Just e -> List.map mkEnv e  +            _ -> []         envLen = case processEnv of-            Just e -> fromIntegral $ List.length e-            _ -> -1+            Just e -> List.length e+            _ -> -1     -- use -1 to inherit from parent      (slot, pid) <- withCBytesUnsafe processFile $ \ pfile ->         withCBytesUnsafe processCWD $ \ pcwd ->-            withPrimArrayUnsafe allArgs $ \ pargs _ ->-                withPrimArrayUnsafe allEnv $ \ penv _ ->+            withCBytesListUnsafe processArgs $ \ pargs argsLen ->+                withCBytesListUnsafe allEnv $ \ penv _ ->                     withUVManager uvm $ \ loop -> do                         slot <- throwUVIfMinus (hs_uv_spawn loop popts## pfile                                         pargs argsLen penv envLen pcwd pstdio##)
Z/IO/Resource.hs view
@@ -26,8 +26,8 @@   , Pool   , PoolState(..)   , initPool-  , statPool   , initInPool+  , poolStat, poolInUse ) where  import           Control.Concurrent.STM@@ -171,12 +171,12 @@ -- can be opened. -- data Pool a = Pool-    { poolResource :: Resource a-    , poolLimit :: Int-    , poolIdleTime :: Int-    , poolEntries :: TVar [Entry a]-    , poolInUse :: TVar Int-    , poolState :: TVar PoolState+    { _poolResource :: Resource a+    , _poolLimit :: Int+    , _poolIdleTime :: Int+    , _poolEntries :: TVar [Entry a]+    , _poolInUse :: TVar Int+    , _poolState :: TVar PoolState     }  -- | Initialize a resource pool with given 'Resource'@@ -214,16 +214,26 @@ -- should be adjusted to a higher number. On the otherhand, lots of 'PoolScanning' -- may indicate there're too much free resources. ---statPool :: Pool a -> IO PoolState-statPool pool = readTVarIO (poolState pool)+poolStat :: Pool a -> IO PoolState+poolStat pool = readTVarIO (_poolState pool) +-- | Get how many resource is being used within a resource pool.+--+-- This function is useful when debug, under load in use number alway reaches limit may indicate+-- contention on resources, i.e. the limit on maximum number of resources can be opened+-- should be adjusted to a higher number.+--+poolInUse :: Pool a -> IO Int+poolInUse pool = readTVarIO (_poolInUse pool)+ -- | Obtain the pooled resource inside a given resource pool. -- -- You shouldn't use 'withResource' with this resource after you closed the pool,--- an 'ResourceVanished' will be thrown.+-- a 'ResourceVanished' will be thrown. -- initInPool :: Pool a -> Resource a-initInPool (Pool res limit itime entries inuse state) = fst <$> initResource takeFromPool returnToPool+initInPool (Pool res limit itime entries inuse state) =+    fst <$> initResource takeFromPool returnToPool   where     takeFromPool = join . atomically $ do         c <- readTVar state
Z/IO/StdStream.hs view
@@ -83,13 +83,13 @@  data StdStream     = StdTTY {-# UNPACK #-}!(Ptr UVHandle) {-# UNPACK #-}!UVSlot UVManager -- similar to UVStream-    | StdFile {-# UNPACK #-}!UVFD                                          -- similar to UVFile+    | StdFile {-# UNPACK #-}!FD                                          -- similar to UVFile  isStdStreamTTY :: StdStream -> Bool isStdStreamTTY (StdTTY _ _ _) = True isStdStreamTTY _              = False -getStdStreamFD :: StdStream -> IO UVFD+getStdStreamFD :: StdStream -> IO FD getStdStreamFD (StdTTY hdl _ _) = throwUVIfMinus (hs_uv_fileno hdl) getStdStreamFD (StdFile fd) = return fd @@ -106,9 +106,14 @@                 throwUVIfMinus_ $ withUVManager' uvm (uv_read_stop hdl)                 void (tryTakeMVar m))         if  | r > 0  -> return r-            -- r == 0 should be impossible, since we guard this situation in c side             | r == fromIntegral UV_EOF -> return 0             | r < 0 ->  throwUVIfMinus (return r)+            -- r == 0 should be impossible, since we guard this situation in c side+            | otherwise -> throwUVError UV_UNKNOWN IOEInfo{+                                  ioeName = "StdStream read error"+                                , ioeDescription = "StdStream read should never return 0 before EOF"+                                , ioeCallStack = callStack+                                }     readInput (StdFile fd) buf len =         throwUVIfMinus $ hs_uv_fs_read fd buf len (-1) @@ -167,7 +172,7 @@ {-# NOINLINE stderrBuf #-} stderrBuf = unsafePerformIO (newBufferedOutput stderr >>= newMVar) -makeStdStream :: HasCallStack => UVFD -> IO StdStream+makeStdStream :: HasCallStack => FD -> IO StdStream makeStdStream fd = do     typ <- uv_guess_handle fd     if typ == UV_TTY
+ Z/IO/StdStream/Ansi.hs view
@@ -0,0 +1,267 @@+{-|+Module      : Z.IO.StdStream.Ansi+Description : Ansi control code sequences+Copyright   : (c) Winterland, 2017-2020+License     : BSD+Maintainer  : drkoster@qq.com+Stability   : experimental+Portability : non-portable++Provides utilities to build <ANSI code sequences https://en.wikipedia.org/wiki/ANSI_escape_code>.++@+> putStd . bold . italicize . color Red  $ "hello"+hello   -- bold, italicize and red+@++-}++module Z.IO.StdStream.Ansi+  ( -- * Style modifier+    bold, italicize, underline,+    color, color', palette, palette', rgb, rgb',+    -- * Control codes+    cursorUp, cursorDown, cursorForward, cursorBackward,+    cursorDownLine, cursorUpLine ,+    setCursorColumn, setCursorPosition, saveCursor, restoreCursor,+    clearFromCursorToScreenEnd, clearFromCursorToScreenBeginning, clearScreen,+    clearFromCursorToLineEnd, clearFromCursorToLineBeginning, clearLine,+    scrollPageUp, scrollPageDown,+    hideCursor, showCursor,+    setTitle,+    -- * Style codes+    reset,+    boldIntensity, faintIntensity, resetIntensity,+    italicized, noItalicized,+    singleUnderline, doubleUnderline, noUnderline,+    slowBlink, rapidBlink, blinkOff,+    conceal, reveal,+    invert, invertOff,+    setForeground, setBrightForeground, setBackground, setBrightBackground,+    setPaletteForeground, setPaletteBackground,+    setRGBForeground, setRGBBackground,+    setDefaultForeground, setDefaultBackground,+    AnsiColor(..), PaletteColor, RGBColor,+    -- * Internal helper+    csi, sgr, colorToCode+  ) where++import qualified Z.Data.Builder as B+import qualified Z.Data.Text    as T+import qualified Z.Data.Text.ShowT  as T+import Data.Word+import GHC.Generics++csi :: [Int]  -- ^ List of parameters for the control sequence+    -> B.Builder () -- ^ Character(s) that identify the control function+    -> B.Builder ()+csi args code = do+    B.char8 '\ESC'+    B.char8 '['+    B.intercalateList (B.char8 ';') B.int args+    code++cursorUp, cursorDown, cursorForward, cursorBackward+  :: Int -- ^ Number of lines or characters to move+  -> B.Builder ()+cursorDownLine, cursorUpLine :: Int -- ^ Number of lines to move+                                     -> B.Builder ()+cursorUp n       = csi [n] (B.char8 'A')+cursorDown n     = csi [n] (B.char8 'B')+cursorForward n  = csi [n] (B.char8 'C')+cursorBackward n = csi [n] (B.char8 'D')+cursorDownLine n = csi [n] (B.char8 'E')+cursorUpLine n   = csi [n] (B.char8 'F')++-- | Code to move the cursor to the specified column. The column numbering is+-- 1-based (that is, the left-most column is numbered 1).+setCursorColumn :: Int -- ^ 1-based column to move to+                -> B.Builder ()+setCursorColumn n = csi [n] (B.char8 'G')++-- | Code to move the cursor to the specified position (row and column). The+-- position is 1-based (that is, the top-left corner is at row 1 column 1).+setCursorPosition :: Int -- ^ 1-based row to move to+                  -> Int -- ^ 1-based column to move to+                  -> B.Builder ()+setCursorPosition n m = csi [n, m] (B.char8 'G')++saveCursor, restoreCursor :: B.Builder ()+saveCursor    = B.char8 '\ESC' >> B.char8 '7'+restoreCursor = B.char8 '\ESC' >> B.char8 '8'+++clearFromCursorToScreenEnd, clearFromCursorToScreenBeginning, clearScreen :: B.Builder ()+clearFromCursorToLineEnd, clearFromCursorToLineBeginning, clearLine :: B.Builder ()++clearFromCursorToScreenEnd       = csi [0] (B.char8 'J')+clearFromCursorToScreenBeginning = csi [1] (B.char8 'J')+clearScreen                      = csi [2] (B.char8 'J')+clearFromCursorToLineEnd         = csi [0] (B.char8 'K')+clearFromCursorToLineBeginning   = csi [1] (B.char8 'K')+clearLine                        = csi [2] (B.char8 'K')++scrollPageUp, scrollPageDown :: Int -- ^ Number of lines to scroll by+                             -> B.Builder()+scrollPageUp n   = csi [n] (B.char8 'S')+scrollPageDown n = csi [n] (B.char8 'T')++hideCursor, showCursor :: B.Builder ()+hideCursor = csi [] "?25l"+showCursor = csi [] "?25h"++-- | XTerm control sequence to set the Icon Name and Window Title.+setTitle :: T.Text  -- ^ New Icon Name and Window Title+         -> B.Builder ()+setTitle title = do+    "\ESC]0;"+    B.text (T.filter (/= '\007') title)+    B.char8 '\007'++sgr :: [Word8]  -- ^ List of sgr code for the control sequence+    -> B.Builder ()+sgr args = do+    B.char8 '\ESC'+    B.char8 '['+    B.intercalateList (B.char8 ';') B.int args+    B.char8 'm'++reset :: B.Builder ()+reset = sgr [0]++boldIntensity, faintIntensity, resetIntensity :: B.Builder ()+boldIntensity  = sgr [1]+faintIntensity = sgr [2]+resetIntensity    = sgr [22]++bold :: B.Builder () -> B.Builder ()+bold t = boldIntensity >> t >> resetIntensity++italicized, noItalicized :: B.Builder ()+italicized      = sgr [3]+noItalicized    = sgr [23]++-- | Italicize some text+italicize :: B.Builder () -> B.Builder ()+italicize t = italicized >> t >> noItalicized++singleUnderline, doubleUnderline, noUnderline :: B.Builder ()+singleUnderline = sgr [4]+doubleUnderline = sgr [21]+noUnderline   = sgr [24]++-- | Add single underline to some text+underline  :: B.Builder () -> B.Builder ()+underline t = singleUnderline >> t >> singleUnderline++slowBlink, rapidBlink, blinkOff :: B.Builder ()+-- | less than 150 per minute+slowBlink   = sgr [5]+-- | MS-DOS ANSI.SYS, 150+ per minute; not widely supported+rapidBlink  = sgr [6]+blinkOff     = sgr [25]++conceal, reveal :: B.Builder ()+-- | Aka Hide, not widely supported.+conceal = sgr [8]+reveal = sgr [28]++invert, invertOff :: B.Builder ()+-- | Swap foreground and background colors, inconsistent emulation+invert = sgr [7]+invertOff = sgr [27]++-- | Colorized some text+color :: AnsiColor -> B.Builder () -> B.Builder ()+color c t = do+    setForeground c+    t+    setDefaultForeground++-- | Colorized some text with background color+color' :: AnsiColor -> AnsiColor -> B.Builder () -> B.Builder ()+color' c1 c2 t = do+    setForeground c1+    setBackground c2+    t+    setDefaultForeground+    setDefaultBackground++-- | Colorized some text+palette :: PaletteColor -> B.Builder () -> B.Builder ()+palette c t = do+    setPaletteForeground c+    t+    setDefaultForeground++-- | Colorized some text with background color+palette' :: PaletteColor -> PaletteColor -> B.Builder () -> B.Builder ()+palette' c1 c2 t = do+    setPaletteForeground c1+    setPaletteBackground c2+    t+    setDefaultForeground+    setDefaultBackground++-- | Colorized some text+rgb :: RGBColor -> B.Builder () -> B.Builder ()+rgb c t = do+    setRGBForeground c+    t+    setDefaultForeground++-- | Colorized some text with background color+rgb' :: RGBColor -> RGBColor -> B.Builder () -> B.Builder ()+rgb' c1 c2 t = do+    setRGBForeground c1+    setRGBBackground c2+    t+    setDefaultForeground+    setDefaultBackground++setForeground, setBrightForeground, setBackground, setBrightBackground :: AnsiColor -> B.Builder ()+setForeground c       = sgr [30 + colorToCode c]+setBrightForeground c = sgr [90 + colorToCode c]+setBackground c       = sgr [40 + colorToCode c]+setBrightBackground c = sgr [100 + colorToCode c]++setPaletteForeground, setPaletteBackground :: PaletteColor -> B.Builder ()+setPaletteForeground index = sgr [38, 5, index]+setPaletteBackground index = sgr [48, 5, index]++setRGBForeground, setRGBBackground :: RGBColor -> B.Builder ()+setRGBForeground (r,g,b) = sgr [38, 2, r, g, b]+setRGBBackground (r,g,b) = sgr [48, 2, r, g, b]++setDefaultForeground, setDefaultBackground :: B.Builder ()+setDefaultForeground = sgr [39]+setDefaultBackground = sgr [49]++-- | ANSI's eight standard colors+data AnsiColor = Black+               | Red+               | Green+               | Yellow+               | Blue+               | Magenta+               | Cyan+               | White+        deriving (Eq, Ord, Bounded, Enum, Show, Read, Generic)+        deriving anyclass T.ShowT++colorToCode :: AnsiColor -> Word8+colorToCode c = case c of+    Black   -> 0+    Red     -> 1+    Green   -> 2+    Yellow  -> 3+    Blue    -> 4+    Magenta -> 5+    Cyan    -> 6+    White   -> 7++-- | 8-bit palette color, see https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit+type PaletteColor = Word8++-- | 24-bit RGB color+type RGBColor = (Word8, Word8, Word8)
+ Z/IO/Time.hs view
@@ -0,0 +1,154 @@+{-|+Module      : Z.IO.Time+Description : Fast time functions+Copyright   : (c) Dong Han, 2020+              (c) Kazu Yamamoto 2019+License     : BSD+Maintainer  : winterland1989@gmail.com+Stability   : experimental+Portability : non-portable++This module provides functions directly work on 'SystemTime' type, a compact time type from @time@ library.+For advanced time editing, use @time@ library.++-}+module Z.IO.Time+  ( -- * SystemTime+    SystemTime(..), getSystemTime+    -- * Parsing+  , parseSystemTime, parseSystemTimeGMT+    -- * Formatting+  , formatSystemTime, formatSystemTimeGMT+    -- * Format+  , TimeFormat, simpleDateFormat, webDateFormat, mailDateFormat+  ) where++import Data.Time.Clock.System+import Data.Word+import Data.Int+import Foreign.C.Types+import Z.Foreign+import Z.Data.CBytes+import System.IO.Unsafe (unsafePerformIO)++-- | <https://man7.org/linux/man-pages/man3/strftime.3.html strftime> time format.+type TimeFormat = CBytes++-- | Simple format @2020-10-16 03:15:29@.+--+-- The value is \"%Y-%m-%d %H:%M:%S\".+-- This should be used with 'formatSystemTime' and 'parseSystemTime'.+simpleDateFormat :: TimeFormat+simpleDateFormat = "%Y-%m-%d %H:%M:%S"++-- | Format for web (RFC 2616).+--+-- The value is \"%a, %d %b %Y %H:%M:%S GMT\".+-- This should be used with 'formatSystemTimeGMT' and 'parseSystemTimeGMT'.+webDateFormat :: TimeFormat+webDateFormat = "%a, %d %b %Y %H:%M:%S GMT"++-- | Format for e-mail (RFC 5322).+--+-- The value is \"%a, %d %b %Y %H:%M:%S %z\".+-- This should be used with 'formatSystemTime' and 'parseSystemTime'.+mailDateFormat :: TimeFormat+mailDateFormat = "%a, %d %b %Y %H:%M:%S %z"++----------------------------------------------------------------+-- | Formatting 'SystemTime' to 'CBytes' in local time.+--+-- This is a wrapper for strftime_l(), 'systemNanoseconds' is ignored.+-- The result depends on the TZ environment variable.+--+formatSystemTime :: TimeFormat -> SystemTime -> IO CBytes+formatSystemTime fmt t = formatSystemTimeHelper c_format_unix_time fmt t+{-# INLINE formatSystemTime #-}++-- | Formatting 'SystemTime' to 'CBytes' in GMT.+--+-- This is a wrapper for strftime_l(), 'systemNanoseconds' is ignored.+--+-- >>> formatSystemTimeGMT webDateFormat $ MkSystemTime 0 0+-- "Thu, 01 Jan 1970 00:00:00 GMT"+-- >>> let ut = MkSystemTime 100 200+-- >>> let str = formatSystemTimeGMT "%s" ut+-- >>> let ut' = parseSystemTimeGMT "%s" str+-- >>> ((==) `on` systemSeconds) ut ut'+-- True+-- >>> ((==) `on` systemNanoseconds) ut ut'+-- False+--+formatSystemTimeGMT :: TimeFormat -> SystemTime -> CBytes+formatSystemTimeGMT fmt t =+    unsafePerformIO $ formatSystemTimeHelper c_format_unix_time_gmt fmt t+{-# INLINE formatSystemTimeGMT #-}++----------------------------------------------------------------+-- | Parsing 'CBytes' to 'SystemTime' interpreting as localtime.+--+-- This is a wrapper for strptime_l().+-- Many implementations of strptime_l() do not support %Z and+-- some implementations of strptime_l() do not support %z, either.+-- 'systemNanoSeconds' is always set to 0.+--+-- The result depends on the TZ environment variable.+--+-- @+-- > setEnv "TZ" "Africa/Algiers"+-- parseSystemTime simpleDateFormat "1970-01-01 00:00:00"+-- MkSystemTime {systemSeconds = 0, systemNanoseconds = 0}+-- > setEnv "TZ" "Asia/Shanghai"+-- parseSystemTime simpleDateFormat "1970-01-01 00:00:00"+-- MkSystemTime {systemSeconds = -28800, systemNanoseconds = 0}+-- @+--+parseSystemTime :: TimeFormat -> CBytes -> IO SystemTime+parseSystemTime fmt str =+    withCBytesUnsafe fmt $ \cfmt ->+        withCBytesUnsafe str $ \cstr -> do+            sec <- c_parse_unix_time cfmt cstr+            return $ MkSystemTime sec 0++-- | Parsing 'CBytes' to 'SystemTime' interpreting as GMT.+-- This is a wrapper for strptime_l().+-- 'systemNanoSeconds' is always set to 0.+--+-- >>> parseSystemTimeGMT webDateFormat "Thu, 01 Jan 1970 00:00:00 GMT"+-- MkSystemTime {systemSeconds = 0, systemNanoseconds = 0}++parseSystemTimeGMT :: TimeFormat -> CBytes -> SystemTime+parseSystemTimeGMT fmt str = unsafePerformIO $+    withCBytesUnsafe fmt $ \cfmt ->+        withCBytesUnsafe str $ \cstr -> do+            sec <- c_parse_unix_time_gmt cfmt cstr+            return $ MkSystemTime sec 0++--------------------------------------------------------------------------------++foreign import ccall unsafe "c_parse_unix_time"+        c_parse_unix_time :: BA# Word8 -> BA# Word8 -> IO Int64++foreign import ccall unsafe "c_parse_unix_time_gmt"+        c_parse_unix_time_gmt :: BA# Word8 -> BA# Word8 -> IO Int64++foreign import ccall unsafe "c_format_unix_time"+        c_format_unix_time :: BA# Word8 -> Int64 -> MBA# Word8 -> CInt -> IO CSize++foreign import ccall unsafe "c_format_unix_time_gmt"+        c_format_unix_time_gmt :: BA# Word8 -> Int64 -> MBA# Word8 -> CInt -> IO CSize++-- | Helper handling memory allocation for formatSystemTime and formatSystemTimeGMT.+formatSystemTimeHelper+    :: (BA# Word8 -> Int64 -> MBA# Word8 -> CInt -> IO CSize)+    -> TimeFormat+    -> SystemTime+    -> IO CBytes+formatSystemTimeHelper formatFun fmt t = go 80+  where+    MkSystemTime sec _ = t+    go !siz = do+        (bs, r)<- allocCBytesUnsafe siz $ \ pbuf ->+            withCBytesUnsafe fmt $ \ pfmt ->+                formatFun pfmt sec pbuf (fromIntegral siz)+        if r <= 0 then go (siz*2) else return bs
Z/IO/UV/FFI.hsc view
@@ -23,12 +23,11 @@ import           Foreign.Storable import           Z.Data.Array.Unaligned import qualified Z.Data.Array  as A -import qualified Z.Data.Text   as T import           Z.Data.Text.ShowT   (ShowT(..)) import           Z.Data.JSON         (EncodeJSON, ToValue, FromValue) import           Z.Data.CBytes as CBytes import           Z.Foreign-import           Z.IO.Exception (throwUVIfMinus_)+import           Z.IO.Exception (throwUVIfMinus_, bracket) import           Z.IO.Network.SocketAddr    (SocketAddr) import           System.Posix.Types (CSsize (..)) import           GHC.Generics@@ -49,7 +48,7 @@ -- | UVSlotUnsafe wrap a slot which may not have a 'MVar' in blocking table,  --   i.e. the blocking table need to be resized. newtype UVSlotUnsafe = UVSlotUnsafe { unsafeGetSlot :: UVSlot }-type UVFD = CInt+type FD = CInt  -------------------------------------------------------------------------------- -- CONSTANT@@ -115,7 +114,7 @@ peekUVHandleData :: Ptr UVHandle -> IO UVSlotUnsafe peekUVHandleData p =  UVSlotUnsafe <$> (#{peek uv_handle_t, data} p :: IO Int) -foreign import ccall unsafe hs_uv_fileno :: Ptr UVHandle -> IO UVFD+foreign import ccall unsafe hs_uv_fileno :: Ptr UVHandle -> IO FD foreign import ccall unsafe hs_uv_handle_alloc :: Ptr UVLoop -> IO (Ptr UVHandle) foreign import ccall unsafe hs_uv_handle_free  :: Ptr UVHandle -> IO () foreign import ccall unsafe hs_uv_handle_close :: Ptr UVHandle -> IO ()@@ -145,7 +144,7 @@  -------------------------------------------------------------------------------- -- tcp & pipe-foreign import ccall unsafe hs_uv_tcp_open :: Ptr UVHandle -> UVFD -> IO CInt+foreign import ccall unsafe hs_uv_tcp_open :: Ptr UVHandle -> FD -> IO CInt foreign import ccall unsafe uv_tcp_init :: Ptr UVLoop -> Ptr UVHandle -> IO CInt foreign import ccall unsafe uv_tcp_init_ex :: Ptr UVLoop -> Ptr UVHandle -> CUInt -> IO CInt foreign import ccall unsafe uv_tcp_nodelay :: Ptr UVHandle -> CInt -> IO CInt@@ -160,7 +159,7 @@ foreign import ccall unsafe hs_uv_tcp_connect :: Ptr UVHandle -> MBA## SocketAddr -> IO UVSlotUnsafe foreign import ccall unsafe hs_set_socket_reuse :: Ptr UVHandle -> IO CInt -foreign import ccall unsafe hs_uv_pipe_open :: Ptr UVHandle -> UVFD -> IO CInt+foreign import ccall unsafe hs_uv_pipe_open :: Ptr UVHandle -> FD -> IO CInt foreign import ccall unsafe uv_pipe_init :: Ptr UVLoop -> Ptr UVHandle -> CInt -> IO CInt foreign import ccall unsafe uv_pipe_bind :: Ptr UVHandle -> BA## Word8 -> IO CInt foreign import ccall unsafe hs_uv_pipe_connect :: Ptr UVHandle -> BA## Word8 -> IO UVSlotUnsafe@@ -169,7 +168,7 @@ -- udp foreign import ccall unsafe uv_udp_init :: Ptr UVLoop -> Ptr UVHandle -> IO CInt foreign import ccall unsafe uv_udp_init_ex :: Ptr UVLoop -> Ptr UVHandle -> CUInt -> IO CInt-foreign import ccall unsafe uv_udp_open :: Ptr UVHandle -> UVFD -> IO CInt+foreign import ccall unsafe uv_udp_open :: Ptr UVHandle -> FD -> IO CInt foreign import ccall unsafe uv_udp_bind :: Ptr UVHandle -> MBA## SocketAddr -> UDPFlag -> IO CInt  type Membership = CInt @@ -211,8 +210,11 @@ foreign import ccall unsafe hs_uv_udp_recv_start :: Ptr UVHandle -> IO CInt foreign import ccall unsafe uv_udp_recv_stop :: Ptr UVHandle -> IO CInt -foreign import ccall unsafe hs_uv_udp_check_alloc :: Ptr UVHandle -> IO (Ptr UVHandle)-foreign import ccall unsafe hs_uv_udp_check_init :: Ptr UVHandle -> IO CInt+foreign import ccall unsafe hs_uv_udp_check_alloc :: IO (Ptr UVHandle)+foreign import ccall unsafe hs_uv_udp_check_init :: Ptr UVHandle    -- ^ uv_check_t+                                                 -> Ptr UVHandle    -- ^ udp server+                                                 -> IO CInt+foreign import ccall unsafe hs_uv_udp_check_start :: Ptr UVHandle -> IO CInt foreign import ccall unsafe hs_uv_udp_check_close :: Ptr UVHandle -> IO ()  foreign import ccall unsafe hs_uv_udp_send @@ -304,10 +306,10 @@ pattern DEFAULT_MODE = 0o666  -- non-threaded functions-foreign import ccall unsafe hs_uv_fs_open    :: BA## Word8 -> FileFlag -> FileMode -> IO UVFD-foreign import ccall unsafe hs_uv_fs_close   :: UVFD -> IO Int-foreign import ccall unsafe hs_uv_fs_read    :: UVFD -> Ptr Word8 -> Int -> Int64 -> IO Int-foreign import ccall unsafe hs_uv_fs_write   :: UVFD -> Ptr Word8 -> Int -> Int64 -> IO Int+foreign import ccall unsafe hs_uv_fs_open    :: BA## Word8 -> FileFlag -> FileMode -> IO FD+foreign import ccall unsafe hs_uv_fs_close   :: FD -> IO Int+foreign import ccall unsafe hs_uv_fs_read    :: FD -> Ptr Word8 -> Int -> Int64 -> IO Int+foreign import ccall unsafe hs_uv_fs_write   :: FD -> Ptr Word8 -> Int -> Int64 -> IO Int foreign import ccall unsafe hs_uv_fs_unlink  :: BA## Word8 -> IO Int foreign import ccall unsafe hs_uv_fs_mkdir   :: BA## Word8 -> FileMode -> IO Int foreign import ccall unsafe hs_uv_fs_rmdir   :: BA## Word8 -> IO Int@@ -317,11 +319,11 @@ foreign import ccall unsafe hs_uv_fs_open_threaded      :: BA## Word8 -> FileFlag -> FileMode -> Ptr UVLoop -> IO UVSlotUnsafe foreign import ccall unsafe hs_uv_fs_close_threaded -    :: UVFD -> Ptr UVLoop -> IO UVSlotUnsafe+    :: FD -> Ptr UVLoop -> IO UVSlotUnsafe foreign import ccall unsafe hs_uv_fs_read_threaded  -    :: UVFD -> Ptr Word8 -> Int -> Int64 -> Ptr UVLoop -> IO UVSlotUnsafe+    :: FD -> Ptr Word8 -> Int -> Int64 -> Ptr UVLoop -> IO UVSlotUnsafe foreign import ccall unsafe hs_uv_fs_write_threaded -    :: UVFD -> Ptr Word8 -> Int -> Int64 -> Ptr UVLoop -> IO UVSlotUnsafe+    :: FD -> Ptr Word8 -> Int -> Int64 -> Ptr UVLoop -> IO UVSlotUnsafe foreign import ccall unsafe hs_uv_fs_unlink_threaded     :: BA## Word8 -> Ptr UVLoop -> IO UVSlotUnsafe foreign import ccall unsafe hs_uv_fs_mkdir_threaded @@ -550,27 +552,27 @@     <*> (#{peek uv_stat_t, st_birthtim     } p)  foreign import ccall unsafe hs_uv_fs_stat :: BA## Word8 -> Ptr FStat -> IO Int-foreign import ccall unsafe hs_uv_fs_fstat :: UVFD -> Ptr FStat -> IO Int+foreign import ccall unsafe hs_uv_fs_fstat :: FD -> Ptr FStat -> IO Int foreign import ccall unsafe hs_uv_fs_lstat :: BA## Word8 -> Ptr FStat -> IO Int foreign import ccall unsafe hs_uv_fs_rename :: BA## Word8 -> BA## Word8 -> IO Int-foreign import ccall unsafe hs_uv_fs_fsync :: UVFD -> IO Int-foreign import ccall unsafe hs_uv_fs_fdatasync :: UVFD -> IO Int-foreign import ccall unsafe hs_uv_fs_ftruncate :: UVFD -> Int64 -> IO Int+foreign import ccall unsafe hs_uv_fs_fsync :: FD -> IO Int+foreign import ccall unsafe hs_uv_fs_fdatasync :: FD -> IO Int+foreign import ccall unsafe hs_uv_fs_ftruncate :: FD -> Int64 -> IO Int  foreign import ccall unsafe hs_uv_fs_stat_threaded     :: BA## Word8 -> Ptr FStat -> Ptr UVLoop -> IO UVSlotUnsafe foreign import ccall unsafe hs_uv_fs_fstat_threaded-    :: UVFD -> Ptr FStat -> Ptr UVLoop -> IO UVSlotUnsafe+    :: FD -> Ptr FStat -> Ptr UVLoop -> IO UVSlotUnsafe foreign import ccall unsafe hs_uv_fs_lstat_threaded     :: BA## Word8 -> Ptr FStat -> Ptr UVLoop -> IO UVSlotUnsafe foreign import ccall unsafe hs_uv_fs_rename_threaded     :: BA## Word8 -> BA## Word8 -> Ptr UVLoop -> IO UVSlotUnsafe foreign import ccall unsafe hs_uv_fs_fsync_threaded-    :: UVFD -> Ptr UVLoop -> IO UVSlotUnsafe+    :: FD -> Ptr UVLoop -> IO UVSlotUnsafe foreign import ccall unsafe hs_uv_fs_fdatasync_threaded-    :: UVFD -> Ptr UVLoop -> IO UVSlotUnsafe+    :: FD -> Ptr UVLoop -> IO UVSlotUnsafe foreign import ccall unsafe hs_uv_fs_ftruncate_threaded -    :: UVFD -> Int64 -> Ptr UVLoop -> IO UVSlotUnsafe+    :: FD -> Int64 -> Ptr UVLoop -> IO UVSlotUnsafe  -- | Flags control copying. -- @@ -618,17 +620,17 @@ foreign import ccall unsafe hs_uv_fs_chmod_threaded     :: BA## Word8 -> FileMode -> Ptr UVLoop -> IO UVSlotUnsafe -foreign import ccall unsafe hs_uv_fs_fchmod :: UVFD -> FileMode -> IO Int+foreign import ccall unsafe hs_uv_fs_fchmod :: FD -> FileMode -> IO Int foreign import ccall unsafe hs_uv_fs_fchmod_threaded-    :: UVFD -> FileMode -> Ptr UVLoop -> IO UVSlotUnsafe+    :: FD -> FileMode -> Ptr UVLoop -> IO UVSlotUnsafe  foreign import ccall unsafe hs_uv_fs_utime :: BA## Word8 -> Double -> Double -> IO Int foreign import ccall unsafe hs_uv_fs_utime_threaded     :: BA## Word8 -> Double -> Double -> Ptr UVLoop -> IO UVSlotUnsafe -foreign import ccall unsafe hs_uv_fs_futime :: UVFD -> Double -> Double -> IO Int+foreign import ccall unsafe hs_uv_fs_futime :: FD -> Double -> Double -> IO Int foreign import ccall unsafe hs_uv_fs_futime_threaded-    :: UVFD -> Double -> Double -> Ptr UVLoop -> IO UVSlotUnsafe+    :: FD -> Double -> Double -> Ptr UVLoop -> IO UVSlotUnsafe  foreign import ccall unsafe hs_uv_fs_lutime :: BA## Word8 -> Double -> Double -> IO Int foreign import ccall unsafe hs_uv_fs_lutime_threaded@@ -671,6 +673,16 @@ foreign import ccall unsafe hs_uv_fs_realpath_threaded     :: BA## Word8  -> Ptr CString -> Ptr UVLoop -> IO UVSlotUnsafe +foreign import ccall unsafe hs_uv_fs_chown :: BA## Word8 -> UID -> GID -> IO Int+foreign import ccall unsafe hs_uv_fs_chown_threaded+    :: BA## Word8 -> UID -> GID -> Ptr UVLoop -> IO UVSlotUnsafe+foreign import ccall unsafe hs_uv_fs_fchown :: FD -> UID -> GID -> IO Int+foreign import ccall unsafe hs_uv_fs_fchown_threaded+    :: FD -> UID -> GID -> Ptr UVLoop -> IO UVSlotUnsafe+foreign import ccall unsafe hs_uv_fs_lchown :: BA## Word8 -> UID -> GID -> IO Int+foreign import ccall unsafe hs_uv_fs_lchown_threaded+    :: BA## Word8 -> UID -> GID -> Ptr UVLoop -> IO UVSlotUnsafe+ -------------------------------------------------------------------------------- -- process @@ -681,7 +693,7 @@     Word32 #endif    deriving (Eq, Ord, Show, Generic)-   deriving newtype (Storable, Prim, Unaligned, EncodeJSON, ToValue, FromValue)+   deriving newtype (Storable, Prim, Unaligned, Num, EncodeJSON, ToValue, FromValue)    deriving anyclass ShowT  newtype GID = GID @@ -691,7 +703,7 @@     Word32 #endif    deriving (Eq, Ord, Show, Generic)-   deriving newtype (Storable, Prim, Unaligned, EncodeJSON, ToValue, FromValue)+   deriving newtype (Storable, Prim, Unaligned, Num, EncodeJSON, ToValue, FromValue)    deriving anyclass ShowT  type ProcessFlag = CUInt@@ -762,15 +774,15 @@     , processGID :: GID -- ^ This happens only when the appropriate bits are set in the flags fields.     , processStdStreams :: (ProcessStdStream, ProcessStdStream, ProcessStdStream) -- ^ Specifying how (stdin, stdout, stderr) should be passed/created to the child, see 'ProcessStdStream'                             -    }  deriving (Eq, Ord, Show, Generic)-       deriving anyclass ShowT+    }   deriving (Eq, Ord, Show, Generic)+        deriving anyclass (ShowT, EncodeJSON, ToValue, FromValue)  data ProcessStdStream     = ProcessIgnore     -- ^ redirect process std stream to /dev/null     | ProcessCreate     -- ^ create a new std stream-    | ProcessInherit UVFD -- ^ pass an existing FD to child process as std stream+    | ProcessInherit FD -- ^ pass an existing FD to child process as std stream   deriving  (Eq, Ord, Show, Generic)-  deriving anyclass ShowT+  deriving anyclass (ShowT, EncodeJSON, ToValue, FromValue)  processStdStreamFlag :: ProcessStdStream -> CInt processStdStreamFlag ProcessIgnore = #const UV_IGNORE@@ -782,10 +794,10 @@ foreign import ccall unsafe hs_uv_spawn :: Ptr UVLoop                                          -> MBA## ProcessOptions         --  option                                         -> BA## Word8                   --  file-                                        -> BA## Word8                   --  all args-                                        -> CSize                        --  args len-                                        -> BA## Word8                   --  all envs-                                        -> CSsize                        --  envs len+                                        -> BAArray## Word8              --  all args+                                        -> Int                          --  args len+                                        -> BAArray## Word8              --  all envs+                                        -> Int                          --  envs len                                         -> BA## Word8                   --  cwd                                         -> MBA## ProcessStdStream       -- stdio                                         -> IO Int@@ -834,7 +846,7 @@ pattern UV_FILE :: UVHandleType pattern UV_FILE = #const UV_FILE -foreign import ccall unsafe uv_guess_handle :: UVFD -> IO UVHandleType+foreign import ccall unsafe uv_guess_handle :: FD -> IO UVHandleType  foreign import ccall unsafe uv_resident_set_memory :: MBA## CSize -> IO CInt foreign import ccall unsafe uv_uptime :: MBA## Double -> IO CInt@@ -936,10 +948,10 @@ foreign import ccall unsafe uv_os_gethostname :: MBA## Word8 -> MBA## CSize -> IO CInt  data OSName = OSName-    { os_sysname :: T.Text-    , os_release :: T.Text-    , os_version :: T.Text-    , os_machine :: T.Text+    { os_sysname :: CBytes+    , os_release :: CBytes+    , os_version :: CBytes+    , os_machine :: CBytes     }   deriving (Eq, Ord, Show, Generic)         deriving anyclass (ShowT, EncodeJSON, ToValue, FromValue) @@ -947,13 +959,50 @@ getOSName = do     (A.MutablePrimArray mba## :: A.MutablePrimArray A.RealWorld Word8) <- A.newArr (#size uv_utsname_t)     throwUVIfMinus_ (uv_os_uname mba##)-    sn <- toText <$> peekMBA mba## (#offset uv_utsname_t, sysname)-    re <- toText <$> peekMBA mba## (#offset uv_utsname_t, release)-    ve <- toText <$> peekMBA mba## (#offset uv_utsname_t, version)-    ma <- toText <$> peekMBA mba##  (#offset uv_utsname_t, machine) +    sn <- peekMBA mba## (#offset uv_utsname_t, sysname)+    re <- peekMBA mba## (#offset uv_utsname_t, release)+    ve <- peekMBA mba## (#offset uv_utsname_t, version)+    ma <- peekMBA mba##  (#offset uv_utsname_t, machine)      return (OSName sn re ve ma)      foreign import ccall unsafe uv_os_uname :: MBA## OSName -> IO CInt  foreign import ccall unsafe hs_uv_random :: MBA## Word8 -> CSize -> CInt -> IO CInt foreign import ccall unsafe hs_uv_random_threaded :: Ptr Word8 -> CSize -> CInt -> Ptr UVLoop -> IO UVSlotUnsafe++-- | Data type for password file information.+data PassWD = PassWD+    { passwd_username :: CBytes+    , passwd_uid :: UID+    , passwd_gid :: GID+    , passwd_shell :: CBytes+    , passwd_homedir :: CBytes+    }   deriving (Eq, Ord, Show, Generic)+        deriving anyclass (ShowT, EncodeJSON, ToValue, FromValue)++foreign import ccall unsafe uv_os_get_passwd :: MBA## PassWD -> IO CInt+foreign import ccall unsafe uv_os_free_passwd :: MBA## PassWD -> IO ()++-- | Gets a subset of the password file entry for the current effective uid (not the real uid). +--+-- The populated data includes the username, euid, gid, shell, and home directory.+-- On non-Windows systems, all data comes from getpwuid_r(3). +-- On Windows, uid and gid are set to -1 and have no meaning, and shell is empty.+getPassWD :: IO PassWD+getPassWD =  bracket+    (do mpa@(A.MutablePrimArray mba## :: A.MutablePrimArray A.RealWorld Word8) <- A.newArr (#size uv_passwd_t)+        throwUVIfMinus_ (uv_os_get_passwd mba##)+        return mpa)+    (\ (A.MutablePrimArray mba##) -> uv_os_free_passwd mba##)+    (\ (A.MutablePrimArray mba##) -> do+        username <- fromCString =<< peekMBA mba## (#offset uv_passwd_t, username)+        uid <- fromIntegral <$> (peekMBA mba## (#offset uv_passwd_t, uid) :: IO CLong)+        gid <- fromIntegral <$> (peekMBA mba## (#offset uv_passwd_t, gid) :: IO CLong)+        shell <- fromCString =<< peekMBA mba## (#offset uv_passwd_t, shell)+        homedir <- fromCString =<< peekMBA mba## (#offset uv_passwd_t, homedir)+        return (PassWD username uid gid shell homedir))++foreign import ccall unsafe uv_cwd :: MBA## Word8 -> MBA## CSize -> IO CInt+foreign import ccall unsafe uv_chdir :: BA## Word8 -> IO CInt+foreign import ccall unsafe uv_os_homedir :: MBA## Word8 -> MBA## CSize -> IO CInt+foreign import ccall unsafe uv_os_tmpdir :: MBA## Word8 -> MBA## CSize -> IO CInt
Z/IO/UV/UVStream.hs view
@@ -85,7 +85,7 @@     unless c $ writeIORef closed True >> hs_uv_handle_close hdl  -- | Get stream fd-getUVStreamFD :: HasCallStack => UVStream -> IO UVFD+getUVStreamFD :: HasCallStack => UVStream -> IO FD getUVStreamFD (UVStream hdl _ _ closed) = do     c <- readIORef closed     when c throwECLOSED@@ -113,9 +113,14 @@                 void (tryTakeMVar m))          if  | r > 0  -> return r-            -- r == 0 should be impossible, since we guard this situation in c side             | r == fromIntegral UV_EOF -> return 0             | r < 0 ->  throwUVIfMinus (return r)+            -- r == 0 should be impossible, since we guard this situation in c side+            | otherwise -> throwUVError UV_UNKNOWN IOEInfo{+                                  ioeName = "UVStream read error"+                                , ioeDescription = "UVStream read should never return 0 before EOF"+                                , ioeCallStack = callStack+                                }  instance Output UVStream where     -- writeOutput :: HasCallStack => UVStream -> Ptr Word8 -> Int -> IO ()
+ cbits/hs_cwalk.c view
@@ -0,0 +1,88 @@+/*+ * Copyright (c) 2020 Dong Han+ *+ * All rights reserved.+ *+ * Redistribution and use in source and binary forms, with or without+ * modification, are permitted provided that the following conditions+ * are met:+ * 1. Redistributions of source code must retain the above copyright+ *    notice, this list of conditions and the following disclaimer.+ * 2. 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.+ * 3. Neither the names of the authors or the names of any contributors+ *    may be used to endorse or promote products derived from this software+ *    without specific prior written permission.+ *+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 AUTHORS 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.+ */++#include <hs_cwalk.h>+#include <stdlib.h>++HsInt hs_cwk_path_get_basename(const char *path, HsInt *base_off){+    size_t length;+    const char *basename = NULL;+    cwk_path_get_basename(path, &basename, &length);+    if (basename == NULL) {+        *base_off = 0;+        return 0;+    } else {+        *base_off = (HsInt)(basename - path);+        return (HsInt)length;+    }+}++HsInt hs_cwk_path_get_dirname(const char *path){+    size_t length;+    cwk_path_get_dirname(path, &length);+    return (HsInt)length;+}++HsInt hs_cwk_path_get_root(const char *path){+    size_t length;+    cwk_path_get_root(path, &length);+    return (HsInt)length;+}++#if __GLASGOW_HASKELL__ < 810+HsInt hs_cwk_path_join_multiple(const StgMutArrPtrs *paths_arr, HsInt path_n, char *buffer, size_t buffer_size){+    StgArrBytes **paths = (StgArrBytes**)paths_arr->payload;+#else+HsInt hs_cwk_path_join_multiple(const StgArrBytes **paths, HsInt path_n, char *buffer, size_t buffer_size){+#endif+    HsInt r;+    const char **path_list = (const char**)malloc(sizeof(char*)*(path_n+1));+    if (path_list == NULL) {+        *buffer = 0;+        return 0;+    }+    path_list[path_n--] = NULL;+    while(path_n >= 0){+        path_list[path_n] = (char*)paths[path_n]->payload;+        path_n--;+    } +    r = (HsInt)cwk_path_join_multiple(path_list, buffer, buffer_size);+    free(path_list);+    return r;+}++HsInt hs_cwk_path_get_extension(const char *path, size_t *length){+    const char *extension;+    if (cwk_path_get_extension(path, &extension, length)){+        return (extension - path);+    } else {+        return -1;+    }+}
cbits/hs_uv_file.c view
@@ -254,6 +254,27 @@     return (HsInt)req.result; } +HsInt hs_uv_fs_chown(const char* path, uv_uid_t uid, uv_gid_t gid){+    uv_fs_t req;+    uv_fs_chown(NULL, &req, path, uid, gid, NULL);+    uv_fs_req_cleanup(&req);    // maybe not neccessary+    return (HsInt)req.result;+}++HsInt hs_uv_fs_fchown(int32_t file, uv_uid_t uid, uv_gid_t gid){+    uv_fs_t req;+    uv_fs_fchown(NULL, &req, file, uid, gid, NULL);+    uv_fs_req_cleanup(&req);    // maybe not neccessary+    return (HsInt)req.result;+}++HsInt hs_uv_fs_lchown(const char* path, uv_uid_t uid, uv_gid_t gid){+    uv_fs_t req;+    uv_fs_lchown(NULL, &req, path, uid, gid, NULL);+    uv_fs_req_cleanup(&req);    // maybe not neccessary+    return (HsInt)req.result;+}+ //////////////////////////////////////////////////////////////////////////////// // fs, thread pool version //@@ -753,6 +774,48 @@     req->data = (void*)slot;     loop_data->buffer_table[slot] = (char*)result_path;     int r = uv_fs_realpath(loop, req, path, hs_uv_fs_readlink_callback);+    if (r < 0) {+        free_slot(loop_data, slot);+        return (HsInt)r;+    } else return slot;+}++HsInt hs_uv_fs_chown_threaded(const char* path, uv_uid_t uid, uv_gid_t gid, uv_loop_t* loop){+    hs_loop_data* loop_data = loop->data;+    HsInt slot = alloc_slot(loop_data);+    if (slot < 0) return UV_ENOMEM;+    uv_fs_t* req = +        (uv_fs_t*)fetch_uv_struct(loop_data, slot);+    req->data = (void*)slot;+    int r = uv_fs_chown(loop, req, path, uid, gid, hs_uv_fs_callback);+    if (r < 0) {+        free_slot(loop_data, slot);+        return (HsInt)r;+    } else return slot;+}++HsInt hs_uv_fs_fchown_threaded(int32_t file, uv_uid_t uid, uv_gid_t gid, uv_loop_t* loop){+    hs_loop_data* loop_data = loop->data;+    HsInt slot = alloc_slot(loop_data);+    if (slot < 0) return UV_ENOMEM;+    uv_fs_t* req = +        (uv_fs_t*)fetch_uv_struct(loop_data, slot);+    req->data = (void*)slot;+    int r = uv_fs_fchown(loop, req, file, uid, gid, hs_uv_fs_callback);+    if (r < 0) {+        free_slot(loop_data, slot);+        return (HsInt)r;+    } else return slot;+}++HsInt hs_uv_fs_lchown_threaded(const char* path, uv_uid_t uid, uv_gid_t gid, uv_loop_t* loop){+    hs_loop_data* loop_data = loop->data;+    HsInt slot = alloc_slot(loop_data);+    if (slot < 0) return UV_ENOMEM;+    uv_fs_t* req = +        (uv_fs_t*)fetch_uv_struct(loop_data, slot);+    req->data = (void*)slot;+    int r = uv_fs_lchown(loop, req, path, uid, gid, hs_uv_fs_callback);     if (r < 0) {         free_slot(loop_data, slot);         return (HsInt)r;
cbits/hs_uv_process.c view
@@ -49,14 +49,25 @@ HsInt hs_uv_spawn(uv_loop_t* loop                  , uv_process_options_t* options                  , const char* file-                 , const char* all_args-                 , const size_t args_len-                 , const char* all_env-                 , const ssize_t env_len+#if __GLASGOW_HASKELL__ < 810+                 , const StgMutArrPtrs* all_args_arr+                 , const HsInt args_len+                 , const StgMutArrPtrs* all_env_arr+                 , const HsInt env_len+#else+                 , const StgArrBytes** all_args+                 , const HsInt args_len+                 , const StgArrBytes** all_env+                 , const HsInt env_len+#endif                  , const char* cwd-                 , uv_stdio_container_t* stdio){-    int r, i;-    char* p;+                 , uv_stdio_container_t* container){+#if __GLASGOW_HASKELL__ < 810+    StgArrBytes **all_args = (StgArrBytes**)all_args_arr->payload;+    StgArrBytes **all_env = (StgArrBytes**)all_env_arr->payload;+#endif+    int r;+    HsInt i;     char **args = NULL, **env = NULL;      hs_loop_data* loop_data = loop->data;@@ -73,31 +84,28 @@     args = (char**)malloc(sizeof(char*)*(args_len + 2));     if (args == NULL) return UV_ENOMEM;     args[i++] = (char*)file;-    if (args_len > 0) args[i++] = (char*)all_args;-    for(p = (char*)all_args; i <= args_len; p++){-        if (*p == 0) {-            args[i++] = p+1;-        }+    while(i <= args_len) {+        args[i] = (char*)all_args[i-1]->payload;+        i++;     }     args[i] = NULL;     options->args = args;      i = 0;+    // env_len == -1 for inherit from parent     if (env_len >= 0){         env = (char**)malloc(sizeof(char*)*(env_len+1));         if (env == NULL) return UV_ENOMEM;-        if (env_len > 0) env[i++] = (char*)all_env;-        for(p = (char*)all_env; i < env_len; p++){-            if (*p == 0) {-                env[i++] = p+1;-            }+        while(i < env_len) {+            env[i] = (char*)all_env[i]->payload;+            i++;         }         env[i] = NULL;     }     options->env = env;      options->stdio_count = 3;-    options->stdio = stdio;+    options->stdio = container;     options->cwd = cwd;      r = uv_spawn(loop, handle, options);
cbits/hs_uv_stream.c view
@@ -353,7 +353,6 @@ // do it in listen's callback, since it'll be called multiple times during uv_run. uv_check_t* hs_uv_accept_check_alloc(){     uv_check_t* check = malloc(sizeof(uv_check_t));-    if (check == NULL) return NULL;     return check; } 
cbits/hs_uv_udp.c view
@@ -115,23 +115,25 @@  // It's hard to arrange receiving notification without check handler, we can't // do it in recv's callback, since it'll be called multiple times during uv_run.-uv_check_t* hs_uv_udp_check_alloc(uv_udp_t* server){+uv_check_t* hs_uv_udp_check_alloc(){     uv_check_t* check = malloc(sizeof(uv_check_t));-    if (check == NULL) return NULL;-    check->data = (void*)server;    // we link server to check's data field     return check; } -int hs_uv_udp_check_init(uv_check_t* check){-    uv_udp_t* server = check->data;+int hs_uv_udp_check_init(uv_check_t* check, uv_udp_t* server){     int r = uv_check_init(server->loop, check);-    if (r < 0) return r;+    check->data = (void*)server;    // we link server to check's data field+    return r;+}++int hs_uv_udp_check_start(uv_check_t* check){     return uv_check_start(check, hs_udp_check_cb); }  void hs_uv_udp_check_close(uv_check_t* check){     uv_close((uv_handle_t*)check, (uv_close_cb)free); }+ void hs_uv_udp_send_cb(uv_udp_send_t* req, int status){     HsInt slot = (HsInt)req->data;     uv_loop_t* loop = req->handle->loop;
+ include/hs_cwalk.h view
@@ -0,0 +1,43 @@+/*+ * Copyright (c) 2017-2018 Dong Han+ *+ * All rights reserved.+ *+ * Redistribution and use in source and binary forms, with or without+ * modification, are permitted provided that the following conditions+ * are met:+ * 1. Redistributions of source code must retain the above copyright+ *    notice, this list of conditions and the following disclaimer.+ * 2. 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.+ * 3. Neither the names of the authors or the names of any contributors+ *    may be used to endorse or promote products derived from this software+ *    without specific prior written permission.+ *+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 AUTHORS 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.+ */++#include <HsFFI.h>  // for HsInt+#include <Rts.h>    // for StgArrBytes+#include <cwalk.h>++HsInt hs_cwk_path_get_basename(const char *path, HsInt *base_off);+HsInt hs_cwk_path_get_dirname(const char *path);+HsInt hs_cwk_path_get_root(const char *path);+#if __GLASGOW_HASKELL__ < 810+HsInt hs_cwk_path_join_multiple(const StgMutArrPtrs *paths_arr, HsInt path_n, char *buffer, size_t buffer_size);+#else+HsInt hs_cwk_path_join_multiple(const StgArrBytes **paths, HsInt path_n, char *buffer, size_t buffer_size);+#endif+HsInt hs_cwk_path_get_extension(const char *path, size_t *length);
include/hs_uv.h view
@@ -29,6 +29,7 @@  */  #include <HsFFI.h>  // for HsInt+#include <Rts.h> #include <assert.h> #include <stdlib.h> // for malloc, free, etc. #include <string.h> // for fs path@@ -299,8 +300,9 @@ int hs_uv_udp_recv_start(uv_udp_t* handle); HsInt hs_uv_udp_send_connected(uv_udp_t* handle, char* buf, HsInt buf_siz); HsInt hs_uv_upd_send(uv_udp_t* handle, const struct sockaddr* addr, char* buf, HsInt buf_siz);-uv_check_t* hs_uv_udp_check_alloc(uv_udp_t* server);-int hs_uv_udp_check_init(uv_check_t* check);+uv_check_t* hs_uv_udp_check_alloc();+int hs_uv_udp_check_init(uv_check_t* check, uv_udp_t* server);+int hs_uv_udp_check_start(uv_check_t* check); void hs_uv_udp_check_close(uv_check_t* check);  ////////////////////////////////////////////////////////////////////////////////@@ -616,7 +618,9 @@ HsInt hs_uv_fs_symlink(const char* path, const char* path2, int flag); HsInt hs_uv_fs_readlink(const char* path, char** result_path); HsInt hs_uv_fs_realpath(const char* path, char** result_path);-+HsInt hs_uv_fs_chown(const char* path, uv_uid_t uid, uv_gid_t gid);+HsInt hs_uv_fs_fchown(int32_t file, uv_uid_t uid, uv_gid_t gid);+HsInt hs_uv_fs_lchown(const char* path, uv_uid_t uid, uv_gid_t gid);  //////////////////////////////////////////////////////////////////////////////// // fs, thread pool version@@ -647,6 +651,9 @@ HsInt hs_uv_fs_symlink_threaded(const char* path, const char* path2, int flag, uv_loop_t* loop); HsInt hs_uv_fs_readlink_threaded(const char* path, char** result_path, uv_loop_t* loop); HsInt hs_uv_fs_realpath_threaded(const char* path, char** result_path, uv_loop_t* loop);+HsInt hs_uv_fs_chown_threaded(const char* path, uv_uid_t uid, uv_gid_t gid, uv_loop_t* loop);+HsInt hs_uv_fs_fchown_threaded(int32_t file, uv_uid_t uid, uv_gid_t gid, uv_loop_t* loop);+HsInt hs_uv_fs_lchown_threaded(const char* path, uv_uid_t uid, uv_gid_t gid, uv_loop_t* loop);  //////////////////////////////////////////////////////////////////////////////// // process@@ -654,10 +661,17 @@ HsInt hs_uv_spawn(uv_loop_t* loop                  , uv_process_options_t* options                  , const char* file-                 , const char* all_args-                 , const size_t args_len-                 , const char* all_env-                 , const ssize_t env_len+#if __GLASGOW_HASKELL__ < 810+                 , const StgMutArrPtrs* all_args_arr+                 , const HsInt args_len+                 , const StgMutArrPtrs* all_env_arr+                 , const HsInt env_len+#else+                 , const StgArrBytes** all_args+                 , const HsInt args_len+                 , const StgArrBytes** all_env+                 , const HsInt env_len+#endif                  , const char* cwd                  , uv_stdio_container_t* container); 
test/Z/IO/ResourceSpec.hs view
@@ -26,66 +26,67 @@             resPool = initPool res 100 1         R.withResource resPool $ \ pool -> do             let res = initInPool pool-            replicateM_ 300 . forkIO. R.withResource res $ \ _ -> do+            forM_ [1..1000] $ \ k -> forkIO. R.withResource res $ \ i -> do                 atomicAddCounter_ workerCounter 1                 r <- readPrimIORef resCounter-                threadDelay 1000000+                threadDelay (k * 1000)                 assertEqual "pool should limit max usage" True (r <= 100) -            threadDelay 4000000 -- first 100 worker quickly get resources+            threadDelay 5000000 -- first 100 worker quickly get resources                                 -- then hold for 1s, rest 100 worker have to wait, and so on-                                -- so here we wait for 4s to make sure every worker got a resource+                                -- so here we wait for 2s to make sure every worker got a resource                                 -- we used to use replicateConcurrently_ from async, but it's                                 -- not really neccessary              w <- readPrimIORef workerCounter-            assertEqual "worker should be able to get resource" 300 w+            assertEqual "worker should be able to get resource" 1000 w              r <- readPrimIORef resCounter             assertEqual "pool should keep returned resources alive" 100 r -            s <- statPool pool+            s <- poolStat pool             assertEqual "pool should be scanning returned resources" PoolScanning s -            threadDelay 1200000  -- another 1.2s+            threadDelay 5000000  -- after 5s, 1000 thread should release all resources              r <- readPrimIORef resCounter             assertEqual "pool should reap unused resources" 0 r              threadDelay 1200000  -- another 1.2s -            s <- statPool pool+            s <- poolStat pool             assertEqual "pool should stop scanning returned resources" PoolEmpty s              -- Let's test again              writePrimIORef workerCounter 0 -            replicateM_ 300 . forkIO. R.withResource res $ \ _ -> do+            forM_ [1..1000] $ \ k -> forkIO. R.withResource res $ \ i -> do                 atomicAddCounter_ workerCounter 1                 r <- readPrimIORef resCounter-                threadDelay 1000000+                threadDelay (k * 1000)                 assertEqual "pool should limit max usage" True (r <= 100) -            threadDelay 4000000 +            threadDelay 5000000+             w <- readPrimIORef workerCounter-            assertEqual "worker should be able to get resource" 300 w+            assertEqual "worker should be able to get resource" 1000 w              r <- readPrimIORef resCounter             assertEqual "pool should keep returned resources alive" 100 r -            s <- statPool pool+            s <- poolStat pool             assertEqual "pool should be scanning returned resources" PoolScanning s -            threadDelay 1200000  -- another 1.2s+            threadDelay 5000000  -- after 5s, 1000 thread should release all resources              r <- readPrimIORef resCounter             assertEqual "pool should reap unused resources" 0 r              threadDelay 1200000  -- another 1.2s -            s <- statPool pool+            s <- poolStat pool             assertEqual "pool should stop scanning returned resources" PoolEmpty s      it "resource pool under exceptions" $ do@@ -95,27 +96,27 @@             resPool = initPool res 100 1         R.withResource resPool $ \ pool -> do             let res = initInPool pool-            handle (\ (e :: WorkerException) -> return ()) .-                    replicateM_ 300 . forkIO. R.withResource res $ \ i -> do-                        r <- readPrimIORef resCounter-                        threadDelay 1000000-                        when (even i) (throwIO WorkerException)-                        assertEqual "pool should limit max usage" True (r <= 100) -            threadDelay 4000000+            forM_ [1..1000] $ \ k -> forkIO. R.withResource res $ \ i -> do+                r <- readPrimIORef resCounter+                threadDelay (k * 1000)+                when (even i) (throwIO WorkerException)+                assertEqual "pool should limit max usage" True (r <= 100) +            threadDelay 5000000+             r <- readPrimIORef resCounter             assertEqual "pool should keep returned resources alive" 100 r -            s <- statPool pool+            s <- poolStat pool             assertEqual "pool should be scanning returned resources" PoolScanning s -            threadDelay 1200000  -- another 1.2s+            threadDelay 5000000  -- after 5s, 1000 thread should release all resources              r <- readPrimIORef resCounter             assertEqual "pool should reap unused resources" 0 r -            threadDelay 1200000  -- another 1.2s+            threadDelay 2000000  -- after 2s, scanning thread should stoped -            s <- statPool pool+            s <- poolStat pool             assertEqual "pool should stop scanning returned resources" PoolEmpty s
+ third_party/cwalk/include/cwalk.h view
@@ -0,0 +1,466 @@+#pragma once
+
+#ifndef CWK_LIBRARY_H
+#define CWK_LIBRARY_H
+
+#include <stdbool.h>
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" 
+{
+#endif
+
+/**
+ * A segment represents a single component of a path. For instance, on linux a
+ * path might look like this "/var/log/", which consists of two segments "var"
+ * and "log".
+ */
+struct cwk_segment
+{
+  const char *path;
+  const char *segments;
+  const char *begin;
+  const char *end;
+  size_t size;
+};
+
+/**
+ * The segment type can be used to identify whether a segment is a special
+ * segment or not.
+ *
+ * CWK_NORMAL - normal folder or file segment
+ * CWK_CURRENT - "./" current folder segment
+ * CWK_BACK - "../" relative back navigation segment
+ */
+enum cwk_segment_type
+{
+  CWK_NORMAL,
+  CWK_CURRENT,
+  CWK_BACK
+};
+
+/**
+ * @brief Determines the style which is used for the path parsing and
+ * generation.
+ */
+enum cwk_path_style
+{
+  CWK_STYLE_WINDOWS,
+  CWK_STYLE_UNIX
+};
+
+/**
+ * @brief Generates an absolute path based on a base.
+ *
+ * This function generates an absolute path based on a base path and another
+ * path. It is guaranteed to return an absolute path. If the second submitted
+ * path is absolute, it will override the base path. The result will be written
+ * to a buffer, which might be truncated if the buffer is not large enough to
+ * hold the full path. However, the truncated result will always be
+ * null-terminated. The returned value is the amount of characters which the
+ * resulting path would take if it was not truncated (excluding the
+ * null-terminating character).
+ *
+ * @param base The absolute base path on which the relative path will be applied.
+ * @param path The relative path which will be applied on the base path.
+ * @param buffer The buffer where the result will be written to.
+ * @param buffer_size The size of the result buffer.
+ * @return Returns the total amount of characters of the new absolute path.
+ */
+size_t cwk_path_get_absolute(const char *base, const char *path, char *buffer,
+  size_t buffer_size);
+
+/**
+ * @brief Generates a relative path based on a base.
+ *
+ * This function generates a relative path based on a base path and another
+ * path. It determines how to get to the submitted path, starting from the base
+ * directory. The result will be written to a buffer, which might be truncated
+ * if the buffer is not large enough to hold the full path. However, the
+ * truncated result will always be null-terminated. The returned value is the
+ * amount of characters which the resulting path would take if it was not
+ * truncated (excluding the null-terminating character).
+ *
+ * @param base_directory The base path from which the relative path will start.
+ * @param path The target path where the relative path will point to.
+ * @param buffer The buffer where the result will be written to.
+ * @param buffer_size The size of the result buffer.
+ * @return Returns the total amount of characters of the full path.
+ */
+size_t cwk_path_get_relative(const char *base_directory, const char *path,
+  char *buffer, size_t buffer_size);
+
+/**
+ * @brief Joins two paths together.
+ *
+ * This function generates a new path by combining the two submitted paths. It
+ * will remove double separators, and unlike cwk_path_get_absolute it permits
+ * the use of two relative paths to combine. The result will be written to a
+ * buffer, which might be truncated if the buffer is not large enough to hold
+ * the full path. However, the truncated result will always be null-terminated.
+ * The returned value is the amount of characters which the resulting path would
+ * take if it was not truncated (excluding the null-terminating character).
+ *
+ * @param path_a The first path which comes first.
+ * @param path_b The second path which comes after the first.
+ * @param buffer The buffer where the result will be written to.
+ * @param buffer_size The size of the result buffer.
+ * @return Returns the total amount of characters of the full, combined path.
+ */
+size_t cwk_path_join(const char *path_a, const char *path_b, char *buffer,
+  size_t buffer_size);
+
+/**
+ * @brief Joins multiple paths together.
+ *
+ * This function generates a new path by joining multiple paths together. It
+ * will remove double separators, and unlike cwk_path_get_absolute it permits
+ * the use of multiple relative paths to combine. The last path of the submitted
+ * string array must be set to NULL. The result will be written to a buffer,
+ * which might be truncated if the buffer is not large enough to hold the full
+ * path. However, the truncated result will always be null-terminated. The
+ * returned value is the amount of characters which the resulting path would
+ * take if it was not truncated (excluding the null-terminating character).
+ *
+ * @param paths An array of paths which will be joined.
+ * @param buffer The buffer where the result will be written to.
+ * @param buffer_size The size of the result buffer.
+ * @return Returns the total amount of characters of the full, combined path.
+ */
+size_t cwk_path_join_multiple(const char **paths, char *buffer,
+  size_t buffer_size);
+
+/**
+ * @brief Determines the root of a path.
+ *
+ * This function determines the root of a path by finding it's length. The root
+ * always starts at the submitted path. If the path has no root, the length will
+ * be set to zero.
+ *
+ * @param path The path which will be inspected.
+ * @param length The output of the root length.
+ */
+void cwk_path_get_root(const char *path, size_t *length);
+
+/**
+ * @brief Changes the root of a path.
+ *
+ * This function changes the root of a path. It does not normalize the result.
+ * The result will be written to a buffer, which might be truncated if the
+ * buffer is not large enough to hold the full path. However, the truncated
+ * result will always be null-terminated. The returned value is the amount of
+ * characters which the resulting path would take if it was not truncated
+ * (excluding the null-terminating character).
+ *
+ * @param path The original path which will get a new root.
+ * @param new_root The new root which will be placed in the path.
+ * @param buffer The output buffer where the result is written to.
+ * @param buffer_size The size of the output buffer where the result is written
+ * to.
+ * @return Returns the total amount of characters of the new path.
+ */
+size_t cwk_path_change_root(const char *path, const char *new_root,
+  char *buffer, size_t buffer_size);
+
+/**
+ * @brief Determine whether the path is absolute or not.
+ *
+ * This function checks whether the path is an absolute path or not. A path is
+ * considered to be absolute if the root ends with a separator.
+ *
+ * @param path The path which will be checked.
+ * @return Returns true if the path is absolute or false otherwise.
+ */
+bool cwk_path_is_absolute(const char *path);
+
+/**
+ * @brief Determine whether the path is relative or not.
+ *
+ * This function checks whether the path is a relative path or not. A path is
+ * considered to be relative if the root does not end with a separator.
+ *
+ * @param path The path which will be checked.
+ * @return Returns true if the path is relative or false otherwise.
+ */
+bool cwk_path_is_relative(const char *path);
+
+/**
+ * @brief Gets the basename of a file path.
+ *
+ * This function gets the basename of a file path. A pointer to the beginning of
+ * the basename will be returned through the basename parameter. This pointer
+ * will be positioned on the first letter after the separator. The length of the
+ * file path will be returned through the length parameter. The length will be
+ * set to zero and the basename to NULL if there is no basename available.
+ *
+ * @param path The path which will be inspected.
+ * @param basename The output of the basename pointer.
+ * @param length The output of the length of the basename.
+ */
+void cwk_path_get_basename(const char *path, const char **basename,
+  size_t *length);
+
+/**
+ * @brief Changes the basename of a file path.
+ *
+ * This function changes the basename of a file path. This function will not
+ * write out more than the specified buffer can contain. However, the generated
+ * string is always null-terminated - even if not the whole path is written out.
+ * The function returns the total number of characters the complete buffer would
+ * have, even if it was not written out completely. The path may be the same
+ * memory address as the buffer.
+ *
+ * @param path The original path which will be used for the modified path.
+ * @param new_basename The new basename which will replace the old one.
+ * @param buffer The buffer where the changed path will be written to.
+ * @param buffer_size The size of the result buffer where the changed path is
+ * written to.
+ * @return Returns the size which the complete new path would have if it was not
+ * truncated.
+ */
+size_t cwk_path_change_basename(const char *path, const char *new_basename,
+  char *buffer, size_t buffer_size);
+
+/**
+ * @brief Gets the dirname of a file path.
+ *
+ * This function determines the dirname of a file path and returns the length up
+ * to which character is considered to be part of it. If no dirname is found,
+ * the length will be set to zero. The beginning of the dirname is always equal
+ * to the submitted path pointer.
+ *
+ * @param path The path which will be inspected.
+ * @param length The length of the dirname.
+ */
+void cwk_path_get_dirname(const char *path, size_t *length);
+
+/**
+ * @brief Gets the extension of a file path.
+ *
+ * This function extracts the extension portion of a file path. A pointer to
+ * the beginning of the extension will be returned through the extension
+ * parameter if an extension is found and true is returned. This pointer will be
+ * positioned on the dot. The length of the extension name will be returned
+ * through the length parameter. If no extension is found both parameters won't
+ * be touched and false will be returned.
+ *
+ * @param path The path which will be inspected.
+ * @param extension The output of the extension pointer.
+ * @param length The output of the length of the extension.
+ * @return Returns true if an extension is found or false otherwise.
+ */
+bool cwk_path_get_extension(const char *path, const char **extension,
+  size_t *length);
+
+/**
+ * @brief Determines whether the file path has an extension.
+ *
+ * This function determines whether the submitted file path has an extension.
+ * This will evaluate to true if the last segment of the path contains a dot.
+ *
+ * @param path The path which will be inspected.
+ * @return Returns true if the path has an extension or false otherwise.
+ */
+bool cwk_path_has_extension(const char *path);
+
+/**
+ * @brief Changes the extension of a file path.
+ *
+ * This function changes the extension of a file name. The function will append
+ * an extension if the basename does not have an extension, or use the extension
+ * as a basename if the path does not have a basename. This function will not
+ * write out more than the specified buffer can contain. However, the generated
+ * string is always null-terminated - even if not the whole path is written out.
+ * The function returns the total number of characters the complete buffer would
+ * have, even if it was not written out completely. The path may be the same
+ * memory address as the buffer.
+ *
+ * @param path The path which will be used to make the change.
+ * @param new_extension The extension which will be placed within the new path.
+ * @param buffer The output buffer where the result will be written to.
+ * @param buffer_size The size of the output buffer where the result will be
+ * written to.
+ * @return Returns the total size which the output would have if it was not
+ * truncated.
+ */
+size_t cwk_path_change_extension(const char *path, const char *new_extension,
+  char *buffer, size_t buffer_size);
+
+/**
+ * @brief Creates a normalized version of the path.
+ *
+ * This function creates a normalized version of the path within the specified
+ * buffer. This function will not write out more than the specified buffer can
+ * contain. However, the generated string is always null-terminated - even if
+ * not the whole path is written out. The function returns the total number of
+ * characters the complete buffer would have, even if it was not written out
+ * completely. The path may be the same memory address as the buffer.
+ *
+ * The following will be true for the normalized path:
+ * 1) "../" will be resolved.
+ * 2) "./" will be removed.
+ * 3) double separators will be fixed with a single separator.
+ * 4) separator suffixes will be removed.
+ *
+ * @param path The path which will be normalized.
+ * @param buffer The buffer where the new path is written to.
+ * @param buffer_size The size of the buffer.
+ * @return The size which the complete normalized path has if it was not
+ * truncated.
+ */
+size_t cwk_path_normalize(const char *path, char *buffer, size_t buffer_size);
+
+/**
+ * @brief Finds common portions in two paths.
+ *
+ * This function finds common portions in two paths and returns the number
+ * characters from the beginning of the base path which are equal to the other
+ * path.
+ *
+ * @param path_base The base path which will be compared with the other path.
+ * @param path_other The other path which will compared with the base path.
+ * @return Returns the number of characters which are common in the base path.
+ */
+size_t cwk_path_get_intersection(const char *path_base, const char *path_other);
+
+/**
+ * @brief Gets the first segment of a path.
+ *
+ * This function finds the first segment of a path. The position of the segment
+ * is set to the first character after the separator, and the length counts all
+ * characters until the next separator (excluding the separator).
+ *
+ * @param path The path which will be inspected.
+ * @param segment The segment which will be extracted.
+ * @return Returns true if there is a segment or false if there is none.
+ */
+bool cwk_path_get_first_segment(const char *path, struct cwk_segment *segment);
+
+/**
+ * @brief Gets the last segment of the path.
+ *
+ * This function gets the last segment of a path. This function may return false
+ * if the path doesn't contain any segments, in which case the submitted segment
+ * parameter is not modified. The position of the segment is set to the first
+ * character after the separator, and the length counts all characters until the
+ * end of the path (excluding the separator).
+ *
+ * @param path The path which will be inspected.
+ * @param segment The segment which will be extracted.
+ * @return Returns true if there is a segment or false if there is none.
+ */
+bool cwk_path_get_last_segment(const char *path, struct cwk_segment *segment);
+
+/**
+ * @brief Advances to the next segment.
+ *
+ * This function advances the current segment to the next segment. If there are
+ * no more segments left, the submitted segment structure will stay unchanged
+ * and false is returned.
+ *
+ * @param segment The current segment which will be advanced to the next one.
+ * @return Returns true if another segment was found or false otherwise.
+ */
+bool cwk_path_get_next_segment(struct cwk_segment *segment);
+
+/**
+ * @brief Moves to the previous segment.
+ *
+ * This function moves the current segment to the previous segment. If the
+ * current segment is the first one, the submitted segment structure will stay
+ * unchanged and false is returned.
+ *
+ * @param segment The current segment which will be moved to the previous one.
+ * @return Returns true if there is a segment before this one or false
+ * otherwise.
+ */
+bool cwk_path_get_previous_segment(struct cwk_segment *segment);
+
+/**
+ * @brief Gets the type of the submitted path segment.
+ *
+ * This function inspects the contents of the segment and determines the type of
+ * it. Currently, there are three types CWK_NORMAL, CWK_CURRENT and CWK_BACK. A
+ * CWK_NORMAL segment is a normal folder or file entry. A CWK_CURRENT is a "./"
+ * and a CWK_BACK a "../" segment.
+ *
+ * @param segment The segment which will be inspected.
+ * @return Returns the type of the segment.
+ */
+enum cwk_segment_type cwk_path_get_segment_type(
+  const struct cwk_segment *segment);
+
+/**
+ * @brief Changes the content of a segment.
+ *
+ * This function overrides the content of a segment to the submitted value and
+ * outputs the whole new path to the submitted buffer. The result might require
+ * less or more space than before if the new value length differs from the
+ * original length. The output is truncated if the new path is larger than the
+ * submitted buffer size, but it is always null-terminated. The source of the
+ * segment and the submitted buffer may be the same.
+ *
+ * @param segment The segment which will be modifier.
+ * @param value The new content of the segment.
+ * @param buffer The buffer where the modified path will be written to.
+ * @param buffer_size The size of the output buffer.
+ * @return Returns the total size which would have been written if the output
+ * was not truncated.
+ */
+size_t cwk_path_change_segment(struct cwk_segment *segment, const char *value,
+  char *buffer, size_t buffer_size);
+
+/**
+ * @brief Checks whether the submitted pointer points to a separator.
+ *
+ * This function simply checks whether the submitted pointer points to a
+ * separator, which has to be null-terminated (but not necessarily after the
+ * separator). The function will return true if it is a separator, or false
+ * otherwise.
+ *
+ * @param symbol A pointer to a string.
+ * @return Returns true if it is a separator, or false otherwise.
+ */
+bool cwk_path_is_separator(const char *str);
+
+/**
+ * @brief Guesses the path style.
+ *
+ * This function guesses the path style based on a submitted path-string. The
+ * guessing will look at the root and the type of slashes contained in the path
+ * and return the style which is more likely used in the path.
+ *
+ * @param path The path which will be inspected.
+ * @return Returns the style which is most likely used for the path.
+ */
+enum cwk_path_style cwk_path_guess_style(const char *path);
+
+/**
+ * @brief Configures which path style is used.
+ *
+ * This function configures which path style is used. The following styles are
+ * currently supported.
+ *
+ * CWK_STYLE_WINDOWS: Use backslashes as a separator and volume for the root.
+ * CWK_STYLE_UNIX: Use slashes as a separator and a slash for the root.
+ *
+ * @param style The style which will be used from now on.
+ */
+void cwk_path_set_style(enum cwk_path_style style);
+
+/**
+ * @brief Gets the path style configuration.
+ *
+ * This function gets the style configuration which is currently used for the
+ * paths. This configuration determines how paths are parsed and generated.
+ *
+ * @return Returns the current path style configuration.
+ */
+enum cwk_path_style cwk_path_get_style(void);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif
+ third_party/cwalk/src/cwalk.c view
@@ -0,0 +1,1432 @@+#include <assert.h>
+#include <ctype.h>
+#include <cwalk.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+/**
+ * We try to default to a different path style depending on the operating
+ * system. So this should detect whether we should use windows or unix paths.
+ */
+#if defined(WIN32) || defined(_WIN32) ||                                       \
+  defined(__WIN32) && !defined(__CYGWIN__)
+static enum cwk_path_style path_style = CWK_STYLE_WINDOWS;
+#else
+static enum cwk_path_style path_style = CWK_STYLE_UNIX;
+#endif
+
+/**
+ * This is a list of separators used in different styles. Windows can read
+ * multiple separators, but it generally outputs just a backslash. The output
+ * will always use the first character for the output.
+ */
+static const char *separators[] = {
+  "\\/", // CWK_STYLE_WINDOWS
+  "/"    // CWK_STYLE_UNIX
+};
+
+/**
+ * A joined path represents multiple path strings which are concatenated, but
+ * not (necessarily) stored in contiguous memory. The joined path allows to
+ * iterate over the segments as if it was one piece of path.
+ */
+struct cwk_segment_joined
+{
+  struct cwk_segment segment;
+  const char **paths;
+  size_t path_index;
+};
+
+static size_t cwk_path_output_sized(char *buffer, size_t buffer_size,
+  size_t position, const char *str, size_t length)
+{
+  size_t amount_written;
+
+  // First we determine the amount which we can write to the buffer. There are
+  // three cases. In the first case we have enough to store the whole string in
+  // it. In the second one we can only store a part of it, and in the third we
+  // have no space left.
+  if (buffer_size > position + length) {
+    amount_written = length;
+  } else if (buffer_size > position) {
+    amount_written = buffer_size - position;
+  } else {
+    amount_written = 0;
+  }
+
+  // If we actually want to write out something we will do that here. We will
+  // always append a '\0', this way we are guaranteed to have a valid string at
+  // all times.
+  if (amount_written > 0) {
+    memmove(&buffer[position], str, amount_written);
+  }
+
+  // Return the theoretical length which would have been written when everything
+  // would have fit in the buffer.
+  return length;
+}
+
+static size_t cwk_path_output_current(char *buffer, size_t buffer_size,
+  size_t position)
+{
+  // We output a "current" directory, which is a single character. This
+  // character is currently not style dependant.
+  return cwk_path_output_sized(buffer, buffer_size, position, ".", 1);
+}
+
+static size_t cwk_path_output_back(char *buffer, size_t buffer_size,
+  size_t position)
+{
+  // We output a "back" directory, which ahs two characters. This
+  // character is currently not style dependant.
+  return cwk_path_output_sized(buffer, buffer_size, position, "..", 2);
+}
+
+static size_t cwk_path_output_separator(char *buffer, size_t buffer_size,
+  size_t position)
+{
+  // We output a separator, which is a single character.
+  return cwk_path_output_sized(buffer, buffer_size, position,
+    separators[path_style], 1);
+}
+
+static size_t cwk_path_output_dot(char *buffer, size_t buffer_size,
+  size_t position)
+{
+  // We output a dot, which is a single character. This is used for extensions.
+  return cwk_path_output_sized(buffer, buffer_size, position, ".", 1);
+}
+
+static size_t cwk_path_output(char *buffer, size_t buffer_size, size_t position,
+  const char *str)
+{
+  size_t length;
+
+  // This just does a sized output internally, but first measuring the
+  // null-terminated string.
+  length = strlen(str);
+  return cwk_path_output_sized(buffer, buffer_size, position, str, length);
+}
+
+static void cwk_path_terminate_output(char *buffer, size_t buffer_size,
+  size_t pos)
+{
+  if (buffer_size > 0) {
+    if (pos >= buffer_size) {
+      buffer[buffer_size - 1] = '\0';
+    } else {
+      buffer[pos] = '\0';
+    }
+  }
+}
+
+static bool cwk_path_is_string_equal(const char *first, const char *second,
+  size_t n)
+{
+  // If the path style is UNIX, we will compare case sensitively. This can be
+  // done easily using strncmp.
+  if (path_style == CWK_STYLE_UNIX) {
+    return strncmp(first, second, n) == 0;
+  }
+
+  // However, if this is windows we will have to compare case insensitively.
+  // Since there is no standard method to do that we will have to do it on our
+  // own.
+  while (*first && *second && n > 0) {
+    // We can consider the string to be not equal if the two lowercase
+    // characters are not equal.
+    if (tolower(*first++) != tolower(*second++)) {
+      return false;
+    }
+
+    --n;
+  }
+
+  // We can consider the string to be equal if we either reached n == 0 or both
+  // cursors point to a null character.
+  return n == 0 || (*first == '\0' && *second == '\0');
+}
+
+static const char *cwk_path_find_next_stop(const char *c)
+{
+  // We just move forward until we find a '\0' or a separator, which will be our
+  // next "stop".
+  while (*c != '\0' && !cwk_path_is_separator(c)) {
+    ++c;
+  }
+
+  // Return the pointer of the next stop.
+  return c;
+}
+
+static const char *cwk_path_find_previous_stop(const char *begin, const char *c)
+{
+  // We just move back until we find a separator or reach the beginning of the
+  // path, which will be our previous "stop".
+  while (c > begin && !cwk_path_is_separator(c)) {
+    --c;
+  }
+
+  // Return the pointer to the previous stop. We have to return the first
+  // character after the separator, not on the separator itself.
+  if (cwk_path_is_separator(c)) {
+    return c + 1;
+  } else {
+    return c;
+  }
+}
+
+static bool cwk_path_get_first_segment_without_root(const char *path,
+  const char *segments, struct cwk_segment *segment)
+{
+  // Let's remember the path. We will move the path pointer afterwards, that's
+  // why this has to be done first.
+  segment->path = path;
+  segment->segments = segments;
+
+  // Now let's check whether this is an empty string. An empty string has no
+  // segment it could use.
+  if (*segments == '\0') {
+    return false;
+  }
+
+  // If the string starts with separators, we will jump over those. If there is
+  // only a slash and a '\0' after it, we can't determine the first segment
+  // since there is none.
+  while (cwk_path_is_separator(segments)) {
+    ++segments;
+    if (*segments == '\0') {
+      return false;
+    }
+  }
+
+  // So this is the beginning of our segment.
+  segment->begin = segments;
+
+  // Now let's determine the end of the segment, which we do by moving the path
+  // pointer further until we find a separator.
+  segments = cwk_path_find_next_stop(segments);
+
+  // And finally, calculate the size of the segment by subtracting the position
+  // from the end.
+  segment->size = (size_t)(segments - segment->begin);
+  segment->end = segments;
+
+  // Tell the caller that we found a segment.
+  return true;
+}
+
+static bool cwk_path_get_last_segment_without_root(const char *path,
+  struct cwk_segment *segment)
+{
+  // Now this is fairly similar to the normal algorithm, however, it will assume
+  // that there is no root in the path. So we grab the first segment at this
+  // position, assuming there is no root.
+  if (!cwk_path_get_first_segment_without_root(path, path, segment)) {
+    return false;
+  }
+
+  // Now we find our last segment. The segment struct of the caller
+  // will contain the last segment, since the function we call here will not
+  // change the segment struct when it reaches the end.
+  while (cwk_path_get_next_segment(segment)) {
+    // We just loop until there is no other segment left.
+  }
+
+  return true;
+}
+
+static bool cwk_path_get_first_segment_joined(const char **paths,
+  struct cwk_segment_joined *sj)
+{
+  bool result;
+
+  // Prepare the first segment. We position the joined segment on the first path
+  // and assign the path array to the struct.
+  sj->path_index = 0;
+  sj->paths = paths;
+
+  // We loop through all paths until we find one which has a segment. The result
+  // is stored in a variable, so we can let the caller know whether we found one
+  // or not.
+  result = false;
+  while (paths[sj->path_index] != NULL &&
+         (result = cwk_path_get_first_segment(paths[sj->path_index],
+            &sj->segment)) == false) {
+    ++sj->path_index;
+  }
+
+  return result;
+}
+
+static bool cwk_path_get_next_segment_joined(struct cwk_segment_joined *sj)
+{
+  bool result;
+
+  if (sj->paths[sj->path_index] == NULL) {
+    // We reached already the end of all paths, so there is no other segment
+    // left.
+    return false;
+  } else if (cwk_path_get_next_segment(&sj->segment)) {
+    // There was another segment on the current path, so we are good to
+    // continue.
+    return true;
+  }
+
+  // We try to move to the next path which has a segment available. We must at
+  // least move one further since the current path reached the end.
+  result = false;
+
+  do {
+    ++sj->path_index;
+
+    // And we obviously have to stop this loop if there are no more paths left.
+    if (sj->paths[sj->path_index] == NULL) {
+      break;
+    }
+
+    // Grab the first segment of the next path and determine whether this path
+    // has anything useful in it. There is one more thing we have to consider
+    // here - for the first time we do this we want to skip the root, but
+    // afterwards we will consider that to be part of the segments.
+    result = cwk_path_get_first_segment_without_root(sj->paths[sj->path_index],
+      sj->paths[sj->path_index], &sj->segment);
+
+  } while (!result);
+
+  // Finally, report the result back to the caller.
+  return result;
+}
+
+static bool cwk_path_get_previous_segment_joined(struct cwk_segment_joined *sj)
+{
+  bool result;
+
+  if (*sj->paths == NULL) {
+    // It's possible that there is no initialized segment available in the
+    // struct since there are no paths. In that case we can return false, since
+    // there is no previous segment.
+    return false;
+  } else if (cwk_path_get_previous_segment(&sj->segment)) {
+    // Now we try to get the previous segment from the current path. If we can
+    // do that successfully, we can let the caller know that we found one.
+    return true;
+  }
+
+  result = false;
+
+  do {
+    // We are done once we reached index 0. In that case there are no more
+    // segments left.
+    if (sj->path_index == 0) {
+      break;
+    }
+
+    // There is another path which we have to inspect. So we decrease the path
+    // index.
+    --sj->path_index;
+
+    // If this is the first path we will have to consider that this path might
+    // include a root, otherwise we just treat is as a segment.
+    if (sj->path_index == 0) {
+      result = cwk_path_get_last_segment(sj->paths[sj->path_index],
+        &sj->segment);
+    } else {
+      result = cwk_path_get_last_segment_without_root(sj->paths[sj->path_index],
+        &sj->segment);
+    }
+
+  } while (!result);
+
+  return result;
+}
+
+static bool cwk_path_segment_back_will_be_removed(struct cwk_segment_joined *sj)
+{
+  enum cwk_segment_type type;
+  int counter;
+
+  // We are handling back segments here. We must verify how many back segments
+  // and how many normal segments come before this one to decide whether we keep
+  // or remove it.
+
+  // The counter determines how many normal segments are our current segment,
+  // which will popped off before us. If the counter goes above zero it means
+  // that our segment will be popped as well.
+  counter = 0;
+
+  // We loop over all previous segments until we either reach the beginning,
+  // which means our segment will not be dropped or the counter goes above zero.
+  while (cwk_path_get_previous_segment_joined(sj)) {
+
+    // Now grab the type. The type determines whether we will increase or
+    // decrease the counter. We don't handle a CWK_CURRENT frame here since it
+    // has no influence.
+    type = cwk_path_get_segment_type(&sj->segment);
+    if (type == CWK_NORMAL) {
+      // This is a normal segment. The normal segment will increase the counter
+      // since it neutralizes one back segment. If we go above zero we can
+      // return immediately.
+      ++counter;
+      if (counter > 0) {
+        return true;
+      }
+    } else if (type == CWK_BACK) {
+      // A CWK_BACK segment will reduce the counter by one. We can not remove a
+      // back segment as long we are not above zero since we don't have the
+      // opposite normal segment which we would remove.
+      --counter;
+    }
+  }
+
+  // We never got a count larger than zero, so we will keep this segment alive.
+  return false;
+}
+
+static bool cwk_path_segment_normal_will_be_removed(
+  struct cwk_segment_joined *sj)
+{
+  enum cwk_segment_type type;
+  int counter;
+
+  // The counter determines how many segments are above our current segment,
+  // which will popped off before us. If the counter goes below zero it means
+  // that our segment will be popped as well.
+  counter = 0;
+
+  // We loop over all following segments until we either reach the end, which
+  // means our segment will not be dropped or the counter goes below zero.
+  while (cwk_path_get_next_segment_joined(sj)) {
+
+    // First, grab the type. The type determines whether we will increase or
+    // decrease the counter. We don't handle a CWK_CURRENT frame here since it
+    // has no influence.
+    type = cwk_path_get_segment_type(&sj->segment);
+    if (type == CWK_NORMAL) {
+      // This is a normal segment. The normal segment will increase the counter
+      // since it will be removed by a "../" before us.
+      ++counter;
+    } else if (type == CWK_BACK) {
+      // A CWK_BACK segment will reduce the counter by one. If we are below zero
+      // we can return immediately.
+      --counter;
+      if (counter < 0) {
+        return true;
+      }
+    }
+  }
+
+  // We never got a negative count, so we will keep this segment alive.
+  return false;
+}
+
+static bool
+cwk_path_segment_will_be_removed(const struct cwk_segment_joined *sj,
+  bool absolute)
+{
+  enum cwk_segment_type type;
+  struct cwk_segment_joined sjc;
+
+  // We copy the joined path so we don't need to modify it.
+  sjc = *sj;
+
+  // First we check whether this is a CWK_CURRENT or CWK_BACK segment, since
+  // those will always be dropped.
+  type = cwk_path_get_segment_type(&sj->segment);
+  if (type == CWK_CURRENT) {
+    return true;
+  } else if (type == CWK_BACK && absolute) {
+    return true;
+  } else if (type == CWK_BACK) {
+    return cwk_path_segment_back_will_be_removed(&sjc);
+  } else {
+    return cwk_path_segment_normal_will_be_removed(&sjc);
+  }
+}
+
+static bool
+cwk_path_segment_joined_skip_invisible(struct cwk_segment_joined *sj,
+  bool absolute)
+{
+  while (cwk_path_segment_will_be_removed(sj, absolute)) {
+    if (!cwk_path_get_next_segment_joined(sj)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+static void cwk_path_get_root_windows(const char *path, size_t *length)
+{
+  const char *c;
+  bool is_device_path;
+
+  // A device path is a path which starts with "\\." or "\\?". A device path can
+  // be a UNC path as well, in which case it will take up one more segment.
+  is_device_path = false;
+
+  // We can not determine the root if this is an empty string. So we set the
+  // root to NULL and the length to zero and cancel the whole thing.
+  c = path;
+  *length = 0;
+  if (!*c) {
+    return;
+  }
+
+  // Now we have to verify whether this is a windows network path (UNC), which
+  // we will consider our root.
+  if (cwk_path_is_separator(c)) {
+    ++c;
+
+    // Check whether the path starts with a single back slash, which means this
+    // is not a network path - just a normal path starting with a backslash.
+    if (!cwk_path_is_separator(c)) {
+      // Okay, this is not a network path but we still use the backslash as a
+      // root.
+      ++(*length);
+      return;
+    }
+
+    // Yes, this is a network or device path. Skip the previous separator. Now
+    // we need to determine whether this is a device path. We might advance one
+    // character here if the server name starts with a '?' or a '.', but that's
+    // fine since we will search for a separator afterwards anyway.
+    ++c;
+    is_device_path = (*c == '?' || *c == '.') && cwk_path_is_separator(++c);
+    if (is_device_path) {
+      // That's a device path, and the root must be either "\\.\" or "\\?\"
+      // which is 4 characters long. (at least that's how Windows
+      // GetFullPathName behaves.)
+      *length = 4;
+      return;
+    }
+
+    // We will grab anything up to the next stop. The next stop might be a '\0'
+    // or another separator. That will be the server name.
+    c = cwk_path_find_next_stop(c);
+
+    // If this is a separator and not the end of a string we wil have to include
+    // it. However, if this is a '\0' we must not skip it.
+    while (cwk_path_is_separator(c)) {
+      ++c;
+    }
+
+    // We are now skipping the shared folder name, which will end after the
+    // next stop.
+    c = cwk_path_find_next_stop(c);
+
+    // Then there might be a separator at the end. We will include that as well,
+    // it will mark the path as absolute.
+    if (cwk_path_is_separator(c)) {
+      ++c;
+    }
+
+    // Finally, calculate the size of the root.
+    *length = (size_t)(c - path);
+    return;
+  }
+
+  // Move to the next and check whether this is a colon.
+  if (*++c == ':') {
+    *length = 2;
+
+    // Now check whether this is a backslash (or slash). If it is not, we could
+    // assume that the next character is a '\0' if it is a valid path. However,
+    // we will not assume that - since ':' is not valid in a path it must be a
+    // mistake by the caller than. We will try to understand it anyway.
+    if (cwk_path_is_separator(++c)) {
+      *length = 3;
+    }
+  }
+}
+
+static void cwk_path_get_root_unix(const char *path, size_t *length)
+{
+  // The slash of the unix path represents the root. There is no root if there
+  // is no slash.
+  if (cwk_path_is_separator(path)) {
+    *length = 1;
+  } else {
+    *length = 0;
+  }
+}
+
+static bool cwk_path_is_root_absolute(const char *path, size_t length)
+{
+  // This is definitely not absolute if there is no root.
+  if (length == 0) {
+    return false;
+  }
+
+  // If there is a separator at the end of the root, we can safely consider this
+  // to be an absolute path.
+  return cwk_path_is_separator(&path[length - 1]);
+}
+
+static size_t cwk_path_join_and_normalize_multiple(const char **paths,
+  char *buffer, size_t buffer_size)
+{
+  size_t pos;
+  bool absolute, has_segment_output;
+  struct cwk_segment_joined sj;
+
+  // We initialize the position after the root, which should get us started.
+  cwk_path_get_root(paths[0], &pos);
+
+  // Determine whether the path is absolute or not. We need that to determine
+  // later on whether we can remove superfluous "../" or not.
+  absolute = cwk_path_is_root_absolute(paths[0], pos);
+
+  // First copy the root to the output. We will not modify the root.
+  cwk_path_output_sized(buffer, buffer_size, 0, paths[0], pos);
+
+  // So we just grab the first segment. If there is no segment we will always
+  // output a "/", since we currently only support absolute paths here.
+  if (!cwk_path_get_first_segment_joined(paths, &sj)) {
+    goto done;
+  }
+
+  // Let's assume that we don't have any segment output for now. We will toggle
+  // this flag once there is some output.
+  has_segment_output = false;
+
+  do {
+    // Check whether we have to drop this segment because of resolving a
+    // relative path or because it is a CWK_CURRENT segment.
+    if (cwk_path_segment_will_be_removed(&sj, absolute)) {
+      continue;
+    }
+
+    // We add a separator if we previously wrote a segment. The last segment
+    // must not have a trailing separator. This must happen before the segment
+    // output, since we would override the null terminating character with
+    // reused buffers if this was done afterwards.
+    if (has_segment_output) {
+      pos += cwk_path_output_separator(buffer, buffer_size, pos);
+    }
+
+    // Remember that we have segment output, so we can handle the trailing slash
+    // later on. This is necessary since we might have segments but they are all
+    // removed.
+    has_segment_output = true;
+
+    // Write out the segment but keep in mind that we need to follow the
+    // buffer size limitations. That's why we use the path output functions
+    // here.
+    pos += cwk_path_output_sized(buffer, buffer_size, pos, sj.segment.begin,
+      sj.segment.size);
+  } while (cwk_path_get_next_segment_joined(&sj));
+
+  // Remove the trailing slash, but only if we have segment output. We don't
+  // want to remove anything from the root.
+  if (!has_segment_output && pos == 0) {
+    // This may happen if the path is absolute and all segments have been
+    // removed. We can not have an empty output - and empty output means we stay
+    // in the current directory. So we will output a ".".
+    assert(absolute == false);
+    pos += cwk_path_output_current(buffer, buffer_size, pos);
+  }
+
+  // We must append a '\0' in any case, unless the buffer size is zero. If the
+  // buffer size is zero, which means we can not.
+done:
+  cwk_path_terminate_output(buffer, buffer_size, pos);
+
+  // And finally let our caller know about the total size of the normalized
+  // path.
+  return pos;
+}
+
+size_t cwk_path_get_absolute(const char *base, const char *path, char *buffer,
+  size_t buffer_size)
+{
+  size_t i;
+  const char *paths[4];
+
+  // The basename should be an absolute path if the caller is using the API
+  // correctly. However, he might not and in that case we will append a fake
+  // root at the beginning.
+  if (cwk_path_is_absolute(base)) {
+    i = 0;
+  } else if (path_style == CWK_STYLE_WINDOWS) {
+    paths[0] = "\\";
+    i = 1;
+  } else {
+    paths[0] = "/";
+    i = 1;
+  }
+
+  if (cwk_path_is_absolute(path)) {
+    // If the submitted path is not relative the base path becomes irrelevant.
+    // We will only normalize the submitted path instead.
+    paths[i++] = path;
+    paths[i] = NULL;
+  } else {
+    // Otherwise we append the relative path to the base path and normalize it.
+    // The result will be a new absolute path.
+    paths[i++] = base;
+    paths[i++] = path;
+    paths[i] = NULL;
+  }
+
+  // Finally join everything together and normalize it.
+  return cwk_path_join_and_normalize_multiple(paths, buffer, buffer_size);
+}
+
+static void cwk_path_skip_segments_until_diverge(struct cwk_segment_joined *bsj,
+  struct cwk_segment_joined *osj, bool absolute, bool *base_available,
+  bool *other_available)
+{
+  // Now looping over all segments until they start to diverge. A path may
+  // diverge if two segments are not equal or if one path reaches the end.
+  do {
+
+    // Check whether there is anything available after we skip everything which
+    // is invisible. We do that for both paths, since we want to let the caller
+    // know which path has some trailing segments after they diverge.
+    *base_available = cwk_path_segment_joined_skip_invisible(bsj, absolute);
+    *other_available = cwk_path_segment_joined_skip_invisible(osj, absolute);
+
+    // We are done if one or both of those paths reached the end. They either
+    // diverge or both reached the end - but in both cases we can not continue
+    // here.
+    if (!*base_available || !*other_available) {
+      break;
+    }
+
+    // Compare the content of both segments. We are done if they are not equal,
+    // since they diverge.
+    if (!cwk_path_is_string_equal(bsj->segment.begin, osj->segment.begin,
+          bsj->segment.size)) {
+      break;
+    }
+
+    // We keep going until one of those segments reached the end. The next
+    // segment might be invisible, but we will check for that in the beginning
+    // of the loop once again.
+    *base_available = cwk_path_get_next_segment_joined(bsj);
+    *other_available = cwk_path_get_next_segment_joined(osj);
+  } while (*base_available && *other_available);
+}
+
+size_t cwk_path_get_relative(const char *base_directory, const char *path,
+  char *buffer, size_t buffer_size)
+{
+  size_t pos, base_root_length, path_root_length;
+  bool absolute, base_available, other_available, has_output;
+  const char *base_paths[2], *other_paths[2];
+  struct cwk_segment_joined bsj, osj;
+
+  pos = 0;
+
+  // First we compare the roots of those two paths. If the roots are not equal
+  // we can't continue, since there is no way to get a relative path from
+  // different roots.
+  cwk_path_get_root(base_directory, &base_root_length);
+  cwk_path_get_root(path, &path_root_length);
+  if (!cwk_path_is_string_equal(base_directory, path, base_root_length)) {
+    return pos;
+  }
+
+  // Verify whether this is an absolute path. We need to know that since we can
+  // remove all back-segments if it is.
+  absolute = cwk_path_is_root_absolute(base_directory, base_root_length);
+
+  // Initialize our joined segments. This will allow us to use the internal
+  // functions to skip until diverge and invisible. We only have one path in
+  // them though.
+  base_paths[0] = base_directory;
+  base_paths[1] = NULL;
+  other_paths[0] = path;
+  other_paths[1] = NULL;
+  cwk_path_get_first_segment_joined(base_paths, &bsj);
+  cwk_path_get_first_segment_joined(other_paths, &osj);
+
+  // Okay, now we skip until the segments diverge. We don't have anything to do
+  // with the segments which are equal.
+  cwk_path_skip_segments_until_diverge(&bsj, &osj, absolute, &base_available,
+    &other_available);
+
+  // Assume there is no output until we have got some. We will need this
+  // information later on to remove trailing slashes or alternatively output a
+  // current-segment.
+  has_output = false;
+
+  // So if we still have some segments left in the base path we will now output
+  // a back segment for all of them.
+  if (base_available) {
+    do {
+      // Skip any invisible segment. We don't care about those and we don't need
+      // to navigate back because of them.
+      if (!cwk_path_segment_joined_skip_invisible(&bsj, absolute)) {
+        break;
+      }
+
+      // Toggle the flag if we have output. We need to remember that, since we
+      // want to remove the trailing slash.
+      has_output = true;
+
+      // Output the back segment and a separator. No need to worry about the
+      // superfluous segment since it will be removed later on.
+      pos += cwk_path_output_back(buffer, buffer_size, pos);
+      pos += cwk_path_output_separator(buffer, buffer_size, pos);
+    } while (cwk_path_get_next_segment_joined(&bsj));
+  }
+
+  // And if we have some segments available of the target path we will output
+  // all of those.
+  if (other_available) {
+    do {
+      // Again, skip any invisible segments since we don't need to navigate into
+      // them.
+      if (!cwk_path_segment_joined_skip_invisible(&osj, absolute)) {
+        break;
+      }
+
+      // Toggle the flag if we have output. We need to remember that, since we
+      // want to remove the trailing slash.
+      has_output = true;
+
+      // Output the current segment and a separator. No need to worry about the
+      // superfluous segment since it will be removed later on.
+      pos += cwk_path_output_sized(buffer, buffer_size, pos, osj.segment.begin,
+        osj.segment.size);
+      pos += cwk_path_output_separator(buffer, buffer_size, pos);
+    } while (cwk_path_get_next_segment_joined(&osj));
+  }
+
+  // If we have some output by now we will have to remove the trailing slash. We
+  // simply do that by moving back one character. The terminate output function
+  // will then place the '\0' on this position. Otherwise, if there is no
+  // output, we will have to output a "current directory", since the target path
+  // points to the base path.
+  if (has_output) {
+    --pos;
+  } else {
+    pos += cwk_path_output_current(buffer, buffer_size, pos);
+  }
+
+  // Finally, we can terminate the output - which means we place a '\0' at the
+  // current position or at the end of the buffer.
+  cwk_path_terminate_output(buffer, buffer_size, pos);
+
+  return pos;
+}
+
+size_t cwk_path_join(const char *path_a, const char *path_b, char *buffer,
+  size_t buffer_size)
+{
+  const char *paths[3];
+
+  // This is simple. We will just create an array with the two paths which we
+  // wish to join.
+  paths[0] = path_a;
+  paths[1] = path_b;
+  paths[2] = NULL;
+
+  // And then call the join and normalize function which will do the hard work
+  // for us.
+  return cwk_path_join_and_normalize_multiple(paths, buffer, buffer_size);
+}
+
+size_t cwk_path_join_multiple(const char **paths, char *buffer,
+  size_t buffer_size)
+{
+  // We can just call the internal join and normalize function for this one,
+  // since it will handle everything.
+  return cwk_path_join_and_normalize_multiple(paths, buffer, buffer_size);
+}
+
+void cwk_path_get_root(const char *path, size_t *length)
+{
+  // We use a different implementation here based on the configuration of the
+  // library.
+  if (path_style == CWK_STYLE_WINDOWS) {
+    cwk_path_get_root_windows(path, length);
+  } else {
+    cwk_path_get_root_unix(path, length);
+  }
+}
+
+size_t cwk_path_change_root(const char *path, const char *new_root,
+  char *buffer, size_t buffer_size)
+{
+  const char *tail;
+  size_t root_length, path_length, tail_length, new_root_length, new_path_size;
+
+  // First we need to determine the actual size of the root which we will
+  // change.
+  cwk_path_get_root(path, &root_length);
+
+  // Now we determine the sizes of the new root and the path. We need that to
+  // determine the size of the part after the root (the tail).
+  new_root_length = strlen(new_root);
+  path_length = strlen(path);
+
+  // Okay, now we calculate the position of the tail and the length of it.
+  tail = path + root_length;
+  tail_length = path_length - root_length;
+
+  // We first output the tail and then the new root, that's because the source
+  // path and the buffer may be overlapping. This way the root will not
+  // overwrite the tail.
+  cwk_path_output_sized(buffer, buffer_size, new_root_length, tail,
+    tail_length);
+  cwk_path_output_sized(buffer, buffer_size, 0, new_root, new_root_length);
+
+  // Finally we calculate the size o the new path and terminate the output with
+  // a '\0'.
+  new_path_size = tail_length + new_root_length;
+  cwk_path_terminate_output(buffer, buffer_size, new_path_size);
+
+  return new_path_size;
+}
+
+bool cwk_path_is_absolute(const char *path)
+{
+  size_t length;
+
+  // We grab the root of the path. This root does not include the first
+  // separator of a path.
+  cwk_path_get_root(path, &length);
+
+  // Now we can determine whether the root is absolute or not.
+  return cwk_path_is_root_absolute(path, length);
+}
+
+bool cwk_path_is_relative(const char *path)
+{
+  // The path is relative if it is not absolute.
+  return !cwk_path_is_absolute(path);
+}
+
+void cwk_path_get_basename(const char *path, const char **basename,
+  size_t *length)
+{
+  struct cwk_segment segment;
+
+  // We get the last segment of the path. The last segment will contain the
+  // basename if there is any. If there are no segments we will set the basename
+  // to NULL and the length to 0.
+  if (!cwk_path_get_last_segment(path, &segment)) {
+    *basename = NULL;
+    *length = 0;
+    return;
+  }
+
+  // Now we can just output the segment contents, since that's our basename.
+  // There might be trailing separators after the basename, but the size does
+  // not include those.
+  *basename = segment.begin;
+  *length = segment.size;
+}
+
+size_t cwk_path_change_basename(const char *path, const char *new_basename,
+  char *buffer, size_t buffer_size)
+{
+  struct cwk_segment segment;
+  size_t pos, root_size, new_basename_size;
+
+  // First we try to get the last segment. We may only have a root without any
+  // segments, in which case we will create one.
+  if (!cwk_path_get_last_segment(path, &segment)) {
+
+    // So there is no segment in this path. First we grab the root and output
+    // that. We are not going to modify the root in any way.
+    cwk_path_get_root(path, &root_size);
+    pos = cwk_path_output_sized(buffer, buffer_size, 0, path, root_size);
+
+    // We have to trim the separators from the beginning of the new basename.
+    // This is quite easy to do.
+    while (cwk_path_is_separator(new_basename)) {
+      ++new_basename;
+    }
+
+    // Now we measure the length of the new basename, this is a two step
+    // process. First we find the '\0' character at the end of the string.
+    new_basename_size = 0;
+    while (new_basename[new_basename_size]) {
+      ++new_basename_size;
+    }
+
+    // And then we trim the separators at the end of the basename until we reach
+    // the first valid character.
+    while (new_basename_size > 0 &&
+           cwk_path_is_separator(&new_basename[new_basename_size - 1])) {
+      --new_basename_size;
+    }
+
+    // Now we will output the new basename after the root.
+    pos += cwk_path_output_sized(buffer, buffer_size, pos, new_basename,
+      new_basename_size);
+
+    // And finally terminate the output and return the total size of the path.
+    cwk_path_terminate_output(buffer, buffer_size, pos);
+    return pos;
+  }
+
+  // If there is a last segment we can just forward this call, which is fairly
+  // easy.
+  return cwk_path_change_segment(&segment, new_basename, buffer, buffer_size);
+}
+
+void cwk_path_get_dirname(const char *path, size_t *length)
+{
+  struct cwk_segment segment;
+
+  // We get the last segment of the path. The last segment will contain the
+  // basename if there is any. If there are no segments we will set the length
+  // to 0.
+  if (!cwk_path_get_last_segment(path, &segment)) {
+    *length = 0;
+    return;
+  }
+
+  // We can now return the length from the beginning of the string up to the
+  // beginning of the last segment.
+  *length = (size_t)(segment.begin - path);
+}
+
+bool cwk_path_get_extension(const char *path, const char **extension,
+  size_t *length)
+{
+  struct cwk_segment segment;
+  const char *c;
+
+  // We get the last segment of the path. The last segment will contain the
+  // extension if there is any.
+  if (!cwk_path_get_last_segment(path, &segment)) {
+    return false;
+  }
+
+  // Now we search for a dot within the segment. If there is a dot, we consider
+  // the rest of the segment the extension. We do this from the end towards the
+  // beginning, since we want to find the last dot.
+  for (c = segment.end; c >= segment.begin; --c) {
+    if (*c == '.') {
+      // Okay, we found an extension. We can stop looking now.
+      *extension = c;
+      *length = (size_t)(segment.end - c);
+      return true;
+    }
+  }
+
+  // We couldn't find any extension.
+  return false;
+}
+
+bool cwk_path_has_extension(const char *path)
+{
+  const char *extension;
+  size_t length;
+
+  // We just wrap the get_extension call which will then do the work for us.
+  return cwk_path_get_extension(path, &extension, &length);
+}
+
+size_t cwk_path_change_extension(const char *path, const char *new_extension,
+  char *buffer, size_t buffer_size)
+{
+  struct cwk_segment segment;
+  const char *c, *old_extension;
+  size_t pos, root_size, trail_size, new_extension_size;
+
+  // First we try to get the last segment. We may only have a root without any
+  // segments, in which case we will create one.
+  if (!cwk_path_get_last_segment(path, &segment)) {
+
+    // So there is no segment in this path. First we grab the root and output
+    // that. We are not going to modify the root in any way. If there is no
+    // root, this will end up with a root size 0, and nothing will be written.
+    cwk_path_get_root(path, &root_size);
+    pos = cwk_path_output_sized(buffer, buffer_size, 0, path, root_size);
+
+    // Add a dot if the submitted value doesn't have any.
+    if (*new_extension != '.') {
+      pos += cwk_path_output_dot(buffer, buffer_size, pos);
+    }
+
+    // And finally terminate the output and return the total size of the path.
+    pos += cwk_path_output(buffer, buffer_size, pos, new_extension);
+    cwk_path_terminate_output(buffer, buffer_size, pos);
+    return pos;
+  }
+
+  // Now we seek the old extension in the last segment, which we will replace
+  // with the new one. If there is no old extension, it will point to the end of
+  // the segment.
+  old_extension = segment.end;
+  for (c = segment.begin; c < segment.end; ++c) {
+    if (*c == '.') {
+      old_extension = c;
+    }
+  }
+
+  pos = cwk_path_output_sized(buffer, buffer_size, 0, segment.path,
+    (size_t)(old_extension - segment.path));
+
+  // If the new extension starts with a dot, we will skip that dot. We always
+  // output exactly one dot before the extension. If the extension contains
+  // multiple dots, we will output those as part of the extension.
+  if (*new_extension == '.') {
+    ++new_extension;
+  }
+
+  // We calculate the size of the new extension, including the dot, in order to
+  // output the trail - which is any part of the path coming after the
+  // extension. We must output this first, since the buffer may overlap with the
+  // submitted path - and it would be overridden by longer extensions.
+  new_extension_size = strlen(new_extension) + 1;
+  trail_size = cwk_path_output(buffer, buffer_size, pos + new_extension_size,
+    segment.end);
+
+  // Finally we output the dot and the new extension. The new extension itself
+  // doesn't contain the dot anymore, so we must output that first.
+  pos += cwk_path_output_dot(buffer, buffer_size, pos);
+  pos += cwk_path_output(buffer, buffer_size, pos, new_extension);
+
+  // Now we terminate the output with a null-terminating character, but before
+  // we do that we must add the size of the trail to the position which we
+  // output before.
+  pos += trail_size;
+  cwk_path_terminate_output(buffer, buffer_size, pos);
+
+  // And the position is our output size now.
+  return pos;
+}
+
+size_t cwk_path_normalize(const char *path, char *buffer, size_t buffer_size)
+{
+  const char *paths[2];
+
+  // Now we initialize the paths which we will normalize. Since this function
+  // only supports submitting a single path, we will only add that one.
+  paths[0] = path;
+  paths[1] = NULL;
+
+  return cwk_path_join_and_normalize_multiple(paths, buffer, buffer_size);
+}
+
+size_t cwk_path_get_intersection(const char *path_base, const char *path_other)
+{
+  bool absolute;
+  size_t base_root_length, other_root_length;
+  const char *end;
+  const char *paths_base[2], *paths_other[2];
+  struct cwk_segment_joined base, other;
+
+  // We first compare the two roots. We just return zero if they are not equal.
+  // This will also happen to return zero if the paths are mixed relative and
+  // absolute.
+  cwk_path_get_root(path_base, &base_root_length);
+  cwk_path_get_root(path_other, &other_root_length);
+  if (!cwk_path_is_string_equal(path_base, path_other, base_root_length)) {
+    return 0;
+  }
+
+  // Configure our paths. We just have a single path in here for now.
+  paths_base[0] = path_base;
+  paths_base[1] = NULL;
+  paths_other[0] = path_other;
+  paths_other[1] = NULL;
+
+  // So we get the first segment of both paths. If one of those paths don't have
+  // any segment, we will return 0.
+  if (!cwk_path_get_first_segment_joined(paths_base, &base) ||
+      !cwk_path_get_first_segment_joined(paths_other, &other)) {
+    return base_root_length;
+  }
+
+  // We now determine whether the path is absolute or not. This is required
+  // because if will ignore removed segments, and this behaves differently if
+  // the path is absolute. However, we only need to check the base path because
+  // we are guaranteed that both paths are either relative or absolute.
+  absolute = cwk_path_is_root_absolute(path_base, base_root_length);
+
+  // We must keep track of the end of the previous segment. Initially, this is
+  // set to the beginning of the path. This means that 0 is returned if the
+  // first segment is not equal.
+  end = path_base + base_root_length;
+
+  // Now we loop over both segments until one of them reaches the end or their
+  // contents are not equal.
+  do {
+    // We skip all segments which will be removed in each path, since we want to
+    // know about the true path.
+    if (!cwk_path_segment_joined_skip_invisible(&base, absolute) ||
+        !cwk_path_segment_joined_skip_invisible(&other, absolute)) {
+      break;
+    }
+
+    if (!cwk_path_is_string_equal(base.segment.begin, other.segment.begin,
+          base.segment.size)) {
+      // So the content of those two segments are not equal. We will return the
+      // size up to the beginning.
+      return (size_t)(end - path_base);
+    }
+
+    // Remember the end of the previous segment before we go to the next one.
+    end = base.segment.end;
+  } while (cwk_path_get_next_segment_joined(&base) &&
+           cwk_path_get_next_segment_joined(&other));
+
+  // Now we calculate the length up to the last point where our paths pointed to
+  // the same place.
+  return (size_t)(end - path_base);
+}
+
+bool cwk_path_get_first_segment(const char *path, struct cwk_segment *segment)
+{
+  size_t length;
+  const char *segments;
+
+  // We skip the root since that's not part of the first segment. The root is
+  // treated as a separate entity.
+  cwk_path_get_root(path, &length);
+  segments = path + length;
+
+  // Now, after we skipped the root we can continue and find the actual segment
+  // content.
+  return cwk_path_get_first_segment_without_root(path, segments, segment);
+}
+
+bool cwk_path_get_last_segment(const char *path, struct cwk_segment *segment)
+{
+  // We first grab the first segment. This might be our last segment as well,
+  // but we don't know yet. There is no last segment if there is no first
+  // segment, so we return false in that case.
+  if (!cwk_path_get_first_segment(path, segment)) {
+    return false;
+  }
+
+  // Now we find our last segment. The segment struct of the caller
+  // will contain the last segment, since the function we call here will not
+  // change the segment struct when it reaches the end.
+  while (cwk_path_get_next_segment(segment)) {
+    // We just loop until there is no other segment left.
+  }
+
+  return true;
+}
+
+bool cwk_path_get_next_segment(struct cwk_segment *segment)
+{
+  const char *c;
+
+  // First we jump to the end of the previous segment. The first character must
+  // be either a '\0' or a separator.
+  c = segment->begin + segment->size;
+  if (*c == '\0') {
+    return false;
+  }
+
+  // Now we skip all separator until we reach something else. We are not yet
+  // guaranteed to have a segment, since the string could just end afterwards.
+  assert(cwk_path_is_separator(c));
+  do {
+    ++c;
+  } while (cwk_path_is_separator(c));
+
+  // If the string ends here, we can safely assume that there is no other
+  // segment after this one.
+  if (*c == '\0') {
+    return false;
+  }
+
+  // Now we are safe to assume there is a segment. We store the beginning of
+  // this segment in the segment struct of the caller.
+  segment->begin = c;
+
+  // And now determine the size of this segment, and store it in the struct of
+  // the caller as well.
+  c = cwk_path_find_next_stop(c);
+  segment->end = c;
+  segment->size = (size_t)(c - segment->begin);
+
+  // Tell the caller that we found a segment.
+  return true;
+}
+
+bool cwk_path_get_previous_segment(struct cwk_segment *segment)
+{
+  const char *c;
+
+  // The current position might point to the first character of the path, which
+  // means there are no previous segments available.
+  c = segment->begin;
+  if (c <= segment->segments) {
+    return false;
+  }
+
+  // We move towards the beginning of the path until we either reached the
+  // beginning or the character is no separator anymore.
+  do {
+    --c;
+    if (c < segment->segments) {
+      // So we reached the beginning here and there is no segment. So we return
+      // false and don't change the segment structure submitted by the caller.
+      return false;
+    }
+  } while (cwk_path_is_separator(c));
+
+  // We are guaranteed now that there is another segment, since we moved before
+  // the previous separator and did not reach the segment path beginning.
+  segment->end = c + 1;
+  segment->begin = cwk_path_find_previous_stop(segment->segments, c);
+  segment->size = (size_t)(segment->end - segment->begin);
+
+  return true;
+}
+
+enum cwk_segment_type cwk_path_get_segment_type(
+  const struct cwk_segment *segment)
+{
+  // We just make a string comparison with the segment contents and return the
+  // appropriate type.
+  if (strncmp(segment->begin, ".", segment->size) == 0) {
+    return CWK_CURRENT;
+  } else if (strncmp(segment->begin, "..", segment->size) == 0) {
+    return CWK_BACK;
+  }
+
+  return CWK_NORMAL;
+}
+
+bool cwk_path_is_separator(const char *str)
+{
+  const char *c;
+
+  // We loop over all characters in the read symbols.
+  c = separators[path_style];
+  while (*c) {
+    if (*c == *str) {
+      return true;
+    }
+
+    ++c;
+  }
+
+  return false;
+}
+
+size_t cwk_path_change_segment(struct cwk_segment *segment, const char *value,
+  char *buffer, size_t buffer_size)
+{
+  size_t pos, value_size, tail_size;
+
+  // First we have to output the head, which is the whole string up to the
+  // beginning of the segment. This part of the path will just stay the same.
+  pos = cwk_path_output_sized(buffer, buffer_size, 0, segment->path,
+    (size_t)(segment->begin - segment->path));
+
+  // In order to trip the submitted value, we will skip any separator at the
+  // beginning of it and behave as if it was never there.
+  while (cwk_path_is_separator(value)) {
+    ++value;
+  }
+
+  // Now we determine the length of the value. In order to do that we first
+  // locate the '\0'.
+  value_size = 0;
+  while (value[value_size]) {
+    ++value_size;
+  }
+
+  // Since we trim separators at the beginning and in the end of the value we
+  // have to subtract from the size until there are either no more characters
+  // left or the last character is no separator.
+  while (value_size > 0 && cwk_path_is_separator(&value[value_size - 1])) {
+    --value_size;
+  }
+
+  // We also have to determine the tail size, which is the part of the string
+  // following the current segment. This part will not change.
+  tail_size = strlen(segment->end);
+
+  // Now we output the tail. We have to do that, because if the buffer and the
+  // source are overlapping we would override the tail if the value is
+  // increasing in length.
+  cwk_path_output_sized(buffer, buffer_size, pos + value_size, segment->end,
+    tail_size);
+
+  // Finally we can output the value in the middle of the head and the tail,
+  // where we have enough space to fit the whole trimmed value.
+  pos += cwk_path_output_sized(buffer, buffer_size, pos, value, value_size);
+
+  // Now we add the tail size to the current position and terminate the output -
+  // basically, ensure that there is a '\0' at the end of the buffer.
+  pos += tail_size;
+  cwk_path_terminate_output(buffer, buffer_size, pos);
+
+  // And now tell the caller how long the whole path would be.
+  return pos;
+}
+
+enum cwk_path_style cwk_path_guess_style(const char *path)
+{
+  const char *c;
+  size_t root_length;
+  struct cwk_segment segment;
+
+  // First we determine the root. Only windows roots can be longer than a single
+  // slash, so if we can determine that it starts with something like "C:", we
+  // know that this is a windows path.
+  cwk_path_get_root_windows(path, &root_length);
+  if (root_length > 1) {
+    return CWK_STYLE_WINDOWS;
+  }
+
+  // Next we check for slashes. Windows uses backslashes, while unix uses
+  // forward slashes. Windows actually supports both, but our best guess is to
+  // assume windows with backslashes and unix with forward slashes.
+  for (c = path; *c; ++c) {
+    if (*c == *separators[CWK_STYLE_UNIX]) {
+      return CWK_STYLE_UNIX;
+    } else if (*c == *separators[CWK_STYLE_WINDOWS]) {
+      return CWK_STYLE_WINDOWS;
+    }
+  }
+
+  // This path does not have any slashes. We grab the last segment (which
+  // actually must be the first one), and determine whether the segment starts
+  // with a dot. A dot is a hidden folder or file in the UNIX world, in that
+  // case we assume the path to have UNIX style.
+  if (!cwk_path_get_last_segment(path, &segment)) {
+    // We couldn't find any segments, so we default to a UNIX path style since
+    // there is no way to make any assumptions.
+    return CWK_STYLE_UNIX;
+  }
+
+  if (*segment.begin == '.') {
+    return CWK_STYLE_UNIX;
+  }
+
+  // And finally we check whether the last segment contains a dot. If it
+  // contains a dot, that might be an extension. Windows is more likely to have
+  // file names with extensions, so our guess would be windows.
+  for (c = segment.begin; *c; ++c) {
+    if (*c == '.') {
+      return CWK_STYLE_WINDOWS;
+    }
+  }
+
+  // All our checks failed, so we will return a default value which is currently
+  // UNIX.
+  return CWK_STYLE_UNIX;
+}
+
+void cwk_path_set_style(enum cwk_path_style style)
+{
+  // We can just set the global path style variable and then the behaviour for
+  // all functions will change accordingly.
+  assert(style == CWK_STYLE_UNIX || style == CWK_STYLE_WINDOWS);
+  path_style = style;
+}
+
+enum cwk_path_style cwk_path_get_style(void)
+{
+  // Simply return the path style which we store in a global variable.
+  return path_style;
+}