diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2013, Sam T.
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Sam T. nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/src/Main.hs b/src/Main.hs
new file mode 100644
--- /dev/null
+++ b/src/Main.hs
@@ -0,0 +1,116 @@
+module Main (main) where
+
+import Control.Applicative as A
+import Control.Concurrent
+import Control.Concurrent.Async
+import Control.Exception
+import Control.Monad
+import Data.Default
+import Data.List as L
+import Data.Maybe
+import Data.Time
+import Data.Time.Clock.POSIX
+import Data.Version (showVersion)
+import Network.URI
+import Text.PrettyPrint.ANSI.Leijen hiding ((<$>), (<>), (</>), width)
+import Text.RSS.Syntax
+import System.Directory
+import System.FilePath ((<.>))
+import System.IO
+
+import Paths_ureader
+
+import UReader.Localization
+import UReader.Options
+import UReader.Rendering
+import UReader.RSS
+
+
+parseFeedList :: String -> [URI]
+parseFeedList = mapMaybe parseURI . L.lines
+
+getFeedList :: FilePath -> IO [URI]
+getFeedList feedList = parseFeedList <$> Prelude.readFile feedList
+
+
+getLastSeen :: FilePath -> IO UTCTime
+getLastSeen lastPath = do
+    exist <- doesFileExist lastPath
+    if exist
+      then do
+        !mLastSeen <- parsePubDate <$> Prelude.readFile lastPath
+        Prelude.writeFile lastPath . formatPubDate =<< getCurrentTime
+        return $ fromMaybe epochStart mLastSeen
+      else do
+        Prelude.writeFile lastPath . formatPubDate =<< getCurrentTime
+        return epochStart
+  where
+    epochStart = posixSecondsToUTCTime 0
+
+putBroken :: [(URI, SomeException)] -> IO ()
+putBroken broken = do
+  forM_ broken $ \(url, e) ->
+    hPrint stderr $ red $ text $ show url ++ " - " ++ show e
+
+timestampExt :: FilePath
+timestampExt = "lastseen"
+
+fetch :: Maybe UTCTime -> [URI] -> IO [RSS]
+fetch t uris = do
+  (broken, feeds) <- fetchFeeds t uris
+  putBroken broken
+  return feeds
+
+filterNew :: FilePath -> [URI] -> IO [RSS]
+filterNew feedList uris = do
+  lastSeen  <- getLastSeen (feedList <.> timestampExt)
+  feeds     <- fetch (Just lastSeen) uris
+  let isNew item = pubDate item > Just lastSeen
+  let userFeeds  =  L.map (filterItems isNew) feeds
+  unless (L.all emptyFeed userFeeds) $ do
+    localTime <- utcToLocalTime <$> getCurrentTimeZone <*> pure lastSeen
+    print $ green $ "Showed from:" <+> text (formatPubDate localTime)
+  return userFeeds
+
+previewFeed :: URI -> IO ()
+previewFeed = getRSS >=> setCurrentZone >=> renderRSS def . return
+
+showBatch :: Style -> FilePath -> [URI] -> IO ()
+showBatch style @ Style {..} feedList uris = do
+  renderRSS style =<< setCurrentZone =<<
+    (if newOnly then filterNew feedList else fetch Nothing) uris
+
+streamStyle :: Style
+streamStyle = Style
+    { feedOrder = OldFirst
+    , feedDesc  = False
+    , feedMerge = True
+    , newOnly   = True
+    }
+
+updateStream :: FilePath -> [URI] -> IO ()
+updateStream feedList uris
+  = filterNew feedList uris >>= setCurrentZone >>= renderRSS streamStyle
+
+pollBy :: Int -> IO () -> IO ()
+pollBy interval action = forever $ do
+    end <- async $ handle handler action
+    threadDelay $ interval * 1000000
+    wait end
+  where
+    handler :: SomeException -> IO ()
+    handler = print
+
+streamFeeds :: FilePath -> Int -> [URI] -> IO ()
+streamFeeds feedList interval uris =
+  pollBy interval $ do updateStream feedList uris
+
+run :: Options -> IO ()
+run Add     {..} = appendFile feedList $ show feedURI ++ "\n"
+run Batch   {..} = getFeedList feedList >>= showBatch feedStyle feedList
+run Preview {..} = previewFeed feedURI
+run Stream  {..} = getFeedList feedList >>= streamFeeds feedList feedInterval
+run Version      = putStrLn $ "ureader version " ++ showVersion version
+
+main :: IO ()
+main = getOptions >>= run
diff --git a/src/UReader/Localization.hs b/src/UReader/Localization.hs
new file mode 100644
--- /dev/null
+++ b/src/UReader/Localization.hs
@@ -0,0 +1,57 @@
+{-# LANGUAGE TypeSynonymInstances #-}
+{-# LANGUAGE OverlappingInstances #-}
+module UReader.Localization
+       ( pubDate
+       , parsePubDate
+       , formatPubDate
+
+       , LocalZone (..)
+       , setCurrentZone
+       ) where
+
+import Control.Applicative
+import Control.Monad
+import Data.Implicit
+import Data.Time
+import Text.RSS.Syntax
+import System.Locale
+
+pubDate :: RSSItem -> Maybe UTCTime
+pubDate = parsePubDate <=< rssItemPubDate
+
+pubDateFormat :: String
+pubDateFormat = "%a, %e %b %Y %H:%M:%S %Z"
+
+parsePubDate :: DateString -> Maybe UTCTime
+parsePubDate = parseTime defaultTimeLocale pubDateFormat
+
+formatPubDate :: FormatTime t => t -> DateString
+formatPubDate = formatTime defaultTimeLocale pubDateFormat
+
+localizeUTC :: Implicit_ TimeZone => UTCTime -> LocalTime
+localizeUTC = utcToLocalTime param_
+
+setCurrentZone :: LocalZone a => a -> IO a
+setCurrentZone x = (localize x $~) <$> getCurrentTimeZone
+
+class LocalZone a where
+  localize :: Implicit_ TimeZone => a -> a
+
+instance (Functor f, LocalZone a) => LocalZone (f a) where
+  localize = fmap localize
+
+instance LocalZone DateString where
+  localize ds = maybe ds (formatPubDate . localizeUTC) $ parsePubDate ds
+
+instance LocalZone RSSItem where
+  localize item = item { rssItemPubDate = localize (rssItemPubDate item) }
+
+instance LocalZone RSSChannel where
+  localize chan @ RSSChannel {..} = chan
+    { rssPubDate    = localize rssPubDate
+    , rssLastUpdate = localize rssLastUpdate
+    , rssItems      = fmap localize rssItems
+    }
+
+instance LocalZone RSS where
+  localize rss @ RSS {..} = rss { rssChannel = localize rssChannel }
diff --git a/src/UReader/Options.hs b/src/UReader/Options.hs
new file mode 100644
--- /dev/null
+++ b/src/UReader/Options.hs
@@ -0,0 +1,149 @@
+module UReader.Options
+       ( Order (..)
+       , Style (..)
+
+       , Options (..)
+       , getOptions
+       ) where
+
+import Control.Monad
+import Data.Implicit
+import Data.Monoid
+import Network.URI
+import Options.Applicative
+import System.Directory
+import System.FilePath
+
+import UReader.Rendering
+
+
+styleParser :: Parser Style
+styleParser = Style
+    <$> option
+      ( long    "order"
+     <> short   'o'
+     <> metavar "ORDER"
+     <> value NewFirst <> showDefault
+     <> help    "Specifies entries order: either NewFirst or OldFirst"
+      )
+    <*> switch
+      ( long    "description"
+     <> short   'd'
+     <> help    "Include auxilary info like rss version and channel title"
+      )
+    <*> switch
+      ( long    "merge"
+     <> short   'm'
+     <> help    "Merge multiple feed info one by time"
+      )
+    <*> switch
+      ( long    "unread"
+     <> short   'u'
+     <> help    "Show only unread feed and ignore feed will shown in future"
+      )
+
+feedListParser :: Implicit_ FilePath => Parser FilePath
+feedListParser = option
+   ( long    "feeds"
+  <> metavar "PATH"
+  <> value   param_ <> showDefault
+  <> help    "Path to file with list of feed urls"
+   )
+
+feedLinkParser :: Parser URI
+feedLinkParser = argument parseURI
+   ( metavar "URI"
+  <> help    "URI to feed"
+   )
+
+intervalTime :: Parser Int
+intervalTime = option
+   ( long    "interval"
+  <> short   'i'
+  <> metavar "SECONDS"
+  <> value   600 <> showDefault
+  <> help    "Minimal interval between feed updates"
+   )
+
+data Options
+   = Add     { feedList   :: FilePath
+             , feedURI    :: URI
+             }
+   | Batch   { feedList   :: FilePath
+             , feedStyle  :: Style
+             }
+   | Preview { feedURI    :: URI  }
+   | Stream  { feedList   :: FilePath
+             , feedInterval :: Int
+             }
+   | Version
+     deriving (Show, Eq)
+
+addParser :: Implicit_ String => Parser Options
+addParser = Add <$> feedListParser <*> feedLinkParser
+
+addInfo :: Implicit_ String => ParserInfo Options
+addInfo = info (helper <*> addParser) modifier
+  where
+    modifier = progDesc "Add a feed to the feed list"
+
+streamParser :: Implicit_ String => Parser Options
+streamParser = Stream <$> feedListParser <*> intervalTime
+
+streamInfo :: Implicit_ String => ParserInfo Options
+streamInfo = info (helper <*> streamParser) modifier
+  where
+    modifier = progDesc "Show feed as never ending stream"
+
+feedParser :: Implicit_ String => Parser Options
+feedParser = Batch <$> feedListParser <*> styleParser
+
+feedInfo :: Implicit_ String => ParserInfo Options
+feedInfo = info (helper <*> feedParser) modifier
+  where
+    modifier = progDesc "Show all feeds specified in feed list"
+
+previewParser :: Parser Options
+previewParser = Preview <$> feedLinkParser
+
+previewInfo :: ParserInfo Options
+previewInfo = info (helper <*> previewParser) modifier
+  where
+    modifier = progDesc "Show feed specified by URI or file path"
+
+versionParser :: Parser Options
+versionParser = flag' Version
+    ( long  "version"
+   <> short 'V'
+   <> hidden
+   <> help  "Show program version and exit"
+    )
+
+optionsParser :: Implicit_ String => Parser Options
+optionsParser = subparser $ mconcat
+  [ command "add"    addInfo
+  , command "feed"   feedInfo
+  , command "stream" streamInfo
+  , command "view"   previewInfo
+  ]
+
+optionsInfo :: Implicit_ String => ParserInfo Options
+optionsInfo = info (helper <*> (versionParser <|> optionsParser)) modifier
+  where
+    modifier = fullDesc <> header hdr <> progDesc desc
+    hdr  = "ureader is minimalistic CLI RSS reader"
+    desc =
+       "$ ureader COMMAND --help # for info about particular command"
+
+getDefaultFeeds :: IO FilePath
+getDefaultFeeds = do
+  udir <- getAppUserDataDirectory "ureader"
+  createDirectoryIfMissing False udir
+  let configPath = udir </> "feeds"
+  exist <- doesFileExist configPath
+  unless exist $ do
+    writeFile configPath ""
+  return configPath
+
+getOptions :: IO Options
+getOptions = getDefaultFeeds >>= (execParser optionsInfo $~)
diff --git a/src/UReader/RSS.hs b/src/UReader/RSS.hs
new file mode 100644
--- /dev/null
+++ b/src/UReader/RSS.hs
@@ -0,0 +1,95 @@
+module UReader.RSS
+       ( filterItems
+       , reverseItems
+       , emptyFeed
+
+       , getRSS
+       , fetchFeeds
+
+       , resolveComments
+       ) where
+
+import Control.Applicative
+import Control.Arrow
+import Control.Concurrent.ParallelIO
+import Control.Exception
+import Data.Char
+import Data.Either
+import Data.List as L
+import Data.Maybe
+import Data.Text.Encoding as T
+import Data.Time
+import Network.URI
+import Network.Curl
+import Network.Curl.Download
+import Text.RSS.Syntax
+import Text.RSS.Import
+import Text.XML.Light.Input
+import System.Locale
+
+
+-- TODO filter by time = drop . find
+filterItems :: (RSSItem -> Bool) -> RSS -> RSS
+filterItems p rss = rss
+    { rssChannel = let ch = rssChannel rss
+                   in ch { rssItems = L.filter p (rssItems ch) }
+    } -- TODO use lens
+
+reverseItems :: RSS -> RSS
+reverseItems rss = rss
+  { rssChannel = let ch = rssChannel rss
+                 in ch { rssItems = L.reverse (rssItems ch) }
+  }
+
+emptyFeed :: RSS -> Bool
+emptyFeed = L.null . rssItems . rssChannel
+
+-- TODO openAsFeed
+getRSS :: URI -> IO RSS
+getRSS uri = do
+  body <- either (throwIO . userError) return =<< openURI (show uri)
+  xml  <- maybe (throwIO $ userError "invalid XML") return $
+            parseXMLDoc (T.decodeUtf8 body)
+  rss  <- maybe  (throwIO $ userError "invalid RSS") return $
+            elementToRSS xml
+  return rss
+
+hdrLastModified :: String
+hdrLastModified = "last-modified"
+
+lastModifiedFormat :: String
+lastModifiedFormat = "%a, %e %b %Y %H:%M:%S %Z"
+
+parseLastModified :: String -> Maybe UTCTime
+parseLastModified = parseTime defaultTimeLocale lastModifiedFormat
+
+resourceExpired :: UTCTime -> URI -> IO Bool
+resourceExpired threshold uri = do
+    (_, headers) <- curlHead (show uri) []
+    let cheaders = L.map (first canonicalize) headers
+    return $ case L.lookup hdrLastModified cheaders of
+      Just (parseLastModified -> Just lm) -> threshold < lm
+      _ -> True
+  where
+    canonicalize = L.map toLower
+
+
+getRSSFrom :: Maybe UTCTime -> URI -> IO (Maybe RSS)
+getRSSFrom  Nothing uri = Just <$> getRSS uri
+getRSSFrom (Just t) uri = do
+  updated <- resourceExpired t uri
+  if updated
+    then Just <$> getRSS uri
+    else return Nothing
+
+fetchFeeds :: Maybe UTCTime -> [URI]
+           -> IO ([(URI, SomeException)], [RSS])
+fetchFeeds mtime urls = do
+    res <- parallelE (L.map (getRSSFrom mtime) urls)
+    return $ second catMaybes $ partitionEithers $ urlfy res
+  where
+    urlfy = L.zipWith (\url -> either (Left .  (,) url) Right) urls
+
+
+resolveComments :: RSS -> IO RSS
+resolveComments = error "resolveComments"
diff --git a/src/UReader/Rendering.hs b/src/UReader/Rendering.hs
new file mode 100644
--- /dev/null
+++ b/src/UReader/Rendering.hs
@@ -0,0 +1,235 @@
+{-# OPTIONS -fno-warn-orphans #-}
+module UReader.Rendering
+       ( Order (..)
+       , Style (..)
+       , renderRSS
+       ) where
+
+import Control.Applicative
+import Control.Monad
+import Data.Char
+import Data.Default
+import Data.Function
+import Data.Maybe
+import Data.Monoid
+import Data.List as L
+import Data.List.Split as L
+import Data.Set as S
+import Text.HTML.TagSoup
+import Text.PrettyPrint.ANSI.Leijen hiding ((<$>), (<>), width)
+import Text.RSS.Syntax
+import Text.XML.Light.Types
+import System.IO
+import System.Console.Terminal.Size as Terminal
+
+import UReader.RSS
+import UReader.Localization
+
+
+data Order = NewFirst
+           | OldFirst
+             deriving (Show, Read, Eq, Ord, Bounded, Enum)
+
+data Style = Style
+  { feedOrder :: !Order
+  , feedDesc  :: !Bool
+  , feedMerge :: !Bool
+  , newOnly   :: !Bool
+  } deriving (Show, Eq)
+
+instance Default Style where
+  def = Style
+    { feedOrder = NewFirst
+    , feedDesc  = True
+    , feedMerge = False
+    , newOnly   = False
+    }
+
+prettyDesc :: Bool -> RSS -> Doc
+prettyDesc keepDesc
+  | keepDesc  = pretty
+  | otherwise
+  = vsep . punctuate linebreak . L.map pretty . rssItems . rssChannel
+
+merge :: Bool -> [RSS] -> [RSS]
+merge byTime
+  |   byTime  = return . mconcat
+  | otherwise = id
+
+formatOrder :: Order -> RSS -> RSS
+formatOrder NewFirst = id
+formatOrder OldFirst = reverseItems
+
+formatFeeds :: Style -> [RSS] -> Doc
+formatFeeds Style {..}
+  = vsep . punctuate linebreak
+  . L.map (prettyDesc feedDesc . formatOrder feedOrder) . merge feedMerge
+
+renderRSS :: Style -> [RSS] -> IO ()
+renderRSS style feeds = do
+  Window {..} <- fromMaybe (Window 80 60) <$> Terminal.size
+  displayIO stdout $ renderPretty 0.8 width $ formatFeeds style feeds
+
+instance Monoid RSS where
+  mempty  = nullRSS "" ""
+  mappend a b = mempty
+      { rssVersion = unwords $ S.toList $
+                     mergeVersions (rssVersion a) (rssVersion b)
+      , rssAttrs   = rssAttrs   a <> rssAttrs   b
+      , rssChannel = rssChannel a <> rssChannel b
+      , rssOther   = rssOther   a <> rssOther   b
+      }
+    where
+      mergeVersions = (<>) `on` S.fromList . words
+
+instance Monoid RSSChannel where
+  mempty = nullChannel "" ""
+  mappend a b = mempty
+      { rssTitle = rssTitle a <> "|" <> rssTitle b
+      , rssLink  = rssLink  a <> " " <> rssLink  b
+      , rssDescription = rssDescription a
+      , rssItems = mergeBy cmpPubDate (rssItems a) (rssItems b)
+      }
+    where
+      cmpPubDate = (>) `on` (parsePubDate <=< rssItemPubDate)
+
+      mergeBy :: (a -> a -> Bool) -> [a] -> [a] -> [a]
+      mergeBy _ [] xs = xs
+      mergeBy _ xs [] = xs
+      mergeBy f (x : xs) (y : ys)
+        |   f x y   = x : mergeBy f xs (y : ys)
+        | otherwise = y : mergeBy f (x : xs) ys
+
+instance Pretty RSS where
+  pretty RSS {..} =
+    pretty rssChannel </>
+    dullblack ("rss version" <+> pretty rssVersion)
+
+instance Pretty RSSChannel where
+  pretty RSSChannel {..} =
+      vcat (L.zipWith heading (splitOn "|" rssTitle) (words rssLink)) </>
+      pretty rssDescription </>
+      pretty rssPubDate <$$>
+      vcat (punctuate linebreak $ L.map pretty rssItems)
+    where
+      heading title link = blue (fill 24 (text title)) </> text link
+
+instance Pretty RSSItem where
+  pretty RSSItem {..} =
+    (bold (magenta (pretty rssItemTitle)) </> pretty rssItemLink) <$$>
+     red  (hsep $ L.map ppCategory rssItemCategories) <$$>
+       indent 2 (maybe mempty ppItemDesc rssItemDescription) <$$>
+    (maybe mempty ppComments rssItemComments) <$$>
+    (green (pretty rssItemGuid))            <$$>
+    (yellow (pretty rssItemPubDate) </>
+      maybe mempty ppAuthor rssItemAuthor)
+
+    where
+      ppItemDesc          = nest 2 . prettySoup False False . extDesc
+      ppComments comments = "Comments: "  <+> pretty comments
+      ppAuthor   author   = "posted by"   <+> red (pretty author)
+      ppCategory category = dullblack "*"  <> pretty category
+
+
+instance Pretty RSSGuid where
+  pretty RSSGuid {..}
+    | Just True <- rssGuidPermanentURL = "Permalink:" <+> pretty rssGuidValue
+    |          otherwise               = "Link:     " <+> pretty rssGuidValue
+
+instance Pretty RSSCategory where
+  pretty RSSCategory {..} =
+    dullyellow (maybe mempty text rssCategoryDomain) <>
+    dullred    (hsep $ L.map pretty rssCategoryAttrs)  <>
+    dullblue   (text rssCategoryValue)
+
+instance Pretty Attr where
+  pretty Attr {..} = text (show attrKey) <+> "=" <+> text attrVal
+
+extDesc :: String -> [Tag String]
+extDesc = canonicalizeTags . parseTags
+
+{- NOTE: the findCloseTag could lead to serious performance
+degradation, but this is very unlikely for HTML embedded in RSS. -}
+
+findCloseTag :: Eq a => a -> [Tag a] -> ([Tag a], [Tag a])
+findCloseTag t = go (0 :: Int) []
+  where
+    go _ acc []       = (reverse acc, [])
+    go n acc (x : xs) =
+      case x of
+        TagOpen  t' _
+          | t == t'   -> go (succ n) (x : acc) xs
+          | otherwise -> go       n  (x : acc) xs
+        TagClose t'
+          | t == t'   -> if n == 0
+                    then (reverse acc, xs)
+                    else go (pred n) (x : acc) xs
+          | otherwise -> go       n  (x : acc) xs
+        _             -> go       n  (x : acc) xs
+
+prettySoup :: Bool -> Bool -> [Tag String] -> Doc
+prettySoup _     _   []       = mempty
+prettySoup upper raw (x : xs) = case x of
+  TagText t -> text (upperize (canonicalize t))
+            <> prettySoup upper raw xs
+    where
+      canonicalize |    raw    = id
+                   | otherwise = L.filter isPrint
+      upperize     |   upper   = L.map toUpper
+                   | otherwise = id
+
+  TagOpen t attrs -> maybe err closeTag $ L.lookup t rules
+    where
+      rules =
+        [ "p"  --> \par -> linebreak <> par <> linebreak
+        , "i"  --> underline
+        , "em" --> underline
+        , "u"  --> underline
+        , "strong" --> bold
+        , "b"  --> bold
+        , "tt" --> dullwhite
+        , "hr" --> \body -> body <> linebreak <>
+                            underline (text (L.replicate 72 ' ')) <> linebreak
+        , "a"  --> \desc -> blue desc </> pretty (L.lookup "href" attrs)
+        , "br" --> (linebreak <>)
+        , "ul" --> id
+        , "li" --> \li -> green "*" <+> li <> linebreak
+        , "span" --> id
+        , "code" ~-> (onwhite . black)
+        , "img"  --> \desc -> blue desc </> pretty (L.lookup "src" attrs)
+        , "pre"  ~-> \body -> linebreak <> align body <> linebreak
+
+        , "h1" ==> heading
+        , "h2" --> heading
+        , "h3" --> heading
+        , "h4" --> heading
+        , "h5" --> heading
+        , "h6" --> heading
+
+        , "div" --> \body -> linebreak <> body <> linebreak
+        , "blockquote" --> indent 4
+
+        , "table" --> \body -> linebreak <> body <> linebreak
+        , "tbody" --> id
+        , "tr"    --> \body -> linebreak <> body <> linebreak
+        , "td"    --> fill 40
+        ]
+        where
+          a --> f = (a, f . prettySoup False False)
+          a ~-> f = (a, f . prettySoup False True)
+          a ==> f = (a, f . prettySoup True  False)
+
+          heading body = linebreak <> bold (underline body) <> linebreak
+
+      err = red ("<" <> text t <+> def_attrs <> ">")
+        <+> prettySoup upper raw xs
+        where
+          def_attrs = hcat $ punctuate space $ L.map pattr attrs
+            where pattr (n, v) = text n <> "=" <> text v
+
+      closeTag m = m a <> prettySoup upper raw b
+        where
+          (a, b) = findCloseTag t xs
+
+  TagClose t -> red ("</" <> text t <> ">") <+> prettySoup upper raw xs
+  t          -> text (show t) <> prettySoup upper raw xs
diff --git a/ureader.cabal b/ureader.cabal
new file mode 100644
--- /dev/null
+++ b/ureader.cabal
@@ -0,0 +1,78 @@
+name:                ureader
+version:             0.1.0.0
+license:             BSD3
+license-file:        LICENSE
+author:              Sam T.
+maintainer:          Sam T. <pxqr.sta@gmail.com>
+copyright:           (c) 2013, Sam T.
+category:            RSS
+build-type:          Simple
+cabal-version:       >= 1.10
+tested-with:         GHC == 7.6.3
+homepage:            https://github.com/pxqr/ureader
+bug-reports:         https://github.com/pxqr/ureader/issues
+synopsis:            Minimalistic CLI RSS reader.
+description:
+
+        `ureader` is minimalistic command line RSS reader with unicode
+        and color support. Everything it does is fetch RSS documents,
+        merge them according to specified options, format and flush
+        resulting feed to stdout. So `ureader` could be used with
+        pagers like `more(1)` or in linux terminal.
+        .
+        [/Release Notes/]
+        .
+          * /0.1.0.0:/ Initial version.
+
+
+source-repository head
+  type:                git
+  location:            git://github.com/pxqr/ureader.git
+
+executable ureader
+  default-language:    Haskell2010
+  default-extensions:  OverloadedStrings
+                     , PatternGuards
+                     , RecordWildCards
+                     , ViewPatterns
+                     , BangPatterns
+
+                     , FlexibleInstances
+                     , FlexibleContexts
+  hs-source-dirs:      src
+  main-is:             Main.hs
+  other-modules:       UReader.Localization
+                       UReader.Options
+                       UReader.Rendering
+                       UReader.RSS
+
+  build-depends:       base       == 4.*
+                     , implicit-params == 0.2.*
+                     , data-default
+
+                     , async
+                     , parallel-io == 0.3.*
+
+                     , bytestring == 0.10.*
+                     , text       == 0.11.*
+                     , containers == 0.5.*
+                     , split      == 0.2.*
+
+                     , time       == 1.4.*
+                     , old-locale == 1.0.*
+
+                     , network == 2.4.*
+                     , curl
+                     , download-curl
+
+                     , directory == 1.2.*
+                     , filepath  == 1.3.*
+                     , ansi-wl-pprint       == 0.6.*
+                     , optparse-applicative == 0.5.*
+                     , terminal-size        == 0.2.*
+
+                     , xml     == 1.3.*
+                     , feed    == 0.3.*
+                     , tagsoup == 0.12.*
+
+  ghc-options:       -Wall -O2
