diff --git a/System/FilePath.hs b/System/FilePath.hs
--- a/System/FilePath.hs
+++ b/System/FilePath.hs
@@ -7,6 +7,10 @@
 -- Maintainer:  jmillikin@gmail.com
 -- Portability:  portable
 --
+-- High-level, byte-based file and directory path manipulations. You probably
+-- want to import "System.FilePath.CurrentOS" instead, since it handles
+-- detecting which rules to use in the current compilation.
+--
 -----------------------------------------------------------------------------
 
 module System.FilePath
@@ -49,7 +53,7 @@
 
 import Prelude hiding (FilePath, concat, null)
 import qualified Prelude as P
-import Data.Maybe (isNothing, listToMaybe)
+import Data.Maybe (isNothing)
 import qualified Data.Monoid as M
 import System.FilePath.Internal
 import qualified Data.ByteString as B
@@ -64,47 +68,71 @@
 -- Basic properties
 -------------------------------------------------------------------------------
 
+-- | @null p == (p == 'empty')@
 null :: FilePath -> Bool
 null = (== empty)
 
+-- | Retrieves the 'FilePath'&#x27;s root.
 root :: FilePath -> FilePath
 root p = empty { pathRoot = pathRoot p }
 
+-- | Retrieves the 'FilePath'&#x27;s directory. If the path is already a
+-- directory, it is returned unchanged.
 directory :: FilePath -> FilePath
 directory p = empty
 	{ pathRoot = pathRoot p
-	, pathComponents = if P.null (pathComponents p) && isNothing (pathRoot p)
-		then [B8.pack "."]
-		else pathComponents p
+	, pathComponents = let
+		starts = map (Just . B8.pack) [".", ".."]
+		dot = if safeHead (pathComponents p) `elem` starts
+			then []
+			else if isNothing (pathRoot p)
+				then [B8.pack "."]
+				else []
+		in dot ++ pathComponents p
 	}
 
+-- | Retrieves the 'FilePath'&#x27;s parent directory.
 parent :: FilePath -> FilePath
 parent p = empty
 	{ pathRoot = pathRoot p
-	, pathComponents = if P.null (pathComponents p) && isNothing (pathRoot p)
-		then [B8.pack "."]
-		else if null (filename p)
+	, pathComponents = let
+		starts = map (Just . B8.pack) [".", ".."]
+		components = if null (filename p)
 			then safeInit (pathComponents p)
 			else pathComponents p
+		
+		dot = if safeHead components `elem` starts
+			then []
+			else if isNothing (pathRoot p)
+				then [B8.pack "."]
+				else []
+		
+		in dot ++ components
 	}
 
+-- | Retrieve the filename component of a 'FilePath'.
+-- @filename \"foo/bar.txt\" == \"bar.txt\"@.
 filename :: FilePath -> FilePath
 filename p = empty
 	{ pathBasename = pathBasename p
 	, pathExtensions = pathExtensions p
 	}
 
+-- | Retrieve the basename component of a 'FilePath'.
+-- @filename \"foo/bar.txt\" == \"bar\"@.
 basename :: FilePath -> FilePath
 basename p = empty
 	{ pathBasename = pathBasename p
 	}
 
+-- | Return whether the path is absolute.
 absolute :: FilePath -> Bool
 absolute p = case pathRoot p of
 	Just RootPosix -> True
 	Just (RootWindowsVolume _) -> True
 	_ -> False
 
+-- | Return whether the path is relative.
 relative :: FilePath -> Bool
 relative p = case pathRoot p of
 	Just _ -> False
@@ -114,6 +142,8 @@
 -- Basic operations
 -------------------------------------------------------------------------------
 
+-- | Appends two 'FilePath's. If the second path is absolute, it is returned
+-- unchanged.
 append :: FilePath -> FilePath -> FilePath
 append x y = if absolute y then y else xy where
 	xy = y
@@ -123,15 +153,18 @@
 	components = xComponents ++ pathComponents y
 	xComponents = (pathComponents x ++) $ if null (filename x)
 		then []
-		else [B.concat $ [maybe (B8.pack "") id (pathBasename x)] ++ pathExtensions x]
+		else [filenameBytes x]
 
+-- | An alias for 'append'.
 (</>) :: FilePath -> FilePath -> FilePath
 (</>) = append
 
+-- | A fold over 'append'.
 concat :: [FilePath] -> FilePath
 concat [] = empty
 concat ps = foldr1 append ps
 
+-- | Find the greatest common prefix between two 'FilePath's.
 commonPrefix :: [FilePath] -> FilePath
 commonPrefix [] = empty
 commonPrefix ps = foldr1 step ps where
@@ -155,48 +188,58 @@
 		else []
 
 -------------------------------------------------------------------------------
--- Basenames
--------------------------------------------------------------------------------
-
--------------------------------------------------------------------------------
 -- Extensions
 -------------------------------------------------------------------------------
 
+-- | Get a 'FilePath'&#x27;s last extension, or 'Nothing' if it has no
+-- extensions.
 extension :: FilePath -> Maybe B.ByteString
 extension p = case extensions p of
 	[] -> Nothing
 	es -> Just (last es)
 
+-- | Get a 'FilePath'&#x27;s full extension list.
 extensions :: FilePath -> [B.ByteString]
 extensions = pathExtensions
 
+-- | Get whether a 'FilePath'&#x27;s last extension is the predicate.
 hasExtension :: FilePath -> B.ByteString -> Bool
 hasExtension p e = extension p == Just e
 
+-- | Append an extension to the end of a 'FilePath'.
 addExtension :: FilePath -> B.ByteString -> FilePath
 addExtension p ext = addExtensions p [ext]
 
+-- | Append many extensions to the end of a 'FilePath'.
 addExtensions :: FilePath -> [B.ByteString] -> FilePath
 addExtensions p exts = p { pathExtensions = pathExtensions p ++ exts }
 
+-- | An alias for 'addExtension'.
 (<.>) :: FilePath -> B.ByteString -> FilePath
 (<.>) = addExtension
 
+-- | Remove a 'FilePath'&#x27;s last extension.
 dropExtension :: FilePath -> FilePath
 dropExtension p = p { pathExtensions = safeInit (pathExtensions p) }
 
+-- | Remove all extensions from a 'FilePath'.
 dropExtensions :: FilePath -> FilePath
 dropExtensions p = p { pathExtensions = [] }
 
+-- | Replace a 'FilePath'&#x27;s last extension.
 replaceExtension :: FilePath -> B.ByteString -> FilePath
 replaceExtension = addExtension . dropExtension
 
+-- | Remove all extensions from a 'FilePath', and replace them with a new
+-- list.
 replaceExtensions :: FilePath -> [B.ByteString] -> FilePath
 replaceExtensions = addExtensions . dropExtensions
 
+-- | @splitExtension p = ('dropExtension' p, 'extension' p)@
 splitExtension :: FilePath -> (FilePath, Maybe B.ByteString)
 splitExtension p = (dropExtension p, extension p)
 
+-- | @splitExtensions p = ('dropExtensions' p, 'extensions' p)@
 splitExtensions :: FilePath -> (FilePath, [B.ByteString])
 splitExtensions p = (dropExtensions p, extensions p)
 
@@ -208,3 +251,7 @@
 safeInit xs = case xs of
 	[] -> []
 	_ -> init xs
+
+safeHead :: [a] -> Maybe a
+safeHead [] = Nothing
+safeHead (x:_) = Just x
diff --git a/System/FilePath/CurrentOS.hs b/System/FilePath/CurrentOS.hs
--- a/System/FilePath/CurrentOS.hs
+++ b/System/FilePath/CurrentOS.hs
@@ -7,9 +7,11 @@
 -- Maintainer:  jmillikin@gmail.com
 -- Portability:  portable
 --
--- Re-exports contents of "System.FilePath", defaulting to the current OS's
--- rules when needed.
+-- Re-exports contents of "System.FilePath.Rules", defaulting to the
+-- current OS's rules when needed.
 --
+-- Also enables 'Show' and 'S.IsString' instances for 'F.FilePath'.
+--
 -----------------------------------------------------------------------------
 
 {-# LANGUAGE CPP #-}
@@ -54,32 +56,42 @@
 	showsPrec d path = showParen (d > 10) $
 		showString "FilePath " . shows (toBytes path)
 
+-- | See 'R.valid'
 valid :: F.FilePath -> Bool
 valid = R.valid currentOS
 
+-- | See 'R.normalise'
 normalise :: F.FilePath -> F.FilePath
 normalise = R.normalise currentOS
 
+-- | See 'R.equivalent'
 equivalent :: F.FilePath -> F.FilePath -> Bool
 equivalent = R.equivalent currentOS
 
+-- | See 'R.toBytes'
 toBytes :: F.FilePath -> B.ByteString
 toBytes = R.toBytes currentOS
 
+-- | See 'R.toLazyBytes'
 toLazyBytes :: F.FilePath -> BL.ByteString
 toLazyBytes = R.toLazyBytes currentOS
 
+-- | See 'R.toString'
 toString :: F.FilePath -> String
 toString = R.toString currentOS
 
+-- | See 'R.fromBytes'
 fromBytes :: B.ByteString -> F.FilePath
 fromBytes = R.fromBytes currentOS
 
+-- | See 'R.fromLazyBytes'
 fromLazyBytes :: BL.ByteString -> F.FilePath
 fromLazyBytes = R.fromLazyBytes currentOS
 
+-- | See 'R.fromString'
 fromString :: String -> F.FilePath
 fromString = R.fromString currentOS
 
+-- | See 'R.splitSearchPath'
 splitSearchPath :: B.ByteString -> [F.FilePath]
 splitSearchPath = R.splitSearchPath currentOS
diff --git a/System/FilePath/Internal.hs b/System/FilePath/Internal.hs
--- a/System/FilePath/Internal.hs
+++ b/System/FilePath/Internal.hs
@@ -16,6 +16,7 @@
 import Data.Data (Data)
 import Data.Typeable (Typeable)
 import qualified Data.ByteString as B
+import qualified Data.ByteString.Char8 as B8
 
 -------------------------------------------------------------------------------
 -- File Paths
@@ -39,9 +40,19 @@
 	}
 	deriving (Eq, Data, Typeable)
 
+-- | A file path with no root, components, or filename
 empty :: FilePath
 empty = FilePath Nothing [] Nothing []
 
+filenameBytes :: FilePath -> B.ByteString
+filenameBytes p = B.append name ext where
+	name = case pathBasename p of
+		Nothing -> B.empty
+		Just name' -> name'
+	ext = case pathExtensions p of
+		[] -> B.empty
+		exts -> B.intercalate (B8.pack ".") (B.empty:exts)
+
 -------------------------------------------------------------------------------
 -- Rules
 -------------------------------------------------------------------------------
@@ -49,10 +60,23 @@
 data Rules = Rules
 	{ rulesName :: String
 	, toByteChunks :: FilePath -> [B.ByteString]
+	
+	-- | Parse a strict 'B.ByteString', such as  those received from
+	-- OS libraries, into a 'FilePath'.
 	, fromBytes :: B.ByteString -> FilePath
 	, caseSensitive :: Bool
+	
+	-- | Check if a 'FilePath' is valid; that is, it must not contain
+	-- any illegal characters, and must have a root appropriate to the
+	-- current 'Rules'.
 	, valid :: FilePath -> Bool
+	
+	-- | Split a search path, such as @$PATH@ or @$PYTHONPATH@, into
+	-- a list of 'FilePath's.
 	, splitSearchPath :: B.ByteString -> [FilePath]
+	
+	-- | Remove redundant characters. On case-insensitive platforms,
+	-- also lowercases any ASCII uppercase characters.
 	, normalise :: FilePath -> FilePath
 	}
 
diff --git a/System/FilePath/Rules.hs b/System/FilePath/Rules.hs
--- a/System/FilePath/Rules.hs
+++ b/System/FilePath/Rules.hs
@@ -40,12 +40,16 @@
 import qualified Data.ByteString.Lazy as BL
 import qualified Data.ByteString.Lazy.Char8 as BL8
 
-import System.FilePath
+import System.FilePath hiding (root, filename)
 import System.FilePath.Internal
 
 -------------------------------------------------------------------------------
 -- Rule-specific path properties
 -------------------------------------------------------------------------------
+
+-- | Check if two different 'FilePath's refer to the same file. This does
+-- not perform any link resolution, so some equivalent files might be
+-- missed.
 equivalent :: Rules -> FilePath -> FilePath -> Bool
 equivalent r x y = n x == n y where
 	n p = if caseSensitive r
@@ -64,18 +68,32 @@
 -------------------------------------------------------------------------------
 -- Public helpers
 -------------------------------------------------------------------------------
+
+-- | Convert a 'FilePath' into a strict 'B.ByteString', suitable for passing
+-- to OS libraries.
 toBytes :: Rules -> FilePath -> B.ByteString
 toBytes r = B.concat . toByteChunks r
 
+-- | Convert a 'FilePath' into a lazy 'BL.ByteString'.
 toLazyBytes :: Rules -> FilePath -> BL.ByteString
 toLazyBytes r = BL.fromChunks . toByteChunks r
 
+-- | Parse a lazy 'BL.ByteString' into a 'FilePath'.
 fromLazyBytes :: Rules -> BL.ByteString -> FilePath
 fromLazyBytes r = fromBytes r . B.concat . BL.toChunks
 
+-- | Convert a 'FilePath' into a lazy 'String'. This is useful for
+-- interoperating with legacy libraries. No decoding is performed; the
+-- string's character ordinals are equal to the path's original bytes. If you
+-- need to display a 'FilePath' to the user, use 'toLazyBytes' and an
+-- appropriate decoding function.
 toString :: Rules -> FilePath -> String
 toString r = BL8.unpack . toLazyBytes r
 
+-- | Parse a lazy 'String' into a 'FilePath'. This is useful for
+-- interoperating with legacy libraries. No encoding is performed;
+-- characters are truncated to 8 bits. If you need to accept a 'FilePath'
+-- from the user, use 'fromLazyBytes' and an appropriate encoding function.
 fromString :: Rules -> String -> FilePath
 fromString r = fromBytes r . B8.pack
 
@@ -90,13 +108,7 @@
 	RootWindowsCurrentVolume -> "\\"
 
 byteComponents :: FilePath -> [B.ByteString]
-byteComponents path = pathComponents path ++ [name] where
-	name = (`B.append` ext) $ case pathBasename path of
-		Nothing -> B.empty
-		Just name' -> name'
-	ext = case pathExtensions path of
-		[] -> B.empty
-		exts -> B.intercalate (B8.pack ".") (B.empty:exts)
+byteComponents path = pathComponents path ++ [filenameBytes path]
 
 upperBytes :: B.ByteString -> B.ByteString
 upperBytes bytes = (`B.map` bytes) $ \b -> if b >= 0x41 && b <= 0x5A
@@ -138,9 +150,12 @@
 			then pastRoot
 			else init pastRoot
 	
-	(name, exts) = case B.split 0x2E (last split') of
-		[] -> (Nothing, [])
-		(name':exts') -> (Just name', exts')
+	filename = last split'
+	(name, exts) = if elem filename [B8.pack ".", B8.pack ".."]
+		then (Just filename, [])
+		else case B.split 0x2E (last split') of
+			[] -> (Nothing, [])
+			(name':exts') -> (Just name', exts')
 
 posixValid :: FilePath -> Bool
 posixValid p = validRoot && validComponents where
@@ -203,9 +218,12 @@
 			then pastRoot
 			else init pastRoot
 	
-	(name, exts) = case B.split 0x2E (last split') of
-		[] -> (Nothing, [])
-		(name':exts') -> (Just name', exts')
+	filename = last split'
+	(name, exts) = if elem filename [B8.pack ".", B8.pack ".."]
+		then (Just filename, [])
+		else case B.split 0x2E (last split') of
+			[] -> (Nothing, [])
+			(name':exts') -> (Just name', exts')
 
 winValid :: FilePath -> Bool
 winValid p = validRoot && noReserved && validCharacters where
diff --git a/system-filepath.cabal b/system-filepath.cabal
--- a/system-filepath.cabal
+++ b/system-filepath.cabal
@@ -1,5 +1,5 @@
 name: system-filepath
-version: 0.1
+version: 0.1.1
 synopsis: High-level, byte-based file and directory path manipulations
 license: MIT
 license-file: license.txt
